1<?php
2/**
3 * Plugin : QueryChangelog
4 * Version : 1 (05/05/2009)
5 *
6 * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
7 * @author     Vincent de Lagabbe <vincent@delagabbe.com>
8 * @based_on   "userhistory" plugin by Ondra Zara <o.z.fw@seznam.cz>
9 * @based_on   "pagemove" plugin by Gary Owen <gary@isection.co.uk>
10 */
11
12if(!defined('DOKU_INC')) define('DOKU_INC',realpath(dirname(__FILE__).'/../../').'/');
13if(!defined('DOKU_PLUGIN')) define('DOKU_PLUGIN',DOKU_INC.'lib/plugins/');
14require_once(DOKU_PLUGIN.'admin.php');
15require_once(DOKU_INC.'inc/search.php');
16
17
18class admin_plugin_querychangelog extends DokuWiki_Admin_Plugin {
19    var $show_form = true;
20    var $errors = array();
21    var $opts = array();
22    var $changes = array();
23
24    /**
25     * function constructor
26     */
27    function admin_plugin_querychangelog(){
28      // enable direct access to language strings
29      $this->setupLocale();
30    }
31
32    /**
33     * return some info
34     */
35    function getInfo(){
36      return array(
37        'author' => 'Vincent de Lagabbe',
38        'email'  => 'vincent@delagabbe.com',
39        'date'   => '2009-05-05',
40        'name'   => 'QueryChangelog',
41        'desc'   => $this->lang['desc'],
42        'url'    => 'http://wiki.splitbrain.org/plugin:querychangelog',
43      );
44    }
45
46    function forAdminOnly(){ return false; }
47    function getMenuText() {
48        return $this->lang['menu'];
49    }
50
51    /**
52     * return sort order for position in admin menu
53     */
54    function getMenuSort() {
55      return 999;
56    }
57
58    /**
59     * handle user request
60     *
61     * @author  Vincent de Lagabbe <vincent@delagabbe.com>
62     */
63    function handle() {
64        global $lang;
65        global $ID;
66        global $opts;
67        global $conf;
68
69        $opts['ns'] = getNS($ID);
70
71        $opts['startd'] = isset($_REQUEST['startd']) ? $_REQUEST['startd'] : "YYYY-MM-DD HH:MM";
72        $opts['endd'] = isset($_REQUEST['endd']) ? $_REQUEST['endd'] : "YYYY-MM-DD HH:MM";
73
74        if (isset($_REQUEST['base_ns'])) {
75            if (!checkSecurityToken())
76                return;
77            // get begining date
78            $date_from = 0;
79            if ($_REQUEST['qcsd'] == '<def>') {
80                $date_from = $this->_qc_getstamp($opts['startd']);
81                if (!$date_from) {
82                    $this->errors[] = $this->lang['qc_err_date'];
83                }
84            }
85            // get ending date
86            $date_to = time();
87            if ($_REQUEST['qced'] == '<def>') {
88                $date_to = $this->_qc_getstamp($opts['endd']);
89                if (!$date_to) {
90                    $this->errors[] = $this->lang['qc_err_date'];
91                }
92            }
93
94            if ($date_from && $date_to - $date_from <= 0) {
95                $this->errors[] = $this->lang['qc_err_period'];
96            }
97
98            // get namespace
99            $opts['base_ns'] = $_REQUEST['base_ns'];
100            $base_dir = $this->_ns2path($opts['base_ns']);
101            if (count($this->errors) == 0) {
102                // get users
103                if (!in_array(".", $_REQUEST['qcusers'])) {
104                    $opts['users'] = $_REQUEST['qcusers'];
105                }
106
107                $opts['major_only'] = $_REQUEST['qcmo'] == '<on>';
108                $opts['date_from'] = $date_from;
109                $opts['date_to'] = $date_to;
110                $this->changes = $this->_getChanges($base_dir);
111                $this->show_form = false;
112            }
113        }
114
115    }
116
117    /**
118     * output appropriate html
119     *
120     * @author  Vincent de Lagabbe <vincent@delagabbe.com>
121     */
122    function html() {
123        global $lang;
124        global $conf;
125        global $ID;
126        global $opts;
127        global $auth;
128
129        ptln('<!-- QueryChangelog Plugin start -->');
130        if ($this->show_form) {
131            ptln($this->locale_xhtml('querychangelog'));
132            $this->_qc_form();
133        } else {
134            $href = wl($ID).'?do=admin&amp;page='.$this->getPluginName();
135            ptln('<p><a href="'.$href.'">['.$this->lang['qc_back'].']</a></p>');
136
137            // Display the query settings
138            $start_date = $opts['date_from'] == 0 ? $this->lang['qc_begining'] : strftime($conf['dformat'], $opts['date_from']);
139            ptln($this->lang['qc_res_from'].': '.$start_date.'<br/>');
140            ptln($this->lang['qc_res_to'].': '.strftime($conf['dformat'], $opts['date_to']).'<br/>');
141            ptln($this->lang['qc_res_ns'].': '.$opts['base_ns'].'<br/>');
142            $str_list = "";
143            if (isset($opts['users'])) {
144                $user_list = $auth->retrieveUsers();
145                foreach ($user_list as $user => $info) {
146                    if (in_array($user, $opts['users'])) {
147                        $str_list .= ($info['name'].', ');
148                    }
149                }
150                $str_list = substr($str_list, 0, -2);
151            } else {
152                $str_list = $this->lang['qc_res_all'];
153            }
154            ptln($this->lang['qc_res_users'].': '.$str_list.'<br/>');
155            ptln('<h2>'.$this->lang['qc_res_title'].'</h2>');
156
157            // Display the changelog
158            if (count($this->changes) == 0) {
159                ptln($this->lang['qc_res_nc'].'<p/>');
160            } else {
161                $this->_show_changes();
162            }
163        }
164        ptln('<!-- QueryChangelog Plugin end -->');
165    }
166
167
168    /**
169     * show the query changelog form
170     *
171     * @author  Gary Owen <gary@isection.co.uk>
172     * @author  Vincent de Lagabbe <vincent@delagabbe.com>
173     */
174    function _qc_form() {
175        global $ID;
176        global $lang;
177        global $conf;
178        global $opts;
179        global $auth;
180
181        ptln('  <div align="center">');
182        ptln('  <script language="Javascript">');
183        ptln('      function setradio( group, choice ) {');
184        ptln('        for ( i = 0 ; i < group.length ; i++ ) {');
185        ptln('          if ( group[i].value == choice )');
186        ptln('            group[i].checked = true;');
187        ptln('        }');
188        ptln('      }');
189        ptln('  </script>');
190        ptln('  <form name="frm" action="'.wl($ID).'" method="post">');
191        formSecurityToken();
192        ptln('  <fieldset>');
193        // output hidden values to ensure dokuwiki will return back to this plugin
194        ptln('    <input type="hidden" name="do"   value="admin" />');
195        ptln('    <input type="hidden" name="page" value="'.$this->getPluginName().'" />');
196        ptln('    <input type="hidden" name="id" value="'.$ID.'" />');
197        ptln('    <table border="0">');
198
199        //Show any errors
200        if (count($this->errors) > 0) {
201            ptln ('<tr><td bgcolor="red" colspan="5">');
202            foreach($this->errors as $error)
203                ptln ($error.'<br>');
204            ptln ('</td></tr>');
205        }
206        // Start and End dates Selection
207        ptln( '      <tr><td align="right" rowspan="2" nowrap><label><span>'.$this->lang['qc_from'].':</span></label></td>');
208        ptln( '        <td><input type="radio" name="qcsd" value="<def>" '.($_REQUEST['qcsd'] == '<def>' ? 'CHECKED' : '').'></td>');
209        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>');
210        ptln( '      <tr><td><input type="radio" name="qcsd" value="<beg>" '.($_REQUEST['qcsd'] != '<def>' ? 'CHECKED' : '').'></td>');
211        ptln( '        <td align="left">'.$this->lang['qc_begining'].'</td>');
212        ptln( '      </tr>');
213        ptln( '      <tr><td>&nbsp;</td></tr>');
214
215        ptln( '      <tr><td align="right" rowspan="2" nowrap><label><span>'.$this->lang['qc_to'].':</span></label></td>');
216        ptln( '        <td><input type="radio" name="qced" value="<def>" '.($_REQUEST['qced'] == '<def>' ? 'CHECKED' : '').'></td>');
217        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>');
218        ptln( '      <tr><td><input type="radio" name="qced" value="<beg>" '.($_REQUEST['qced'] != '<def>' ? 'CHECKED' : '').'></td>');
219        ptln( '        <td align="left">'.$this->lang['qc_now'].'</td>');
220        ptln( '      </tr>');
221        ptln( '      <tr><td colspan="5">&nbsp;</td></tr>');
222
223
224        // NS Selection
225        $namesp = array( 0 => '' );     //Include root in namespace list
226        search($namesp,$conf['metadir'],'search_namespaces',$opts);
227        sort($namesp);
228        ptln( '      <tr>');
229        ptln( '        <td align="right" nowrap><label><span>'.$this->lang['qc_base_ns'].':</span></label></td>');
230        ptln( '        <td>&nbsp;</td>');
231        ptln( '        <td colspan="3" align="left"><select name="base_ns">');
232        foreach($namesp as $row) {
233            if ( auth_quickaclcheck($row['id'].':*') >= AUTH_CREATE or $row['id'] == $opts['ns'] ) {
234                ptln ( '          <option value="'.$row['id'].
235                       ($_REQUEST['base_ns'] ?
236                        (($row['id'] ? $row['id'] : ":") == $_REQUEST['base_ns'] ? '" SELECTED>' : '">') :
237                        ($row['id'] == $opts['ns'] ? '" SELECTED>' : '">') ).
238                       ($row['id'] ? $row['id'].':' : ": ".$this->lang['qc_root']).
239                       ($row['id'] == $opts['ns'] ? ' '.$this->lang['qc_current'] : '').
240                       "</option>" );
241            }
242        }
243        ptln( '          </select></td>');
244        ptln( '      </tr>');
245        ptln( '      <tr><td colspan="5">&nbsp;</td></tr>');
246
247        // User(s) selection
248        ptln( '      <tr>');
249        ptln( '        <td align="right" nowrap><label><span>'.$this->lang['qc_users'].':</span></label></td>');
250        ptln( '        <td>&nbsp;</td>');
251        ptln( '        <td colspan="3" align="left"><select name="qcusers[]" size="10" multiple="yes">');
252        ptln ( '          <option value="." SELECTED>'.$this->lang['qc_all_users'].'</option>');
253        $user_list = $auth->retrieveUsers();
254        foreach ($user_list as $user => $info) {
255            ptln ( '          <option value="'.$user.'">'.$info['name'].' ('.$user.')</option>');
256        }
257        ptln( '          </select></td>');
258        ptln( '      </tr>');
259        ptln( '      <tr><td colspan="5">&nbsp;</td></tr>');
260
261        // Major changes / all changes switch
262        ptln( '      <tr>');
263        ptln( '        <td align="right" nowrap><label><span>'.$this->lang['qc_major_only'].':</span></label></td>');
264        ptln( '        <td><input type="checkbox" name="qcmo" value="<on>" '.($_REQUEST['qcmo'] == '<on>' ? 'checked="checked"' : '').'"></td>');
265        ptln( '      </tr>');
266        ptln( '      <tr><td colspan="5">&nbsp;</td></tr>');
267
268        // Submit
269        ptln( '      <tr>');
270        ptln( '        <td colspan="5" align="center"><input type="submit" value="'.formtext($this->lang['qc_submit']).'" class="button" /></td>');
271        ptln( '      </tr>');
272        ptln( '    </table>');
273        ptln( '  </fieldset>');
274        ptln( '</form>');
275        ptln( '</div>');
276    }
277
278    /**
279     * Convert namespace to its corresponding path in meta
280     */
281    function _ns2path($ns) {
282        global $conf ;
283
284        if ($ns == ':' || $ns == '')
285            return $conf ['metadir'] ;
286        $ns = trim ($ns, ':') ;
287        $path = $conf ['metadir'] . '/' . str_replace (':', '/', $ns) ;
288
289        return $path ;
290    }
291
292    /**
293     * Convert the user input date into a UNIX timestamps.
294     *
295     * @author  Vincent de Lagabbe <vincent@delagabbe.com>
296     */
297    function _qc_getstamp($str_date) {
298        $date = array();
299        $tstamp = false;
300
301        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)) {
302            return false;
303        }
304        $tstamp = mktime($date[4], $date[5], 0, $date[2], $date[3], $date[1]);
305        return $tstamp;
306    }
307
308    /**
309     * Get the changelogs entries according to the user query
310     *
311     * @author  Ondra Zara <o.z.fw@seznam.cz>
312     * @author  Vincent de Lagabbe <vincent@delagabbe.com>
313     */
314    function _getChanges($base_dir, $user = 0) {
315		global $conf;
316        global $opts;
317
318        function globr($dir, $pattern) {
319			$files = glob($dir.'/'.$pattern);
320			foreach (glob($dir.'/*', GLOB_ONLYDIR) as $subdir) {
321				$subfiles = globr($subdir, $pattern);
322				$files = array_merge($files, $subfiles);
323			}
324			return $files;
325		}
326
327		$changes = array();
328		$alllist = globr($base_dir, '*.changes');
329		$skip = array('_comments.changes', '_dokuwiki.changes');
330
331		for ($i = 0; $i < count($alllist); $i++) {
332			$fullname = $alllist[$i];
333			if (in_array(basename($fullname), $skip)) {
334                continue;
335            }
336			$f = file($fullname);
337			for ($j = 0; $j < count($f); $j++) {
338				$change = parseChangelogLine($f[$j]);
339                if ($opts['major_only'] && $change['type'] === DOKU_CHANGE_TYPE_MINOR_EDIT) {
340                    continue;
341                } elseif (isset($opts['users']) && !in_array($change['user'], $opts['users'])) {
342                    continue;
343                } if ($change['date'] > $opts['date_from'] &&
344                    $change['date'] < $opts['date_to']) {
345                    $changes[] = $change;
346                }
347			} /* for all lines */
348		} /* for all files */
349
350		function cmp($a,$b) {
351			$time1 = $a['date'];
352			$time2 = $b['date'];
353			if ($time1 == $time2) { return 0; }
354			return ($time1 < $time2 ? 1 : -1);
355		}
356
357		uasort($changes, 'cmp');
358
359		return $changes;
360    }
361
362    /**
363     * Display the changes as DK
364     *
365     * @author  Ondra Zara <o.z.fw@seznam.cz>
366     * @author  Vincent de Lagabbe <vincent@delagabbe.com>
367     */
368    function _show_changes() {
369        global $conf;
370        global $lang;
371
372        ptln('<div class="level2">');
373		ptln('<ul>');
374
375		foreach($this->changes as $change){
376			$date = strftime($conf['dformat'], $change['date']);
377			ptln($change['type']==='e' ? '<li class="minor">' : '<li>');
378			ptln('<div class="li">');
379
380			ptln($date.' ');
381
382			ptln('<a href="'.wl($change['id'],"do=diff&rev=".$change['date']).'">');
383			$p = array();
384			$p['src']    = DOKU_BASE.'lib/images/diff.png';
385			$p['width']  = 15;
386			$p['height'] = 11;
387			$p['title']  = $lang['diff'];
388			$p['alt']    = $lang['diff'];
389			$att = buildAttributes($p);
390			ptln("<img $att />");
391			ptln('</a> ');
392
393			ptln('<a href="'.wl($change['id'],"do=revisions").'">');
394			$p = array();
395			$p['src']    = DOKU_BASE.'lib/images/history.png';
396			$p['width']  = 12;
397			$p['height'] = 14;
398			$p['title']  = $lang['btn_revs'];
399			$p['alt']    = $lang['btn_revs'];
400			$att = buildAttributes($p);
401			ptln("<img $att />");
402			ptln('</a> ');
403
404			ptln(html_wikilink(':'.$change['id'],$conf['useheading'] ? NULL : $change['id']));
405			ptln(' &ndash; '.hsc($change['sum']));
406
407            ptln('<span class="user" >'.$change['user'].' ('.$change['ip'].')</span>');
408
409			ptln('</div>');
410			ptln('</li>');
411		}
412		ptln('</ul>');
413
414		ptln('</div>');
415    }
416}
417