1<?php 2 3namespace dokuwiki; 4 5use Doku_Event; 6use Sitemapper; 7use Subscription; 8 9class TaskRunner 10{ 11 public function run() 12 { 13 global $INPUT, $conf, $ID; 14 15 // keep running after browser closes connection 16 @ignore_user_abort(true); 17 18 // check if user abort worked, if yes send output early 19 $defer = !@ignore_user_abort() || $conf['broken_iua']; 20 $output = $INPUT->has('debug') && $conf['allowdebug']; 21 if(!$defer && !$output){ 22 $this->sendGIF(); 23 } 24 25 $ID = cleanID($INPUT->str('id')); 26 27 // Catch any possible output (e.g. errors) 28 if(!$output) { 29 ob_start(); 30 } else { 31 header('Content-Type: text/plain'); 32 } 33 34 // run one of the jobs 35 $tmp = []; // No event data 36 $evt = new Doku_Event('INDEXER_TASKS_RUN', $tmp); 37 if ($evt->advise_before()) { 38 $this->runIndexer() or 39 $this->runSitemapper() or 40 $this->sendDigest() or 41 $this->runTrimRecentChanges() or 42 $this->runTrimRecentChanges(true) or 43 $evt->advise_after(); 44 } 45 46 if(!$output) { 47 ob_end_clean(); 48 if($defer) { 49 $this->sendGIF(); 50 } 51 } 52 } 53 54 /** 55 * Just send a 1x1 pixel blank gif to the browser 56 * 57 * @author Andreas Gohr <andi@splitbrain.org> 58 * @author Harry Fuecks <fuecks@gmail.com> 59 */ 60 function sendGIF(){ 61 $img = base64_decode('R0lGODlhAQABAIAAAAAAAP///yH5BAEAAAEALAAAAAABAAEAAAIBTAA7'); 62 header('Content-Type: image/gif'); 63 header('Content-Length: '.strlen($img)); 64 header('Connection: Close'); 65 print $img; 66 tpl_flush(); 67 // Browser should drop connection after this 68 // Thinks it's got the whole image 69 } 70 71 /** 72 * Trims the recent changes cache (or imports the old changelog) as needed. 73 * 74 * @param bool $media_changes If the media changelog shall be trimmed instead of 75 * the page changelog 76 * 77 * @return bool 78 * 79 * @author Ben Coburn <btcoburn@silicodon.net> 80 */ 81 protected function runTrimRecentChanges($media_changes = false) 82 { 83 global $conf; 84 85 echo "runTrimRecentChanges($media_changes): started" . NL; 86 87 $fn = ($media_changes ? $conf['media_changelog'] : $conf['changelog']); 88 89 // Trim the Recent Changes 90 // Trims the recent changes cache to the last $conf['changes_days'] recent 91 // changes or $conf['recent'] items, which ever is larger. 92 // The trimming is only done once a day. 93 if (file_exists($fn) && 94 (@filemtime($fn . '.trimmed') + 86400) < time() && 95 !file_exists($fn . '_tmp')) { 96 @touch($fn . '.trimmed'); 97 io_lock($fn); 98 $lines = file($fn); 99 if (count($lines) <= $conf['recent']) { 100 // nothing to trim 101 io_unlock($fn); 102 echo "runTrimRecentChanges($media_changes): finished" . NL; 103 return false; 104 } 105 106 io_saveFile($fn . '_tmp', ''); // presave tmp as 2nd lock 107 $trim_time = time() - $conf['recent_days'] * 86400; 108 $out_lines = []; 109 $old_lines = []; 110 for ($i = 0; $i < count($lines); $i++) { 111 $log = parseChangelogLine($lines[$i]); 112 if ($log === false) { 113 continue; 114 } // discard junk 115 if ($log['date'] < $trim_time) { 116 $old_lines[$log['date'] . ".$i"] = $lines[$i]; // keep old lines for now (append .$i to prevent key collisions) 117 } else { 118 $out_lines[$log['date'] . ".$i"] = $lines[$i]; // definitely keep these lines 119 } 120 } 121 122 if (count($lines) == count($out_lines)) { 123 // nothing to trim 124 @unlink($fn . '_tmp'); 125 io_unlock($fn); 126 echo "runTrimRecentChanges($media_changes): finished" . NL; 127 return false; 128 } 129 130 // sort the final result, it shouldn't be necessary, 131 // however the extra robustness in making the changelog cache self-correcting is worth it 132 ksort($out_lines); 133 $extra = $conf['recent'] - count($out_lines); // do we need extra lines do bring us up to minimum 134 if ($extra > 0) { 135 ksort($old_lines); 136 $out_lines = array_merge(array_slice($old_lines, -$extra), $out_lines); 137 } 138 139 $eventData = [ 140 'trimmedChangelogLines' => $out_lines, 141 'removedChangelogLines' => $extra > 0 ? array_slice($old_lines, 0, -$extra) : $old_lines, 142 ]; 143 trigger_event('TRIM_RECENT_CHANGES', $eventData); 144 $out_lines = $eventData['trimmedChangelogLines']; 145 146 // save trimmed changelog 147 io_saveFile($fn . '_tmp', implode('', $out_lines)); 148 @unlink($fn); 149 if (!rename($fn . '_tmp', $fn)) { 150 // rename failed so try another way... 151 io_unlock($fn); 152 io_saveFile($fn, implode('', $out_lines)); 153 @unlink($fn . '_tmp'); 154 } else { 155 io_unlock($fn); 156 } 157 echo "runTrimRecentChanges($media_changes): finished" . NL; 158 return true; 159 } 160 161 // nothing done 162 echo "runTrimRecentChanges($media_changes): finished" . NL; 163 return false; 164 } 165 166 167 /** 168 * Runs the indexer for the current page 169 * 170 * @author Andreas Gohr <andi@splitbrain.org> 171 */ 172 protected function runIndexer() 173 { 174 global $ID; 175 global $conf; 176 print 'runIndexer(): started' . NL; 177 178 if (!$ID) { 179 return false; 180 } 181 182 // do the work 183 return idx_addPage($ID, true); 184 } 185 186 /** 187 * Builds a Google Sitemap of all public pages known to the indexer 188 * 189 * The map is placed in the root directory named sitemap.xml.gz - This 190 * file needs to be writable! 191 * 192 * @author Andreas Gohr 193 * @link https://www.google.com/webmasters/sitemaps/docs/en/about.html 194 */ 195 protected function runSitemapper() 196 { 197 print 'runSitemapper(): started' . NL; 198 $result = Sitemapper::generate() && Sitemapper::pingSearchEngines(); 199 print 'runSitemapper(): finished' . NL; 200 return $result; 201 } 202 203 /** 204 * Send digest and list mails for all subscriptions which are in effect for the 205 * current page 206 * 207 * @author Adrian Lang <lang@cosmocode.de> 208 */ 209 protected function sendDigest() 210 { 211 global $conf; 212 global $ID; 213 214 echo 'sendDigest(): started' . NL; 215 if (!actionOK('subscribe')) { 216 echo 'sendDigest(): disabled' . NL; 217 return false; 218 } 219 $sub = new Subscription(); 220 $sent = $sub->send_bulk($ID); 221 222 echo "sendDigest(): sent $sent mails" . NL; 223 echo 'sendDigest(): finished' . NL; 224 return (bool)$sent; 225 } 226} 227