/** * Record a log entry on the review action * @param Title $title * @param array $dims * @param array $oldDims * @param string $comment * @param int $revId, revision ID * @param int $stableId, prior stable revision ID * @param bool $approve, approved? (otherwise unapproved) * @param bool $auto */ public static function updateReviewLog(Title $title, array $dims, array $oldDims, $comment, $revId, $stableId, $approve, $auto = false) { $log = new LogPage('review', false, $auto ? "skipUDP" : "UDP"); # Tag rating list (e.g. accuracy=x, depth=y, style=z) $ratings = array(); # Skip rating list if flagging is just an 0/1 feature... if (!FlaggedRevs::binaryFlagging()) { foreach ($dims as $quality => $level) { $ratings[] = wfMsgForContent("revreview-{$quality}") . wfMsgForContent('colon-separator') . wfMsgForContent("revreview-{$quality}-{$level}"); } } $isAuto = $auto && !FlaggedRevs::isQuality($dims); // Paranoid check // Approved revisions if ($approve) { if ($isAuto) { $comment = wfMsgForContent('revreview-auto'); // override this } # Make comma-separated list of ratings $rating = !empty($ratings) ? '[' . implode(', ', $ratings) . ']' : ''; # Append comment with ratings if ($rating != '') { $comment .= $comment ? " {$rating}" : $rating; } # Sort into the proper action (useful for filtering) $action = FlaggedRevs::isQuality($dims) || FlaggedRevs::isQuality($oldDims) ? 'approve2' : 'approve'; if (!$stableId) { // first time $action .= $isAuto ? "-ia" : "-i"; } elseif ($isAuto) { // automatic $action .= "-a"; } // De-approved revisions } else { $action = FlaggedRevs::isQuality($oldDims) ? 'unapprove2' : 'unapprove'; } $ts = Revision::getTimestampFromId($title, $revId); # Param format is <rev id, old stable id, rev timestamp> $logid = $log->addEntry($action, $title, $comment, array($revId, $stableId, $ts)); # Make log easily searchable by rev_id $log->addRelations('rev_id', array($revId), $logid); }
protected function doDBUpdates() { $db = $this->getDB(DB_MASTER); if (!$db->tableExists('log_search')) { $this->error("log_search does not exist"); return false; } $start = $db->selectField('logging', 'MIN(log_id)', false, __FUNCTION__); if (!$start) { $this->output("Nothing to do.\n"); return true; } $end = $db->selectField('logging', 'MAX(log_id)', false, __FUNCTION__); # Do remaining chunk $end += $this->mBatchSize - 1; $blockStart = $start; $blockEnd = $start + $this->mBatchSize - 1; $delTypes = array('delete', 'suppress'); // revisiondelete types while ($blockEnd <= $end) { $this->output("...doing log_id from {$blockStart} to {$blockEnd}\n"); $cond = "log_id BETWEEN {$blockStart} AND {$blockEnd}"; $res = $db->select('logging', '*', $cond, __FUNCTION__); foreach ($res as $row) { // RevisionDelete logs - revisions if (LogEventsList::typeAction($row, $delTypes, 'revision')) { $params = LogPage::extractParams($row->log_params); // Param format: <urlparam> <item CSV> [<ofield> <nfield>] if (count($params) < 2) { continue; // bad row? } $field = RevisionDeleter::getRelationType($params[0]); // B/C, the params may start with a title key (<title> <urlparam> <CSV>) if ($field == null) { array_shift($params); // remove title param $field = RevisionDeleter::getRelationType($params[0]); if ($field == null) { $this->output("Invalid param type for {$row->log_id}\n"); continue; // skip this row } else { // Clean up the row... $db->update('logging', array('log_params' => implode(',', $params)), array('log_id' => $row->log_id)); } } $items = explode(',', $params[1]); $log = new LogPage($row->log_type); // Add item relations... $log->addRelations($field, $items, $row->log_id); // Determine what table to query... $prefix = substr($field, 0, strpos($field, '_')); // db prefix if (!isset(self::$tableMap[$prefix])) { continue; // bad row? } $table = self::$tableMap[$prefix]; $userField = $prefix . '_user'; $userTextField = $prefix . '_user_text'; // Add item author relations... $userIds = $userIPs = array(); $sres = $db->select($table, array($userField, $userTextField), array($field => $items)); foreach ($sres as $srow) { if ($srow->{$userField} > 0) { $userIds[] = intval($srow->{$userField}); } elseif ($srow->{$userTextField} != '') { $userIPs[] = $srow->{$userTextField}; } } // Add item author relations... $log->addRelations('target_author_id', $userIds, $row->log_id); $log->addRelations('target_author_ip', $userIPs, $row->log_id); } elseif (LogEventsList::typeAction($row, $delTypes, 'event')) { // RevisionDelete logs - log events $params = LogPage::extractParams($row->log_params); // Param format: <item CSV> [<ofield> <nfield>] if (count($params) < 1) { continue; // bad row } $items = explode(',', $params[0]); $log = new LogPage($row->log_type); // Add item relations... $log->addRelations('log_id', $items, $row->log_id); // Add item author relations... $userIds = $userIPs = array(); $sres = $db->select('logging', array('log_user', 'log_user_text'), array('log_id' => $items)); foreach ($sres as $srow) { if ($srow->log_user > 0) { $userIds[] = intval($srow->log_user); } elseif (IP::isIPAddress($srow->log_user_text)) { $userIPs[] = $srow->log_user_text; } } $log->addRelations('target_author_id', $userIds, $row->log_id); $log->addRelations('target_author_ip', $userIPs, $row->log_id); } } $blockStart += $this->mBatchSize; $blockEnd += $this->mBatchSize; wfWaitForSlaves(); } $this->output("Done populating log_search table.\n"); return true; }
/** * Given the form data, actually implement a block * @param $data Array * @param $context IContextSource * @return Bool|String */ public static function processForm(array $data, IContextSource $context) { global $wgBlockAllowsUTEdit; $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); 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 overriden by (non-normalized) request variable # from previous request. if ($target === $performer->getName() && ($data['PreviousTarget'] !== $target || !$data['Confirm'])) { return array('ipb-blockingself'); } } 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 oversight and sysop, loads block form, # and is de-oversighted 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 (!in_array($data['Expiry'], array('infinite', 'infinity', 'indefinite'))) { # Bad expiry. return array('ipb_expiry_temp'); } elseif ($user->getEditCount() > self::HIDEUSER_CONTRIBLIMIT) { # Typically, the user should have a handful of edits. # Disallow hiding users with many edits for performance. return array('ipb_hide_invalid'); } elseif (!$data['Confirm']) { return array('ipb-confirmhideuser'); } } # Create block object. $block = new Block(); $block->setTarget($target); $block->setBlocker($performer); $block->mReason = $data['Reason'][0]; $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']; if (!wfRunHooks('BlockIp', array(&$block, &$performer))) { return array('hookaborted'); } # Try to insert block. Is there a conflicting block? $status = $block->insert(); if (!$status) { # Show form unless the user is already aware of this... if (!$data['Confirm'] || array_key_exists('PreviousTarget', $data) && $data['PreviousTarget'] !== $target) { 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->delete(); $status = $block->insert(); $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'; } wfRunHooks('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']) { $performer->addWatch(Title::makeTitle(NS_USER, $target)); } # Block constructor sanitizes certain block options on insert $data['BlockEmail'] = $block->prevents('sendemail'); $data['AutoBlock'] = $block->isAutoblocking(); # Prepare log parameters $logParams = array(); $logParams[] = $data['Expiry']; $logParams[] = self::blockLogFlags($data, $type); # Make log entry, if the name is hidden, put it in the oversight log $log_type = $data['HideUser'] ? 'suppress' : 'block'; $log = new LogPage($log_type); $log_id = $log->addEntry($logaction, Title::makeTitle(NS_USER, $target), $data['Reason'][0], $logParams); # Relate log ID to block IDs (bug 25763) $blockIds = array_merge(array($status['id']), $status['autoIds']); $log->addRelations('ipb_id', $blockIds, $log_id); # Report to the user return true; }
/** * Record a log entry on the action * @param $params array 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 */ 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 oversight log if (($params['newBits'] | $params['oldBits']) & $this->getSuppressBit()) { $logType = 'suppress'; } else { $logType = 'delete'; } // Add params for effected page and ids $logParams = $this->getLogParams($params); // Actually add the deletion log entry $log = new LogPage($logType); $logid = $log->addEntry($this->getLogAction(), $params['title'], $params['comment'], $logParams); // Allow for easy searching of deletion log items for revision/log items $log->addRelations($field, $params['ids'], $logid); $log->addRelations('target_author_id', $params['authorIds'], $logid); $log->addRelations('target_author_ip', $params['authorIPs'], $logid); }
/** * Move a title to a new location * * @param Title $nt The new title * @param bool $auth Indicates whether $wgUser's permissions * should be checked * @param string $reason The reason for the move * @param bool $createRedirect Whether to create a redirect from the old title to the new title. * Ignored if the user doesn't have the suppressredirect right. * @return array|bool True on success, getUserPermissionsErrors()-like array on failure */ public function moveTo(&$nt, $auth = true, $reason = '', $createRedirect = true) { global $wgUser; $err = $this->isValidMoveOperation($nt, $auth, $reason); if (is_array($err)) { // Auto-block user's IP if the account was "hard" blocked $wgUser->spreadAnyEditBlock(); return $err; } // Check suppressredirect permission if ($auth && !$wgUser->isAllowed('suppressredirect')) { $createRedirect = true; } wfRunHooks('TitleMove', array($this, $nt, $wgUser)); // 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->getNamespace() == NS_FILE) { $file = wfLocalFile($this); if ($file->exists()) { $status = $file->move($nt); if (!$status->isOk()) { return $status->getErrorsArray(); } } // Clear RepoGroup process cache RepoGroup::singleton()->clearCache($this); RepoGroup::singleton()->clearCache($nt); # clear false negative cache } $dbw->begin(__METHOD__); # If $file was a LocalFile, its transaction would have closed our own. $pageid = $this->getArticleID(self::GAID_FOR_UPDATE); $protected = $this->isProtected(); // Do the actual move $this->moveToInternal($nt, $reason, $createRedirect); // Refresh the sortkey for this row. Be careful to avoid resetting // cl_timestamp, which may disturb time-based lists on some sites. $prefixes = $dbw->select('categorylinks', array('cl_sortkey_prefix', 'cl_to'), array('cl_from' => $pageid), __METHOD__); foreach ($prefixes as $prefixRow) { $prefix = $prefixRow->cl_sortkey_prefix; $catTo = $prefixRow->cl_to; $dbw->update('categorylinks', array('cl_sortkey' => Collation::singleton()->getSortKey($nt->getCategorySortkey($prefix)), 'cl_timestamp=cl_timestamp'), array('cl_from' => $pageid, 'cl_to' => $catTo), __METHOD__); } $redirid = $this->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')); # Update the protection log $log = new LogPage('protect'); $comment = wfMessage('prot_1movedto2', $this->getPrefixedText(), $nt->getPrefixedText())->inContentLanguage()->text(); if ($reason) { $comment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } // @todo FIXME: $params? $logId = $log->addEntry('move_prot', $nt, $comment, array($this->getPrefixedText()), $wgUser); // 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; } $log->addRelations('pr_id', $logRelationsValues, $logId); } // Update *_from_namespace fields as needed if ($this->getNamespace() != $nt->getNamespace()) { $dbw->update('pagelinks', array('pl_from_namespace' => $nt->getNamespace()), array('pl_from' => $pageid), __METHOD__); $dbw->update('templatelinks', array('tl_from_namespace' => $nt->getNamespace()), array('tl_from' => $pageid), __METHOD__); $dbw->update('imagelinks', array('il_from_namespace' => $nt->getNamespace()), array('il_from' => $pageid), __METHOD__); } # Update watchlists $oldtitle = $this->getDBkey(); $newtitle = $nt->getDBkey(); $oldsnamespace = MWNamespace::getSubject($this->getNamespace()); $newsnamespace = MWNamespace::getSubject($nt->getNamespace()); if ($oldsnamespace != $newsnamespace || $oldtitle != $newtitle) { WatchedItem::duplicateEntries($this, $nt); } $dbw->commit(__METHOD__); wfRunHooks('TitleMoveComplete', array(&$this, &$nt, &$wgUser, $pageid, $redirid, $reason)); return true; }
/** * 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 * @return Status */ public function doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user) { 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 = array(); $logRelationsField = null; if ($id) { // Protection of existing page if (!Hooks::run('ArticleProtect', array(&$this, &$user, $limit, $reason))) { return Status::newGood(); } // Only certain restrictions can cascade... $editrestriction = isset($limit['edit']) ? array($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', array('pr_page' => $id, 'pr_type' => $action), __METHOD__); if ($restrictions != '') { $dbw->insert('page_restrictions', array('pr_id' => $dbw->nextSequenceValue('page_restrictions_pr_id_seq'), 'pr_page' => $id, 'pr_type' => $action, 'pr_level' => $restrictions, 'pr_cascade' => $cascade && $action == 'edit' ? 1 : 0, 'pr_expiry' => $dbw->encodeExpiry($expiry[$action])), __METHOD__); $logRelationsValues[] = $dbw->insertId(); } } // Clear out legacy restriction fields $dbw->update('page', array('page_restrictions' => ''), array('page_id' => $id), __METHOD__); Hooks::run('NewRevisionFromEditComplete', array($this, $nullRevision, $latest, $user)); Hooks::run('ArticleProtectComplete', array(&$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', array(array('pt_namespace', 'pt_title')), array('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__); } else { $dbw->delete('protected_titles', array('pt_namespace' => $this->mTitle->getNamespace(), 'pt_title' => $this->mTitle->getDBkey()), __METHOD__); } } $this->mTitle->flushRestrictions(); InfoAction::invalidateCache($this->mTitle); if ($logAction == 'unprotect') { $params = array(); } else { $protectDescriptionLog = $this->protectDescriptionLog($limit, $expiry); $params = array($protectDescriptionLog, $cascade ? 'cascade' : ''); } // Update the protection log $log = new LogPage('protect'); $logId = $log->addEntry($logAction, $this->mTitle, $reason, $params, $user); if ($logRelationsField !== null && count($logRelationsValues)) { $log->addRelations($logRelationsField, $logRelationsValues, $logId); } return Status::newGood(); }
/** * @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')); # Update the protection log $log = new LogPage('protect'); $comment = wfMessage('prot_1movedto2', $this->oldTitle->getPrefixedText(), $this->newTitle->getPrefixedText())->inContentLanguage()->text(); if ($reason) { $comment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } // @todo FIXME: $params? $logId = $log->addEntry('move_prot', $this->newTitle, $comment, array($this->oldTitle->getPrefixedText()), $user); // 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; } $log->addRelations('pr_id', $logRelationsValues, $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(); }