[ Index ]

PHP Cross Reference of MediaWiki-1.24.0

title

Body

[close]

/includes/logging/ -> LogPage.php (source)

   1  <?php
   2  /**
   3   * Contain log classes
   4   *
   5   * Copyright © 2002, 2004 Brion Vibber <[email protected]>
   6   * https://www.mediawiki.org/
   7   *
   8   * This program is free software; you can redistribute it and/or modify
   9   * it under the terms of the GNU General Public License as published by
  10   * the Free Software Foundation; either version 2 of the License, or
  11   * (at your option) any later version.
  12   *
  13   * This program is distributed in the hope that it will be useful,
  14   * but WITHOUT ANY WARRANTY; without even the implied warranty of
  15   * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16   * GNU General Public License for more details.
  17   *
  18   * You should have received a copy of the GNU General Public License along
  19   * with this program; if not, write to the Free Software Foundation, Inc.,
  20   * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
  21   * http://www.gnu.org/copyleft/gpl.html
  22   *
  23   * @file
  24   */
  25  
  26  /**
  27   * Class to simplify the use of log pages.
  28   * The logs are now kept in a table which is easier to manage and trim
  29   * than ever-growing wiki pages.
  30   *
  31   */
  32  class LogPage {
  33      const DELETED_ACTION = 1;
  34      const DELETED_COMMENT = 2;
  35      const DELETED_USER = 4;
  36      const DELETED_RESTRICTED = 8;
  37  
  38      // Convenience fields
  39      const SUPPRESSED_USER = 12;
  40      const SUPPRESSED_ACTION = 9;
  41  
  42      /** @var bool */
  43      public $updateRecentChanges;
  44  
  45      /** @var bool */
  46      public $sendToUDP;
  47  
  48      /** @var string Plaintext version of the message for IRC */
  49      private $ircActionText;
  50  
  51      /** @var string Plaintext version of the message */
  52      private $actionText;
  53  
  54      /** @var string One of '', 'block', 'protect', 'rights', 'delete',
  55       *    'upload', 'move'
  56       */
  57      private $type;
  58  
  59      /** @var string One of '', 'block', 'protect', 'rights', 'delete',
  60       *   'upload', 'move', 'move_redir' */
  61      private $action;
  62  
  63      /** @var string Comment associated with action */
  64      private $comment;
  65  
  66      /** @var string Blob made of a parameters array */
  67      private $params;
  68  
  69      /** @var User The user doing the action */
  70      private $doer;
  71  
  72      /** @var Title */
  73      private $target;
  74  
  75      /**
  76       * Constructor
  77       *
  78       * @param string $type One of '', 'block', 'protect', 'rights', 'delete',
  79       *   'upload', 'move'
  80       * @param bool $rc Whether to update recent changes as well as the logging table
  81       * @param string $udp Pass 'UDP' to send to the UDP feed if NOT sent to RC
  82       */
  83  	public function __construct( $type, $rc = true, $udp = 'skipUDP' ) {
  84          $this->type = $type;
  85          $this->updateRecentChanges = $rc;
  86          $this->sendToUDP = ( $udp == 'UDP' );
  87      }
  88  
  89      /**
  90       * @return int The log_id of the inserted log entry
  91       */
  92  	protected function saveContent() {
  93          global $wgLogRestrictions;
  94  
  95          $dbw = wfGetDB( DB_MASTER );
  96          $log_id = $dbw->nextSequenceValue( 'logging_log_id_seq' );
  97  
  98          // @todo FIXME private/protected/public property?
  99          $this->timestamp = $now = wfTimestampNow();
 100          $data = array(
 101              'log_id' => $log_id,
 102              'log_type' => $this->type,
 103              'log_action' => $this->action,
 104              'log_timestamp' => $dbw->timestamp( $now ),
 105              'log_user' => $this->doer->getId(),
 106              'log_user_text' => $this->doer->getName(),
 107              'log_namespace' => $this->target->getNamespace(),
 108              'log_title' => $this->target->getDBkey(),
 109              'log_page' => $this->target->getArticleID(),
 110              'log_comment' => $this->comment,
 111              'log_params' => $this->params
 112          );
 113          $dbw->insert( 'logging', $data, __METHOD__ );
 114          $newId = !is_null( $log_id ) ? $log_id : $dbw->insertId();
 115  
 116          # And update recentchanges
 117          if ( $this->updateRecentChanges ) {
 118              $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
 119  
 120              RecentChange::notifyLog(
 121                  $now, $titleObj, $this->doer, $this->getRcComment(), '',
 122                  $this->type, $this->action, $this->target, $this->comment,
 123                  $this->params, $newId, $this->getRcCommentIRC()
 124              );
 125          } elseif ( $this->sendToUDP ) {
 126              # Don't send private logs to UDP
 127              if ( isset( $wgLogRestrictions[$this->type] ) && $wgLogRestrictions[$this->type] != '*' ) {
 128                  return $newId;
 129              }
 130  
 131              # Notify external application via UDP.
 132              # We send this to IRC but do not want to add it the RC table.
 133              $titleObj = SpecialPage::getTitleFor( 'Log', $this->type );
 134              $rc = RecentChange::newLogEntry(
 135                  $now, $titleObj, $this->doer, $this->getRcComment(), '',
 136                  $this->type, $this->action, $this->target, $this->comment,
 137                  $this->params, $newId, $this->getRcCommentIRC()
 138              );
 139              $rc->notifyRCFeeds();
 140          }
 141  
 142          return $newId;
 143      }
 144  
 145      /**
 146       * Get the RC comment from the last addEntry() call
 147       *
 148       * @return string
 149       */
 150  	public function getRcComment() {
 151          $rcComment = $this->actionText;
 152  
 153          if ( $this->comment != '' ) {
 154              if ( $rcComment == '' ) {
 155                  $rcComment = $this->comment;
 156              } else {
 157                  $rcComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() .
 158                      $this->comment;
 159              }
 160          }
 161  
 162          return $rcComment;
 163      }
 164  
 165      /**
 166       * Get the RC comment from the last addEntry() call for IRC
 167       *
 168       * @return string
 169       */
 170  	public function getRcCommentIRC() {
 171          $rcComment = $this->ircActionText;
 172  
 173          if ( $this->comment != '' ) {
 174              if ( $rcComment == '' ) {
 175                  $rcComment = $this->comment;
 176              } else {
 177                  $rcComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() .
 178                      $this->comment;
 179              }
 180          }
 181  
 182          return $rcComment;
 183      }
 184  
 185      /**
 186       * Get the comment from the last addEntry() call
 187       * @return string
 188       */
 189  	public function getComment() {
 190          return $this->comment;
 191      }
 192  
 193      /**
 194       * Get the list of valid log types
 195       *
 196       * @return array Array of strings
 197       */
 198  	public static function validTypes() {
 199          global $wgLogTypes;
 200  
 201          return $wgLogTypes;
 202      }
 203  
 204      /**
 205       * Is $type a valid log type
 206       *
 207       * @param string $type Log type to check
 208       * @return bool
 209       */
 210  	public static function isLogType( $type ) {
 211          return in_array( $type, LogPage::validTypes() );
 212      }
 213  
 214      /**
 215       * Get the name for the given log type
 216       *
 217       * @param string $type Log type
 218       * @return string Log name
 219       * @deprecated since 1.19, warnings in 1.21. Use getName()
 220       */
 221  	public static function logName( $type ) {
 222          global $wgLogNames;
 223  
 224          wfDeprecated( __METHOD__, '1.21' );
 225  
 226          if ( isset( $wgLogNames[$type] ) ) {
 227              return str_replace( '_', ' ', wfMessage( $wgLogNames[$type] )->text() );
 228          } else {
 229              // Bogus log types? Perhaps an extension was removed.
 230              return $type;
 231          }
 232      }
 233  
 234      /**
 235       * Get the log header for the given log type
 236       *
 237       * @todo handle missing log types
 238       * @param string $type Logtype
 239       * @return string Header text of this logtype
 240       * @deprecated since 1.19, warnings in 1.21. Use getDescription()
 241       */
 242  	public static function logHeader( $type ) {
 243          global $wgLogHeaders;
 244  
 245          wfDeprecated( __METHOD__, '1.21' );
 246  
 247          return wfMessage( $wgLogHeaders[$type] )->parse();
 248      }
 249  
 250      /**
 251       * Generate text for a log entry.
 252       * Only LogFormatter should call this function.
 253       *
 254       * @param string $type Log type
 255       * @param string $action Log action
 256       * @param Title|null $title Title object or null
 257       * @param Skin|null $skin Skin object or null. If null, we want to use the wiki
 258       *   content language, since that will go to the IRC feed.
 259       * @param array $params Parameters
 260       * @param bool $filterWikilinks Whether to filter wiki links
 261       * @return string HTML
 262       */
 263  	public static function actionText( $type, $action, $title = null, $skin = null,
 264          $params = array(), $filterWikilinks = false
 265      ) {
 266          global $wgLang, $wgContLang, $wgLogActions;
 267  
 268          if ( is_null( $skin ) ) {
 269              $langObj = $wgContLang;
 270              $langObjOrNull = null;
 271          } else {
 272              $langObj = $wgLang;
 273              $langObjOrNull = $wgLang;
 274          }
 275  
 276          $key = "$type/$action";
 277  
 278          if ( isset( $wgLogActions[$key] ) ) {
 279              if ( is_null( $title ) ) {
 280                  $rv = wfMessage( $wgLogActions[$key] )->inLanguage( $langObj )->escaped();
 281              } else {
 282                  $titleLink = self::getTitleLink( $type, $langObjOrNull, $title, $params );
 283  
 284                  if ( count( $params ) == 0 ) {
 285                      $rv = wfMessage( $wgLogActions[$key] )->rawParams( $titleLink )
 286                          ->inLanguage( $langObj )->escaped();
 287                  } else {
 288                      $details = '';
 289                      array_unshift( $params, $titleLink );
 290  
 291                      // User suppression
 292                      if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) {
 293                          if ( $skin ) {
 294                              // Localize the duration, and add a tooltip
 295                              // in English to help visitors from other wikis.
 296                              // The lrm is needed to make sure that the number
 297                              // is shown on the correct side of the tooltip text.
 298                              $durationTooltip = '&lrm;' . htmlspecialchars( $params[1] );
 299                              $params[1] = "<span class='blockExpiry' title='$durationTooltip'>" .
 300                                  $wgLang->translateBlockExpiry( $params[1] ) . '</span>';
 301                          } else {
 302                              $params[1] = $wgContLang->translateBlockExpiry( $params[1] );
 303                          }
 304  
 305                          $params[2] = isset( $params[2] ) ?
 306                              self::formatBlockFlags( $params[2], $langObj ) : '';
 307                      // Page protections
 308                      } elseif ( $type == 'protect' && count( $params ) == 3 ) {
 309                          // Restrictions and expiries
 310                          if ( $skin ) {
 311                              $details .= $wgLang->getDirMark() . htmlspecialchars( " {$params[1]}" );
 312                          } else {
 313                              $details .= " {$params[1]}";
 314                          }
 315  
 316                          // Cascading flag...
 317                          if ( $params[2] ) {
 318                              $text = wfMessage( 'protect-summary-cascade' )
 319                                  ->inLanguage( $langObj )->text();
 320                              $details .= ' ';
 321                              $details .= wfMessage( 'brackets', $text )->inLanguage( $langObj )->text();
 322  
 323                          }
 324                      }
 325  
 326                      $rv = wfMessage( $wgLogActions[$key] )->rawParams( $params )
 327                              ->inLanguage( $langObj )->escaped() . $details;
 328                  }
 329              }
 330          } else {
 331              global $wgLogActionsHandlers;
 332  
 333              if ( isset( $wgLogActionsHandlers[$key] ) ) {
 334                  $args = func_get_args();
 335                  $rv = call_user_func_array( $wgLogActionsHandlers[$key], $args );
 336              } else {
 337                  wfDebug( "LogPage::actionText - unknown action $key\n" );
 338                  $rv = "$action";
 339              }
 340          }
 341  
 342          // For the perplexed, this feature was added in r7855 by Erik.
 343          // The feature was added because we liked adding [[$1]] in our log entries
 344          // but the log entries are parsed as Wikitext on RecentChanges but as HTML
 345          // on Special:Log. The hack is essentially that [[$1]] represented a link
 346          // to the title in question. The first parameter to the HTML version (Special:Log)
 347          // is that link in HTML form, and so this just gets rid of the ugly [[]].
 348          // However, this is a horrible hack and it doesn't work like you expect if, say,
 349          // you want to link to something OTHER than the title of the log entry.
 350          // The real problem, which Erik was trying to fix (and it sort-of works now) is
 351          // that the same messages are being treated as both wikitext *and* HTML.
 352          if ( $filterWikilinks ) {
 353              $rv = str_replace( '[[', '', $rv );
 354              $rv = str_replace( ']]', '', $rv );
 355          }
 356  
 357          return $rv;
 358      }
 359  
 360      /**
 361       * @todo Document
 362       * @param string $type
 363       * @param Language|null $lang
 364       * @param Title $title
 365       * @param array $params
 366       * @return string
 367       */
 368  	protected static function getTitleLink( $type, $lang, $title, &$params ) {
 369          if ( !$lang ) {
 370              return $title->getPrefixedText();
 371          }
 372  
 373          switch ( $type ) {
 374              case 'move':
 375                  $titleLink = Linker::link(
 376                      $title,
 377                      htmlspecialchars( $title->getPrefixedText() ),
 378                      array(),
 379                      array( 'redirect' => 'no' )
 380                  );
 381  
 382                  $targetTitle = Title::newFromText( $params[0] );
 383  
 384                  if ( !$targetTitle ) {
 385                      # Workaround for broken database
 386                      $params[0] = htmlspecialchars( $params[0] );
 387                  } else {
 388                      $params[0] = Linker::link(
 389                          $targetTitle,
 390                          htmlspecialchars( $params[0] )
 391                      );
 392                  }
 393                  break;
 394              case 'block':
 395                  if ( substr( $title->getText(), 0, 1 ) == '#' ) {
 396                      $titleLink = $title->getText();
 397                  } else {
 398                      // @todo Store the user identifier in the parameters
 399                      // to make this faster for future log entries
 400                      $id = User::idFromName( $title->getText() );
 401                      $titleLink = Linker::userLink( $id, $title->getText() )
 402                          . Linker::userToolLinks( $id, $title->getText(), false, Linker::TOOL_LINKS_NOBLOCK );
 403                  }
 404                  break;
 405              case 'merge':
 406                  $titleLink = Linker::link(
 407                      $title,
 408                      $title->getPrefixedText(),
 409                      array(),
 410                      array( 'redirect' => 'no' )
 411                  );
 412                  $params[0] = Linker::link(
 413                      Title::newFromText( $params[0] ),
 414                      htmlspecialchars( $params[0] )
 415                  );
 416                  $params[1] = $lang->timeanddate( $params[1] );
 417                  break;
 418              default:
 419                  if ( $title->isSpecialPage() ) {
 420                      list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() );
 421  
 422                      # Use the language name for log titles, rather than Log/X
 423                      if ( $name == 'Log' ) {
 424                          $logPage = new LogPage( $par );
 425                          $titleLink = Linker::link( $title, $logPage->getName()->escaped() );
 426                          $titleLink = wfMessage( 'parentheses' )
 427                              ->inLanguage( $lang )
 428                              ->rawParams( $titleLink )
 429                              ->escaped();
 430                      } else {
 431                          $titleLink = Linker::link( $title );
 432                      }
 433                  } else {
 434                      $titleLink = Linker::link( $title );
 435                  }
 436          }
 437  
 438          return $titleLink;
 439      }
 440  
 441      /**
 442       * Add a log entry
 443       *
 444       * @param string $action One of '', 'block', 'protect', 'rights', 'delete',
 445       *   'upload', 'move', 'move_redir'
 446       * @param Title $target Title object
 447       * @param string $comment Description associated
 448       * @param array $params Parameters passed later to wfMessage function
 449       * @param null|int|User $doer The user doing the action. null for $wgUser
 450       *
 451       * @return int The log_id of the inserted log entry
 452       */
 453  	public function addEntry( $action, $target, $comment, $params = array(), $doer = null ) {
 454          global $wgContLang;
 455  
 456          if ( !is_array( $params ) ) {
 457              $params = array( $params );
 458          }
 459  
 460          if ( $comment === null ) {
 461              $comment = '';
 462          }
 463  
 464          # Trim spaces on user supplied text
 465          $comment = trim( $comment );
 466  
 467          # Truncate for whole multibyte characters.
 468          $comment = $wgContLang->truncate( $comment, 255 );
 469  
 470          $this->action = $action;
 471          $this->target = $target;
 472          $this->comment = $comment;
 473          $this->params = LogPage::makeParamBlob( $params );
 474  
 475          if ( $doer === null ) {
 476              global $wgUser;
 477              $doer = $wgUser;
 478          } elseif ( !is_object( $doer ) ) {
 479              $doer = User::newFromId( $doer );
 480          }
 481  
 482          $this->doer = $doer;
 483  
 484          $logEntry = new ManualLogEntry( $this->type, $action );
 485          $logEntry->setTarget( $target );
 486          $logEntry->setPerformer( $doer );
 487          $logEntry->setParameters( $params );
 488  
 489          $formatter = LogFormatter::newFromEntry( $logEntry );
 490          $context = RequestContext::newExtraneousContext( $target );
 491          $formatter->setContext( $context );
 492  
 493          $this->actionText = $formatter->getPlainActionText();
 494          $this->ircActionText = $formatter->getIRCActionText();
 495  
 496          return $this->saveContent();
 497      }
 498  
 499      /**
 500       * Add relations to log_search table
 501       *
 502       * @param string $field
 503       * @param array $values
 504       * @param int $logid
 505       * @return bool
 506       */
 507  	public function addRelations( $field, $values, $logid ) {
 508          if ( !strlen( $field ) || empty( $values ) ) {
 509              return false; // nothing
 510          }
 511  
 512          $data = array();
 513  
 514          foreach ( $values as $value ) {
 515              $data[] = array(
 516                  'ls_field' => $field,
 517                  'ls_value' => $value,
 518                  'ls_log_id' => $logid
 519              );
 520          }
 521  
 522          $dbw = wfGetDB( DB_MASTER );
 523          $dbw->insert( 'log_search', $data, __METHOD__, 'IGNORE' );
 524  
 525          return true;
 526      }
 527  
 528      /**
 529       * Create a blob from a parameter array
 530       *
 531       * @param array $params
 532       * @return string
 533       */
 534  	public static function makeParamBlob( $params ) {
 535          return implode( "\n", $params );
 536      }
 537  
 538      /**
 539       * Extract a parameter array from a blob
 540       *
 541       * @param string $blob
 542       * @return array
 543       */
 544  	public static function extractParams( $blob ) {
 545          if ( $blob === '' ) {
 546              return array();
 547          } else {
 548              return explode( "\n", $blob );
 549          }
 550      }
 551  
 552      /**
 553       * Convert a comma-delimited list of block log flags
 554       * into a more readable (and translated) form
 555       *
 556       * @param string $flags Flags to format
 557       * @param Language $lang
 558       * @return string
 559       */
 560  	public static function formatBlockFlags( $flags, $lang ) {
 561          $flags = trim( $flags );
 562          if ( $flags === '' ) {
 563              return ''; //nothing to do
 564          }
 565          $flags = explode( ',', $flags );
 566          $flagsCount = count( $flags );
 567  
 568          for ( $i = 0; $i < $flagsCount; $i++ ) {
 569              $flags[$i] = self::formatBlockFlag( $flags[$i], $lang );
 570          }
 571  
 572          return wfMessage( 'parentheses' )->inLanguage( $lang )
 573              ->rawParams( $lang->commaList( $flags ) )->escaped();
 574      }
 575  
 576      /**
 577       * Translate a block log flag if possible
 578       *
 579       * @param int $flag Flag to translate
 580       * @param Language $lang Language object to use
 581       * @return string
 582       */
 583  	public static function formatBlockFlag( $flag, $lang ) {
 584          static $messages = array();
 585  
 586          if ( !isset( $messages[$flag] ) ) {
 587              $messages[$flag] = htmlspecialchars( $flag ); // Fallback
 588  
 589              // For grepping. The following core messages can be used here:
 590              // * block-log-flags-angry-autoblock
 591              // * block-log-flags-anononly
 592              // * block-log-flags-hiddenname
 593              // * block-log-flags-noautoblock
 594              // * block-log-flags-nocreate
 595              // * block-log-flags-noemail
 596              // * block-log-flags-nousertalk
 597              $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang );
 598  
 599              if ( $msg->exists() ) {
 600                  $messages[$flag] = $msg->escaped();
 601              }
 602          }
 603  
 604          return $messages[$flag];
 605      }
 606  
 607      /**
 608       * Name of the log.
 609       * @return Message
 610       * @since 1.19
 611       */
 612  	public function getName() {
 613          global $wgLogNames;
 614  
 615          // BC
 616          if ( isset( $wgLogNames[$this->type] ) ) {
 617              $key = $wgLogNames[$this->type];
 618          } else {
 619              $key = 'log-name-' . $this->type;
 620          }
 621  
 622          return wfMessage( $key );
 623      }
 624  
 625      /**
 626       * Description of this log type.
 627       * @return Message
 628       * @since 1.19
 629       */
 630  	public function getDescription() {
 631          global $wgLogHeaders;
 632          // BC
 633          if ( isset( $wgLogHeaders[$this->type] ) ) {
 634              $key = $wgLogHeaders[$this->type];
 635          } else {
 636              $key = 'log-description-' . $this->type;
 637          }
 638  
 639          return wfMessage( $key );
 640      }
 641  
 642      /**
 643       * Returns the right needed to read this log type.
 644       * @return string
 645       * @since 1.19
 646       */
 647  	public function getRestriction() {
 648          global $wgLogRestrictions;
 649          if ( isset( $wgLogRestrictions[$this->type] ) ) {
 650              $restriction = $wgLogRestrictions[$this->type];
 651          } else {
 652              // '' always returns true with $user->isAllowed()
 653              $restriction = '';
 654          }
 655  
 656          return $restriction;
 657      }
 658  
 659      /**
 660       * Tells if this log is not viewable by all.
 661       * @return bool
 662       * @since 1.19
 663       */
 664  	public function isRestricted() {
 665          $restriction = $this->getRestriction();
 666  
 667          return $restriction !== '' && $restriction !== '*';
 668      }
 669  }


Generated: Fri Nov 28 14:03:12 2014 Cross-referenced by PHPXref 0.7.1