1<?php 2 3use dokuwiki\Extension\ActionPlugin; 4use dokuwiki\Extension\Event; 5use dokuwiki\Extension\EventHandler; 6use dokuwiki\File\PageFile; 7 8/** 9 * DokuWiki Plugin renderrevisions (Action Component) 10 * 11 * @license GPL 2 http://www.gnu.org/licenses/gpl-2.0.html 12 * @author Andreas Gohr <dokuwiki@cosmocode.de> 13 */ 14class action_plugin_renderrevisions extends ActionPlugin 15{ 16 /** @var array list of pages that are processed by the plugin */ 17 protected $pages = []; 18 19 /** @var string|null the current page being saved, used to overwrite the contentchanged check */ 20 protected $current = null; 21 22 /** @inheritDoc */ 23 public function register(EventHandler $controller) 24 { 25 $controller->register_hook('PARSER_CACHE_USE', 'AFTER', $this, 'handleParserCacheUse'); 26 27 $controller->register_hook( 28 'RENDERER_CONTENT_POSTPROCESS', 29 'AFTER', 30 $this, 31 'handleRenderContent', 32 null, 33 PHP_INT_MAX // other plugins might want to change the content before we see it 34 ); 35 36 $controller->register_hook('COMMON_WIKIPAGE_SAVE', 'BEFORE', $this, 'handleCommonWikipageSave'); 37 } 38 39 /** 40 * Event handler for PARSER_CACHE_USE 41 * 42 * @see https://www.dokuwiki.org/devel:event:PARSER_CACHE_USE 43 * @param Event $event Event object 44 * @param mixed $param optional parameter passed when event was registered 45 * @return void 46 */ 47 public function handleParserCacheUse(Event $event, $param) 48 { 49 $cacheObject = $event->data; 50 51 if (!$cacheObject->page) return; 52 if ($cacheObject->mode !== 'xhtml') return; 53 54 // only process pages that match both the skip and match regex 55 56 $page = $cacheObject->page; 57 try { 58 [$skipRE, $matchRE] = $this->getRegexps(); 59 } catch (\Exception $e) { 60 msg(hsc($e->getMessage()), -1); 61 return; 62 } 63 if ( 64 ($skipRE && preg_match($skipRE, ":$page")) || 65 ($matchRE && !preg_match($matchRE, ":$page")) 66 ) { 67 return; 68 } 69 70 // remember that this page was processed 71 // This is a somewhat ugly workaround for when text snippets are rendered within the same page. 72 // Those snippets will not have a page context set during cache use event and thus not be processed 73 // later on in the RENDERER_CONTENT_POSTPROCESS event 74 $this->pages[$page] = true; 75 } 76 77 78 /** 79 * Event handler for RENDERER_CONTENT_POSTPROCESS 80 * 81 * @see https://www.dokuwiki.org/devel:event:RENDERER_CONTENT_POSTPROCESS 82 * @param Event $event Event object 83 * @param mixed $param optional parameter passed when event was registered 84 * @return void 85 */ 86 public function handleRenderContent(Event $event, $param) 87 { 88 [$format, $xhtml] = $event->data; 89 if ($format !== 'xhtml') return; 90 91 // thanks to the $this->pages property we might be able to skip some of those checks, but they don't hurt 92 global $ACT; 93 global $REV; 94 global $DATE_AT; 95 global $ID; 96 global $INFO; 97 if ($ACT !== 'show') return; 98 if ($REV) return; 99 if ($DATE_AT) return; 100 if (!$INFO['exists']) return; 101 if (!$ID) return; 102 if (!isset($this->pages[$ID])) return; 103 104 $md5cache = getCacheName($ID, '.renderrevision'); 105 106 // no or outdated MD5 cache, create new one 107 // this means a new revision of the page has been created naturally 108 // we store the new render result and are done 109 if (!file_exists($md5cache) || filemtime(wikiFN($ID)) > filemtime($md5cache)) { 110 file_put_contents($md5cache, md5($xhtml)); 111 return; 112 } 113 114 // only act on pages that have not been changed very recently 115 if (time() - filemtime(wikiFN($ID)) < $this->getConf('maxfrequency')) { 116 return; 117 } 118 119 // get the render result as it were when the page was last changed 120 $oldMd5 = file_get_contents($md5cache); 121 122 // did the rendered content change? 123 if ($oldMd5 === md5($xhtml)) { 124 return; 125 } 126 127 // time to create a new revision 128 $this->current = $ID; 129 (new PageFile($ID))->saveWikiText(rawWiki($ID), $this->getLang('summary')); 130 $this->current = null; 131 } 132 133 /** 134 * Event handler for COMMON_WIKIPAGE_SAVE 135 * 136 * Overwrite the contentChanged flag to force a new revision even though the content did not change 137 * 138 * @see https://www.dokuwiki.org/devel:event:COMMON_WIKIPAGE_SAVE 139 * @param Event $event Event object 140 * @param mixed $param optional parameter passed when event was registered 141 * @return void 142 */ 143 public function handleCommonWikipageSave(Event $event, $param) 144 { 145 if ($this->current !== $event->data['id']) return; 146 $event->data['contentChanged'] = true; 147 } 148 149 150 /** 151 * Read the skip and match regex from the config 152 * 153 * Ensures the regular expressions are valid 154 * 155 * @return string[] [$skipRE, $matchRE] 156 * @throws \Exception if the regular expressions are invalid 157 */ 158 public function getRegexps() 159 { 160 $skip = $this->getConf('skipRegex'); 161 $skipRE = ''; 162 $match = $this->getConf('matchRegex'); 163 $matchRE = ''; 164 165 if ($skip) { 166 $skipRE = '/' . $skip . '/'; 167 if (@preg_match($skipRE, '') === false) { 168 throw new \Exception('Invalid regular expression in $conf[\'skipRegex\']. ' . preg_last_error_msg()); 169 } 170 } 171 172 if ($match) { 173 $matchRE = '/' . $match . '/'; 174 if (@preg_match($matchRE, '') === false) { 175 throw new \Exception('Invalid regular expression in $conf[\'matchRegex\']. ' . preg_last_error_msg()); 176 } 177 } 178 return [$skipRE, $matchRE]; 179 } 180} 181