diff options
Diffstat (limited to 'includes/logging')
-rw-r--r-- | includes/logging/BlockLogFormatter.php | 224 | ||||
-rw-r--r-- | includes/logging/DeleteLogFormatter.php | 78 | ||||
-rw-r--r-- | includes/logging/LogEntry.php | 38 | ||||
-rw-r--r-- | includes/logging/LogEventsList.php | 76 | ||||
-rw-r--r-- | includes/logging/LogFormatter.php | 253 | ||||
-rw-r--r-- | includes/logging/LogPage.php | 156 | ||||
-rw-r--r-- | includes/logging/LogPager.php | 2 | ||||
-rw-r--r-- | includes/logging/MergeLogFormatter.php | 91 | ||||
-rw-r--r-- | includes/logging/MoveLogFormatter.php | 29 | ||||
-rw-r--r-- | includes/logging/PatrolLogFormatter.php | 20 | ||||
-rw-r--r-- | includes/logging/RightsLogFormatter.php | 65 | ||||
-rw-r--r-- | includes/logging/TagLogFormatter.php | 49 | ||||
-rw-r--r-- | includes/logging/UploadLogFormatter.php | 49 |
13 files changed, 876 insertions, 254 deletions
diff --git a/includes/logging/BlockLogFormatter.php b/includes/logging/BlockLogFormatter.php new file mode 100644 index 00000000..07ef24b4 --- /dev/null +++ b/includes/logging/BlockLogFormatter.php @@ -0,0 +1,224 @@ +<?php +/** + * Formatter for block log entries. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + * @since 1.25 + */ + +/** + * This class formats block log entries. + * + * @since 1.25 + */ +class BlockLogFormatter extends LogFormatter { + protected function getMessageParameters() { + $params = parent::getMessageParameters(); + + $title = $this->entry->getTarget(); + if ( substr( $title->getText(), 0, 1 ) === '#' ) { + // autoblock - no user link possible + $params[2] = $title->getText(); + $params[3] = ''; // no user name for gender use + } else { + // Create a user link for the blocked + $username = $title->getText(); + // @todo Store the user identifier in the parameters + // to make this faster for future log entries + $targetUser = User::newFromName( $username, false ); + $params[2] = Message::rawParam( $this->makeUserLink( $targetUser, Linker::TOOL_LINKS_NOBLOCK ) ); + $params[3] = $username; // plain user name for gender use + } + + $subtype = $this->entry->getSubtype(); + if ( $subtype === 'block' || $subtype === 'reblock' ) { + if ( !isset( $params[4] ) ) { + // Very old log entry without duration: means infinite + $params[4] = 'infinite'; + } + // Localize the duration, and add a tooltip + // in English to help visitors from other wikis. + // The lrm is needed to make sure that the number + // is shown on the correct side of the tooltip text. + $durationTooltip = '‎' . htmlspecialchars( $params[4] ); + $params[4] = Message::rawParam( "<span class='blockExpiry' title='$durationTooltip'>" . + $this->context->getLanguage()->translateBlockExpiry( $params[4] ) . '</span>' ); + $params[5] = isset( $params[5] ) ? + self::formatBlockFlags( $params[5], $this->context->getLanguage() ) : ''; + } + + return $params; + } + + protected function extractParameters() { + $params = parent::extractParameters(); + // Legacy log params returning the params in index 3 and 4, moved to 4 and 5 + if ( $this->entry->isLegacy() && isset( $params[3] ) ) { + if ( isset( $params[4] ) ) { + $params[5] = $params[4]; + } + $params[4] = $params[3]; + $params[3] = ''; + } + return $params; + } + + public function getPreloadTitles() { + $title = $this->entry->getTarget(); + // Preload user page for non-autoblocks + if ( substr( $title->getText(), 0, 1 ) !== '#' ) { + return array( $title->getTalkPage() ); + } + return array(); + } + + public function getActionLinks() { + $subtype = $this->entry->getSubtype(); + if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden + || !( $subtype === 'block' || $subtype === 'reblock' ) + || !$this->context->getUser()->isAllowed( 'block' ) + ) { + return ''; + } + + // Show unblock/change block link + $title = $this->entry->getTarget(); + $links = array( + Linker::linkKnown( + SpecialPage::getTitleFor( 'Unblock', $title->getDBkey() ), + $this->msg( 'unblocklink' )->escaped() + ), + Linker::linkKnown( + SpecialPage::getTitleFor( 'Block', $title->getDBkey() ), + $this->msg( 'change-blocklink' )->escaped() + ) + ); + + return $this->msg( 'parentheses' )->rawParams( + $this->context->getLanguage()->pipeList( $links ) )->escaped(); + } + + /** + * Convert a comma-delimited list of block log flags + * into a more readable (and translated) form + * + * @param string $flags Flags to format + * @param Language $lang + * @return string + */ + public static function formatBlockFlags( $flags, $lang ) { + $flags = trim( $flags ); + if ( $flags === '' ) { + return ''; //nothing to do + } + $flags = explode( ',', $flags ); + $flagsCount = count( $flags ); + + for ( $i = 0; $i < $flagsCount; $i++ ) { + $flags[$i] = self::formatBlockFlag( $flags[$i], $lang ); + } + + return wfMessage( 'parentheses' )->inLanguage( $lang ) + ->rawParams( $lang->commaList( $flags ) )->escaped(); + } + + /** + * Translate a block log flag if possible + * + * @param int $flag Flag to translate + * @param Language $lang Language object to use + * @return string + */ + public static function formatBlockFlag( $flag, $lang ) { + static $messages = array(); + + if ( !isset( $messages[$flag] ) ) { + $messages[$flag] = htmlspecialchars( $flag ); // Fallback + + // For grepping. The following core messages can be used here: + // * block-log-flags-angry-autoblock + // * block-log-flags-anononly + // * block-log-flags-hiddenname + // * block-log-flags-noautoblock + // * block-log-flags-nocreate + // * block-log-flags-noemail + // * block-log-flags-nousertalk + $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang ); + + if ( $msg->exists() ) { + $messages[$flag] = $msg->escaped(); + } + } + + return $messages[$flag]; + } + + protected function getParametersForApi() { + $entry = $this->entry; + $params = $entry->getParameters(); + + static $map = array( + // While this looks wrong to be starting at 5 rather than 4, it's + // because getMessageParameters uses $4 for its own purposes. + '5::duration', + '6:array:flags', + '6::flags' => '6:array:flags', + ); + foreach ( $map as $index => $key ) { + if ( isset( $params[$index] ) ) { + $params[$key] = $params[$index]; + unset( $params[$index] ); + } + } + + $subtype = $entry->getSubtype(); + if ( $subtype === 'block' || $subtype === 'reblock' ) { + // Defaults for old log entries missing some fields + $params += array( + '5::duration' => 'infinite', + '6:array:flags' => array(), + ); + + if ( !is_array( $params['6:array:flags'] ) ) { + $params['6:array:flags'] = $params['6:array:flags'] === '' + ? array() + : explode( ',', $params['6:array:flags'] ); + } + + if ( !wfIsInfinity( $params['5::duration'] ) ) { + $ts = wfTimestamp( TS_UNIX, $entry->getTimestamp() ); + $expiry = strtotime( $params['5::duration'], $ts ); + if ( $expiry !== false && $expiry > 0 ) { + $params[':timestamp:expiry'] = $expiry; + } + } + } + + return $params; + } + + public function formatParametersForApi() { + $ret = parent::formatParametersForApi(); + if ( isset( $ret['flags'] ) ) { + ApiResult::setIndexedTagName( $ret['flags'], 'f' ); + } + return $ret; + } + +} diff --git a/includes/logging/DeleteLogFormatter.php b/includes/logging/DeleteLogFormatter.php index 8b30e9ba..f0598aa7 100644 --- a/includes/logging/DeleteLogFormatter.php +++ b/includes/logging/DeleteLogFormatter.php @@ -79,8 +79,10 @@ class DeleteLogFormatter extends LogFormatter { $newParams = array_slice( $params, 0, 3 ); $newParams[3] = $changeText; - $count = count( explode( ',', $params[$paramStart] ) ); - $newParams[4] = $this->context->getLanguage()->formatNum( $count ); + $ids = is_array( $params[$paramStart] ) + ? $params[$paramStart] + : explode( ',', $params[$paramStart] ); + $newParams[4] = $this->context->getLanguage()->formatNum( count( $ids ) ); $this->parsedParametersDeleteLog = $newParams; return $this->parsedParametersDeleteLog; @@ -137,8 +139,10 @@ class DeleteLogFormatter extends LogFormatter { // Different revision types use different URL params... $key = $params[3]; - // This is a CSV of the IDs - $ids = explode( ',', $params[4] ); + // This is a array or CSV of the IDs + $ids = is_array( $params[4] ) + ? $params[4] + : explode( ',', $params[4] ); $links = array(); @@ -192,6 +196,9 @@ class DeleteLogFormatter extends LogFormatter { } // This is a CSV of the IDs $query = $params[3]; + if ( is_array( $query ) ) { + $query = implode( ',', $query ); + } // Link to each hidden object ID, $params[1] is the url param $revert = Linker::linkKnown( SpecialPage::getTitleFor( 'Revisiondelete' ), @@ -209,4 +216,67 @@ class DeleteLogFormatter extends LogFormatter { return ''; } } + + protected function getParametersForApi() { + $entry = $this->entry; + $params = array(); + + $subtype = $this->entry->getSubtype(); + if ( in_array( $subtype, array( 'event', 'revision' ) ) ) { + $rawParams = $entry->getParameters(); + if ( $subtype === 'event' ) { + array_unshift( $rawParams, 'logging' ); + } + + static $map = array( + '4::type', + '5::ids', + '6::ofield', + '7::nfield', + '4::ids' => '5::ids', + '5::ofield' => '6::ofield', + '6::nfield' => '7::nfield', + ); + foreach ( $map as $index => $key ) { + if ( isset( $rawParams[$index] ) ) { + $rawParams[$key] = $rawParams[$index]; + unset( $rawParams[$index] ); + } + } + + $old = $this->parseBitField( $rawParams['6::ofield'] ); + $new = $this->parseBitField( $rawParams['7::nfield'] ); + if ( !is_array( $rawParams['5::ids'] ) ) { + $rawParams['5::ids'] = explode( ',', $rawParams['5::ids'] ); + } + + $params = array( + '::type' => $rawParams['4::type'], + ':array:ids' => $rawParams['5::ids'], + ':assoc:old' => array( 'bitmask' => $old ), + ':assoc:new' => array( 'bitmask' => $new ), + ); + + static $fields = array( + Revision::DELETED_TEXT => 'content', + Revision::DELETED_COMMENT => 'comment', + Revision::DELETED_USER => 'user', + Revision::DELETED_RESTRICTED => 'restricted', + ); + foreach ( $fields as $bit => $key ) { + $params[':assoc:old'][$key] = (bool)( $old & $bit ); + $params[':assoc:new'][$key] = (bool)( $new & $bit ); + } + } + + return $params; + } + + public function formatParametersForApi() { + $ret = parent::formatParametersForApi(); + if ( isset( $ret['ids'] ) ) { + ApiResult::setIndexedTagName( $ret['ids'], 'id' ); + } + return $ret; + } } diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php index 46c55157..66c2bde1 100644 --- a/includes/logging/LogEntry.php +++ b/includes/logging/LogEntry.php @@ -370,6 +370,9 @@ class ManualLogEntry extends LogEntryBase { /** @var int ID of the log entry */ protected $id; + /** @var bool Whether this is a legacy log entry */ + protected $legacy = false; + /** * Constructor. * @@ -385,13 +388,14 @@ class ManualLogEntry extends LogEntryBase { /** * Set extra log parameters. - * You can pass params to the log action message - * by prefixing the keys with a number and colon. - * The numbering should start with number 4, the - * first three parameters are hardcoded for every - * message. Example: + * + * You can pass params to the log action message by prefixing the keys with + * a number and optional type, using colons to separate the fields. The + * numbering should start with number 4, the first three parameters are + * hardcoded for every message. Example: * $entry->setParameters( - * '4:color' => 'blue', + * '4::color' => 'blue', + * '5:number:count' => 3000, * 'animal' => 'dog' * ); * @@ -459,6 +463,16 @@ class ManualLogEntry extends LogEntryBase { } /** + * Set the 'legacy' flag + * + * @since 1.25 + * @param bool $legacy + */ + public function setLegacy( $legacy ) { + $this->legacy = $legacy; + } + + /** * TODO: document * * @since 1.19 @@ -533,10 +547,6 @@ class ManualLogEntry extends LogEntryBase { $dbw->insert( 'log_search', $rows, __METHOD__, 'IGNORE' ); } - // Update any bloom filter cache - $member = $this->getTarget()->getNamespace() . ':' . $this->getTarget()->getDBkey(); - BloomCache::get( 'main' )->insert( wfWikiId(), 'TitleHasLogs', $member ); - return $this->id; } @@ -640,6 +650,14 @@ class ManualLogEntry extends LogEntryBase { return $this->comment; } + /** + * @since 1.25 + * @return bool + */ + public function isLegacy() { + return $this->legacy; + } + public function getDeleted() { return (int)$this->deleted; } diff --git a/includes/logging/LogEventsList.php b/includes/logging/LogEventsList.php index 4dc25ef3..dfe31365 100644 --- a/includes/logging/LogEventsList.php +++ b/includes/logging/LogEventsList.php @@ -26,7 +26,7 @@ class LogEventsList extends ContextSource { const NO_ACTION_LINK = 1; const NO_EXTRA_USER_LINKS = 2; - const USE_REVDEL_CHECKBOXES = 4; + const USE_CHECKBOXES = 4; public $flags; @@ -44,7 +44,7 @@ class LogEventsList extends ContextSource { * a Skin object. Use of Skin is deprecated. * @param null $unused Unused; used to be an OutputPage object. * @param int $flags Can be a combination of self::NO_ACTION_LINK, - * self::NO_EXTRA_USER_LINKS or self::USE_REVDEL_CHECKBOXES. + * self::NO_EXTRA_USER_LINKS or self::USE_CHECKBOXES. */ public function __construct( $context, $unused = null, $flags = 0 ) { if ( $context instanceof IContextSource ) { @@ -58,17 +58,6 @@ class LogEventsList extends ContextSource { } /** - * Deprecated alias for getTitle(); do not use. - * - * @deprecated since 1.20; use getTitle() instead. - * @return Title - */ - public function getDisplayTitle() { - wfDeprecated( __METHOD__, '1.20' ); - return $this->getTitle(); - } - - /** * Show options for the log list * * @param array|string $types @@ -234,7 +223,8 @@ class LogEventsList extends ContextSource { 'user', 'mw-log-user', 15, - $user + $user, + array( 'class' => 'mw-autocomplete-user' ) ); return '<span style="white-space: nowrap">' . $label . '</span>'; @@ -271,14 +261,21 @@ class LogEventsList extends ContextSource { * @return string */ private function getExtraInputs( $types ) { - $offender = $this->getRequest()->getVal( 'offender' ); - $user = User::newFromName( $offender, false ); - if ( !$user || ( $user->getId() == 0 && !IP::isIPAddress( $offender ) ) ) { - $offender = ''; // Blank field if invalid - } - if ( count( $types ) == 1 && $types[0] == 'suppress' ) { - return Xml::inputLabel( $this->msg( 'revdelete-offender' )->text(), 'offender', - 'mw-log-offender', 20, $offender ); + if ( count( $types ) == 1 ) { + if ( $types[0] == 'suppress' ) { + $offender = $this->getRequest()->getVal( 'offender' ); + $user = User::newFromName( $offender, false ); + if ( !$user || ( $user->getId() == 0 && !IP::isIPAddress( $offender ) ) ) { + $offender = ''; // Blank field if invalid + } + return Xml::inputLabel( $this->msg( 'revdelete-offender' )->text(), 'offender', + 'mw-log-offender', 20, $offender ); + } else { + // Allow extensions to add their own extra inputs + $input = ''; + Hooks::run( 'LogEventsListGetExtraInputs', array( $types[0], $this, &$input ) ); + return $input; + } } return ''; @@ -344,14 +341,27 @@ class LogEventsList extends ContextSource { */ private function getShowHideLinks( $row ) { // We don't want to see the links and + if ( $this->flags == self::NO_ACTION_LINK ) { + return ''; + } + + $user = $this->getUser(); + + // If change tag editing is available to this user, return the checkbox + if ( $this->flags & self::USE_CHECKBOXES && ChangeTags::showTagEditingUI( $user ) ) { + return Xml::check( + 'showhiderevisions', + false, + array( 'name' => 'ids[' . $row->log_id . ']' ) + ); + } + // no one can hide items from the suppress log. - if ( ( $this->flags == self::NO_ACTION_LINK ) - || $row->log_type == 'suppress' - ) { + if ( $row->log_type == 'suppress' ) { return ''; } + $del = ''; - $user = $this->getUser(); // Don't show useless checkbox to people who cannot hide log entries if ( $user->isAllowed( 'deletedhistory' ) ) { $canHide = $user->isAllowed( 'deletelogentry' ); @@ -361,7 +371,7 @@ class LogEventsList extends ContextSource { $canViewThisSuppressedEntry = $canViewSuppressedOnly && $entryIsSuppressed; if ( $row->log_deleted || $canHide ) { // Show checkboxes instead of links. - if ( $canHide && $this->flags & self::USE_REVDEL_CHECKBOXES && !$canViewThisSuppressedEntry ) { + if ( $canHide && $this->flags & self::USE_CHECKBOXES && !$canViewThisSuppressedEntry ) { // If event was hidden from sysops if ( !self::userCan( $row, LogPage::DELETED_RESTRICTED, $user ) ) { $del = Xml::check( 'deleterevisions', false, array( 'disabled' => 'disabled' ) ); @@ -544,14 +554,16 @@ class LogEventsList extends ContextSource { if ( $lim > 0 ) { $pager->mLimit = $lim; } - + // Fetch the log rows and build the HTML if needed $logBody = $pager->getBody(); + $numRows = $pager->getNumRows(); + $s = ''; if ( $logBody ) { if ( $msgKey[0] ) { $dir = $context->getLanguage()->getDir(); - $lang = $context->getLanguage()->getCode(); + $lang = $context->getLanguage()->getHtmlCode(); $s = Xml::openElement( 'div', array( 'class' => "mw-warning-with-logexcerpt mw-content-$dir", @@ -575,7 +587,7 @@ class LogEventsList extends ContextSource { $context->msg( 'logempty' )->parse() ); } - if ( $pager->getNumRows() > $pager->mLimit ) { # Show "Full log" link + if ( $numRows > $pager->mLimit ) { # Show "Full log" link $urlParam = array(); if ( $page instanceof Title ) { $urlParam['page'] = $page->getPrefixedDBkey(); @@ -613,7 +625,7 @@ class LogEventsList extends ContextSource { } /* hook can return false, if we don't want the message to be emitted (Wikia BugId:7093) */ - if ( wfRunHooks( 'LogEventsListShowLogExtract', array( &$s, $types, $page, $user, $param ) ) ) { + if ( Hooks::run( 'LogEventsListShowLogExtract', array( &$s, $types, $page, $user, $param ) ) ) { // $out can be either an OutputPage object or a String-by-reference if ( $out instanceof OutputPage ) { $out->addHTML( $s ); @@ -622,7 +634,7 @@ class LogEventsList extends ContextSource { } } - return $pager->getNumRows(); + return $numRows; } /** diff --git a/includes/logging/LogFormatter.php b/includes/logging/LogFormatter.php index 48a565f2..9c2fdd35 100644 --- a/includes/logging/LogFormatter.php +++ b/includes/logging/LogFormatter.php @@ -25,9 +25,12 @@ /** * Implements the default log formatting. - * Can be overridden by subclassing and setting - * $wgLogActionsHandlers['type/subtype'] = 'class'; or - * $wgLogActionsHandlers['type/*'] = 'class'; + * + * Can be overridden by subclassing and setting: + * + * $wgLogActionsHandlers['type/subtype'] = 'class'; or + * $wgLogActionsHandlers['type/*'] = 'class'; + * * @since 1.19 */ class LogFormatter { @@ -80,6 +83,9 @@ class LogFormatter { /** @var int Constant for handling log_deleted */ protected $audience = self::FOR_PUBLIC; + /** @var IContextSource Context for logging */ + public $context; + /** @var bool Whether to output user tool links */ protected $linkFlood = false; @@ -322,6 +328,57 @@ class LogFormatter { break; } break; + + case 'merge': + $text = wfMessage( 'pagemerge-logentry' ) + ->rawParams( $target, $parameters['4::dest'], $parameters['5::mergepoint'] ) + ->inContentLanguage()->escaped(); + break; + + case 'block': + switch ( $entry->getSubtype() ) { + case 'block': + global $wgContLang; + // Keep compatibility with extensions by checking for + // new key (5::duration/6::flags) or old key (0/optional 1) + if ( $entry->isLegacy() ) { + $rawDuration = $parameters[0]; + $rawFlags = isset( $parameters[1] ) ? $parameters[1] : ''; + } else { + $rawDuration = $parameters['5::duration']; + $rawFlags = $parameters['6::flags']; + } + $duration = $wgContLang->translateBlockExpiry( $rawDuration ); + $flags = BlockLogFormatter::formatBlockFlags( $rawFlags, $wgContLang ); + $text = wfMessage( 'blocklogentry' ) + ->rawParams( $target, $duration, $flags )->inContentLanguage()->escaped(); + break; + case 'unblock': + $text = wfMessage( 'unblocklogentry' ) + ->rawParams( $target )->inContentLanguage()->escaped(); + break; + case 'reblock': + global $wgContLang; + $duration = $wgContLang->translateBlockExpiry( $parameters['5::duration'] ); + $flags = BlockLogFormatter::formatBlockFlags( $parameters['6::flags'], $wgContLang ); + $text = wfMessage( 'reblock-logentry' ) + ->rawParams( $target, $duration, $flags )->inContentLanguage()->escaped(); + break; + } + break; + + case 'import': + switch ( $entry->getSubtype() ) { + case 'upload': + $text = wfMessage( 'import-logentry-upload' ) + ->rawParams( $target )->inContentLanguage()->escaped(); + break; + case 'interwiki': + $text = wfMessage( 'import-logentry-interwiki' ) + ->rawParams( $target )->inContentLanguage()->escaped(); + break; + } + break; // case 'suppress' --private log -- aaron (so we know who to blame in a few years :-D) // default: } @@ -349,8 +406,10 @@ class LogFormatter { $element = $this->styleRestricedElement( $element ); } } else { - $performer = $this->getPerformerElement() . $this->msg( 'word-separator' )->text(); - $element = $performer . $this->getRestrictedElement( 'rev-deleted-event' ); + $sep = $this->msg( 'word-separator' ); + $sep = $this->plaintext ? $sep->text() : $sep->escaped(); + $performer = $this->getPerformerElement(); + $element = $performer . $sep . $this->getRestrictedElement( 'rev-deleted-event' ); } return $element; @@ -413,7 +472,9 @@ class LogFormatter { continue; } list( $index, $type, ) = explode( ':', $key, 3 ); - $params[$index - 1] = $this->formatParameterValue( $type, $value ); + if ( ctype_digit( $index ) ) { + $params[$index - 1] = $this->formatParameterValue( $type, $value ); + } } /* Message class doesn't like non consecutive numbering. @@ -422,7 +483,8 @@ class LogFormatter { */ if ( count( $params ) ) { $max = max( array_keys( $params ) ); - for ( $i = 4; $i < $max; $i++ ) { + // index 0 to 2 are added in getMessageParameters + for ( $i = 3; $i < $max; $i++ ) { if ( !isset( $params[$i] ) ) { $params[$i] = ''; } @@ -480,8 +542,8 @@ class LogFormatter { * * title-link: The value is a page title, * returns link to this page * * number: Format value as number - * @param string $value The parameter value that should - * be formated + * * list: Format value as a comma-separated list + * @param mixed $value The parameter value that should be formatted * @return string|array Formated value * @since 1.21 */ @@ -492,6 +554,9 @@ class LogFormatter { case 'raw': $value = Message::rawParam( $value ); break; + case 'list': + $value = $this->context->getLanguage()->commaList( $value ); + break; case 'msg': $value = $this->msg( $value )->text(); break; @@ -629,7 +694,7 @@ class LogFormatter { return $this->context->msg( $key ); } - protected function makeUserLink( User $user ) { + protected function makeUserLink( User $user, $toolFlags = 0 ) { if ( $this->plaintext ) { $element = $user->getName(); } else { @@ -639,9 +704,11 @@ class LogFormatter { ); if ( $this->linkFlood ) { - $element .= Linker::userToolLinksRedContribs( + $element .= Linker::userToolLinks( $user->getId(), $user->getName(), + true, // redContribsWhenNoEdits + $toolFlags, $user->getEditCount() ); } @@ -666,6 +733,120 @@ class LogFormatter { // problems with extensions return $this->getMessageParameters(); } + + /** + * Get the array of parameters, converted from legacy format if necessary. + * @since 1.25 + * @return array + */ + protected function getParametersForApi() { + return $this->entry->getParameters(); + } + + /** + * Format parameters for API output + * + * The result array should generally map named keys to values. Index and + * type should be omitted, e.g. "4::foo" should be returned as "foo" in the + * output. Values should generally be unformatted. + * + * Renames or removals of keys besides from the legacy numeric format to + * modern named style should be avoided. Any renames should be announced to + * the mediawiki-api-announce mailing list. + * + * @since 1.25 + * @return array + */ + public function formatParametersForApi() { + $logParams = array(); + foreach ( $this->getParametersForApi() as $key => $value ) { + $vals = explode( ':', $key, 3 ); + if ( count( $vals ) !== 3 ) { + $logParams[$key] = $value; + continue; + } + $logParams += $this->formatParameterValueForApi( $vals[2], $vals[1], $value ); + } + ApiResult::setIndexedTagName( $logParams, 'param' ); + ApiResult::setArrayType( $logParams, 'assoc' ); + + return $logParams; + } + + /** + * Format a single parameter value for API output + * + * @since 1.25 + * @param string $name + * @param string $type + * @param string $value + * @return array + */ + protected function formatParameterValueForApi( $name, $type, $value ) { + $type = strtolower( trim( $type ) ); + switch ( $type ) { + case 'bool': + $value = (bool)$value; + break; + + case 'number': + if ( ctype_digit( $value ) ) { + $value = (int)$value; + } else { + $value = (float)$value; + } + break; + + case 'array': + case 'assoc': + case 'kvp': + if ( is_array( $value ) ) { + ApiResult::setArrayType( $value, $type ); + } + break; + + case 'timestamp': + $value = wfTimestamp( TS_ISO_8601, $value ); + break; + + case 'msg': + case 'msg-content': + $msg = $this->msg( $value ); + if ( $type === 'msg-content' ) { + $msg->inContentLanguage(); + } + $value = array(); + $value["{$name}_key"] = $msg->getKey(); + if ( $msg->getParams() ) { + $value["{$name}_params"] = $msg->getParams(); + } + $value["{$name}_text"] = $msg->text(); + return $value; + + case 'title': + case 'title-link': + $title = Title::newFromText( $value ); + if ( $title ) { + $value = array(); + ApiQueryBase::addTitleInfo( $value, $title, "{$name}_" ); + } + return $value; + + case 'user': + case 'user-link': + $user = User::newFromName( $value ); + if ( $user ) { + $value = $user->getName(); + } + break; + + default: + // do nothing + break; + } + + return array( $name => $value ); + } } /** @@ -725,7 +906,9 @@ class LegacyLogFormatter extends LogFormatter { $performer = $this->getPerformerElement(); if ( !$this->irctext ) { - $action = $performer . $this->msg( 'word-separator' )->text() . $action; + $sep = $this->msg( 'word-separator' ); + $sep = $this->plaintext ? $sep->text() : $sep->escaped(); + $action = $performer . $sep . $action; } return $action; @@ -745,29 +928,7 @@ class LegacyLogFormatter extends LogFormatter { $type = $this->entry->getType(); $subtype = $this->entry->getSubtype(); - // Show unblock/change block link - if ( ( $type == 'block' || $type == 'suppress' ) - && ( $subtype == 'block' || $subtype == 'reblock' ) - ) { - if ( !$this->context->getUser()->isAllowed( 'block' ) ) { - return ''; - } - - $links = array( - Linker::linkKnown( - SpecialPage::getTitleFor( 'Unblock', $title->getDBkey() ), - $this->msg( 'unblocklink' )->escaped() - ), - Linker::linkKnown( - SpecialPage::getTitleFor( 'Block', $title->getDBkey() ), - $this->msg( 'change-blocklink' )->escaped() - ) - ); - - return $this->msg( 'parentheses' )->rawParams( - $this->context->getLanguage()->pipeList( $links ) )->escaped(); - // Show change protection link - } elseif ( $type == 'protect' + if ( $type == 'protect' && ( $subtype == 'protect' || $subtype == 'modify' || $subtype == 'unprotect' ) ) { $links = array( @@ -791,26 +952,6 @@ class LegacyLogFormatter extends LogFormatter { return $this->msg( 'parentheses' )->rawParams( $this->context->getLanguage()->pipeList( $links ) )->escaped(); - // Show unmerge link - } elseif ( $type == 'merge' && $subtype == 'merge' ) { - if ( !$this->context->getUser()->isAllowed( 'mergehistory' ) ) { - return ''; - } - - $params = $this->extractParameters(); - $revert = Linker::linkKnown( - SpecialPage::getTitleFor( 'MergeHistory' ), - $this->msg( 'revertmerge' )->escaped(), - array(), - array( - 'target' => $params[3], - 'dest' => $title->getPrefixedDBkey(), - 'mergepoint' => $params[4], - 'submitted' => 1 // show the revisions immediately - ) - ); - - return $this->msg( 'parentheses' )->rawParams( $revert )->escaped(); } // Do nothing. The implementation is handled by the hook modifiying the @@ -826,7 +967,7 @@ class LegacyLogFormatter extends LogFormatter { $params = $this->entry->getParameters(); - wfRunHooks( 'LogLine', array( $type, $subtype, $title, $params, + Hooks::run( 'LogLine', array( $type, $subtype, $title, $params, &$this->comment, &$this->revert, $this->entry->getTimestamp() ) ); return $this->revert; diff --git a/includes/logging/LogPage.php b/includes/logging/LogPage.php index ce5b972f..82e5808a 100644 --- a/includes/logging/LogPage.php +++ b/includes/logging/LogPage.php @@ -288,24 +288,8 @@ class LogPage { $details = ''; array_unshift( $params, $titleLink ); - // User suppression - if ( preg_match( '/^(block|suppress)\/(block|reblock)$/', $key ) ) { - if ( $skin ) { - // Localize the duration, and add a tooltip - // in English to help visitors from other wikis. - // The lrm is needed to make sure that the number - // is shown on the correct side of the tooltip text. - $durationTooltip = '‎' . htmlspecialchars( $params[1] ); - $params[1] = "<span class='blockExpiry' title='$durationTooltip'>" . - $wgLang->translateBlockExpiry( $params[1] ) . '</span>'; - } else { - $params[1] = $wgContLang->translateBlockExpiry( $params[1] ); - } - - $params[2] = isset( $params[2] ) ? - self::formatBlockFlags( $params[2], $langObj ) : ''; // Page protections - } elseif ( $type == 'protect' && count( $params ) == 3 ) { + if ( $type == 'protect' && count( $params ) == 3 ) { // Restrictions and expiries if ( $skin ) { $details .= $wgLang->getDirMark() . htmlspecialchars( " {$params[1]}" ); @@ -370,69 +354,22 @@ class LogPage { return $title->getPrefixedText(); } - switch ( $type ) { - case 'move': - $titleLink = Linker::link( - $title, - htmlspecialchars( $title->getPrefixedText() ), - array(), - array( 'redirect' => 'no' ) - ); - - $targetTitle = Title::newFromText( $params[0] ); - - if ( !$targetTitle ) { - # Workaround for broken database - $params[0] = htmlspecialchars( $params[0] ); - } else { - $params[0] = Linker::link( - $targetTitle, - htmlspecialchars( $params[0] ) - ); - } - break; - case 'block': - if ( substr( $title->getText(), 0, 1 ) == '#' ) { - $titleLink = $title->getText(); - } else { - // @todo Store the user identifier in the parameters - // to make this faster for future log entries - $id = User::idFromName( $title->getText() ); - $titleLink = Linker::userLink( $id, $title->getText() ) - . Linker::userToolLinks( $id, $title->getText(), false, Linker::TOOL_LINKS_NOBLOCK ); - } - break; - case 'merge': - $titleLink = Linker::link( - $title, - $title->getPrefixedText(), - array(), - array( 'redirect' => 'no' ) - ); - $params[0] = Linker::link( - Title::newFromText( $params[0] ), - htmlspecialchars( $params[0] ) - ); - $params[1] = $lang->timeanddate( $params[1] ); - break; - default: - if ( $title->isSpecialPage() ) { - list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); - - # Use the language name for log titles, rather than Log/X - if ( $name == 'Log' ) { - $logPage = new LogPage( $par ); - $titleLink = Linker::link( $title, $logPage->getName()->escaped() ); - $titleLink = wfMessage( 'parentheses' ) - ->inLanguage( $lang ) - ->rawParams( $titleLink ) - ->escaped(); - } else { - $titleLink = Linker::link( $title ); - } - } else { - $titleLink = Linker::link( $title ); - } + if ( $title->isSpecialPage() ) { + list( $name, $par ) = SpecialPageFactory::resolveAlias( $title->getDBkey() ); + + # Use the language name for log titles, rather than Log/X + if ( $name == 'Log' ) { + $logPage = new LogPage( $par ); + $titleLink = Linker::link( $title, $logPage->getName()->escaped() ); + $titleLink = wfMessage( 'parentheses' ) + ->inLanguage( $lang ) + ->rawParams( $titleLink ) + ->escaped(); + } else { + $titleLink = Linker::link( $title ); + } + } else { + $titleLink = Linker::link( $title ); } return $titleLink; @@ -485,6 +422,10 @@ class LogPage { $logEntry->setTarget( $target ); $logEntry->setPerformer( $doer ); $logEntry->setParameters( $params ); + // All log entries using the LogPage to insert into the logging table + // are using the old logging system and therefore the legacy flag is + // needed to say the LogFormatter the parameters have numeric keys + $logEntry->setLegacy( true ); $formatter = LogFormatter::newFromEntry( $logEntry ); $context = RequestContext::newExtraneousContext( $target ); @@ -550,61 +491,6 @@ class LogPage { } /** - * Convert a comma-delimited list of block log flags - * into a more readable (and translated) form - * - * @param string $flags Flags to format - * @param Language $lang - * @return string - */ - public static function formatBlockFlags( $flags, $lang ) { - $flags = trim( $flags ); - if ( $flags === '' ) { - return ''; //nothing to do - } - $flags = explode( ',', $flags ); - $flagsCount = count( $flags ); - - for ( $i = 0; $i < $flagsCount; $i++ ) { - $flags[$i] = self::formatBlockFlag( $flags[$i], $lang ); - } - - return wfMessage( 'parentheses' )->inLanguage( $lang ) - ->rawParams( $lang->commaList( $flags ) )->escaped(); - } - - /** - * Translate a block log flag if possible - * - * @param int $flag Flag to translate - * @param Language $lang Language object to use - * @return string - */ - public static function formatBlockFlag( $flag, $lang ) { - static $messages = array(); - - if ( !isset( $messages[$flag] ) ) { - $messages[$flag] = htmlspecialchars( $flag ); // Fallback - - // For grepping. The following core messages can be used here: - // * block-log-flags-angry-autoblock - // * block-log-flags-anononly - // * block-log-flags-hiddenname - // * block-log-flags-noautoblock - // * block-log-flags-nocreate - // * block-log-flags-noemail - // * block-log-flags-nousertalk - $msg = wfMessage( 'block-log-flags-' . $flag )->inLanguage( $lang ); - - if ( $msg->exists() ) { - $messages[$flag] = $msg->escaped(); - } - } - - return $messages[$flag]; - } - - /** * Name of the log. * @return Message * @since 1.19 diff --git a/includes/logging/LogPager.php b/includes/logging/LogPager.php index 256934e4..bf489ab9 100644 --- a/includes/logging/LogPager.php +++ b/includes/logging/LogPager.php @@ -323,7 +323,6 @@ class LogPager extends ReverseChronologicalPager { } public function getStartBody() { - wfProfileIn( __METHOD__ ); # Do a link batch query if ( $this->getNumRows() > 0 ) { $lb = new LinkBatch; @@ -339,7 +338,6 @@ class LogPager extends ReverseChronologicalPager { $lb->execute(); $this->mResult->seek( 0 ); } - wfProfileOut( __METHOD__ ); return ''; } diff --git a/includes/logging/MergeLogFormatter.php b/includes/logging/MergeLogFormatter.php new file mode 100644 index 00000000..36e383b0 --- /dev/null +++ b/includes/logging/MergeLogFormatter.php @@ -0,0 +1,91 @@ +<?php +/** + * Formatter for merge log entries. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + * @since 1.25 + */ + +/** + * This class formats merge log entries. + * + * @since 1.25 + */ +class MergeLogFormatter extends LogFormatter { + public function getPreloadTitles() { + $params = $this->extractParameters(); + + return array( Title::newFromText( $params[3] ) ); + } + + protected function getMessageParameters() { + $params = parent::getMessageParameters(); + $oldname = $this->makePageLink( $this->entry->getTarget(), array( 'redirect' => 'no' ) ); + $newname = $this->makePageLink( Title::newFromText( $params[3] ) ); + $params[2] = Message::rawParam( $oldname ); + $params[3] = Message::rawParam( $newname ); + $params[4] = $this->context->getLanguage() + ->userTimeAndDate( $params[4], $this->context->getUser() ); + return $params; + } + + public function getActionLinks() { + if ( $this->entry->isDeleted( LogPage::DELETED_ACTION ) // Action is hidden + || !$this->context->getUser()->isAllowed( 'mergehistory' ) + ) { + return ''; + } + + // Show unmerge link + $params = $this->extractParameters(); + $revert = Linker::linkKnown( + SpecialPage::getTitleFor( 'MergeHistory' ), + $this->msg( 'revertmerge' )->escaped(), + array(), + array( + 'target' => $params[3], + 'dest' => $this->entry->getTarget()->getPrefixedDBkey(), + 'mergepoint' => $params[4], + 'submitted' => 1 // show the revisions immediately + ) + ); + + return $this->msg( 'parentheses' )->rawParams( $revert )->escaped(); + } + + protected function getParametersForApi() { + $entry = $this->entry; + $params = $entry->getParameters(); + + static $map = array( + '4:title:dest', + '5:timestamp:mergepoint', + '4::dest' => '4:title:dest', + '5::mergepoint' => '5:timestamp:mergepoint', + ); + foreach ( $map as $index => $key ) { + if ( isset( $params[$index] ) ) { + $params[$key] = $params[$index]; + unset( $params[$index] ); + } + } + + return $params; + } +} diff --git a/includes/logging/MoveLogFormatter.php b/includes/logging/MoveLogFormatter.php index 39130163..7f5e9efa 100644 --- a/includes/logging/MoveLogFormatter.php +++ b/includes/logging/MoveLogFormatter.php @@ -37,8 +37,9 @@ class MoveLogFormatter extends LogFormatter { protected function getMessageKey() { $key = parent::getMessageKey(); - $params = $this->getMessageParameters(); + $params = $this->extractParameters(); if ( isset( $params[4] ) && $params[4] === '1' ) { + // Messages: logentry-move-move-noredirect, logentry-move-move_redir-noredirect $key .= '-noredirect'; } @@ -51,6 +52,7 @@ class MoveLogFormatter extends LogFormatter { $newname = $this->makePageLink( Title::newFromText( $params[3] ) ); $params[2] = Message::rawParam( $oldname ); $params[3] = Message::rawParam( $newname ); + unset( $params[4] ); // handled in getMessageKey return $params; } @@ -83,4 +85,29 @@ class MoveLogFormatter extends LogFormatter { return $this->msg( 'parentheses' )->rawParams( $revert )->escaped(); } + + protected function getParametersForApi() { + $entry = $this->entry; + $params = $entry->getParameters(); + + static $map = array( + '4:title:target', + '5:bool:suppressredirect', + '4::target' => '4:title:target', + '5::noredir' => '5:bool:suppressredirect', + ); + foreach ( $map as $index => $key ) { + if ( isset( $params[$index] ) ) { + $params[$key] = $params[$index]; + unset( $params[$index] ); + } + } + + if ( !isset( $params['5:bool:suppressredirect'] ) ) { + $params['5:bool:suppressredirect'] = false; + } + + return $params; + } + } diff --git a/includes/logging/PatrolLogFormatter.php b/includes/logging/PatrolLogFormatter.php index 2abaf173..00337432 100644 --- a/includes/logging/PatrolLogFormatter.php +++ b/includes/logging/PatrolLogFormatter.php @@ -62,4 +62,24 @@ class PatrolLogFormatter extends LogFormatter { return $params; } + + protected function getParametersForApi() { + $entry = $this->entry; + $params = $entry->getParameters(); + + static $map = array( + '4::curid', + '5::previd', + '6:bool:auto', + '6::auto' => '6:bool:auto', + ); + foreach ( $map as $index => $key ) { + if ( isset( $params[$index] ) ) { + $params[$key] = $params[$index]; + unset( $params[$index] ); + } + } + + return $params; + } } diff --git a/includes/logging/RightsLogFormatter.php b/includes/logging/RightsLogFormatter.php index ac252aeb..352bda58 100644 --- a/includes/logging/RightsLogFormatter.php +++ b/includes/logging/RightsLogFormatter.php @@ -67,20 +67,8 @@ class RightsLogFormatter extends LogFormatter { return $params; } - $oldGroups = $params[3]; - $newGroups = $params[4]; - - // Less old entries - if ( $oldGroups === '' ) { - $oldGroups = array(); - } elseif ( is_string( $oldGroups ) ) { - $oldGroups = array_map( 'trim', explode( ',', $oldGroups ) ); - } - if ( $newGroups === '' ) { - $newGroups = array(); - } elseif ( is_string( $newGroups ) ) { - $newGroups = array_map( 'trim', explode( ',', $newGroups ) ); - } + $oldGroups = $this->makeGroupArray( $params[3] ); + $newGroups = $this->makeGroupArray( $params[4] ); $userName = $this->entry->getTarget()->getText(); if ( !$this->plaintext && count( $oldGroups ) ) { @@ -110,4 +98,53 @@ class RightsLogFormatter extends LogFormatter { return $params; } + + protected function getParametersForApi() { + $entry = $this->entry; + $params = $entry->getParameters(); + + static $map = array( + '4:array:oldgroups', + '5:array:newgroups', + '4::oldgroups' => '4:array:oldgroups', + '5::newgroups' => '5:array:newgroups', + ); + foreach ( $map as $index => $key ) { + if ( isset( $params[$index] ) ) { + $params[$key] = $params[$index]; + unset( $params[$index] ); + } + } + + // Really old entries does not have log params + if ( isset( $params['4:array:oldgroups'] ) ) { + $params['4:array:oldgroups'] = $this->makeGroupArray( $params['4:array:oldgroups'] ); + } + if ( isset( $params['5:array:newgroups'] ) ) { + $params['5:array:newgroups'] = $this->makeGroupArray( $params['5:array:newgroups'] ); + } + + return $params; + } + + public function formatParametersForApi() { + $ret = parent::formatParametersForApi(); + if ( isset( $ret['oldgroups'] ) ) { + ApiResult::setIndexedTagName( $ret['oldgroups'], 'g' ); + } + if ( isset( $ret['newgroups'] ) ) { + ApiResult::setIndexedTagName( $ret['newgroups'], 'g' ); + } + return $ret; + } + + private function makeGroupArray( $group ) { + // Migrate old group params from string to array + if ( $group === '' ) { + $group = array(); + } elseif ( is_string( $group ) ) { + $group = array_map( 'trim', explode( ',', $group ) ); + } + return $group; + } } diff --git a/includes/logging/TagLogFormatter.php b/includes/logging/TagLogFormatter.php new file mode 100644 index 00000000..5a58c331 --- /dev/null +++ b/includes/logging/TagLogFormatter.php @@ -0,0 +1,49 @@ +<?php +/** + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + */ + +/** + * This class formats tag log entries. + * + * Parameters (one-based indexes): + * 4::revid + * 5::logid + * 6:list:tagsAdded + * 7:number:tagsAddedCount + * 8:list:tagsRemoved + * 9:number:tagsRemovedCount + * + * @since 1.25 + */ +class TagLogFormatter extends LogFormatter { + protected function getMessageKey() { + $key = parent::getMessageKey(); + $params = $this->getMessageParameters(); + + $add = ( isset( $params[6] ) && isset( $params[6]['num'] ) && $params[6]['num'] ); + $remove = ( isset( $params[8] ) && isset( $params[8]['num'] ) && $params[8]['num'] ); + $key .= ( $remove ? ( $add ? '' : '-remove' ) : '-add' ); + + if ( isset( $params[4] ) && $params[4] ) { + $key .= '-logentry'; + } else { + $key .= '-revision'; + } + + return $key; + } +} diff --git a/includes/logging/UploadLogFormatter.php b/includes/logging/UploadLogFormatter.php new file mode 100644 index 00000000..52961dc4 --- /dev/null +++ b/includes/logging/UploadLogFormatter.php @@ -0,0 +1,49 @@ +<?php +/** + * Formatter for upload log entries. + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License as published by + * the Free Software Foundation; either version 2 of the License, or + * (at your option) any later version. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. + * http://www.gnu.org/copyleft/gpl.html + * + * @file + * @license http://www.gnu.org/copyleft/gpl.html GNU General Public License 2.0 or later + * @since 1.25 + */ + +/** + * This class formats upload log entries. + * + * @since 1.25 + */ +class UploadLogFormatter extends LogFormatter { + + protected function getParametersForApi() { + $entry = $this->entry; + $params = $entry->getParameters(); + + static $map = array( + 'img_timestamp' => ':timestamp:img_timestamp', + ); + foreach ( $map as $index => $key ) { + if ( isset( $params[$index] ) ) { + $params[$key] = $params[$index]; + unset( $params[$index] ); + } + } + + return $params; + } + +} |