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