[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> TaskRunner.php (source)

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