<?php /** * Plugin : QueryChangelog * Version : 1 (05/05/2009) * * @license GPL 2 (http://www.gnu.org/licenses/gpl.html) * @author Vincent de Lagabbe <vincent@delagabbe.com> * @based_on "userhistory" plugin by Ondra Zara <o.z.fw@seznam.cz> * @based_on "pagemove" plugin by Gary Owen <gary@isection.co.uk> */ if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/'); if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/'); require_once(DOKU_PLUGIN.'admin.php'); require_once(DOKU_INC.'inc/search.php'); class admin_plugin_querychangelog extends DokuWiki_Admin_Plugin { var $show_form = true; var $errors = array(); var $opts = array(); var $changes = array(); /** * function constructor */ function admin_plugin_querychangelog(){ // enable direct access to language strings $this->setupLocale(); } /** * return some info */ function getInfo(){ return array( 'author' => 'Vincent de Lagabbe', 'email' => 'vincent@delagabbe.com', 'date' => '2009-05-05', 'name' => 'QueryChangelog', 'desc' => $this->lang['desc'], 'url' => 'http://wiki.splitbrain.org/plugin:querychangelog', ); } function forAdminOnly(){ return false; } function getMenuText() { return $this->lang['menu']; } /** * return sort order for position in admin menu */ function getMenuSort() { return 999; } /** * handle user request * * @author Vincent de Lagabbe <vincent@delagabbe.com> */ function handle() { global $lang; global $ID; global $opts; global $conf; $opts['ns'] = getNS($ID); $opts['startd'] = isset($_REQUEST['startd']) ? $_REQUEST['startd'] : "YYYY-MM-DD HH:MM"; $opts['endd'] = isset($_REQUEST['endd']) ? $_REQUEST['endd'] : "YYYY-MM-DD HH:MM"; if (isset($_REQUEST['base_ns'])) { if (!checkSecurityToken()) return; // get begining date $date_from = 0; if ($_REQUEST['qcsd'] == '<def>') { $date_from = $this->_qc_getstamp($opts['startd']); if (!$date_from) { $this->errors[] = $this->lang['qc_err_date']; } } // get ending date $date_to = time(); if ($_REQUEST['qced'] == '<def>') { $date_to = $this->_qc_getstamp($opts['endd']); if (!$date_to) { $this->errors[] = $this->lang['qc_err_date']; } } if ($date_from && $date_to - $date_from <= 0) { $this->errors[] = $this->lang['qc_err_period']; } // get namespace $opts['base_ns'] = $_REQUEST['base_ns']; $base_dir = $this->_ns2path($opts['base_ns']); if (count($this->errors) == 0) { // get users if (!in_array(".", $_REQUEST['qcusers'])) { $opts['users'] = $_REQUEST['qcusers']; } $opts['major_only'] = $_REQUEST['qcmo'] == '<on>'; $opts['date_from'] = $date_from; $opts['date_to'] = $date_to; $this->changes = $this->_getChanges($base_dir); $this->show_form = false; } if ($_REQUEST['as_csv']) { $this->_generate_csv(); } } } /** * output appropriate html * * @author Vincent de Lagabbe <vincent@delagabbe.com> */ function html() { global $lang; global $conf; global $ID; global $opts; global $auth; ptln('<!-- QueryChangelog Plugin start -->'); if ($this->show_form) { ptln($this->locale_xhtml('querychangelog')); $this->_qc_form(); } else { $href = wl($ID).'?do=admin&page='.$this->getPluginName(); ptln('<p><a href="'.$href.'">['.$this->lang['qc_back'].']</a></p>'); // Display the query settings $start_date = $opts['date_from'] == 0 ? $this->lang['qc_begining'] : strftime($conf['dformat'], $opts['date_from']); ptln($this->lang['qc_res_from'].': '.$start_date.'<br/>'); ptln($this->lang['qc_res_to'].': '.strftime($conf['dformat'], $opts['date_to']).'<br/>'); ptln($this->lang['qc_res_ns'].': '.$opts['base_ns'].'<br/>'); $str_list = ""; if (isset($opts['users'])) { $user_list = $auth->retrieveUsers(); foreach ($user_list as $user => $info) { if (in_array($user, $opts['users'])) { $str_list .= ($info['name'].', '); } } $str_list = substr($str_list, 0, -2); } else { $str_list = $this->lang['qc_res_all']; } ptln($this->lang['qc_res_users'].': '.$str_list.'<br/>'); ptln('<h2>'.$this->lang['qc_res_title'].'</h2>'); // Display the changelog if (count($this->changes) == 0) { ptln($this->lang['qc_res_nc'].'<p/>'); } else { $this->_show_changes(); } } ptln('<!-- QueryChangelog Plugin end -->'); } /** * show the query changelog form * * @author Gary Owen <gary@isection.co.uk> * @author Vincent de Lagabbe <vincent@delagabbe.com> */ function _qc_form() { global $ID; global $lang; global $conf; global $opts; global $auth; ptln(' <div align="center">'); ptln(' <script language="Javascript">'); ptln(' function setradio( group, choice ) {'); ptln(' for ( i = 0 ; i < group.length ; i++ ) {'); ptln(' if ( group[i].value == choice )'); ptln(' group[i].checked = true;'); ptln(' }'); ptln(' }'); ptln(' </script>'); ptln(' <form name="frm" action="'.wl($ID).'" method="post">'); formSecurityToken(); ptln(' <fieldset>'); // output hidden values to ensure dokuwiki will return back to this plugin ptln(' <input type="hidden" name="do" value="admin" />'); ptln(' <input type="hidden" name="page" value="'.$this->getPluginName().'" />'); ptln(' <input type="hidden" name="id" value="'.$ID.'" />'); ptln(' <table border="0">'); //Show any errors if (count($this->errors) > 0) { ptln ('<tr><td bgcolor="red" colspan="5">'); foreach($this->errors as $error) ptln ($error.'<br>'); ptln ('</td></tr>'); } // Start and End dates Selection ptln( ' <tr><td align="right" rowspan="2" nowrap><label><span>'.$this->lang['qc_from'].':</span></label></td>'); ptln( ' <td><input type="radio" name="qcsd" value="<def>" '.($_REQUEST['qcsd'] == '<def>' ? 'CHECKED' : '').'></td>'); ptln( ' <td align="left"><input type="text" name="startd" size="25" maxlength="16" value="'.formtext($opts['startd']).'" class="edit" onChange="setradio(document.frm.qcsd, \'<new>\');" /></td></tr>'); ptln( ' <tr><td><input type="radio" name="qcsd" value="<beg>" '.($_REQUEST['qcsd'] != '<def>' ? 'CHECKED' : '').'></td>'); ptln( ' <td align="left">'.$this->lang['qc_begining'].'</td>'); ptln( ' </tr>'); ptln( ' <tr><td> </td></tr>'); ptln( ' <tr><td align="right" rowspan="2" nowrap><label><span>'.$this->lang['qc_to'].':</span></label></td>'); ptln( ' <td><input type="radio" name="qced" value="<def>" '.($_REQUEST['qced'] == '<def>' ? 'CHECKED' : '').'></td>'); ptln( ' <td align="left"><input type="text" name="endd" size="25" maxlength="16" value="'.formtext($opts['endd']).'" class="edit" onChange="setradio(document.frm.qced, \'<new>\');" /></td></tr>'); ptln( ' <tr><td><input type="radio" name="qced" value="<beg>" '.($_REQUEST['qced'] != '<def>' ? 'CHECKED' : '').'></td>'); ptln( ' <td align="left">'.$this->lang['qc_now'].'</td>'); ptln( ' </tr>'); ptln( ' <tr><td colspan="5"> </td></tr>'); // NS Selection $namesp = array( 0 => '' ); //Include root in namespace list search($namesp,$conf['metadir'],'search_namespaces',$opts); sort($namesp); ptln( ' <tr>'); ptln( ' <td align="right" nowrap><label><span>'.$this->lang['qc_base_ns'].':</span></label></td>'); ptln( ' <td> </td>'); ptln( ' <td colspan="3" align="left"><select name="base_ns">'); foreach($namesp as $row) { if ( auth_quickaclcheck($row['id'].':*') >= AUTH_CREATE or $row['id'] == $opts['ns'] ) { ptln ( ' <option value="'.$row['id']. ($_REQUEST['base_ns'] ? (($row['id'] ? $row['id'] : ":") == $_REQUEST['base_ns'] ? '" SELECTED>' : '">') : ($row['id'] == $opts['ns'] ? '" SELECTED>' : '">') ). ($row['id'] ? $row['id'].':' : ": ".$this->lang['qc_root']). ($row['id'] == $opts['ns'] ? ' '.$this->lang['qc_current'] : ''). "</option>" ); } } ptln( ' </select></td>'); ptln( ' </tr>'); ptln( ' <tr><td colspan="5"> </td></tr>'); // User(s) selection ptln( ' <tr>'); ptln( ' <td align="right" nowrap><label><span>'.$this->lang['qc_users'].':</span></label></td>'); ptln( ' <td> </td>'); ptln( ' <td colspan="3" align="left"><select name="qcusers[]" size="10" multiple="yes">'); ptln ( ' <option value="." SELECTED>'.$this->lang['qc_all_users'].'</option>'); $user_list = $auth->retrieveUsers(); foreach ($user_list as $user => $info) { ptln ( ' <option value="'.$user.'">'.$info['name'].' ('.$user.')</option>'); } ptln( ' </select></td>'); ptln( ' </tr>'); ptln( ' <tr><td colspan="5"> </td></tr>'); // Major changes / all changes switch ptln( ' <tr>'); ptln( ' <td align="right" nowrap><label><span>'.$this->lang['qc_major_only'].':</span></label></td>'); ptln( ' <td><input type="checkbox" name="qcmo" value="<on>" '.($_REQUEST['qcmo'] == '<on>' ? 'checked="checked"' : '').'"></td>'); ptln( ' </tr>'); ptln( ' <tr>'); ptln( ' <td align="right" nowrap><label><span>'.$this->lang['qc_as_csv'].':</span></label></td>'); ptln( ' <td><input type="checkbox" name="as_csv" value="<on>"></td>'); ptln( ' </tr>'); ptln( ' <tr><td colspan="5"> </td></tr>'); // Submit ptln( ' <tr>'); ptln( ' <td colspan="5" align="center"><input type="submit" value="'.formtext($this->lang['qc_submit']).'" class="button" /></td>'); ptln( ' </tr>'); ptln( ' </table>'); ptln( ' </fieldset>'); ptln( '</form>'); ptln( '</div>'); } /** * Convert namespace to its corresponding path in meta */ function _ns2path($ns) { global $conf ; if ($ns == ':' || $ns == '') return $conf ['metadir'] ; $ns = trim ($ns, ':') ; $path = $conf ['metadir'] . '/' . str_replace (':', '/', $ns) ; return $path ; } /** * Convert the user input date into a UNIX timestamps. * * @author Vincent de Lagabbe <vincent@delagabbe.com> */ function _qc_getstamp($str_date) { $date = array(); $tstamp = false; if (0 == preg_match('/([0-9]{4})-([01][0-9])-([0-3][0-9]) ([0-2][0-9]):([0-6][0-9])/', $str_date, $date)) { return false; } $tstamp = mktime($date[4], $date[5], 0, $date[2], $date[3], $date[1]); return $tstamp; } /** * Get the changelogs entries according to the user query * * @author Ondra Zara <o.z.fw@seznam.cz> * @author Vincent de Lagabbe <vincent@delagabbe.com> */ function _getChanges($base_dir, $user = 0) { global $conf; global $opts; function globr($dir, $pattern) { $files = glob($dir.'/'.$pattern); foreach (glob($dir.'/*', GLOB_ONLYDIR) as $subdir) { $subfiles = globr($subdir, $pattern); $files = array_merge($files, $subfiles); } return $files; } $changes = array(); $alllist = globr($base_dir, '*.changes'); $skip = array('_comments.changes', '_dokuwiki.changes'); for ($i = 0; $i < count($alllist); $i++) { $fullname = $alllist[$i]; if (in_array(basename($fullname), $skip)) { continue; } $f = file($fullname); for ($j = 0; $j < count($f); $j++) { $change = parseChangelogLine($f[$j]); if ($opts['major_only'] && $change['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) { continue; } elseif (isset($opts['users']) && !in_array($change['user'], $opts['users'])) { continue; } if ($change['date'] > $opts['date_from'] && $change['date'] < $opts['date_to']) { $changes[] = $change; } } /* for all lines */ } /* for all files */ function cmp($a,$b) { $time1 = $a['date']; $time2 = $b['date']; if ($time1 == $time2) { return 0; } return ($time1 < $time2 ? 1 : -1); } uasort($changes, 'cmp'); return $changes; } /** * Display the changes as DK * * @author Ondra Zara <o.z.fw@seznam.cz> * @author Vincent de Lagabbe <vincent@delagabbe.com> */ function _show_changes() { global $conf; global $lang; ptln('<div class="level2">'); ptln('<ul>'); foreach($this->changes as $change){ $date = strftime($conf['dformat'], $change['date']); ptln($change['type']==='e' ? '<li class="minor">' : '<li>'); ptln('<div class="li">'); ptln($date.' '); ptln('<a href="'.wl($change['id'],"do=diff&rev=".$change['date']).'">'); $p = array(); $p['src'] = DOKU_BASE.'lib/images/diff.png'; $p['width'] = 15; $p['height'] = 11; $p['title'] = $lang['diff']; $p['alt'] = $lang['diff']; $att = buildAttributes($p); ptln("<img $att />"); ptln('</a> '); ptln('<a href="'.wl($change['id'],"do=revisions").'">'); $p = array(); $p['src'] = DOKU_BASE.'lib/images/history.png'; $p['width'] = 12; $p['height'] = 14; $p['title'] = $lang['btn_revs']; $p['alt'] = $lang['btn_revs']; $att = buildAttributes($p); ptln("<img $att />"); ptln('</a> '); ptln(html_wikilink(':'.$change['id'],$conf['useheading'] ? NULL : $change['id'])); ptln(' – '.hsc($change['sum'])); ptln('<span class="user" >'.$change['user'].' ('.$change['ip'].')</span>'); ptln('</div>'); ptln('</li>'); } ptln('</ul>'); ptln('</div>'); } /** * Generate CSV data from the list of changes * * @author Emmanuel BenoĆ®t <tseeker@nocternity.net> */ function _generate_csv() { global $conf; global $lang; global $auth; global $ID; $titles = []; $users = []; $xhtml_renderer = p_get_renderer('xhtml'); header('Content-type: text/csv;charset=utf-8'); header('Content-Disposition: attachment; filename="changes.csv"'); $fd = fopen('php://output', 'w'); fputcsv($fd, [ 'date', 'time', 'minor', 'pageid', 'pagetitle', 'userid', 'username', 'ip' ]); foreach($this->changes as $change){ $id = $change['id']; $user = $change['user']; if (!array_key_exists($id, $titles)) { resolve_pageid(getNS($id), $id, $exists, $change['date'], true); $titles[$id] = p_get_first_heading($id); } if (!array_key_exists($user, $users)) { $users[$user] = $auth->getUserData($user); } fputcsv($fd, [ strftime('%y-%m-%d', $change['date']), strftime('%T', $change['date']), $change['type'] === 'e' ? 'y' : 'n', $id, $titles[$id], $user, $users[$user]['name'], $change['ip'], ]); } fclose($fd); die; } }