1<?php 2/** 3 * DokuWiki Plugin cleanoldips (Action Component) 4 * 5 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 6 * @author Michael Große <dokuwiki@cosmocode.de> 7 */ 8 9class action_plugin_cleanoldips extends DokuWiki_Action_Plugin 10{ 11 12 const SECONDS_IN_A_DAY = 86400; 13 14 /** 15 * Registers a callback function for a given event 16 * 17 * @param Doku_Event_Handler $controller DokuWiki's event controller object 18 * 19 * @return void 20 */ 21 public function register(Doku_Event_Handler $controller) 22 { 23 $controller->register_hook('INDEXER_TASKS_RUN', 'BEFORE', $this, 'handleIndexerTasksRun'); 24 $controller->register_hook('TASK_RECENTCHANGES_TRIM', 'BEFORE', $this, 'initiateMediaChangelogClean'); 25 } 26 27 /** 28 * [Custom event handler which performs action] 29 * 30 * Called for event: INDEXER_TASKS_RUN 31 * 32 * @param Doku_Event $event event object by reference 33 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 34 * handler was registered] 35 * 36 * @return void 37 */ 38 public function handleIndexerTasksRun(Doku_Event $event, $param) 39 { 40 global $ID; 41 $changelogFN = metaFN($ID, '.changes'); 42 if (!file_exists($changelogFN)) { 43 return; 44 } 45 $cacheFile = $this->getOurCacheFilename($ID); 46 if (file_exists($cacheFile) && (time() - filemtime($cacheFile) < self::SECONDS_IN_A_DAY)) { 47 // we already cleaned this page in the last 24h 48 return; 49 } 50 51 $event->preventDefault(); 52 $event->stopPropagation(); 53 54 touch($cacheFile); 55 56 $this->cleanChangelog($ID, $changelogFN); 57 } 58 59 /** 60 * [Custom event handler which performs action] 61 * 62 * Called for event: TASK_RECENTCHANGES_TRIM 63 * 64 * @param Doku_Event $event event object by reference 65 * @param mixed $param [the parameters passed as fifth argument to register_hook() when this 66 * handler was registered] 67 * 68 * @return void 69 */ 70 public function initiateMediaChangelogClean(Doku_Event $event, $param) 71 { 72 if (!$event->data['isMedia']) { 73 return; 74 } 75 76 foreach ($event->data['removedChangelogLines'] as $mediaChangelogLine) { 77 list(, , , $mediaID,) = explode("\t", $mediaChangelogLine, 5); 78 79 $changelogFN = mediaMetaFN($mediaID, '.changes'); 80 if (!file_exists($changelogFN)) { 81 continue; 82 } 83 84 $this->cleanChangelog($mediaID, $changelogFN); 85 } 86 } 87 88 /** 89 * Remove IPs from changelog entries that are older than $conf['recent_days'] 90 * 91 * @param string $id 92 * @param string $changelogFN 93 */ 94 public function cleanChangelog($id, $changelogFN) 95 { 96 if (!file_exists($changelogFN)) { 97 return; 98 } 99 global $conf; 100 101 $cacheFile = $this->getOurCacheFilename($id, true); 102 $cacheStartPosition = (int)file_get_contents($cacheFile); 103 $startPosition = $this->validateStartPosition($cacheStartPosition, $changelogFN); 104 105 $handle = fopen($changelogFN, 'rb+'); 106 fseek($handle, $startPosition); 107 $ageCutoff = (int)$conf['recent_days'] * self::SECONDS_IN_A_DAY; 108 109 while (($line = fgets($handle)) !== false) { 110 list($timestamp, $ip, $rest) = explode("\t", $line, 3); 111 $ageOfEntry = time() - (int)$timestamp; 112 if ($ageOfEntry < $ageCutoff) { 113 // this and the remaining lines are newer than $conf['recent_days'] 114 $positionAtBeginningOfLine = ftell($handle) - strlen($line); 115 fseek($handle, $positionAtBeginningOfLine); 116 break; 117 } 118 119 $cleanedLine = implode("\t", [$timestamp, str_pad('', strlen($ip)), $rest]); 120 $writeOffset = ftell($handle) - strlen($line); 121 fseek($handle, $writeOffset); 122 $bytesWritten = fwrite($handle, $cleanedLine); 123 if ($bytesWritten === false) { 124 throw new RuntimeException('There was an unknown error writing the changlog for ' . $id); 125 } 126 } 127 128 file_put_contents($cacheFile, ftell($handle)); 129 fclose($handle); 130 } 131 132 /** 133 * Get the start position from cache and ensure its valid by performing some sanity checks 134 * 135 * @param int $cacheStartPosition 136 * @param string $changelogFile the changelog for pageid 137 * 138 * @return int the start position 139 */ 140 public function validateStartPosition($cacheStartPosition, $changelogFile) 141 { 142 if ($cacheStartPosition > filesize($changelogFile)) { 143 return 0; 144 } 145 if ($cacheStartPosition > 0) { 146 $handle = fopen($changelogFile, 'rb'); 147 fseek($handle, $cacheStartPosition - 1); 148 $previousChar = fread($handle, 1); 149 fclose($handle); 150 151 if ($previousChar !== "\n") { 152 return 0; 153 } 154 } 155 return $cacheStartPosition; 156 } 157 158 /** 159 * Get the filename of this plugin's cachefile for a page 160 * 161 * @param string $pageid full id of the page 162 * @param bool $create create the cache file if it doesn't exists 163 * 164 * @return string the filename of this plugin's cachefile 165 */ 166 protected function getOurCacheFilename($pageid, $create = false) 167 { 168 $cacheFN = getCacheName('_' . $pageid . 'cleanoldips'); 169 if ($create && !file_exists($cacheFN)) { 170 touch($cacheFN); 171 } 172 return $cacheFN; 173 } 174} 175