1<?php 2 3/** 4 * This plugin is used to cleanup the history 5 * 6 * @see http://dokuwiki.org/plugin:clearhistory 7 * @author Dominik Eckelmann <deckelmann@gmail.com> 8 */ 9class admin_plugin_clearhistory extends DokuWiki_Admin_Plugin 10{ 11 12 /** 13 * pages in a hiracical view 14 * 15 * "<namespace>" => array( ... ) 16 * "pages" => array ( pagenames ) 17 */ 18 protected $pages = array(); 19 20 /** 21 * counts deleted pages for a run 22 */ 23 public $delcounter = 0; 24 25 /** 26 * handle the request befor html output 27 * 28 * @see html() 29 */ 30 public function handle() 31 { 32 $onlySmall = false; 33 $onlyNoComment = false; 34 if (isset($_REQUEST['onlysmall']) && $_REQUEST['onlysmall'] == 'on') $onlySmall = true; 35 if (isset($_REQUEST['onlynocomment']) && $_REQUEST['onlynocomment'] == 'on') $onlyNoComment = true; 36 37 if (isset($_GET['clear'])) { 38 if ($_GET['clear'] == 1) { 39 $this->_scanRecents(30, $onlySmall, $onlyNoComment); 40 } else { 41 if ($_GET['clear'] == 2) { 42 $_GET['ns'] = cleanID($_GET['ns']); 43 $this->_scanNamespace($_GET['ns'], $onlySmall, $onlyNoComment); 44 } 45 } 46 msg(sprintf($this->getLang('deleted'), $this->delcounter), 1); 47 } 48 } 49 50 /** 51 * output html for the admin page 52 */ 53 public function html() 54 { 55 echo '<h1>' . $this->getLang('menu') . '</h1>'; 56 echo '<form action="' . wl() . '" method="GET"><fieldset class="clearhistory">'; 57 echo '<input type="hidden" name="do" value="admin" />'; 58 echo '<input type="hidden" name="page" value="clearhistory" />'; 59 60 echo '<fieldset><input type="radio" name="clear" value="1" id="c1" checked="checked" /> <label for="c1">'; 61 echo $this->getLang('clean on recent changes') . '</label></fieldset><br/>'; 62 63 echo '<fieldset><input type="radio" name="clear" value="2" id="c2" /> <label for="c2">'; 64 echo $this->getLang('clean on namespace'); 65 echo '</label><br /><input type="text" name="ns" class="edit" />'; 66 echo '</fieldset><br/>'; 67 echo '<fieldset>'; 68 echo '<input type="checkbox" name="onlysmall" id="c3" ' . ($this->getConf('autoclearonlysmall') ? 'checked="checked"' : '') . ' /> <label for="c3">' . $this->getLang('onlysmall') . '</label><br />'; 69 echo '<input type="checkbox" name="onlynocomment" id="c4" ' . ($this->getConf('autoclearonlynocomment') ? 'checked="checked"' : '') . ' /> <label for="c4">' . $this->getLang('onlynocomment') . '</label><br />'; 70 echo '</fieldset>'; 71 72 echo '<input type="submit" value="' . $this->getLang('do') . '" class="button" /></fieldset>'; 73 echo '</form>'; 74 75 echo '<div class="clearhistory">' . $this->locale_xhtml('intro') . '</div>'; 76 } 77 78 /** 79 * Scans throu a namespace and count the deleted pages in $this->delcounter 80 * 81 * @param string $ns the namespace to search in 82 * @param boolean $onlySmall only delete small changes on true 83 * @param boolean $onlyNoComment don't delete changes with a comment on true 84 */ 85 protected function _scanNamespace($ns, $onlySmall = false, $onlyNoComment = false) 86 { 87 $this->delcounter = 0; 88 $this->_scan($ns, $onlySmall, $onlyNoComment); 89 } 90 91 /** 92 * Scans namespaces for deletable revisions 93 * 94 * @param string $ns the namespace to search in 95 * @param boolean $onlySmall only delete small changes on true 96 * @param boolean $onlyNoComment don't delete changes with a comment on true 97 */ 98 protected function _scan($ns = '', $onlySmall = false, $onlyNoComment = false) 99 { 100 $dir = preg_replace('/\.txt(\.gz)?/i', '', wikiFN($ns)); 101 $dir = rtrim($dir, '/'); 102 if (!is_dir($dir)) return; 103 $dh = opendir($dir); 104 if (!$dh) { 105 echo 'error'; 106 return; 107 } 108 while (($file = readdir($dh)) !== false) { 109 if ($file == '.' || $file == '..') continue; 110 if (is_dir($dir . '/' . $file)) { 111 $this->_scan($ns . ':' . $file); 112 continue; 113 } 114 if ($file[0] == '_') continue; 115 if (substr($file, -4) == '.txt') { 116 $name = substr($file, 0, -4); 117 $this->_parseChangesFile(metaFN($ns . ':' . $name, '.changes'), $ns . ':' . $name, $onlySmall, 118 $onlyNoComment); 119 } 120 } 121 closedir($dh); 122 } 123 124 /** 125 * Scans the recent changed files for changes 126 * 127 * @param int $num number of last changed files to scan 128 * @param boolean $onlySmall only delete small changes on true 129 * @param boolean $onlyNoComment don't delete changes with a comment on true 130 */ 131 public function _scanRecents($num = 30, $onlySmall = false, $onlyNoComment = false) 132 { 133 $recents = getRecents(0, $num); 134 135 $this->delcounter = 0; 136 foreach ($recents as $recent) { 137 $this->_parseChangesFile(metaFN($recent['id'], '.changes'), $recent['id'], $onlySmall, $onlyNoComment); 138 } 139 } 140 141 /** 142 * Parses a .changes file for deletable pages and deletes them. 143 * 144 * @param string $file the path to the change file 145 * @param string $page wiki pagename 146 * @param boolean $onlySmall deletes only small changes 147 * @param boolean $onlyNoComment deletes only entrys without a comment 148 */ 149 protected function _parseChangesFile($file, $page, $onlySmall = false, $onlyNoComment = false) 150 { 151 if (!is_file($file)) return; 152 if (checklock($page)) return; 153 lock($page); 154 $content = file_get_contents($file); 155 // get page informations 156 $max = preg_match_all('/^([0-9]+)\s+?(\S+)\s+?([ECDR])\s+?(\S+)\s+?(\S*)\s+?(.*)$/im', 157 $content, $match); 158 if ($max <= 1) return; 159 // set mark to creation entry 160 $cmptime = $match[1][0] + 9999999999; 161 $cmpuser = (empty($match[5][$max - 1])) ? $match[2][$max - 1] : $match[5][$max - 1]; // user or if not logged in ip 162 $newcontent = ''; 163 for ($i = $max - 1; $i >= 0; $i--) { 164 $user = (empty($match[5][$i])) ? $match[2][$i] : $match[5][$i]; // user or if not logged in ip 165 $time = $match[1][$i]; 166 // Creations arnt touched 167 if ($match[3][$i] != "E" && $match[3][$i] != "e") { 168 $cmpuser = $user; 169 $cmptime = $time; 170 $newcontent = $this->_addLine($match, $i) . $newcontent; 171 continue; 172 } 173 // if not the same user -> new mark and continue 174 if ($user != $cmpuser) { 175 $cmpuser = $user; 176 $cmptime = $time; 177 $newcontent = $this->_addLine($match, $i) . $newcontent; 178 continue; 179 } 180 181 // if onlySmall is set we just want to handle the small matches 182 if ($onlySmall && $match[3][$i] == 'E') { 183 $cmpuser = $user; 184 $cmptime = $time; 185 $newcontent = $this->_addLine($match, $i) . $newcontent; 186 continue; 187 } 188 189 // if onlyNoComment is set we pass all lines with a comment 190 if ($onlyNoComment && trim($match[6][$i]) != '') { 191 $cmpuser = $user; 192 $cmptime = $time; 193 $newcontent = $this->_addLine($match, $i) . $newcontent; 194 continue; 195 } 196 197 // check the time difference between the entrys 198 if ($cmptime - (60 * 60) < $time) { 199 @unlink(wikiFN($match[4][$i], $time)); //try the original name 200 @unlink(wikiFN($match[4][$max - 1], $time)); //try the latest name (move plugin) issue #1 201 $this->delcounter++; 202 continue; 203 } 204 $cmptime = $time; 205 $newcontent = $this->_addLine($match, $i) . $newcontent; 206 } 207 unlock($page); 208 io_saveFile($file, $newcontent); 209 } 210 211 /** 212 * Return a line from the set of regex matches, terminated with a newline char 213 * 214 * @param array $match 215 * @param int $i 216 * @return string 217 */ 218 protected function _addLine($match, $i) 219 { 220 return $match[0][$i] . "\n"; 221 } 222 223} 224