You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 

249 lines
7.6 KiB

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