X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2Flogging%2FLogEntry.php;h=ce68a918970205f8d40c81f71cff11e444bd6396;hb=529fc12d2ad2032337594389448fdb5b55802830;hp=5cad31f2fe69ff66d318706eaa0e7672675f583c;hpb=09ee5d2505f99cc146f871307be7ac00b7368d76;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/logging/LogEntry.php b/includes/logging/LogEntry.php index 5cad31f2fe..ce68a91897 100644 --- a/includes/logging/LogEntry.php +++ b/includes/logging/LogEntry.php @@ -1,11 +1,6 @@ getType() . '/' . $this->getSubtype(); - } - - public function isDeleted( $field ) { - return ( $this->getDeleted() & $field ) === $field; - } - - /** - * Whether the parameters for this log are stored in new or - * old format. - * - * @return bool - */ - public function isLegacy() { - return false; - } - - /** - * Create a blob from a parameter array - * - * @since 1.26 - * @param array $params - * @return string - */ - public static function makeParamBlob( $params ) { - return serialize( (array)$params ); - } - - /** - * Extract a parameter array from a blob - * - * @since 1.26 - * @param string $blob - * @return array - */ - public static function extractParams( $blob ) { - return unserialize( $blob ); - } -} - -/** - * A value class to process existing log entries. In other words, this class caches a log - * entry from the database and provides an immutable object-oriented representation of it. - * - * @since 1.19 - */ -class DatabaseLogEntry extends LogEntryBase { - - /** - * Returns array of information that is needed for querying - * log entries. Array contains the following keys: - * tables, fields, conds, options and join_conds - * - * @return array - */ - public static function getSelectQueryData() { - $commentQuery = CommentStore::getStore()->getJoin( 'log_comment' ); - $actorQuery = ActorMigration::newMigration()->getJoin( 'log_user' ); - - $tables = array_merge( - [ 'logging' ], $commentQuery['tables'], $actorQuery['tables'], [ 'user' ] - ); - $fields = [ - 'log_id', 'log_type', 'log_action', 'log_timestamp', - 'log_namespace', 'log_title', // unused log_page - 'log_params', 'log_deleted', - 'user_id', 'user_name', 'user_editcount', - ] + $commentQuery['fields'] + $actorQuery['fields']; - - $joins = [ - // IPs don't have an entry in user table - 'user' => [ 'LEFT JOIN', 'user_id=' . $actorQuery['fields']['log_user'] ], - ] + $commentQuery['joins'] + $actorQuery['joins']; - - return [ - 'tables' => $tables, - 'fields' => $fields, - 'conds' => [], - 'options' => [], - 'join_conds' => $joins, - ]; - } - - /** - * Constructs new LogEntry from database result row. - * Supports rows from both logging and recentchanges table. - * - * @param stdClass|array $row - * @return DatabaseLogEntry - */ - public static function newFromRow( $row ) { - $row = (object)$row; - if ( isset( $row->rc_logid ) ) { - return new RCDatabaseLogEntry( $row ); - } else { - return new self( $row ); - } - } - - /** - * Loads a LogEntry with the given id from database - * - * @param int $id - * @param IDatabase $db - * @return DatabaseLogEntry|null - */ - public static function newFromId( $id, IDatabase $db ) { - $queryInfo = self::getSelectQueryData(); - $queryInfo['conds'] += [ 'log_id' => $id ]; - $row = $db->selectRow( - $queryInfo['tables'], - $queryInfo['fields'], - $queryInfo['conds'], - __METHOD__, - $queryInfo['options'], - $queryInfo['join_conds'] - ); - if ( !$row ) { - return null; - } - return self::newFromRow( $row ); - } - - /** @var stdClass Database result row. */ - protected $row; - - /** @var User */ - protected $performer; - - /** @var array Parameters for log entry */ - protected $params; - - /** @var int A rev id associated to the log entry */ - protected $revId = null; - - /** @var bool Whether the parameters for this log entry are stored in new or old format. */ - protected $legacy; - - protected function __construct( $row ) { - $this->row = $row; - } - - /** - * Returns the unique database id. - * - * @return int - */ - public function getId() { - return (int)$this->row->log_id; - } - - /** - * Returns whatever is stored in the database field. - * - * @return string - */ - protected function getRawParameters() { - return $this->row->log_params; - } - - public function isLegacy() { - // This extracts the property - $this->getParameters(); - return $this->legacy; - } - - public function getType() { - return $this->row->log_type; - } - - public function getSubtype() { - return $this->row->log_action; - } - - public function getParameters() { - if ( !isset( $this->params ) ) { - $blob = $this->getRawParameters(); - Wikimedia\suppressWarnings(); - $params = LogEntryBase::extractParams( $blob ); - Wikimedia\restoreWarnings(); - if ( $params !== false ) { - $this->params = $params; - $this->legacy = false; - } else { - $this->params = LogPage::extractParams( $blob ); - $this->legacy = true; - } - - if ( isset( $this->params['associated_rev_id'] ) ) { - $this->revId = $this->params['associated_rev_id']; - unset( $this->params['associated_rev_id'] ); - } - } - - return $this->params; - } - - public function getAssociatedRevId() { - // This extracts the property - $this->getParameters(); - return $this->revId; - } - - public function getPerformer() { - if ( !$this->performer ) { - $actorId = isset( $this->row->log_actor ) ? (int)$this->row->log_actor : 0; - $userId = (int)$this->row->log_user; - if ( $userId !== 0 || $actorId !== 0 ) { - // logged-in users - if ( isset( $this->row->user_name ) ) { - $this->performer = User::newFromRow( $this->row ); - } elseif ( $actorId !== 0 ) { - $this->performer = User::newFromActorId( $actorId ); - } else { - $this->performer = User::newFromId( $userId ); - } - } else { - // IP users - $userText = $this->row->log_user_text; - $this->performer = User::newFromName( $userText, false ); - } - } - - return $this->performer; - } - - public function getTarget() { - $namespace = $this->row->log_namespace; - $page = $this->row->log_title; - $title = Title::makeTitle( $namespace, $page ); - - return $title; - } - - public function getTimestamp() { - return wfTimestamp( TS_MW, $this->row->log_timestamp ); - } - - public function getComment() { - return CommentStore::getStore()->getComment( 'log_comment', $this->row )->text; - } - - public function getDeleted() { - return $this->row->log_deleted; - } -} - -/** - * A subclass of DatabaseLogEntry for objects constructed from entries in the - * recentchanges table (rather than the logging table). - */ -class RCDatabaseLogEntry extends DatabaseLogEntry { - - public function getId() { - return $this->row->rc_logid; - } - - protected function getRawParameters() { - return $this->row->rc_params; - } - - public function getAssociatedRevId() { - return $this->row->rc_this_oldid; - } - - public function getType() { - return $this->row->rc_log_type; - } - - public function getSubtype() { - return $this->row->rc_log_action; - } - - public function getPerformer() { - if ( !$this->performer ) { - $actorId = isset( $this->row->rc_actor ) ? (int)$this->row->rc_actor : 0; - $userId = (int)$this->row->rc_user; - if ( $actorId !== 0 ) { - $this->performer = User::newFromActorId( $actorId ); - } elseif ( $userId !== 0 ) { - $this->performer = User::newFromId( $userId ); - } else { - $userText = $this->row->rc_user_text; - // Might be an IP, don't validate the username - $this->performer = User::newFromName( $userText, false ); - } - } - - return $this->performer; - } - - public function getTarget() { - $namespace = $this->row->rc_namespace; - $page = $this->row->rc_title; - $title = Title::makeTitle( $namespace, $page ); - - return $title; - } - - public function getTimestamp() { - return wfTimestamp( TS_MW, $this->row->rc_timestamp ); - } - - public function getComment() { - return CommentStore::getStore() - // Legacy because the row may have used RecentChange::selectFields() - ->getCommentLegacy( wfGetDB( DB_REPLICA ), 'rc_comment', $this->row )->text; - } - - public function getDeleted() { - return $this->row->rc_deleted; - } -} - -/** - * Class for creating new log entries and inserting them into the database. - * - * @since 1.19 - */ -class ManualLogEntry extends LogEntryBase implements Taggable { - /** @var string Type of log entry */ - protected $type; - - /** @var string Sub type of log entry */ - protected $subtype; - - /** @var array Parameters for log entry */ - protected $parameters = []; - - /** @var array */ - protected $relations = []; - - /** @var User Performer of the action for the log entry */ - protected $performer; - - /** @var Title Target title for the log entry */ - protected $target; - - /** @var string Timestamp of creation of the log entry */ - protected $timestamp; - - /** @var string Comment for the log entry */ - protected $comment = ''; - - /** @var int A rev id associated to the log entry */ - protected $revId = 0; - - /** @var string[] Change tags add to the log entry */ - protected $tags = []; - - /** @var int Deletion state of the log entry */ - protected $deleted; - - /** @var int ID of the log entry */ - protected $id; - - /** @var bool Can this log entry be patrolled? */ - protected $isPatrollable = false; - - /** @var bool Whether this is a legacy log entry */ - protected $legacy = false; - - /** - * @since 1.19 - * @param string $type - * @param string $subtype - */ - public function __construct( $type, $subtype ) { - $this->type = $type; - $this->subtype = $subtype; - } - - /** - * Set extra log parameters. - * - * 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. - * - * If you want to store stuff that should not be available in messages, don't - * prefix the array key with a number and don't use the colons. - * - * Example: - * $entry->setParameters( - * '4::color' => 'blue', - * '5:number:count' => 3000, - * 'animal' => 'dog' - * ); - * - * @since 1.19 - * @param array $parameters Associative array - */ - public function setParameters( $parameters ) { - $this->parameters = $parameters; - } - - /** - * Declare arbitrary tag/value relations to this log entry. - * These can be used to filter log entries later on. - * - * @param array $relations Map of (tag => (list of values|value)) - * @since 1.22 - */ - public function setRelations( array $relations ) { - $this->relations = $relations; - } - - /** - * Set the user that performed the action being logged. - * - * @since 1.19 - * @param UserIdentity $performer - */ - public function setPerformer( UserIdentity $performer ) { - $this->performer = User::newFromIdentity( $performer ); - } - - /** - * Set the title of the object changed. - * - * @since 1.19 - * @param LinkTarget $target - */ - public function setTarget( LinkTarget $target ) { - $this->target = Title::newFromLinkTarget( $target ); - } - - /** - * Set the timestamp of when the logged action took place. - * - * @since 1.19 - * @param string $timestamp - */ - public function setTimestamp( $timestamp ) { - $this->timestamp = $timestamp; - } - - /** - * Set a comment associated with the action being logged. - * - * @since 1.19 - * @param string $comment - */ - public function setComment( $comment ) { - $this->comment = $comment; - } - - /** - * Set an associated revision id. - * - * For example, the ID of the revision that was inserted to mark a page move - * or protection, file upload, etc. - * - * @since 1.27 - * @param int $revId - */ - public function setAssociatedRevId( $revId ) { - $this->revId = $revId; - } - - /** - * Set change tags for the log entry. - * - * Passing `null` means the same as empty array, - * for compatibility with WikiPage::doUpdateRestrictions(). - * - * @since 1.27 - * @param string|string[]|null $tags - * @deprecated since 1.33 Please use addTags() instead - */ - public function setTags( $tags ) { - if ( $this->tags ) { - wfDebug( 'Overwriting existing ManualLogEntry tags' ); - } - $this->tags = []; - if ( $tags !== null ) { - $this->addTags( $tags ); - } - } - - /** - * Add change tags for the log entry - * - * @since 1.33 - * @param string|string[] $tags Tags to apply - */ - public function addTags( $tags ) { - if ( is_string( $tags ) ) { - $tags = [ $tags ]; - } - Assert::parameterElementType( 'string', $tags, 'tags' ); - $this->tags = array_unique( array_merge( $this->tags, $tags ) ); - } - - /** - * Set whether this log entry should be made patrollable - * This shouldn't depend on config, only on whether there is full support - * in the software for patrolling this log entry. - * False by default - * - * @since 1.27 - * @param bool $patrollable - */ - public function setIsPatrollable( $patrollable ) { - $this->isPatrollable = (bool)$patrollable; - } - - /** - * Set the 'legacy' flag - * - * @since 1.25 - * @param bool $legacy - */ - public function setLegacy( $legacy ) { - $this->legacy = $legacy; - } - - /** - * Set the 'deleted' flag. - * - * @since 1.19 - * @param int $deleted One of LogPage::DELETED_* bitfield constants - */ - public function setDeleted( $deleted ) { - $this->deleted = $deleted; - } - - /** - * Insert the entry into the `logging` table. - * - * @param IDatabase|null $dbw - * @return int ID of the log entry - * @throws MWException - */ - public function insert( IDatabase $dbw = null ) { - global $wgActorTableSchemaMigrationStage; - - $dbw = $dbw ?: wfGetDB( DB_MASTER ); - - if ( $this->timestamp === null ) { - $this->timestamp = wfTimestampNow(); - } - - // Trim spaces on user supplied text - $comment = trim( $this->getComment() ); - - $params = $this->getParameters(); - $relations = $this->relations; - - // Ensure actor relations are set - if ( ( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_NEW ) && - empty( $relations['target_author_actor'] ) - ) { - $actorIds = []; - if ( !empty( $relations['target_author_id'] ) ) { - foreach ( $relations['target_author_id'] as $id ) { - $actorIds[] = User::newFromId( $id )->getActorId( $dbw ); - } - } - if ( !empty( $relations['target_author_ip'] ) ) { - foreach ( $relations['target_author_ip'] as $ip ) { - $actorIds[] = User::newFromName( $ip, false )->getActorId( $dbw ); - } - } - if ( $actorIds ) { - $relations['target_author_actor'] = $actorIds; - $params['authorActors'] = $actorIds; - } - } - if ( !( $wgActorTableSchemaMigrationStage & SCHEMA_COMPAT_WRITE_OLD ) ) { - unset( $relations['target_author_id'], $relations['target_author_ip'] ); - unset( $params['authorIds'], $params['authorIPs'] ); - } - - // Additional fields for which there's no space in the database table schema - $revId = $this->getAssociatedRevId(); - if ( $revId ) { - $params['associated_rev_id'] = $revId; - $relations['associated_rev_id'] = $revId; - } - - $data = [ - 'log_type' => $this->getType(), - 'log_action' => $this->getSubtype(), - 'log_timestamp' => $dbw->timestamp( $this->getTimestamp() ), - 'log_namespace' => $this->getTarget()->getNamespace(), - 'log_title' => $this->getTarget()->getDBkey(), - 'log_page' => $this->getTarget()->getArticleID(), - 'log_params' => LogEntryBase::makeParamBlob( $params ), - ]; - if ( isset( $this->deleted ) ) { - $data['log_deleted'] = $this->deleted; - } - $data += CommentStore::getStore()->insert( $dbw, 'log_comment', $comment ); - $data += ActorMigration::newMigration() - ->getInsertValues( $dbw, 'log_user', $this->getPerformer() ); - - $dbw->insert( 'logging', $data, __METHOD__ ); - $this->id = $dbw->insertId(); - - $rows = []; - foreach ( $relations as $tag => $values ) { - if ( !strlen( $tag ) ) { - throw new MWException( "Got empty log search tag." ); - } - - if ( !is_array( $values ) ) { - $values = [ $values ]; - } - - foreach ( $values as $value ) { - $rows[] = [ - 'ls_field' => $tag, - 'ls_value' => $value, - 'ls_log_id' => $this->id - ]; - } - } - if ( count( $rows ) ) { - $dbw->insert( 'log_search', $rows, __METHOD__, 'IGNORE' ); - } - - return $this->id; - } - - /** - * Get a RecentChanges object for the log entry - * - * @param int $newId - * @return RecentChange - * @since 1.23 - */ - public function getRecentChange( $newId = 0 ) { - $formatter = LogFormatter::newFromEntry( $this ); - $context = RequestContext::newExtraneousContext( $this->getTarget() ); - $formatter->setContext( $context ); - - $logpage = SpecialPage::getTitleFor( 'Log', $this->getType() ); - $user = $this->getPerformer(); - $ip = ""; - if ( $user->isAnon() ) { - // "MediaWiki default" and friends may have - // no IP address in their name - if ( IP::isIPAddress( $user->getName() ) ) { - $ip = $user->getName(); - } - } - - return RecentChange::newLogEntry( - $this->getTimestamp(), - $logpage, - $user, - $formatter->getPlainActionText(), - $ip, - $this->getType(), - $this->getSubtype(), - $this->getTarget(), - $this->getComment(), - LogEntryBase::makeParamBlob( $this->getParameters() ), - $newId, - $formatter->getIRCActionComment(), // Used for IRC feeds - $this->getAssociatedRevId(), // Used for e.g. moves and uploads - $this->getIsPatrollable() - ); - } - - /** - * Publish the log entry. - * - * @param int $newId Id of the log entry. - * @param string $to One of: rcandudp (default), rc, udp - */ - public function publish( $newId, $to = 'rcandudp' ) { - $canAddTags = true; - // FIXME: this code should be removed once all callers properly call publish() - if ( $to === 'udp' && !$newId && !$this->getAssociatedRevId() ) { - \MediaWiki\Logger\LoggerFactory::getInstance( 'logging' )->warning( - 'newId and/or revId must be set when calling ManualLogEntry::publish()', - [ - 'newId' => $newId, - 'to' => $to, - 'revId' => $this->getAssociatedRevId(), - // pass a new exception to register the stack trace - 'exception' => new RuntimeException() - ] - ); - $canAddTags = false; - } - - DeferredUpdates::addCallableUpdate( - function () use ( $newId, $to, $canAddTags ) { - $log = new LogPage( $this->getType() ); - if ( !$log->isRestricted() ) { - Hooks::runWithoutAbort( 'ManualLogEntryBeforePublish', [ $this ] ); - $rc = $this->getRecentChange( $newId ); - - if ( $to === 'rc' || $to === 'rcandudp' ) { - // save RC, passing tags so they are applied there - $rc->addTags( $this->getTags() ); - $rc->save( $rc::SEND_NONE ); - } else { - $tags = $this->getTags(); - if ( $tags && $canAddTags ) { - $revId = $this->getAssociatedRevId(); - ChangeTags::addTags( - $tags, - null, - $revId > 0 ? $revId : null, - $newId > 0 ? $newId : null - ); - } - } - - if ( $to === 'udp' || $to === 'rcandudp' ) { - $rc->notifyRCFeeds(); - } - } - }, - DeferredUpdates::POSTSEND, - wfGetDB( DB_MASTER ) - ); - } - - public function getType() { - return $this->type; - } - - public function getSubtype() { - return $this->subtype; - } - - public function getParameters() { - return $this->parameters; - } - - /** - * @return User - */ - public function getPerformer() { - return $this->performer; - } - - /** - * @return Title - */ - public function getTarget() { - return $this->target; - } - - public function getTimestamp() { - $ts = $this->timestamp ?? wfTimestampNow(); - - return wfTimestamp( TS_MW, $ts ); - } - - public function getComment() { - return $this->comment; - } - - /** - * @since 1.27 - * @return int - */ - public function getAssociatedRevId() { - return $this->revId; - } - - /** - * @since 1.27 - * @return string[] - */ - public function getTags() { - return $this->tags; - } - - /** - * Whether this log entry is patrollable - * - * @since 1.27 - * @return bool - */ - public function getIsPatrollable() { - return $this->isPatrollable; - } - - /** - * @since 1.25 - * @return bool - */ - public function isLegacy() { - return $this->legacy; - } - - public function getDeleted() { - return (int)$this->deleted; - } -}