1<?php 2/** 3 * DokuWiki Plugin gdpr (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_gdpr_oldips 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($changelogFN); 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($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($changelogFN); 85 } 86 } 87 88 /** 89 * Remove IPs from changelog entries that are older than $conf['recent_days'] 90 * 91 * @param string $changelogFN 92 */ 93 public function cleanChangelog($changelogFN) 94 { 95 if (!file_exists($changelogFN)) { 96 return; 97 } 98 global $conf; 99 100 $cacheFile = $this->getOurCacheFilename($changelogFN, true); 101 $cacheStartPosition = (int)file_get_contents($cacheFile); 102 $startPosition = $this->validateStartPosition($cacheStartPosition, $changelogFN); 103 104 $handle = fopen($changelogFN, 'rb+'); 105 fseek($handle, $startPosition); 106 $ageCutoff = (int)$conf['recent_days'] * self::SECONDS_IN_A_DAY; 107 108 while (($line = fgets($handle)) !== false) { 109 list($timestamp, $ip, $rest) = explode("\t", $line, 3); 110 $ageOfEntry = time() - (int)$timestamp; 111 if ($ageOfEntry < $ageCutoff) { 112 // this and the remaining lines are newer than $conf['recent_days'] 113 $positionAtBeginningOfLine = ftell($handle) - strlen($line); 114 fseek($handle, $positionAtBeginningOfLine); 115 break; 116 } 117 118 $cleanedLine = implode("\t", [$timestamp, str_pad('', strlen($ip)), $rest]); 119 $writeOffset = ftell($handle) - strlen($line); 120 fseek($handle, $writeOffset); 121 $bytesWritten = fwrite($handle, $cleanedLine); 122 if ($bytesWritten === false) { 123 throw new RuntimeException('There was an unknown error writing the changlog ' . $changelogFN); 124 } 125 } 126 127 file_put_contents($cacheFile, ftell($handle)); 128 fclose($handle); 129 } 130 131 /** 132 * Get the start position from cache and ensure its valid by performing some sanity checks 133 * 134 * @param int $cacheStartPosition 135 * @param string $changelogFile the changelog for pageid 136 * 137 * @return int the start position 138 */ 139 public function validateStartPosition($cacheStartPosition, $changelogFile) 140 { 141 if ($cacheStartPosition > filesize($changelogFile)) { 142 return 0; 143 } 144 if ($cacheStartPosition > 0) { 145 $handle = fopen($changelogFile, 'rb'); 146 fseek($handle, $cacheStartPosition - 1); 147 $previousChar = fread($handle, 1); 148 fclose($handle); 149 150 if ($previousChar !== "\n") { 151 return 0; 152 } 153 } 154 return $cacheStartPosition; 155 } 156 157 /** 158 * Get the filename of this plugin's cachefile for a page 159 * 160 * @param string $changelogFN filename of the changelog 161 * @param bool $create create the cache file if it doesn't exists 162 * 163 * @return string the filename of this plugin's cachefile 164 */ 165 protected function getOurCacheFilename($changelogFN, $create = false) 166 { 167 $cacheFN = getCacheName('_' . $changelogFN, '.plugin_gdpr'); 168 if ($create && !file_exists($cacheFN)) { 169 touch($cacheFN); 170 } 171 return $cacheFN; 172 } 173} 174