/** * Log the promotion of a local unattached to a global * * @param string $oldName * @param string $wiki * @param string $newName * @param string $reason */ public function logPromotion($oldName, $wiki, $newName, $reason) { $logEntry = new ManualLogEntry('gblrename', 'promote'); $logEntry->setPerformer($this->performingUser); $logEntry->setTarget(Title::makeTitleSafe(NS_SPECIAL, 'CentralAuth/' . $newName)); $logEntry->setComment($reason); $logEntry->setParameters(array('4::olduser' => $oldName, '5::newuser' => $newName, '6::oldwiki' => $wiki)); $logEntry->setRelations(array('oldname' => $oldName)); $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * Update the article's restriction field, and leave a log entry. * This works for protection both existing and non-existing pages. * * @param array $limit Set of restriction keys * @param array $expiry Per restriction type expiration * @param int &$cascade Set to false if cascading protection isn't allowed. * @param string $reason * @param User $user The user updating the restrictions * @param string|string[] $tags Change tags to add to the pages and protection log entries * ($user should be able to add the specified tags before this is called) * @return Status Status object; if action is taken, $status->value is the log_id of the * protection log entry. */ public function doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user, $tags = null) { global $wgCascadingRestrictionLevels, $wgContLang; if (wfReadOnly()) { return Status::newFatal('readonlytext', wfReadOnlyReason()); } $this->loadPageData('fromdbmaster'); $restrictionTypes = $this->mTitle->getRestrictionTypes(); $id = $this->getId(); if (!$cascade) { $cascade = false; } // Take this opportunity to purge out expired restrictions Title::purgeExpiredRestrictions(); // @todo FIXME: Same limitations as described in ProtectionForm.php (line 37); // we expect a single selection, but the schema allows otherwise. $isProtected = false; $protect = false; $changed = false; $dbw = wfGetDB(DB_MASTER); foreach ($restrictionTypes as $action) { if (!isset($expiry[$action]) || $expiry[$action] === $dbw->getInfinity()) { $expiry[$action] = 'infinity'; } if (!isset($limit[$action])) { $limit[$action] = ''; } elseif ($limit[$action] != '') { $protect = true; } // Get current restrictions on $action $current = implode('', $this->mTitle->getRestrictions($action)); if ($current != '') { $isProtected = true; } if ($limit[$action] != $current) { $changed = true; } elseif ($limit[$action] != '') { // Only check expiry change if the action is actually being // protected, since expiry does nothing on an not-protected // action. if ($this->mTitle->getRestrictionExpiry($action) != $expiry[$action]) { $changed = true; } } } if (!$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade) { $changed = true; } // If nothing has changed, do nothing if (!$changed) { return Status::newGood(); } if (!$protect) { // No protection at all means unprotection $revCommentMsg = 'unprotectedarticle'; $logAction = 'unprotect'; } elseif ($isProtected) { $revCommentMsg = 'modifiedarticleprotection'; $logAction = 'modify'; } else { $revCommentMsg = 'protectedarticle'; $logAction = 'protect'; } // Truncate for whole multibyte characters $reason = $wgContLang->truncate($reason, 255); $logRelationsValues = []; $logRelationsField = null; $logParamsDetails = []; // Null revision (used for change tag insertion) $nullRevision = null; if ($id) { // Protection of existing page if (!Hooks::run('ArticleProtect', [&$this, &$user, $limit, $reason])) { return Status::newGood(); } // Only certain restrictions can cascade... $editrestriction = isset($limit['edit']) ? [$limit['edit']] : $this->mTitle->getRestrictions('edit'); foreach (array_keys($editrestriction, 'sysop') as $key) { $editrestriction[$key] = 'editprotected'; // backwards compatibility } foreach (array_keys($editrestriction, 'autoconfirmed') as $key) { $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility } $cascadingRestrictionLevels = $wgCascadingRestrictionLevels; foreach (array_keys($cascadingRestrictionLevels, 'sysop') as $key) { $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility } foreach (array_keys($cascadingRestrictionLevels, 'autoconfirmed') as $key) { $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility } // The schema allows multiple restrictions if (!array_intersect($editrestriction, $cascadingRestrictionLevels)) { $cascade = false; } // insert null revision to identify the page protection change as edit summary $latest = $this->getLatest(); $nullRevision = $this->insertProtectNullRevision($revCommentMsg, $limit, $expiry, $cascade, $reason, $user); if ($nullRevision === null) { return Status::newFatal('no-null-revision', $this->mTitle->getPrefixedText()); } $logRelationsField = 'pr_id'; // Update restrictions table foreach ($limit as $action => $restrictions) { $dbw->delete('page_restrictions', ['pr_page' => $id, 'pr_type' => $action], __METHOD__); if ($restrictions != '') { $cascadeValue = $cascade && $action == 'edit' ? 1 : 0; $dbw->insert('page_restrictions', ['pr_id' => $dbw->nextSequenceValue('page_restrictions_pr_id_seq'), 'pr_page' => $id, 'pr_type' => $action, 'pr_level' => $restrictions, 'pr_cascade' => $cascadeValue, 'pr_expiry' => $dbw->encodeExpiry($expiry[$action])], __METHOD__); $logRelationsValues[] = $dbw->insertId(); $logParamsDetails[] = ['type' => $action, 'level' => $restrictions, 'expiry' => $expiry[$action], 'cascade' => (bool) $cascadeValue]; } } // Clear out legacy restriction fields $dbw->update('page', ['page_restrictions' => ''], ['page_id' => $id], __METHOD__); Hooks::run('NewRevisionFromEditComplete', [$this, $nullRevision, $latest, $user]); Hooks::run('ArticleProtectComplete', [&$this, &$user, $limit, $reason]); } else { // Protection of non-existing page (also known as "title protection") // Cascade protection is meaningless in this case $cascade = false; if ($limit['create'] != '') { $dbw->replace('protected_titles', [['pt_namespace', 'pt_title']], ['pt_namespace' => $this->mTitle->getNamespace(), 'pt_title' => $this->mTitle->getDBkey(), 'pt_create_perm' => $limit['create'], 'pt_timestamp' => $dbw->timestamp(), 'pt_expiry' => $dbw->encodeExpiry($expiry['create']), 'pt_user' => $user->getId(), 'pt_reason' => $reason], __METHOD__); $logParamsDetails[] = ['type' => 'create', 'level' => $limit['create'], 'expiry' => $expiry['create']]; } else { $dbw->delete('protected_titles', ['pt_namespace' => $this->mTitle->getNamespace(), 'pt_title' => $this->mTitle->getDBkey()], __METHOD__); } } $this->mTitle->flushRestrictions(); InfoAction::invalidateCache($this->mTitle); if ($logAction == 'unprotect') { $params = []; } else { $protectDescriptionLog = $this->protectDescriptionLog($limit, $expiry); $params = ['4::description' => $protectDescriptionLog, '5:bool:cascade' => $cascade, 'details' => $logParamsDetails]; } // Update the protection log $logEntry = new ManualLogEntry('protect', $logAction); $logEntry->setTarget($this->mTitle); $logEntry->setComment($reason); $logEntry->setPerformer($user); $logEntry->setParameters($params); if (!is_null($nullRevision)) { $logEntry->setAssociatedRevId($nullRevision->getId()); } $logEntry->setTags($tags); if ($logRelationsField !== null && count($logRelationsValues)) { $logEntry->setRelations([$logRelationsField => $logRelationsValues]); } $logId = $logEntry->insert(); $logEntry->publish($logId); return Status::newGood($logId); }
/** * Writes a tag action into the tag management log. * * @param string $action * @param string $tag * @param string $reason * @param User $user Who to attribute the action to * @param int $tagCount For deletion only, how many usages the tag had before * it was deleted. * @return int ID of the inserted log entry * @since 1.25 */ protected static function logTagManagementAction($action, $tag, $reason, User $user, $tagCount = null) { $dbw = wfGetDB(DB_MASTER); $logEntry = new ManualLogEntry('managetags', $action); $logEntry->setPerformer($user); // target page is not relevant, but it has to be set, so we just put in // the title of Special:Tags $logEntry->setTarget(Title::newFromText('Special:Tags')); $logEntry->setComment($reason); $params = array('4::tag' => $tag); if (!is_null($tagCount)) { $params['5:number:count'] = $tagCount; } $logEntry->setParameters($params); $logEntry->setRelations(array('Tag' => $tag)); $logId = $logEntry->insert($dbw); $logEntry->publish($logId); return $logId; }
/** * Given the form data, actually implement a block. This is also called from ApiBlock. * * @param array $data * @param IContextSource $context * @return bool|string */ public static function processForm(array $data, IContextSource $context) { global $wgBlockAllowsUTEdit, $wgHideUserContribLimit, $wgContLang; $performer = $context->getUser(); // Handled by field validator callback // self::validateTargetField( $data['Target'] ); # This might have been a hidden field or a checkbox, so interesting data # can come from it $data['Confirm'] = !in_array($data['Confirm'], array('', '0', null, false), true); /** @var User $target */ list($target, $type) = self::getTargetAndType($data['Target']); if ($type == Block::TYPE_USER) { $user = $target; $target = $user->getName(); $userId = $user->getId(); # Give admins a heads-up before they go and block themselves. Much messier # to do this for IPs, but it's pretty unlikely they'd ever get the 'block' # permission anyway, although the code does allow for it. # Note: Important to use $target instead of $data['Target'] # since both $data['PreviousTarget'] and $target are normalized # but $data['target'] gets overridden by (non-normalized) request variable # from previous request. if ($target === $performer->getName() && ($data['PreviousTarget'] !== $target || !$data['Confirm'])) { return array('ipb-blockingself', 'ipb-confirmaction'); } } elseif ($type == Block::TYPE_RANGE) { $userId = 0; } elseif ($type == Block::TYPE_IP) { $target = $target->getName(); $userId = 0; } else { # This should have been caught in the form field validation return array('badipaddress'); } if (strlen($data['Expiry']) == 0 || strlen($data['Expiry']) > 50 || !self::parseExpiryInput($data['Expiry'])) { return array('ipb_expiry_invalid'); } if (!isset($data['DisableEmail'])) { $data['DisableEmail'] = false; } # If the user has done the form 'properly', they won't even have been given the # option to suppress-block unless they have the 'hideuser' permission if (!isset($data['HideUser'])) { $data['HideUser'] = false; } if ($data['HideUser']) { if (!$performer->isAllowed('hideuser')) { # this codepath is unreachable except by a malicious user spoofing forms, # or by race conditions (user has hideuser and block rights, loads block form, # and loses hideuser rights before submission); so need to fail completely # rather than just silently disable hiding return array('badaccess-group0'); } # Recheck params here... if ($type != Block::TYPE_USER) { $data['HideUser'] = false; # IP users should not be hidden } elseif (!wfIsInfinity($data['Expiry'])) { # Bad expiry. return array('ipb_expiry_temp'); } elseif ($wgHideUserContribLimit !== false && $user->getEditCount() > $wgHideUserContribLimit) { # Typically, the user should have a handful of edits. # Disallow hiding users with many edits for performance. return array(array('ipb_hide_invalid', Message::numParam($wgHideUserContribLimit))); } elseif (!$data['Confirm']) { return array('ipb-confirmhideuser', 'ipb-confirmaction'); } } # Create block object. $block = new Block(); $block->setTarget($target); $block->setBlocker($performer); # Truncate reason for whole multibyte characters $block->mReason = $wgContLang->truncate($data['Reason'][0], 255); $block->mExpiry = self::parseExpiryInput($data['Expiry']); $block->prevents('createaccount', $data['CreateAccount']); $block->prevents('editownusertalk', !$wgBlockAllowsUTEdit || $data['DisableUTEdit']); $block->prevents('sendemail', $data['DisableEmail']); $block->isHardblock($data['HardBlock']); $block->isAutoblocking($data['AutoBlock']); $block->mHideName = $data['HideUser']; $reason = array('hookaborted'); if (!Hooks::run('BlockIp', array(&$block, &$performer, &$reason))) { return $reason; } # Try to insert block. Is there a conflicting block? $status = $block->insert(); if (!$status) { # Indicates whether the user is confirming the block and is aware of # the conflict (did not change the block target in the meantime) $blockNotConfirmed = !$data['Confirm'] || array_key_exists('PreviousTarget', $data) && $data['PreviousTarget'] !== $target; # Special case for API - bug 32434 $reblockNotAllowed = array_key_exists('Reblock', $data) && !$data['Reblock']; # Show form unless the user is already aware of this... if ($blockNotConfirmed || $reblockNotAllowed) { return array(array('ipb_already_blocked', $block->getTarget())); # Otherwise, try to update the block... } else { # This returns direct blocks before autoblocks/rangeblocks, since we should # be sure the user is blocked by now it should work for our purposes $currentBlock = Block::newFromTarget($target); if ($block->equals($currentBlock)) { return array(array('ipb_already_blocked', $block->getTarget())); } # If the name was hidden and the blocking user cannot hide # names, then don't allow any block changes... if ($currentBlock->mHideName && !$performer->isAllowed('hideuser')) { return array('cant-see-hidden-user'); } $currentBlock->isHardblock($block->isHardblock()); $currentBlock->prevents('createaccount', $block->prevents('createaccount')); $currentBlock->mExpiry = $block->mExpiry; $currentBlock->isAutoblocking($block->isAutoblocking()); $currentBlock->mHideName = $block->mHideName; $currentBlock->prevents('sendemail', $block->prevents('sendemail')); $currentBlock->prevents('editownusertalk', $block->prevents('editownusertalk')); $currentBlock->mReason = $block->mReason; $status = $currentBlock->update(); $logaction = 'reblock'; # Unset _deleted fields if requested if ($currentBlock->mHideName && !$data['HideUser']) { RevisionDeleteUser::unsuppressUserName($target, $userId); } # If hiding/unhiding a name, this should go in the private logs if ((bool) $currentBlock->mHideName) { $data['HideUser'] = true; } } } else { $logaction = 'block'; } Hooks::run('BlockIpComplete', array($block, $performer)); # Set *_deleted fields if requested if ($data['HideUser']) { RevisionDeleteUser::suppressUserName($target, $userId); } # Can't watch a rangeblock if ($type != Block::TYPE_RANGE && $data['Watch']) { WatchAction::doWatch(Title::makeTitle(NS_USER, $target), $performer, WatchedItem::IGNORE_USER_RIGHTS); } # Block constructor sanitizes certain block options on insert $data['BlockEmail'] = $block->prevents('sendemail'); $data['AutoBlock'] = $block->isAutoblocking(); # Prepare log parameters $logParams = array(); $logParams['5::duration'] = $data['Expiry']; $logParams['6::flags'] = self::blockLogFlags($data, $type); # Make log entry, if the name is hidden, put it in the suppression log $log_type = $data['HideUser'] ? 'suppress' : 'block'; $logEntry = new ManualLogEntry($log_type, $logaction); $logEntry->setTarget(Title::makeTitle(NS_USER, $target)); $logEntry->setComment($data['Reason'][0]); $logEntry->setPerformer($performer); $logEntry->setParameters($logParams); # Relate log ID to block IDs (bug 25763) $blockIds = array_merge(array($status['id']), $status['autoIds']); $logEntry->setRelations(array('ipb_id' => $blockIds)); $logId = $logEntry->insert(); $logEntry->publish($logId); # Report to the user return true; }
/** * @param User $user * @param string $reason * @param bool $createRedirect * @return Status */ public function move(User $user, $reason, $createRedirect) { global $wgCategoryCollation; Hooks::run('TitleMove', array($this->oldTitle, $this->newTitle, $user)); // If it is a file, move it first. // It is done before all other moving stuff is done because it's hard to revert. $dbw = wfGetDB(DB_MASTER); if ($this->oldTitle->getNamespace() == NS_FILE) { $file = wfLocalFile($this->oldTitle); $file->load(File::READ_LATEST); if ($file->exists()) { $status = $file->move($this->newTitle); if (!$status->isOk()) { return $status; } } // Clear RepoGroup process cache RepoGroup::singleton()->clearCache($this->oldTitle); RepoGroup::singleton()->clearCache($this->newTitle); # clear false negative cache } $dbw->begin(__METHOD__); # If $file was a LocalFile, its transaction would have closed our own. $pageid = $this->oldTitle->getArticleID(Title::GAID_FOR_UPDATE); $protected = $this->oldTitle->isProtected(); // Do the actual move $this->moveToInternal($user, $this->newTitle, $reason, $createRedirect); // Refresh the sortkey for this row. Be careful to avoid resetting // cl_timestamp, which may disturb time-based lists on some sites. // @todo This block should be killed, it's duplicating code // from LinksUpdate::getCategoryInsertions() and friends. $prefixes = $dbw->select('categorylinks', array('cl_sortkey_prefix', 'cl_to'), array('cl_from' => $pageid), __METHOD__); if ($this->newTitle->getNamespace() == NS_CATEGORY) { $type = 'subcat'; } elseif ($this->newTitle->getNamespace() == NS_FILE) { $type = 'file'; } else { $type = 'page'; } foreach ($prefixes as $prefixRow) { $prefix = $prefixRow->cl_sortkey_prefix; $catTo = $prefixRow->cl_to; $dbw->update('categorylinks', array('cl_sortkey' => Collation::singleton()->getSortKey($this->newTitle->getCategorySortkey($prefix)), 'cl_collation' => $wgCategoryCollation, 'cl_type' => $type, 'cl_timestamp=cl_timestamp'), array('cl_from' => $pageid, 'cl_to' => $catTo), __METHOD__); } $redirid = $this->oldTitle->getArticleID(); if ($protected) { # Protect the redirect title as the title used to be... $dbw->insertSelect('page_restrictions', 'page_restrictions', array('pr_page' => $redirid, 'pr_type' => 'pr_type', 'pr_level' => 'pr_level', 'pr_cascade' => 'pr_cascade', 'pr_user' => 'pr_user', 'pr_expiry' => 'pr_expiry'), array('pr_page' => $pageid), __METHOD__, array('IGNORE')); // Build comment for log $comment = wfMessage('prot_1movedto2', $this->oldTitle->getPrefixedText(), $this->newTitle->getPrefixedText())->inContentLanguage()->text(); if ($reason) { $comment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } // reread inserted pr_ids for log relation $insertedPrIds = $dbw->select('page_restrictions', 'pr_id', array('pr_page' => $redirid), __METHOD__); $logRelationsValues = array(); foreach ($insertedPrIds as $prid) { $logRelationsValues[] = $prid->pr_id; } // Update the protection log $logEntry = new ManualLogEntry('protect', 'move_prot'); $logEntry->setTarget($this->newTitle); $logEntry->setComment($comment); $logEntry->setPerformer($user); $logEntry->setParameters(array('4::oldtitle' => $this->oldTitle->getPrefixedText())); $logEntry->setRelations(array('pr_id' => $logRelationsValues)); $logId = $logEntry->insert(); $logEntry->publish($logId); } // Update *_from_namespace fields as needed if ($this->oldTitle->getNamespace() != $this->newTitle->getNamespace()) { $dbw->update('pagelinks', array('pl_from_namespace' => $this->newTitle->getNamespace()), array('pl_from' => $pageid), __METHOD__); $dbw->update('templatelinks', array('tl_from_namespace' => $this->newTitle->getNamespace()), array('tl_from' => $pageid), __METHOD__); $dbw->update('imagelinks', array('il_from_namespace' => $this->newTitle->getNamespace()), array('il_from' => $pageid), __METHOD__); } # Update watchlists $oldtitle = $this->oldTitle->getDBkey(); $newtitle = $this->newTitle->getDBkey(); $oldsnamespace = MWNamespace::getSubject($this->oldTitle->getNamespace()); $newsnamespace = MWNamespace::getSubject($this->newTitle->getNamespace()); if ($oldsnamespace != $newsnamespace || $oldtitle != $newtitle) { WatchedItem::duplicateEntries($this->oldTitle, $this->newTitle); } $dbw->commit(__METHOD__); Hooks::run('TitleMoveComplete', array(&$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason)); return Status::newGood(); }
/** * Record a log entry on the action * @param array $params Associative array of parameters: * newBits: The new value of the *_deleted bitfield * oldBits: The old value of the *_deleted bitfield. * title: The target title * ids: The ID list * comment: The log comment * authorsIds: The array of the user IDs of the offenders * authorsIPs: The array of the IP/anon user offenders * @throws MWException */ protected function updateLog($params) { // Get the URL param's corresponding DB field $field = RevisionDeleter::getRelationType($this->getType()); if (!$field) { throw new MWException("Bad log URL param type!"); } // Put things hidden from sysops in the suppression log if (($params['newBits'] | $params['oldBits']) & $this->getSuppressBit()) { $logType = 'suppress'; } else { $logType = 'delete'; } // Add params for affected page and ids $logParams = $this->getLogParams($params); // Actually add the deletion log entry $logEntry = new ManualLogEntry($logType, $this->getLogAction()); $logEntry->setTarget($params['title']); $logEntry->setComment($params['comment']); $logEntry->setParameters($logParams); $logEntry->setPerformer($this->getUser()); // Allow for easy searching of deletion log items for revision/log items $logEntry->setRelations(array($field => $params['ids'], 'target_author_id' => $params['authorIds'], 'target_author_ip' => $params['authorIPs'])); $logId = $logEntry->insert(); $logEntry->publish($logId); }
protected function logTagAdded($tags, $revId, $user, $reason) { // log it $logEntry = new ManualLogEntry('tag', 'update'); $logEntry->setPerformer($user); $logEntry->setComment($reason); // find the appropriate target page if ($revId) { $rev = Revision::newFromId($revId); if ($rev) { $logEntry->setTarget($rev->getTitle()); } } if (!$logEntry->getTarget()) { // target is required, so we have to set something $logEntry->setTarget(SpecialPage::getTitleFor('Tags')); } $logParams = array('4::revid' => $revId, '6:list:tagsAdded' => $tags, '7:number:tagsAddedCount' => count($tags)); $logEntry->setParameters($logParams); $logEntry->setRelations(array('Tag' => $tags)); $dbw = wfGetDB(DB_MASTER); $logId = $logEntry->insert($dbw); // Only send this to UDP, not RC, similar to patrol events $logEntry->publish($logId, 'udp'); //$logEntry->publish( $logId ); }
/** * @param User $user * @param string $reason * @param bool $createRedirect * @return Status */ public function move(User $user, $reason, $createRedirect) { global $wgCategoryCollation; Hooks::run('TitleMove', [$this->oldTitle, $this->newTitle, $user]); // If it is a file, move it first. // It is done before all other moving stuff is done because it's hard to revert. $dbw = wfGetDB(DB_MASTER); if ($this->oldTitle->getNamespace() == NS_FILE) { $file = wfLocalFile($this->oldTitle); $file->load(File::READ_LATEST); if ($file->exists()) { $status = $file->move($this->newTitle); if (!$status->isOK()) { return $status; } } // Clear RepoGroup process cache RepoGroup::singleton()->clearCache($this->oldTitle); RepoGroup::singleton()->clearCache($this->newTitle); # clear false negative cache } $dbw->startAtomic(__METHOD__); Hooks::run('TitleMoveStarting', [$this->oldTitle, $this->newTitle, $user]); $pageid = $this->oldTitle->getArticleID(Title::GAID_FOR_UPDATE); $protected = $this->oldTitle->isProtected(); // Do the actual move; if this fails, it will throw an MWException(!) $nullRevision = $this->moveToInternal($user, $this->newTitle, $reason, $createRedirect); // Refresh the sortkey for this row. Be careful to avoid resetting // cl_timestamp, which may disturb time-based lists on some sites. // @todo This block should be killed, it's duplicating code // from LinksUpdate::getCategoryInsertions() and friends. $prefixes = $dbw->select('categorylinks', ['cl_sortkey_prefix', 'cl_to'], ['cl_from' => $pageid], __METHOD__); if ($this->newTitle->getNamespace() == NS_CATEGORY) { $type = 'subcat'; } elseif ($this->newTitle->getNamespace() == NS_FILE) { $type = 'file'; } else { $type = 'page'; } foreach ($prefixes as $prefixRow) { $prefix = $prefixRow->cl_sortkey_prefix; $catTo = $prefixRow->cl_to; $dbw->update('categorylinks', ['cl_sortkey' => Collation::singleton()->getSortKey($this->newTitle->getCategorySortkey($prefix)), 'cl_collation' => $wgCategoryCollation, 'cl_type' => $type, 'cl_timestamp=cl_timestamp'], ['cl_from' => $pageid, 'cl_to' => $catTo], __METHOD__); } $redirid = $this->oldTitle->getArticleID(); if ($protected) { # Protect the redirect title as the title used to be... $res = $dbw->select('page_restrictions', '*', ['pr_page' => $pageid], __METHOD__, 'FOR UPDATE'); $rowsInsert = []; foreach ($res as $row) { $rowsInsert[] = ['pr_page' => $redirid, 'pr_type' => $row->pr_type, 'pr_level' => $row->pr_level, 'pr_cascade' => $row->pr_cascade, 'pr_user' => $row->pr_user, 'pr_expiry' => $row->pr_expiry]; } $dbw->insert('page_restrictions', $rowsInsert, __METHOD__, ['IGNORE']); // Build comment for log $comment = wfMessage('prot_1movedto2', $this->oldTitle->getPrefixedText(), $this->newTitle->getPrefixedText())->inContentLanguage()->text(); if ($reason) { $comment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } // reread inserted pr_ids for log relation $insertedPrIds = $dbw->select('page_restrictions', 'pr_id', ['pr_page' => $redirid], __METHOD__); $logRelationsValues = []; foreach ($insertedPrIds as $prid) { $logRelationsValues[] = $prid->pr_id; } // Update the protection log $logEntry = new ManualLogEntry('protect', 'move_prot'); $logEntry->setTarget($this->newTitle); $logEntry->setComment($comment); $logEntry->setPerformer($user); $logEntry->setParameters(['4::oldtitle' => $this->oldTitle->getPrefixedText()]); $logEntry->setRelations(['pr_id' => $logRelationsValues]); $logId = $logEntry->insert(); $logEntry->publish($logId); } // Update *_from_namespace fields as needed if ($this->oldTitle->getNamespace() != $this->newTitle->getNamespace()) { $dbw->update('pagelinks', ['pl_from_namespace' => $this->newTitle->getNamespace()], ['pl_from' => $pageid], __METHOD__); $dbw->update('templatelinks', ['tl_from_namespace' => $this->newTitle->getNamespace()], ['tl_from' => $pageid], __METHOD__); $dbw->update('imagelinks', ['il_from_namespace' => $this->newTitle->getNamespace()], ['il_from' => $pageid], __METHOD__); } # Update watchlists $oldtitle = $this->oldTitle->getDBkey(); $newtitle = $this->newTitle->getDBkey(); $oldsnamespace = MWNamespace::getSubject($this->oldTitle->getNamespace()); $newsnamespace = MWNamespace::getSubject($this->newTitle->getNamespace()); if ($oldsnamespace != $newsnamespace || $oldtitle != $newtitle) { $store = MediaWikiServices::getInstance()->getWatchedItemStore(); $store->duplicateAllAssociatedEntries($this->oldTitle, $this->newTitle); } Hooks::run('TitleMoveCompleting', [$this->oldTitle, $this->newTitle, $user, $pageid, $redirid, $reason, $nullRevision]); $dbw->endAtomic(__METHOD__); $params = [&$this->oldTitle, &$this->newTitle, &$user, $pageid, $redirid, $reason, $nullRevision]; // Keep each single hook handler atomic DeferredUpdates::addUpdate(new AtomicSectionUpdate($dbw, __METHOD__, function () use($params) { Hooks::run('TitleMoveComplete', $params); })); return Status::newGood(); }
/** * Record a log entry on the action * @param string $logType One of (delete,suppress) * @param array $params Associative array of parameters: * newBits: The new value of the *_deleted bitfield * oldBits: The old value of the *_deleted bitfield. * title: The target title * ids: The ID list * comment: The log comment * authorsIds: The array of the user IDs of the offenders * authorsIPs: The array of the IP/anon user offenders * @throws MWException */ private function updateLog($logType, $params) { // Get the URL param's corresponding DB field $field = RevisionDeleter::getRelationType($this->getType()); if (!$field) { throw new MWException("Bad log URL param type!"); } // Add params for affected page and ids $logParams = $this->getLogParams($params); // Actually add the deletion log entry $logEntry = new ManualLogEntry($logType, $this->getLogAction()); $logEntry->setTarget($params['title']); $logEntry->setComment($params['comment']); $logEntry->setParameters($logParams); $logEntry->setPerformer($this->getUser()); // Allow for easy searching of deletion log items for revision/log items $logEntry->setRelations([$field => $params['ids'], 'target_author_id' => $params['authorIds'], 'target_author_ip' => $params['authorIPs']]); $logId = $logEntry->insert(); $logEntry->publish($logId); }