[ Index ]

PHP Cross Reference of DokuWiki

title

Body

[close]

/inc/ -> changelog.php (source)

   1  <?php
   2  /**
   3   * Changelog handling functions
   4   *
   5   * @license    GPL 2 (http://www.gnu.org/licenses/gpl.html)
   6   * @author     Andreas Gohr <andi@splitbrain.org>
   7   */
   8  
   9  // Constants for known core changelog line types.
  10  // Use these in place of string literals for more readable code.
  11  define('DOKU_CHANGE_TYPE_CREATE',       'C');
  12  define('DOKU_CHANGE_TYPE_EDIT',         'E');
  13  define('DOKU_CHANGE_TYPE_MINOR_EDIT',   'e');
  14  define('DOKU_CHANGE_TYPE_DELETE',       'D');
  15  define('DOKU_CHANGE_TYPE_REVERT',       'R');
  16  
  17  /**
  18   * parses a changelog line into it's components
  19   *
  20   * @author Ben Coburn <btcoburn@silicodon.net>
  21   *
  22   * @param string $line changelog line
  23   * @return array|bool parsed line or false
  24   */
  25  function parseChangelogLine($line) {
  26      $line = rtrim($line, "\n");
  27      $tmp = explode("\t", $line);
  28      if ($tmp!==false && count($tmp)>1) {
  29          $info = array();
  30          $info['date']  = (int)$tmp[0]; // unix timestamp
  31          $info['ip']    = $tmp[1]; // IPv4 address (127.0.0.1)
  32          $info['type']  = $tmp[2]; // log line type
  33          $info['id']    = $tmp[3]; // page id
  34          $info['user']  = $tmp[4]; // user name
  35          $info['sum']   = $tmp[5]; // edit summary (or action reason)
  36          $info['extra'] = $tmp[6]; // extra data (varies by line type)
  37          if(isset($tmp[7]) && $tmp[7] !== '') { //last item has line-end||
  38              $info['sizechange'] = (int) $tmp[7];
  39          } else {
  40              $info['sizechange'] = null;
  41          }
  42          return $info;
  43      } else {
  44          return false;
  45      }
  46  }
  47  
  48  /**
  49   * Add's an entry to the changelog and saves the metadata for the page
  50   *
  51   * @param int    $date      Timestamp of the change
  52   * @param String $id        Name of the affected page
  53   * @param String $type      Type of the change see DOKU_CHANGE_TYPE_*
  54   * @param String $summary   Summary of the change
  55   * @param mixed  $extra     In case of a revert the revision (timestmp) of the reverted page
  56   * @param array  $flags     Additional flags in a key value array.
  57   *                             Available flags:
  58   *                             - ExternalEdit - mark as an external edit.
  59   * @param null|int $sizechange Change of filesize
  60   *
  61   * @author Andreas Gohr <andi@splitbrain.org>
  62   * @author Esther Brunner <wikidesign@gmail.com>
  63   * @author Ben Coburn <btcoburn@silicodon.net>
  64   */
  65  function addLogEntry($date, $id, $type=DOKU_CHANGE_TYPE_EDIT, $summary='', $extra='', $flags=null, $sizechange = null){
  66      global $conf, $INFO;
  67      /** @var Input $INPUT */
  68      global $INPUT;
  69  
  70      // check for special flags as keys
  71      if (!is_array($flags)) { $flags = array(); }
  72      $flagExternalEdit = isset($flags['ExternalEdit']);
  73  
  74      $id = cleanid($id);
  75      $file = wikiFN($id);
  76      $created = @filectime($file);
  77      $minor = ($type===DOKU_CHANGE_TYPE_MINOR_EDIT);
  78      $wasRemoved = ($type===DOKU_CHANGE_TYPE_DELETE);
  79  
  80      if(!$date) $date = time(); //use current time if none supplied
  81      $remote = (!$flagExternalEdit)?clientIP(true):'127.0.0.1';
  82      $user   = (!$flagExternalEdit)?$INPUT->server->str('REMOTE_USER'):'';
  83      if($sizechange === null) {
  84          $sizechange = '';
  85      } else {
  86          $sizechange = (int) $sizechange;
  87      }
  88  
  89      $strip = array("\t", "\n");
  90      $logline = array(
  91          'date'       => $date,
  92          'ip'         => $remote,
  93          'type'       => str_replace($strip, '', $type),
  94          'id'         => $id,
  95          'user'       => $user,
  96          'sum'        => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255),
  97          'extra'      => str_replace($strip, '', $extra),
  98          'sizechange' => $sizechange
  99      );
 100  
 101      $wasCreated = ($type===DOKU_CHANGE_TYPE_CREATE);
 102      $wasReverted = ($type===DOKU_CHANGE_TYPE_REVERT);
 103      // update metadata
 104      if (!$wasRemoved) {
 105          $oldmeta = p_read_metadata($id);
 106          $meta    = array();
 107          if (
 108              $wasCreated && (
 109                  empty($oldmeta['persistent']['date']['created']) ||
 110                  $oldmeta['persistent']['date']['created'] === $created
 111              )
 112          ){
 113              // newly created
 114              $meta['date']['created'] = $created;
 115              if ($user){
 116                  $meta['creator'] = $INFO['userinfo']['name'];
 117                  $meta['user']    = $user;
 118              }
 119          } elseif (($wasCreated || $wasReverted) && !empty($oldmeta['persistent']['date']['created'])) {
 120              // re-created / restored
 121              $meta['date']['created']  = $oldmeta['persistent']['date']['created'];
 122              $meta['date']['modified'] = $created; // use the files ctime here
 123              $meta['creator'] = $oldmeta['persistent']['creator'];
 124              if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name'];
 125          } elseif (!$minor) {   // non-minor modification
 126              $meta['date']['modified'] = $date;
 127              if ($user) $meta['contributor'][$user] = $INFO['userinfo']['name'];
 128          }
 129          $meta['last_change'] = $logline;
 130          p_set_metadata($id, $meta);
 131      }
 132  
 133      // add changelog lines
 134      $logline = implode("\t", $logline)."\n";
 135      io_saveFile(metaFN($id,'.changes'),$logline,true); //page changelog
 136      io_saveFile($conf['changelog'],$logline,true); //global changelog cache
 137  }
 138  
 139  /**
 140   * Add's an entry to the media changelog
 141   *
 142   * @author Michael Hamann <michael@content-space.de>
 143   * @author Andreas Gohr <andi@splitbrain.org>
 144   * @author Esther Brunner <wikidesign@gmail.com>
 145   * @author Ben Coburn <btcoburn@silicodon.net>
 146   *
 147   * @param int    $date      Timestamp of the change
 148   * @param String $id        Name of the affected page
 149   * @param String $type      Type of the change see DOKU_CHANGE_TYPE_*
 150   * @param String $summary   Summary of the change
 151   * @param mixed  $extra     In case of a revert the revision (timestmp) of the reverted page
 152   * @param array  $flags     Additional flags in a key value array.
 153   *                             Available flags:
 154   *                             - (none, so far)
 155   * @param null|int $sizechange Change of filesize
 156   */
 157  function addMediaLogEntry(
 158      $date,
 159      $id,
 160      $type=DOKU_CHANGE_TYPE_EDIT,
 161      $summary='',
 162      $extra='',
 163      $flags=null,
 164      $sizechange = null)
 165  {
 166      global $conf;
 167      /** @var Input $INPUT */
 168      global $INPUT;
 169  
 170      $id = cleanid($id);
 171  
 172      if(!$date) $date = time(); //use current time if none supplied
 173      $remote = clientIP(true);
 174      $user   = $INPUT->server->str('REMOTE_USER');
 175      if($sizechange === null) {
 176          $sizechange = '';
 177      } else {
 178          $sizechange = (int) $sizechange;
 179      }
 180  
 181      $strip = array("\t", "\n");
 182      $logline = array(
 183          'date'       => $date,
 184          'ip'         => $remote,
 185          'type'       => str_replace($strip, '', $type),
 186          'id'         => $id,
 187          'user'       => $user,
 188          'sum'        => \dokuwiki\Utf8\PhpString::substr(str_replace($strip, '', $summary), 0, 255),
 189          'extra'      => str_replace($strip, '', $extra),
 190          'sizechange' => $sizechange
 191      );
 192  
 193      // add changelog lines
 194      $logline = implode("\t", $logline)."\n";
 195      io_saveFile($conf['media_changelog'],$logline,true); //global media changelog cache
 196      io_saveFile(mediaMetaFN($id,'.changes'),$logline,true); //media file's changelog
 197  }
 198  
 199  /**
 200   * returns an array of recently changed files using the
 201   * changelog
 202   *
 203   * The following constants can be used to control which changes are
 204   * included. Add them together as needed.
 205   *
 206   * RECENTS_SKIP_DELETED   - don't include deleted pages
 207   * RECENTS_SKIP_MINORS    - don't include minor changes
 208   * RECENTS_SKIP_SUBSPACES - don't include subspaces
 209   * RECENTS_MEDIA_CHANGES  - return media changes instead of page changes
 210   * RECENTS_MEDIA_PAGES_MIXED  - return both media changes and page changes
 211   *
 212   * @param int    $first   number of first entry returned (for paginating
 213   * @param int    $num     return $num entries
 214   * @param string $ns      restrict to given namespace
 215   * @param int    $flags   see above
 216   * @return array recently changed files
 217   *
 218   * @author Ben Coburn <btcoburn@silicodon.net>
 219   * @author Kate Arzamastseva <pshns@ukr.net>
 220   */
 221  function getRecents($first,$num,$ns='',$flags=0){
 222      global $conf;
 223      $recent = array();
 224      $count  = 0;
 225  
 226      if(!$num)
 227          return $recent;
 228  
 229      // read all recent changes. (kept short)
 230      if ($flags & RECENTS_MEDIA_CHANGES) {
 231          $lines = @file($conf['media_changelog']) ?: [];
 232      } else {
 233          $lines = @file($conf['changelog']) ?: [];
 234      }
 235      if (!is_array($lines)) {
 236          $lines = array();
 237      }
 238      $lines_position = count($lines)-1;
 239      $media_lines_position = 0;
 240      $media_lines = array();
 241  
 242      if ($flags & RECENTS_MEDIA_PAGES_MIXED) {
 243          $media_lines = @file($conf['media_changelog']) ?: [];
 244          if (!is_array($media_lines)) {
 245              $media_lines = array();
 246          }
 247          $media_lines_position = count($media_lines)-1;
 248      }
 249  
 250      $seen = array(); // caches seen lines, _handleRecent() skips them
 251  
 252      // handle lines
 253      while ($lines_position >= 0 || (($flags & RECENTS_MEDIA_PAGES_MIXED) && $media_lines_position >=0)) {
 254          if (empty($rec) && $lines_position >= 0) {
 255              $rec = _handleRecent(@$lines[$lines_position], $ns, $flags, $seen);
 256              if (!$rec) {
 257                  $lines_position --;
 258                  continue;
 259              }
 260          }
 261          if (($flags & RECENTS_MEDIA_PAGES_MIXED) && empty($media_rec) && $media_lines_position >= 0) {
 262              $media_rec = _handleRecent(
 263                  @$media_lines[$media_lines_position],
 264                  $ns,
 265                  $flags | RECENTS_MEDIA_CHANGES,
 266                  $seen
 267              );
 268              if (!$media_rec) {
 269                  $media_lines_position --;
 270                  continue;
 271              }
 272          }
 273          if (($flags & RECENTS_MEDIA_PAGES_MIXED) && @$media_rec['date'] >= @$rec['date']) {
 274              $media_lines_position--;
 275              $x = $media_rec;
 276              $x['media'] = true;
 277              $media_rec = false;
 278          } else {
 279              $lines_position--;
 280              $x = $rec;
 281              if ($flags & RECENTS_MEDIA_CHANGES) $x['media'] = true;
 282              $rec = false;
 283          }
 284          if(--$first >= 0) continue; // skip first entries
 285          $recent[] = $x;
 286          $count++;
 287          // break when we have enough entries
 288          if($count >= $num){ break; }
 289      }
 290      return $recent;
 291  }
 292  
 293  /**
 294   * returns an array of files changed since a given time using the
 295   * changelog
 296   *
 297   * The following constants can be used to control which changes are
 298   * included. Add them together as needed.
 299   *
 300   * RECENTS_SKIP_DELETED   - don't include deleted pages
 301   * RECENTS_SKIP_MINORS    - don't include minor changes
 302   * RECENTS_SKIP_SUBSPACES - don't include subspaces
 303   * RECENTS_MEDIA_CHANGES  - return media changes instead of page changes
 304   *
 305   * @param int    $from    date of the oldest entry to return
 306   * @param int    $to      date of the newest entry to return (for pagination, optional)
 307   * @param string $ns      restrict to given namespace (optional)
 308   * @param int    $flags   see above (optional)
 309   * @return array of files
 310   *
 311   * @author Michael Hamann <michael@content-space.de>
 312   * @author Ben Coburn <btcoburn@silicodon.net>
 313   */
 314  function getRecentsSince($from,$to=null,$ns='',$flags=0){
 315      global $conf;
 316      $recent = array();
 317  
 318      if($to && $to < $from)
 319          return $recent;
 320  
 321      // read all recent changes. (kept short)
 322      if ($flags & RECENTS_MEDIA_CHANGES) {
 323          $lines = @file($conf['media_changelog']);
 324      } else {
 325          $lines = @file($conf['changelog']);
 326      }
 327      if(!$lines) return $recent;
 328  
 329      // we start searching at the end of the list
 330      $lines = array_reverse($lines);
 331  
 332      // handle lines
 333      $seen = array(); // caches seen lines, _handleRecent() skips them
 334  
 335      foreach($lines as $line){
 336          $rec = _handleRecent($line, $ns, $flags, $seen);
 337          if($rec !== false) {
 338              if ($rec['date'] >= $from) {
 339                  if (!$to || $rec['date'] <= $to) {
 340                      $recent[] = $rec;
 341                  }
 342              } else {
 343                  break;
 344              }
 345          }
 346      }
 347  
 348      return array_reverse($recent);
 349  }
 350  
 351  /**
 352   * Internal function used by getRecents
 353   *
 354   * don't call directly
 355   *
 356   * @see getRecents()
 357   * @author Andreas Gohr <andi@splitbrain.org>
 358   * @author Ben Coburn <btcoburn@silicodon.net>
 359   *
 360   * @param string $line   changelog line
 361   * @param string $ns     restrict to given namespace
 362   * @param int    $flags  flags to control which changes are included
 363   * @param array  $seen   listing of seen pages
 364   * @return array|bool    false or array with info about a change
 365   */
 366  function _handleRecent($line,$ns,$flags,&$seen){
 367      if(empty($line)) return false;   //skip empty lines
 368  
 369      // split the line into parts
 370      $recent = parseChangelogLine($line);
 371      if ($recent===false) { return false; }
 372  
 373      // skip seen ones
 374      if(isset($seen[$recent['id']])) return false;
 375  
 376      // skip minors
 377      if($recent['type']===DOKU_CHANGE_TYPE_MINOR_EDIT && ($flags & RECENTS_SKIP_MINORS)) return false;
 378  
 379      // remember in seen to skip additional sights
 380      $seen[$recent['id']] = 1;
 381  
 382      // check if it's a hidden page
 383      if(isHiddenPage($recent['id'])) return false;
 384  
 385      // filter namespace
 386      if (($ns) && (strpos($recent['id'],$ns.':') !== 0)) return false;
 387  
 388      // exclude subnamespaces
 389      if (($flags & RECENTS_SKIP_SUBSPACES) && (getNS($recent['id']) != $ns)) return false;
 390  
 391      // check ACL
 392      if ($flags & RECENTS_MEDIA_CHANGES) {
 393          $recent['perms'] = auth_quickaclcheck(getNS($recent['id']).':*');
 394      } else {
 395          $recent['perms'] = auth_quickaclcheck($recent['id']);
 396      }
 397      if ($recent['perms'] < AUTH_READ) return false;
 398  
 399      // check existance
 400      if($flags & RECENTS_SKIP_DELETED){
 401          $fn = (($flags & RECENTS_MEDIA_CHANGES) ? mediaFN($recent['id']) : wikiFN($recent['id']));
 402          if(!file_exists($fn)) return false;
 403      }
 404  
 405      return $recent;
 406  }