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&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> </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"> </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> </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"> </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> </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"> </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"> </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(' – '.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