/** * Update the article's restriction field, and leave a log entry. * * @param array $limit set of restriction keys * @param string $reason * @return bool true on success */ function updateRestrictions($limit = array(), $reason = '', $cascade = 0, $expiry = null) { global $wgUser, $wgRestrictionTypes, $wgContLang; $id = $this->mTitle->getArticleID(); if (array() != $this->mTitle->getUserPermissionsErrors('protect', $wgUser) || wfReadOnly() || $id == 0) { return false; } if (!$cascade) { $cascade = false; } // Take this opportunity to purge out expired restrictions Title::purgeExpiredRestrictions(); # FIXME: Same limitations as described in ProtectionForm.php (line 37); # we expect a single selection, but the schema allows otherwise. $current = array(); foreach ($wgRestrictionTypes as $action) { $current[$action] = implode('', $this->mTitle->getRestrictions($action)); } $current = Article::flattenRestrictions($current); $updated = Article::flattenRestrictions($limit); $changed = $current != $updated; $changed = $changed || $this->mTitle->areRestrictionsCascading() != $cascade; $changed = $changed || $this->mTitle->mRestrictionsExpiry != $expiry; $protect = $updated != ''; # If nothing's changed, do nothing if ($changed) { global $wgGroupPermissions; if (wfRunHooks('ArticleProtect', array(&$this, &$wgUser, $limit, $reason))) { $dbw = wfGetDB(DB_MASTER); $encodedExpiry = Block::encodeExpiry($expiry, $dbw); $expiry_description = ''; if ($encodedExpiry != 'infinity') { $expiry_description = ' (' . wfMsgForContent('protect-expiring', $wgContLang->timeanddate($expiry, false, false)) . ')'; } # Prepare a null revision to be added to the history $modified = $current != '' && $protect; if ($protect) { $comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle'; } else { $comment_type = 'unprotectedarticle'; } $comment = $wgContLang->ucfirst(wfMsgForContent($comment_type, $this->mTitle->getPrefixedText())); foreach ($limit as $action => $restrictions) { # Check if the group level required to edit also can protect pages # Otherwise, people who cannot normally protect can "protect" pages via transclusion $cascade = $cascade && isset($wgGroupPermissions[$restrictions]['protect']) && $wgGroupPermissions[$restrictions]['protect']; } $cascade_description = ''; if ($cascade) { $cascade_description = ' [' . wfMsg('protect-summary-cascade') . ']'; } if ($reason) { $comment .= ": {$reason}"; } if ($protect) { $comment .= " [{$updated}]"; } if ($expiry_description && $protect) { $comment .= "{$expiry_description}"; } if ($cascade) { $comment .= "{$cascade_description}"; } $rowsAffected = false; # Update restrictions table foreach ($limit as $action => $restrictions) { if ($restrictions != '') { $dbw->replace('page_restrictions', array(array('pr_page', 'pr_type')), array('pr_page' => $id, 'pr_type' => $action, 'pr_level' => $restrictions, 'pr_cascade' => $cascade ? 1 : 0, 'pr_expiry' => $encodedExpiry), __METHOD__); if ($dbw->affectedRows() != 0) { $rowsAffected = true; } } else { $dbw->delete('page_restrictions', array('pr_page' => $id, 'pr_type' => $action), __METHOD__); if ($dbw->affectedRows() != 0) { $rowsAffected = true; } } } if (!$rowsAffected) { // No change return true; } # Insert a null revision $nullRevision = Revision::newNullRevision($dbw, $id, $comment, true); $nullRevId = $nullRevision->insertOn($dbw); # Update page record $dbw->update('page', array('page_touched' => $dbw->timestamp(), 'page_restrictions' => '', 'page_catinfo' => $this->mTitle->getCategoryMask(), 'page_latest' => $nullRevId), array('page_id' => $id), 'Article::protect'); wfRunHooks('ArticleProtectComplete', array(&$this, &$wgUser, $limit, $reason)); # Update the protection log $log = new LogPage('protect'); if ($protect) { $log->addEntry($modified ? 'modify' : 'protect', $this->mTitle, trim($reason . " [{$updated}]{$cascade_description}{$expiry_description}")); } else { $log->addEntry('unprotect', $this->mTitle, $reason); } } # End hook } # End "changed" check return true; }
/** * Move page to a title which is either a redirect to the * source page or nonexistent * * @param Title $nt The page to move to, which should be a redirect or nonexistent * @param string $reason The reason for the move * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check * if the user has the suppressredirect right * @throws MWException */ private function moveToInternal(&$nt, $reason = '', $createRedirect = true) { global $wgUser, $wgContLang; if ($nt->exists()) { $moveOverRedirect = true; $logType = 'move_redir'; } else { $moveOverRedirect = false; $logType = 'move'; } if ($createRedirect) { if ($this->getNamespace() == NS_CATEGORY && !wfMessage('category-move-redirect-override')->inContentLanguage()->isDisabled()) { $redirectContent = new WikitextContent(wfMessage('category-move-redirect-override')->params($nt->getPrefixedText())->inContentLanguage()->plain()); } else { $contentHandler = ContentHandler::getForTitle($this); $redirectContent = $contentHandler->makeRedirectContent($nt, wfMessage('move-redirect-text')->inContentLanguage()->plain()); } // NOTE: If this page's content model does not support redirects, $redirectContent will be null. } else { $redirectContent = null; } // bug 57084: log_page should be the ID of the *moved* page $oldid = $this->getArticleID(); $logTitle = clone $this; $logEntry = new ManualLogEntry('move', $logType); $logEntry->setPerformer($wgUser); $logEntry->setTarget($logTitle); $logEntry->setComment($reason); $logEntry->setParameters(array('4::target' => $nt->getPrefixedText(), '5::noredir' => $redirectContent ? '0' : '1')); $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext(RequestContext::newExtraneousContext($this)); $comment = $formatter->getPlainActionText(); if ($reason) { $comment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } # Truncate for whole multibyte characters. $comment = $wgContLang->truncate($comment, 255); $dbw = wfGetDB(DB_MASTER); $newpage = WikiPage::factory($nt); if ($moveOverRedirect) { $newid = $nt->getArticleID(); $newcontent = $newpage->getContent(); # Delete the old redirect. We don't save it to history since # by definition if we've got here it's rather uninteresting. # We have to remove it so that the next step doesn't trigger # a conflict on the unique namespace+title index... $dbw->delete('page', array('page_id' => $newid), __METHOD__); $newpage->doDeleteUpdates($newid, $newcontent); } # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true, $wgUser); if (!is_object($nullRevision)) { throw new MWException('No valid null revision produced in ' . __METHOD__); } $nullRevision->insertOn($dbw); # Change the name of the target page: $dbw->update('page', array('page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey()), array('page_id' => $oldid), __METHOD__); // clean up the old title before reset article id - bug 45348 if (!$redirectContent) { WikiPage::onArticleDelete($this); } $this->resetArticleID(0); // 0 == non existing $nt->resetArticleID($oldid); $newpage->loadPageData(WikiPage::READ_LOCKING); // bug 46397 $newpage->updateRevisionOn($dbw, $nullRevision); wfRunHooks('NewRevisionFromEditComplete', array($newpage, $nullRevision, $nullRevision->getParentId(), $wgUser)); $newpage->doEditUpdates($nullRevision, $wgUser, array('changed' => false)); if (!$moveOverRedirect) { WikiPage::onArticleCreate($nt); } # Recreate the redirect, this time in the other direction. if ($redirectContent) { $redirectArticle = WikiPage::factory($this); $redirectArticle->loadFromRow(false, WikiPage::READ_LOCKING); // bug 46397 $newid = $redirectArticle->insertOn($dbw); if ($newid) { // sanity $this->resetArticleID($newid); $redirectRevision = new Revision(array('title' => $this, 'page' => $newid, 'user_text' => $wgUser->getName(), 'user' => $wgUser->getId(), 'comment' => $comment, 'content' => $redirectContent)); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); wfRunHooks('NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser)); $redirectArticle->doEditUpdates($redirectRevision, $wgUser, array('created' => true)); } } # Log the move $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * Move page to non-existing title. * @param Title &$nt the new Title */ private function moveToNewTitle(&$nt, $reason = '') { global $wgUseSquid; $fname = 'MovePageForm::moveToNewTitle'; $comment = wfMsgForContent('1movedto2', $this->getPrefixedText(), $nt->getPrefixedText()); if ($reason) { $comment .= ": {$reason}"; } $newid = $nt->getArticleID(); $oldid = $this->getArticleID(); $dbw = wfGetDB(DB_MASTER); $now = $dbw->timestamp(); $linkCache =& LinkCache::singleton(); # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true); $nullRevId = $nullRevision->insertOn($dbw); # Rename cur entry $dbw->update('page', array('page_touched' => $now, 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey(), 'page_latest' => $nullRevId), array('page_id' => $oldid), $fname); $linkCache->clearLink($nt->getPrefixedDBkey()); # Insert redirect $mwRedir = MagicWord::get('redirect'); $redirectText = $mwRedir->getSynonym(0) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = new Article($this); $newid = $redirectArticle->insertOn($dbw); $redirectRevision = new Revision(array('page' => $newid, 'comment' => $comment, 'text' => $redirectText)); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); $linkCache->clearLink($this->getPrefixedDBkey()); # Log the move $log = new LogPage('move'); $log->addEntry('move', $this, $reason, array(1 => $nt->getPrefixedText())); # Purge caches as per article creation Article::onArticleCreate($nt); # Record the just-created redirect's linking to the page $dbw->insert('pagelinks', array('pl_from' => $newid, 'pl_namespace' => $nt->getNamespace(), 'pl_title' => $nt->getDBkey()), $fname); # Purge old title from squid # The new title, and links to the new title, are purged in Article::onArticleCreate() $this->purgeSquid(); }
function reportPage($title, $origTitle, $revisionCount) { global $wgOut, $wgUser, $wgLang, $wgContLang; $skin = $wgUser->getSkin(); $this->mPageCount++; $localCount = $wgLang->formatNum($revisionCount); $contentCount = $wgContLang->formatNum($revisionCount); $wgOut->addHtml("<li>" . $skin->makeKnownLinkObj($title) . " " . wfMsgHtml('import-revision-count', $localCount) . "</li>\n"); $log = new LogPage('import'); if ($this->mIsUpload) { $detail = wfMsgForContent('import-logentry-upload-detail', $contentCount); $log->addEntry('upload', $title, $detail); } else { $interwiki = '[[:' . $this->mInterwiki . ':' . $origTitle->getPrefixedText() . ']]'; $detail = wfMsgForContent('import-logentry-interwiki-detail', $contentCount, $interwiki); $log->addEntry('interwiki', $title, $detail); } $comment = $detail; // quick $dbw = wfGetDB(DB_MASTER); $nullRevision = Revision::newNullRevision($dbw, $title->getArticleId(), $comment, true); $nullRevId = $nullRevision->insertOn($dbw); }
/** * Update the article's restriction field, and leave a log entry. * This works for protection both existing and non-existing pages. * * @param $limit Array: set of restriction keys * @param $reason String * @param &$cascade Integer. Set to false if cascading protection isn't allowed. * @param $expiry Array: per restriction type expiration * @param $user User The user updating the restrictions * @return Status */ public function doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user) { global $wgContLang; if (wfReadOnly()) { return Status::newFatal('readonlytext', wfReadOnlyReason()); } $restrictionTypes = $this->mTitle->getRestrictionTypes(); $id = $this->mTitle->getArticleID(); 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(); } 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's 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'; } $encodedExpiry = array(); $protectDescription = ''; foreach ($limit as $action => $restrictions) { $encodedExpiry[$action] = $dbw->encodeExpiry($expiry[$action]); if ($restrictions != '') { $protectDescription .= $wgContLang->getDirMark() . "[{$action}={$restrictions}] ("; if ($encodedExpiry[$action] != 'infinity') { $protectDescription .= wfMessage('protect-expiring', $wgContLang->timeanddate($expiry[$action], false, false), $wgContLang->date($expiry[$action], false, false), $wgContLang->time($expiry[$action], false, false))->inContentLanguage()->text(); } else { $protectDescription .= wfMessage('protect-expiry-indefinite')->inContentLanguage()->text(); } $protectDescription .= ') '; } } $protectDescription = trim($protectDescription); if ($id) { # Protection of existing page if (!wfRunHooks('ArticleProtect', array(&$this, &$user, $limit, $reason))) { return Status::newGood(); } # Only restrictions with the 'protect' right can cascade... # Otherwise, people who cannot normally protect can "protect" pages via transclusion $editrestriction = isset($limit['edit']) ? array($limit['edit']) : $this->mTitle->getRestrictions('edit'); # The schema allows multiple restrictions if (!in_array('protect', $editrestriction) && !in_array('sysop', $editrestriction)) { $cascade = false; } # Update restrictions table foreach ($limit as $action => $restrictions) { if ($restrictions != '') { $dbw->replace('page_restrictions', array(array('pr_page', 'pr_type')), array('pr_page' => $id, 'pr_type' => $action, 'pr_level' => $restrictions, 'pr_cascade' => $cascade && $action == 'edit' ? 1 : 0, 'pr_expiry' => $encodedExpiry[$action]), __METHOD__); } else { $dbw->delete('page_restrictions', array('pr_page' => $id, 'pr_type' => $action), __METHOD__); } } # Prepare a null revision to be added to the history $editComment = $wgContLang->ucfirst(wfMessage($revCommentMsg, $this->mTitle->getPrefixedText())->inContentLanguage()->text()); if ($reason) { $editComment .= ": {$reason}"; } if ($protectDescription) { $editComment .= " ({$protectDescription})"; } if ($cascade) { // FIXME: Should use 'brackets' message. $editComment .= ' [' . wfMessage('protect-summary-cascade')->inContentLanguage()->text() . ']'; } # Insert a null revision $nullRevision = Revision::newNullRevision($dbw, $id, $editComment, true); $nullRevId = $nullRevision->insertOn($dbw); $latest = $this->getLatest(); # Update page record $dbw->update('page', array('page_touched' => $dbw->timestamp(), 'page_restrictions' => '', 'page_latest' => $nullRevId), array('page_id' => $id), __METHOD__); wfRunHooks('NewRevisionFromEditComplete', array($this, $nullRevision, $latest, $user)); wfRunHooks('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->encodeExpiry(wfTimestampNow()), 'pt_expiry' => $encodedExpiry['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(); if ($logAction == 'unprotect') { $logParams = array(); } else { $logParams = array($protectDescription, $cascade ? 'cascade' : ''); } # Update the protection log $log = new LogPage('protect'); $log->addEntry($logAction, $this->mTitle, trim($reason), $logParams, $user); return Status::newGood(); }
/** * Update the article's restriction field, and leave a log entry. * * @param $limit Array: set of restriction keys * @param $reason String * @param &$cascade Integer. Set to false if cascading protection isn't allowed. * @param $expiry Array: per restriction type expiration * @return bool true on success */ public function updateRestrictions($limit = array(), $reason = '', &$cascade = 0, $expiry = array()) { global $wgUser, $wgContLang; $restrictionTypes = $this->mTitle->getRestrictionTypes(); $id = $this->mTitle->getArticleID(); if ($id <= 0) { wfDebug("updateRestrictions failed: article id {$id} <= 0\n"); return false; } if (wfReadOnly()) { wfDebug("updateRestrictions failed: read-only\n"); return false; } if (!$this->mTitle->userCan('protect')) { wfDebug("updateRestrictions failed: insufficient permissions\n"); return false; } if (!$cascade) { $cascade = false; } // Take this opportunity to purge out expired restrictions Title::purgeExpiredRestrictions(); # FIXME: Same limitations as described in ProtectionForm.php (line 37); # we expect a single selection, but the schema allows otherwise. $current = array(); $updated = Article::flattenRestrictions($limit); $changed = false; foreach ($restrictionTypes as $action) { if (isset($expiry[$action])) { # Get current restrictions on $action $aLimits = $this->mTitle->getRestrictions($action); $current[$action] = implode('', $aLimits); # Are any actual restrictions being dealt with here? $aRChanged = count($aLimits) || !empty($limit[$action]); # If something changed, we need to log it. Checking $aRChanged # assures that "unprotecting" a page that is not protected does # not log just because the expiry was "changed". if ($aRChanged && $this->mTitle->mRestrictionsExpiry[$action] != $expiry[$action]) { $changed = true; } } } $current = Article::flattenRestrictions($current); $changed = $changed || $current != $updated; $changed = $changed || $updated && $this->mTitle->areRestrictionsCascading() != $cascade; $protect = $updated != ''; # If nothing's changed, do nothing if ($changed) { if (wfRunHooks('ArticleProtect', array(&$this, &$wgUser, $limit, $reason))) { $dbw = wfGetDB(DB_MASTER); # Prepare a null revision to be added to the history $modified = $current != '' && $protect; if ($protect) { $comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle'; } else { $comment_type = 'unprotectedarticle'; } $comment = $wgContLang->ucfirst(wfMsgForContent($comment_type, $this->mTitle->getPrefixedText())); # Only restrictions with the 'protect' right can cascade... # Otherwise, people who cannot normally protect can "protect" pages via transclusion $editrestriction = isset($limit['edit']) ? array($limit['edit']) : $this->mTitle->getRestrictions('edit'); # The schema allows multiple restrictions if (!in_array('protect', $editrestriction) && !in_array('sysop', $editrestriction)) { $cascade = false; } $cascade_description = ''; if ($cascade) { $cascade_description = ' [' . wfMsgForContent('protect-summary-cascade') . ']'; } if ($reason) { $comment .= ": {$reason}"; } $editComment = $comment; $encodedExpiry = array(); $protect_description = ''; foreach ($limit as $action => $restrictions) { if (!isset($expiry[$action])) { $expiry[$action] = Block::infinity(); } $encodedExpiry[$action] = Block::encodeExpiry($expiry[$action], $dbw); if ($restrictions != '') { $protect_description .= "[{$action}={$restrictions}] ("; if ($encodedExpiry[$action] != 'infinity') { $protect_description .= wfMsgForContent('protect-expiring', $wgContLang->timeanddate($expiry[$action], false, false), $wgContLang->date($expiry[$action], false, false), $wgContLang->time($expiry[$action], false, false)); } else { $protect_description .= wfMsgForContent('protect-expiry-indefinite'); } $protect_description .= ') '; } } $protect_description = trim($protect_description); if ($protect_description && $protect) { $editComment .= " ({$protect_description})"; } if ($cascade) { $editComment .= "{$cascade_description}"; } # Update restrictions table foreach ($limit as $action => $restrictions) { if ($restrictions != '') { $dbw->replace('page_restrictions', array(array('pr_page', 'pr_type')), array('pr_page' => $id, 'pr_type' => $action, 'pr_level' => $restrictions, 'pr_cascade' => $cascade && $action == 'edit' ? 1 : 0, 'pr_expiry' => $encodedExpiry[$action]), __METHOD__); } else { $dbw->delete('page_restrictions', array('pr_page' => $id, 'pr_type' => $action), __METHOD__); } } # Insert a null revision $nullRevision = Revision::newNullRevision($dbw, $id, $editComment, true); $nullRevId = $nullRevision->insertOn($dbw); $latest = $this->getLatest(); # Update page record $dbw->update('page', array('page_touched' => $dbw->timestamp(), 'page_restrictions' => '', 'page_latest' => $nullRevId), array('page_id' => $id), 'Article::protect'); wfRunHooks('NewRevisionFromEditComplete', array($this, $nullRevision, $latest, $wgUser)); wfRunHooks('ArticleProtectComplete', array(&$this, &$wgUser, $limit, $reason)); # Update the protection log $log = new LogPage('protect'); if ($protect) { $params = array($protect_description, $cascade ? 'cascade' : ''); $log->addEntry($modified ? 'modify' : 'protect', $this->mTitle, trim($reason), $params); } else { $log->addEntry('unprotect', $this->mTitle, $reason); } } # End hook } # End "changed" check return true; }
/** * Record a file upload in the upload log and the image table */ private function recordDownload($comment = '', $timestamp = false) { global $wgUser; $dbw = $this->repo->getMasterDB(); if ($timestamp === false) { $timestamp = $dbw->timestamp(); } list($major, $minor) = self::splitMime($this->mime); # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('ic_image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->type, 'img_major_mime' => $major, 'img_minor_mime' => $minor, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $wgUser->getID(), 'img_user_text' => $wgUser->getName(), 'img_metadata' => $this->metadata), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() == 0) { # Collision, this is an update of a file # Update the current image row $dbw->update('ic_image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $wgUser->getID(), 'img_user_text' => $wgUser->getName(), 'img_metadata' => $this->metadata), array('img_name' => $this->getName()), __METHOD__); } else { # This is a new file # Update the image count $site_stats = $dbw->tableName('site_stats'); $dbw->query("UPDATE {$site_stats} SET ss_images=ss_images+1", __METHOD__); } $descTitle = $this->getTitle(); $article = new Article($descTitle); # Add the log entry $log = new LogPage('icdownload'); $log->addEntry('InstantCommons download', $descTitle, $comment); if ($descTitle->exists()) { # Create a null revision $nullRevision = Revision::newNullRevision($dbw, $descTitle->getArticleId(), $log->getRcComment(), false); $nullRevision->insertOn($dbw); $article->updateRevisionOn($dbw, $nullRevision); # Invalidate the cache for the description page $descTitle->invalidateCache(); $descTitle->purgeSquid(); } # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives $dbw->immediateCommit(); # Invalidate cache for all pages using this file $update = new HTMLCacheUpdate($this->getTitle(), 'imagelinks'); $update->doUpdate(); return true; }
function reportPage($title, $origTitle, $revisionCount, $successCount) { global $wgOut, $wgUser, $wgLang, $wgContLang; $skin = $wgUser->getSkin(); $this->mPageCount++; $localCount = $wgLang->formatNum($successCount); $contentCount = $wgContLang->formatNum($successCount); if ($successCount > 0) { $wgOut->addHTML("<li>" . $skin->makeKnownLinkObj($title) . " " . wfMsgExt('import-revision-count', array('parsemag', 'escape'), $localCount) . "</li>\n"); $log = new LogPage('import'); if ($this->mIsUpload) { $detail = wfMsgExt('import-logentry-upload-detail', array('content', 'parsemag'), $contentCount); if ($this->reason) { $detail .= wfMsgForContent('colon-separator') . $this->reason; } $log->addEntry('upload', $title, $detail); } else { $interwiki = '[[:' . $this->mInterwiki . ':' . $origTitle->getPrefixedText() . ']]'; $detail = wfMsgExt('import-logentry-interwiki-detail', array('content', 'parsemag'), $contentCount, $interwiki); if ($this->reason) { $detail .= wfMsgForContent('colon-separator') . $this->reason; } $log->addEntry('interwiki', $title, $detail); } $comment = $detail; // quick $dbw = wfGetDB(DB_MASTER); $latest = $title->getLatestRevID(); $nullRevision = Revision::newNullRevision($dbw, $title->getArticleId(), $comment, true); $nullRevision->insertOn($dbw); $article = new Article($title); # Update page record $article->updateRevisionOn($dbw, $nullRevision); wfRunHooks('NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser)); } else { $wgOut->addHTML('<li>' . wfMsgHtml('import-nonewrevisions') . '</li>'); } }
/** * Move page to non-existing title. * @param &$nt \type{Title} the new Title * @param $reason \type{\string} The reason for the move * @param $createRedirect \type{\bool} Whether to create a redirect from the old title to the new title * Ignored if the user doesn't have the suppressredirect right */ private function moveToNewTitle(&$nt, $reason = '', $createRedirect = true) { global $wgUseSquid, $wgUser; $fname = 'MovePageForm::moveToNewTitle'; $comment = wfMsgForContent('1movedto2', $this->getPrefixedText(), $nt->getPrefixedText()); if ($reason) { $comment .= wfMsgExt('colon-separator', array('escapenoentities', 'content')); $comment .= $reason; } $newid = $nt->getArticleID(); $oldid = $this->getArticleID(); $latest = $this->getLatestRevId(); $dbw = wfGetDB(DB_MASTER); $now = $dbw->timestamp(); # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true); $nullRevId = $nullRevision->insertOn($dbw); $article = new Article($this); wfRunHooks('NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser)); # Rename page entry $dbw->update('page', array('page_touched' => $now, 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey(), 'page_latest' => $nullRevId), array('page_id' => $oldid), $fname); $nt->resetArticleID($oldid); if ($createRedirect || !$wgUser->isAllowed('suppressredirect')) { # Insert redirect $mwRedir = MagicWord::get('redirect'); $redirectText = $mwRedir->getSynonym(0) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = new Article($this); $newid = $redirectArticle->insertOn($dbw); $redirectRevision = new Revision(array('page' => $newid, 'comment' => $comment, 'text' => $redirectText)); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); wfRunHooks('NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser)); # Record the just-created redirect's linking to the page $dbw->insert('pagelinks', array('pl_from' => $newid, 'pl_namespace' => $nt->getNamespace(), 'pl_title' => $nt->getDBkey()), $fname); $redirectSuppressed = false; } else { $this->resetArticleID(0); $redirectSuppressed = true; } # Move an image if this is a file if ($this->getNamespace() == NS_FILE) { $file = wfLocalFile($this); if ($file->exists()) { $status = $file->move($nt); if (!$status->isOk()) { $dbw->rollback(); return $status->getErrorsArray(); } } } # Log the move $log = new LogPage('move'); $log->addEntry('move', $this, $reason, array(1 => $nt->getPrefixedText(), 2 => $redirectSuppressed)); # Purge caches as per article creation Article::onArticleCreate($nt); # Purge old title from squid # The new title, and links to the new title, are purged in Article::onArticleCreate() $this->purgeSquid(); }
/** * Duplicate one page to another, including full histories * Does some basic error-catching, but not as much as the code above [should] * * @param $source Title to duplicate * @param $dest Title to save to * @return bool */ private function duplicate(&$source, &$dest) { global $wgUser, $wgBot; if (!$source->exists() || $dest->exists()) { return false; } # Source doesn't exist, or destination does $dbw = wfGetDB(DB_MASTER); $dbw->begin(); $sid = $source->getArticleId(); # Create an article representing the destination page and save it $destArticle = new Article($dest); $aid = $destArticle->insertOn($dbw); # Perform the revision duplication # An INSERT...SELECT here seems to f**k things up $res = $dbw->select('revision', '*', array('rev_page' => $sid), __METHOD__); if ($res && $dbw->numRows($res) > 0) { while ($row = $dbw->fetchObject($res)) { $values['rev_page'] = $aid; $values['rev_text_id'] = $row->rev_text_id; $values['rev_comment'] = $row->rev_comment; $values['rev_user'] = $row->rev_user; $values['rev_user_text'] = $row->rev_user_text; $values['rev_timestamp'] = $row->rev_timestamp; $values['rev_minor_edit'] = $row->rev_minor_edit; $values['rev_deleted'] = $row->rev_deleted; $dbw->insert('revision', $values, __METHOD__); } $dbw->freeResult($res); } # Update page record $latest = $dbw->selectField('revision', 'MAX(rev_id)', array('rev_page' => $aid), __METHOD__); $rev = Revision::newFromId($latest); $destArticle->updateRevisionOn($dbw, $rev); # Commit transaction $dbw->commit(); # Create a null revision with an explanation; do cache clearances, etc. $dbw->begin(); $comment = wfMsgForContent('duplicator-summary', $source->getPrefixedText()); $nr = Revision::newNullRevision($dbw, $aid, $comment, true); $nid = $nr->insertOn($dbw); $destArticle->updateRevisionOn($dbw, $nr); $destArticle->createUpdates($nr); Article::onArticleCreate($dest); $bot = $wgUser->isAllowed('bot'); RecentChange::notifyNew($nr->getTimestamp(), $dest, true, $wgUser, $comment, $bot); $dest->invalidateCache(); $dbw->commit(); return true; }
protected function updateLogsAndHistory(FlaggableWikiPage $article) { global $wgContLang; $newConfig = $this->getNewConfig(); $oldConfig = $this->getOldConfig(); $reason = $this->getReason(); # Insert stability log entry... FlaggedRevsLog::updateStabilityLog($this->page, $newConfig, $oldConfig, $reason); # Build null-edit comment...<action: reason [settings] (expiry)> if (FRPageConfig::configIsReset($newConfig)) { $type = "stable-logentry-reset"; $settings = ''; // no level, expiry info } else { $type = "stable-logentry-config"; // Settings message in text form (e.g. [x=a,y=b,z]) $params = FlaggedRevsLog::stabilityLogParams($newConfig); $settings = FlaggedRevsLogView::stabilitySettings($params, true); } $comment = $wgContLang->ucfirst(wfMsgForContent($type, $this->page->getPrefixedText())); // action if ($reason != '') { $comment .= wfMsgForContent('colon-separator') . $reason; // add reason } if ($settings != '') { $comment .= " {$settings}"; // add settings } # Insert a null revision... $dbw = wfGetDB(DB_MASTER); $nullRev = Revision::newNullRevision($dbw, $article->getId(), $comment, true); $nullRev->insertOn($dbw); # Update page record and touch page $oldLatest = $nullRev->getParentId(); $article->updateRevisionOn($dbw, $nullRev, $oldLatest); wfRunHooks('NewRevisionFromEditComplete', array($article, $nullRev, $oldLatest, $this->user)); # Return null Revision object for autoreview check return $nullRev; }
/** * 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 $reason String * @param &$cascade Integer. Set to false if cascading protection isn't allowed. * @param array $expiry per restriction type expiration * @param $user User The user updating the restrictions * @return Status */ public function doUpdateRestrictions(array $limit, array $expiry, &$cascade, $reason, User $user) { global $wgContLang, $wgCascadingRestrictionLevels; if (wfReadOnly()) { return Status::newFatal('readonlytext', wfReadOnlyReason()); } $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(); } 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'; } $encodedExpiry = array(); $protectDescription = ''; # Some bots may parse IRC lines, which are generated from log entries which contain plain # protect description text. Keep them in old format to avoid breaking compatibility. # TODO: Fix protection log to store structured description and format it on-the-fly. $protectDescriptionLog = ''; foreach ($limit as $action => $restrictions) { $encodedExpiry[$action] = $dbw->encodeExpiry($expiry[$action]); if ($restrictions != '') { $protectDescriptionLog .= $wgContLang->getDirMark() . "[{$action}={$restrictions}] ("; # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ). # All possible message keys are listed here for easier grepping: # * restriction-create # * restriction-edit # * restriction-move # * restriction-upload $actionText = wfMessage('restriction-' . $action)->inContentLanguage()->text(); # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ), # with '' filtered out. All possible message keys are listed below: # * protect-level-autoconfirmed # * protect-level-sysop $restrictionsText = wfMessage('protect-level-' . $restrictions)->inContentLanguage()->text(); if ($encodedExpiry[$action] != 'infinity') { $expiryText = wfMessage('protect-expiring', $wgContLang->timeanddate($expiry[$action], false, false), $wgContLang->date($expiry[$action], false, false), $wgContLang->time($expiry[$action], false, false))->inContentLanguage()->text(); } else { $expiryText = wfMessage('protect-expiry-indefinite')->inContentLanguage()->text(); } if ($protectDescription !== '') { $protectDescription .= wfMessage('word-separator')->inContentLanguage()->text(); } $protectDescription .= wfMessage('protect-summary-desc')->params($actionText, $restrictionsText, $expiryText)->inContentLanguage()->text(); $protectDescriptionLog .= $expiryText . ') '; } } $protectDescriptionLog = trim($protectDescriptionLog); if ($id) { // Protection of existing page if (!wfRunHooks('ArticleProtect', array(&$this, &$user, $limit, $reason))) { return Status::newGood(); } // Only certain restrictions can cascade... Otherwise, users who cannot normally protect pages // could "protect" them by transcluding them on protected pages they are allowed to edit. $editrestriction = isset($limit['edit']) ? array($limit['edit']) : $this->mTitle->getRestrictions('edit'); $cascadingRestrictionLevels = $wgCascadingRestrictionLevels; if (in_array('sysop', $cascadingRestrictionLevels)) { $cascadingRestrictionLevels[] = 'protect'; // backwards compatibility } // The schema allows multiple restrictions if (!array_intersect($editrestriction, $cascadingRestrictionLevels)) { $cascade = false; } // Update restrictions table foreach ($limit as $action => $restrictions) { if ($restrictions != '') { $dbw->replace('page_restrictions', array(array('pr_page', 'pr_type')), array('pr_page' => $id, 'pr_type' => $action, 'pr_level' => $restrictions, 'pr_cascade' => $cascade && $action == 'edit' ? 1 : 0, 'pr_expiry' => $encodedExpiry[$action]), __METHOD__); } else { $dbw->delete('page_restrictions', array('pr_page' => $id, 'pr_type' => $action), __METHOD__); } } // Prepare a null revision to be added to the history $editComment = $wgContLang->ucfirst(wfMessage($revCommentMsg, $this->mTitle->getPrefixedText())->inContentLanguage()->text()); if ($reason) { $editComment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } if ($protectDescription) { $editComment .= wfMessage('word-separator')->inContentLanguage()->text(); $editComment .= wfMessage('parentheses')->params($protectDescription)->inContentLanguage()->text(); } if ($cascade) { $editComment .= wfMessage('word-separator')->inContentLanguage()->text(); $editComment .= wfMessage('brackets')->params(wfMessage('protect-summary-cascade')->inContentLanguage()->text())->inContentLanguage()->text(); } // Insert a null revision $nullRevision = Revision::newNullRevision($dbw, $id, $editComment, true); $nullRevId = $nullRevision->insertOn($dbw); $latest = $this->getLatest(); // Update page record $dbw->update('page', array('page_touched' => $dbw->timestamp(), 'page_restrictions' => '', 'page_latest' => $nullRevId), array('page_id' => $id), __METHOD__); wfRunHooks('NewRevisionFromEditComplete', array($this, $nullRevision, $latest, $user)); wfRunHooks('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->encodeExpiry(wfTimestampNow()), 'pt_expiry' => $encodedExpiry['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') { $logParams = array(); } else { $logParams = array($protectDescriptionLog, $cascade ? 'cascade' : ''); } // Update the protection log $log = new LogPage('protect'); $log->addEntry($logAction, $this->mTitle, trim($reason), $logParams, $user); return Status::newGood(); }
/** * Record a file upload in the upload log and the image table */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null) { global $wgCityId; if (is_null($user)) { global $wgUser; $user = $wgUser; } $dbw = $this->repo->getMasterDB(); $dbw->begin(); if (!$props) { $props = $this->repo->getFileProps($this->getVirtualUrl()); } if ($timestamp === false) { $timestamp = $dbw->timestamp(); } $props['description'] = $comment; $props['user'] = $user->getId(); $props['user_text'] = $user->getName(); $props['timestamp'] = wfTimestamp(TS_MW, $timestamp); // DB -> TS_MW $this->setProps($props); # Fail now if the file isn't there if (!$this->fileExists) { wfDebug(__METHOD__ . ": File " . $this->getRel() . " went missing!\n"); return false; } $reupload = false; # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() == 0) { if ($oldver == '') { // XXX # (bug 34993) publish() can displace the current file and yet fail to save # a new one. The next publish attempt will treat the file as a brand new file # and pass an empty $oldver. Allow this bogus value so we can displace the # `image` row to `oldimage`, leaving room for the new current file `image` row. #throw new MWException( "Empty oi_archive_name. Database and storage out of sync?" ); Wikia::logBacktrace(__METHOD__ . "::oi_archive_name - [{$this->getName()}]"); // Wikia change (BAC-1068) } $reupload = true; # Collision, this is an update of a file # Insert previous contents into oldimage $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__); # Update the current image row $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__); } else { # This is a new file # Update the image count $dbw->begin(__METHOD__); $dbw->update('site_stats', array('ss_images = ss_images+1'), '*', __METHOD__); $dbw->commit(__METHOD__); } $descTitle = $this->getTitle(); $wikiPage = new WikiFilePage($descTitle); $wikiPage->setFile($this); # Add the log entry $log = new LogPage('upload'); $action = $reupload ? 'overwrite' : 'upload'; $log->addEntry($action, $descTitle, $comment, array(), $user); if ($descTitle->exists()) { # Create a null revision $latest = $descTitle->getLatestRevID(); $nullRevision = Revision::newNullRevision($dbw, $descTitle->getArticleId(), $log->getRcComment(), false); if (!is_null($nullRevision)) { $nullRevision->insertOn($dbw); wfRunHooks('NewRevisionFromEditComplete', array($wikiPage, $nullRevision, $latest, $user)); $wikiPage->updateRevisionOn($dbw, $nullRevision); } # Invalidate the cache for the description page $descTitle->invalidateCache(); $descTitle->purgeSquid(); } else { # New file; create the description page. # There's already a log entry, so don't make a second RC entry # Squid and file cache for the description page are purged by doEdit. $wikiPage->doEdit($pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user); } /* wikia change - begin (VID-1568) */ // Update/Insert video info try { \VideoInfoHooksHelper::upsertVideoInfo($this, $reupload); } catch (\Exception $e) { $dbw->rollback(); return false; } /* wikia change - end (VID-1568) */ # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives $dbw->commit(); # Save to cache and purge the squid # We shall not saveToCache before the commit since otherwise # in case of a rollback there is an usable file from memcached # which in fact doesn't really exist (bug 24978) $this->saveToCache(); if ($reupload) { # Delete old thumbnails wfProfileIn(__METHOD__ . '-purge'); $this->purgeThumbnails(); wfProfileOut(__METHOD__ . '-purge'); # Remove the old file from the squid cache SquidUpdate::purge(array($this->getURL())); /* wikia change - begin (VID-1568) */ \VideoInfoHooksHelper::purgeVideoInfoCache($this); /* wikia change - end (VID-1568) */ } # Hooks, hooks, the magic of hooks... wfRunHooks('FileUpload', array($this, $reupload, $descTitle->exists())); # Invalidate cache for all pages using this file // Wikia change begin @author Scott Rabin (srabin@wikia-inc.com) $task = (new \Wikia\Tasks\Tasks\HTMLCacheUpdateTask())->wikiId($wgCityId)->title($this->getTitle()); $task->call('purge', 'imagelinks'); $task->queue(); // Wikia change end # Invalidate cache for all pages that redirects on this page $redirs = $this->getTitle()->getRedirectsHere(); foreach ($redirs as $redir) { // Wikia change begin @author Scott Rabin (srabin@wikia-inc.com) $task = (new \Wikia\Tasks\Tasks\HTMLCacheUpdateTask())->wikiId($wgCityId)->title($redir); $task->call('purge', 'imagelinks'); $task->queue(); // Wikia change end } return true; }
/** * Record a file upload in the upload log and the image table */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null) { if (is_null($user)) { global $wgUser; $user = $wgUser; } $dbw = $this->repo->getMasterDB(); $dbw->begin(); if (!$props) { $props = $this->repo->getFileProps($this->getVirtualUrl()); } if ($timestamp === false) { $timestamp = $dbw->timestamp(); } $props['description'] = $comment; $props['user'] = $user->getId(); $props['user_text'] = $user->getName(); $props['timestamp'] = wfTimestamp(TS_MW, $timestamp); // DB -> TS_MW $this->setProps($props); # Delete thumbnails $this->purgeThumbnails(); # The file is already on its final location, remove it from the squid cache SquidUpdate::purge(array($this->getURL())); # Fail now if the file isn't there if (!$this->fileExists) { wfDebug(__METHOD__ . ": File " . $this->getRel() . " went missing!\n"); return false; } $reupload = false; # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() == 0) { $reupload = true; # Collision, this is an update of a file # Insert previous contents into oldimage $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__); # Update the current image row $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__); } else { # This is a new file # Update the image count $dbw->begin(); $site_stats = $dbw->tableName('site_stats'); $dbw->query("UPDATE {$site_stats} SET ss_images=ss_images+1", __METHOD__); $dbw->commit(); } $descTitle = $this->getTitle(); $article = new ImagePage($descTitle); $article->setFile($this); # Add the log entry $log = new LogPage('upload'); $action = $reupload ? 'overwrite' : 'upload'; $log->addEntry($action, $descTitle, $comment, array(), $user); if ($descTitle->exists()) { # Create a null revision $latest = $descTitle->getLatestRevID(); $nullRevision = Revision::newNullRevision($dbw, $descTitle->getArticleId(), $log->getRcComment(), false); $nullRevision->insertOn($dbw); wfRunHooks('NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $user)); $article->updateRevisionOn($dbw, $nullRevision); # Invalidate the cache for the description page $descTitle->invalidateCache(); $descTitle->purgeSquid(); } else { # New file; create the description page. # There's already a log entry, so don't make a second RC entry # Squid and file cache for the description page are purged by doEdit. $article->doEdit($pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC); } # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives $dbw->commit(); # Save to cache and purge the squid # We shall not saveToCache before the commit since otherwise # in case of a rollback there is an usable file from memcached # which in fact doesn't really exist (bug 24978) $this->saveToCache(); # Hooks, hooks, the magic of hooks... wfRunHooks('FileUpload', array($this, $reupload, $descTitle->exists())); # Invalidate cache for all pages using this file $update = new HTMLCacheUpdate($this->getTitle(), 'imagelinks'); $update->doUpdate(); # Invalidate cache for all pages that redirects on this page $redirs = $this->getTitle()->getRedirectsHere(); foreach ($redirs as $redir) { $update = new HTMLCacheUpdate($redir, 'imagelinks'); $update->doUpdate(); } return true; }
/** * Move page to non-existing title. * * @param $nt \type{Title} the new Title * @param $reason \type{\string} The reason for the move * @param $createRedirect \type{\bool} Whether to create a redirect from the old title to the new title * Ignored if the user doesn't have the suppressredirect right */ private function moveToNewTitle(&$nt, $reason = '', $createRedirect = true) { global $wgUser, $wgContLang; $comment = wfMsgForContent('1movedto2', $this->getPrefixedText(), $nt->getPrefixedText()); if ($reason) { $comment .= wfMsgExt('colon-separator', array('escapenoentities', 'content')); $comment .= $reason; } # Truncate for whole multibyte characters. +5 bytes for ellipsis $comment = $wgContLang->truncate($comment, 250); $oldid = $this->getArticleID(); $latest = $this->getLatestRevId(); $dbw = wfGetDB(DB_MASTER); $now = $dbw->timestamp(); # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true); if (!is_object($nullRevision)) { throw new MWException('No valid null revision produced in ' . __METHOD__); } $nullRevId = $nullRevision->insertOn($dbw); $article = new Article($this); wfRunHooks('NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser)); # Rename page entry $dbw->update('page', array('page_touched' => $now, 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey(), 'page_latest' => $nullRevId), array('page_id' => $oldid), __METHOD__); $nt->resetArticleID($oldid); if ($createRedirect || !$wgUser->isAllowed('suppressredirect')) { # Insert redirect $mwRedir = MagicWord::get('redirect'); $redirectText = $mwRedir->getSynonym(0) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = new Article($this); $newid = $redirectArticle->insertOn($dbw); $redirectRevision = new Revision(array('page' => $newid, 'comment' => $comment, 'text' => $redirectText)); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); wfRunHooks('NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser)); # Record the just-created redirect's linking to the page $dbw->insert('pagelinks', array('pl_from' => $newid, 'pl_namespace' => $nt->getNamespace(), 'pl_title' => $nt->getDBkey()), __METHOD__); $redirectSuppressed = false; } else { $this->resetArticleID(0); $redirectSuppressed = true; } # Log the move $log = new LogPage('move'); $log->addEntry('move', $this, $reason, array(1 => $nt->getPrefixedText(), 2 => $redirectSuppressed)); # Purge caches as per article creation Article::onArticleCreate($nt); # Purge old title from squid # The new title, and links to the new title, are purged in Article::onArticleCreate() $this->purgeSquid(); }
/** * Record a file upload in the upload log and the image table * @param string $oldver * @param string $comment * @param string $pageText * @param bool|array $props * @param string|bool $timestamp * @param null|User $user * @param string[] $tags * @return bool */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null, $tags = array()) { if (is_null($user)) { global $wgUser; $user = $wgUser; } $dbw = $this->repo->getMasterDB(); # Imports or such might force a certain timestamp; otherwise we generate # it and can fudge it slightly to keep (name,timestamp) unique on re-upload. if ($timestamp === false) { $timestamp = $dbw->timestamp(); $allowTimeKludge = true; } else { $allowTimeKludge = false; } $props = $props ?: $this->repo->getFileProps($this->getVirtualUrl()); $props['description'] = $comment; $props['user'] = $user->getId(); $props['user_text'] = $user->getName(); $props['timestamp'] = wfTimestamp(TS_MW, $timestamp); // DB -> TS_MW $this->setProps($props); # Fail now if the file isn't there if (!$this->fileExists) { wfDebug(__METHOD__ . ": File " . $this->getRel() . " went missing!\n"); return false; } $dbw->startAtomic(__METHOD__); # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $dbw->encodeBlob($this->metadata), 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE'); $reupload = $dbw->affectedRows() == 0; if ($reupload) { if ($allowTimeKludge) { # Use LOCK IN SHARE MODE to ignore any transaction snapshotting $ltimestamp = $dbw->selectField('image', 'img_timestamp', array('img_name' => $this->getName()), __METHOD__, array('LOCK IN SHARE MODE')); $lUnixtime = $ltimestamp ? wfTimestamp(TS_UNIX, $ltimestamp) : false; # Avoid a timestamp that is not newer than the last version # TODO: the image/oldimage tables should be like page/revision with an ID field if ($lUnixtime && wfTimestamp(TS_UNIX, $timestamp) <= $lUnixtime) { sleep(1); // fast enough re-uploads would go far in the future otherwise $timestamp = $dbw->timestamp($lUnixtime + 1); $this->timestamp = wfTimestamp(TS_MW, $timestamp); // DB -> TS_MW } } # (bug 34993) Note: $oldver can be empty here, if the previous # version of the file was broken. Allow registration of the new # version to continue anyway, because that's better than having # an image that's not fixable by user operations. # Collision, this is an update of a file # Insert previous contents into oldimage $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__); # Update the current image row $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $dbw->encodeBlob($this->metadata), 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__); } $descTitle = $this->getTitle(); $descId = $descTitle->getArticleID(); $wikiPage = new WikiFilePage($descTitle); $wikiPage->setFile($this); // Add the log entry... $logEntry = new ManualLogEntry('upload', $reupload ? 'overwrite' : 'upload'); $logEntry->setTimestamp($this->timestamp); $logEntry->setPerformer($user); $logEntry->setComment($comment); $logEntry->setTarget($descTitle); // Allow people using the api to associate log entries with the upload. // Log has a timestamp, but sometimes different from upload timestamp. $logEntry->setParameters(array('img_sha1' => $this->sha1, 'img_timestamp' => $timestamp)); // Note we keep $logId around since during new image // creation, page doesn't exist yet, so log_page = 0 // but we want it to point to the page we're making, // so we later modify the log entry. // For a similar reason, we avoid making an RC entry // now and wait until the page exists. $logId = $logEntry->insert(); if ($descTitle->exists()) { // Use own context to get the action text in content language $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext(RequestContext::newExtraneousContext($descTitle)); $editSummary = $formatter->getPlainActionText(); $nullRevision = Revision::newNullRevision($dbw, $descId, $editSummary, false, $user); if ($nullRevision) { $nullRevision->insertOn($dbw); Hooks::run('NewRevisionFromEditComplete', array($wikiPage, $nullRevision, $nullRevision->getParentId(), $user)); $wikiPage->updateRevisionOn($dbw, $nullRevision); // Associate null revision id $logEntry->setAssociatedRevId($nullRevision->getId()); } $newPageContent = null; } else { // Make the description page and RC log entry post-commit $newPageContent = ContentHandler::makeContent($pageText, $descTitle); } # Defer purges, page creation, and link updates in case they error out. # The most important thing is that files and the DB registry stay synced. $dbw->endAtomic(__METHOD__); # Do some cache purges after final commit so that: # a) Changes are more likely to be seen post-purge # b) They won't cause rollback of the log publish/update above $that = $this; $dbw->onTransactionIdle(function () use($that, $reupload, $wikiPage, $newPageContent, $comment, $user, $logEntry, $logId, $descId, $tags) { # Update memcache after the commit $that->invalidateCache(); $updateLogPage = false; if ($newPageContent) { # New file page; create the description page. # There's already a log entry, so don't make a second RC entry # CDN and file cache for the description page are purged by doEditContent. $status = $wikiPage->doEditContent($newPageContent, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user); if (isset($status->value['revision'])) { // Associate new page revision id $logEntry->setAssociatedRevId($status->value['revision']->getId()); } // This relies on the resetArticleID() call in WikiPage::insertOn(), // which is triggered on $descTitle by doEditContent() above. if (isset($status->value['revision'])) { /** @var $rev Revision */ $rev = $status->value['revision']; $updateLogPage = $rev->getPage(); } } else { # Existing file page: invalidate description page cache $wikiPage->getTitle()->invalidateCache(); $wikiPage->getTitle()->purgeSquid(); # Allow the new file version to be patrolled from the page footer Article::purgePatrolFooterCache($descId); } # Update associated rev id. This should be done by $logEntry->insert() earlier, # but setAssociatedRevId() wasn't called at that point yet... $logParams = $logEntry->getParameters(); $logParams['associated_rev_id'] = $logEntry->getAssociatedRevId(); $update = array('log_params' => LogEntryBase::makeParamBlob($logParams)); if ($updateLogPage) { # Also log page, in case where we just created it above $update['log_page'] = $updateLogPage; } $that->getRepo()->getMasterDB()->update('logging', $update, array('log_id' => $logId), __METHOD__); $that->getRepo()->getMasterDB()->insert('log_search', array('ls_field' => 'associated_rev_id', 'ls_value' => $logEntry->getAssociatedRevId(), 'ls_log_id' => $logId), __METHOD__); # Now that the log entry is up-to-date, make an RC entry. $recentChange = $logEntry->publish($logId); if ($tags) { ChangeTags::addTags($tags, $recentChange ? $recentChange->getAttribute('rc_id') : null, $logEntry->getAssociatedRevId(), $logId); } # Run hook for other updates (typically more cache purging) Hooks::run('FileUpload', array($that, $reupload, !$newPageContent)); if ($reupload) { # Delete old thumbnails $that->purgeThumbnails(); # Remove the old file from the CDN cache DeferredUpdates::addUpdate(new CdnCacheUpdate(array($that->getUrl())), DeferredUpdates::PRESEND); } else { # Update backlink pages pointing to this title if created LinksUpdate::queueRecursiveJobsForTable($that->getTitle(), 'imagelinks'); } }); if (!$reupload) { # This is a new file, so update the image count DeferredUpdates::addUpdate(SiteStatsUpdate::factory(array('images' => 1))); } # Invalidate cache for all pages using this file DeferredUpdates::addUpdate(new HTMLCacheUpdate($this->getTitle(), 'imagelinks')); return true; }
/** * Record a file upload in the upload log and the image table */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false) { global $wgUser; $dbw = $this->repo->getMasterDB(); if (!$props) { $props = $this->repo->getFileProps($this->getVirtualUrl()); } $props['description'] = $comment; $props['user'] = $wgUser->getID(); $props['user_text'] = $wgUser->getName(); $props['timestamp'] = wfTimestamp(TS_MW); $this->setProps($props); // Delete thumbnails and refresh the metadata cache $this->purgeThumbnails(); $this->saveToCache(); wfPurgeSquidServers(array($this->getURL())); // Fail now if the file isn't there if (!$this->fileExists) { wfDebug(__METHOD__ . ": File " . $this->getPath() . " went missing!\n"); return false; } $reupload = false; if ($timestamp === false) { $timestamp = $dbw->timestamp(); } # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $wgUser->getID(), 'img_user_text' => $wgUser->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() == 0) { $reupload = true; # Collision, this is an update of a file # Insert previous contents into oldimage $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__); # Update the current image row $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $wgUser->getID(), 'img_user_text' => $wgUser->getName(), 'img_metadata' => $this->metadata, 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__); } else { # This is a new file # Update the image count $site_stats = $dbw->tableName('site_stats'); $dbw->query("UPDATE {$site_stats} SET ss_images=ss_images+1", __METHOD__); } $descTitle = $this->getTitle(); $article = new Article($descTitle); # Add the log entry $log = new LogPage('upload'); $action = $reupload ? 'overwrite' : 'upload'; $log->addEntry($action, $descTitle, $comment); if ($descTitle->exists()) { # Create a null revision $nullRevision = Revision::newNullRevision($dbw, $descTitle->getArticleId(), $log->getRcComment(), false); $nullRevision->insertOn($dbw); $article->updateRevisionOn($dbw, $nullRevision); # Invalidate the cache for the description page $descTitle->invalidateCache(); $descTitle->purgeSquid(); } else { // New file; create the description page. // There's already a log entry, so don't make a second RC entry $article->doEdit($pageText, $comment, EDIT_NEW | EDIT_SUPPRESS_RC); } # Hooks, hooks, the magic of hooks... wfRunHooks('FileUpload', array($this)); # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives $dbw->immediateCommit(); # Invalidate cache for all pages using this file $update = new HTMLCacheUpdate($this->getTitle(), 'imagelinks'); $update->doUpdate(); return true; }
/** * @covers Revision::newNullRevision */ public function testNewNullRevision() { $page = $this->createPage('RevisionStorageTest_testNewNullRevision', 'some testing text', CONTENT_MODEL_WIKITEXT); $orig = $page->getRevision(); $dbw = wfGetDB(DB_MASTER); $rev = Revision::newNullRevision($dbw, $page->getId(), 'a null revision', false); $this->assertNotEquals($orig->getId(), $rev->getId(), 'new null revision shold have a different id from the original revision'); $this->assertEquals($orig->getTextId(), $rev->getTextId(), 'new null revision shold have the same text id as the original revision'); $this->assertEquals('some testing text', $rev->getContent()->getNativeData()); }
function doProtect($limit = array(), $reason = '', &$expiry = '') { global $wgUser, $wgRestrictionTypes, $wgContLang, $wgTitle; $id = $wgTitle->getArticleID(); if (wfReadOnly() || $id == 0) { return false; } if (strlen($expiry) == 0) { $expiry = 'infinite'; } if ($expiry == 'infinite' || $expiry == 'indefinite') { $expiry = Block::infinity(); } else { # Convert GNU-style date, on error returns -1 for PHP <5.1 and false for PHP >=5.1 $expiry = strtotime($expiry); if ($expiry < 0 || $expiry === false) { //invalid expiry, rewrite to infinity $expiry = Block::infinity(); } else { // Fixme: non-qualified absolute times are not in users specified timezone // and there isn't notice about it in the ui $expiry = wfTimestamp(TS_MW, $expiry); } } // Take this opportunity to purge out expired restrictions Title::purgeExpiredRestrictions(); # FIXME: Same limitations as described in ProtectionForm.php (line 37); # we expect a single selection, but the schema allows otherwise. $current = array(); foreach ($wgRestrictionTypes as $action) { $current[$action] = implode('', $wgTitle->getRestrictions($action)); } $current = Article::flattenRestrictions($current); $updated = Article::flattenRestrictions($limit); $changed = $current != $updated; $changed = $changed || $wgTitle->mRestrictionsExpiry != $expiry; $protect = $updated != ''; # If nothing's changed, do nothing if ($changed) { global $wgGroupPermissions; $dbw = wfGetDB(DB_MASTER); $encodedExpiry = Block::encodeExpiry($expiry, $dbw); $expiry_description = ''; if ($encodedExpiry != 'infinity') { $expiry_description = ' (' . wfMsgForContent('protect-expiring', $wgContLang->timeanddate($expiry, false, false)) . ')'; } # Prepare a null revision to be added to the history $modified = $current != '' && $protect; if ($protect) { $comment_type = $modified ? 'modifiedarticleprotection' : 'protectedarticle'; } else { $comment_type = 'unprotectedarticle'; } $comment = $wgContLang->ucfirst(wfMsgForContent($comment_type, $wgTitle->getPrefixedText())); if ($reason) { $comment .= ": {$reason}"; } if ($protect) { $comment .= " [{$updated}]"; } if ($expiry_description && $protect) { $comment .= "{$expiry_description}"; } # Update restrictions table foreach ($limit as $action => $restrictions) { if ($restrictions != '') { $dbw->replace('page_restrictions', array(array('pr_page', 'pr_type')), array('pr_page' => $id, 'pr_type' => $action, 'pr_level' => $restrictions, 'pr_cascade' => 0, 'pr_expiry' => $encodedExpiry), __METHOD__); } else { $dbw->delete('page_restrictions', array('pr_page' => $id, 'pr_type' => $action), __METHOD__); } } # Insert a null revision $nullRevision = Revision::newNullRevision($dbw, $id, $comment, true); $nullRevId = $nullRevision->insertOn($dbw); # Update page record $dbw->update('page', array('page_touched' => $dbw->timestamp(), 'page_restrictions' => '', 'page_latest' => $nullRevId), array('page_id' => $id), 'Article::protect'); # Update the protection log $log = new LogPage('protect'); if ($protect) { $log->addEntry($modified ? 'modify' : 'protect', $wgTitle, trim($reason . " [{$updated}]{$expiry_description}")); } else { $log->addEntry('unprotect', $wgTitle, $reason); } } # End "changed" check return true; }
/** * Move page to a title which is either a redirect to the * source page or nonexistent * * @param $nt Title the page to move to, which should be a redirect or nonexistent * @param $reason String The reason for the move * @param $createRedirect Bool Whether to leave a redirect at the old title. Ignored * if the user doesn't have the suppressredirect right */ private function moveToInternal(&$nt, $reason = '', $createRedirect = true) { global $wgUser, $wgContLang; $moveOverRedirect = $nt->exists(); $commentMsg = $moveOverRedirect ? '1movedto2_redir' : '1movedto2'; $comment = wfMsgForContent($commentMsg, $this->getPrefixedText(), $nt->getPrefixedText()); if ($reason) { $comment .= wfMsgForContent('colon-separator') . $reason; } # Truncate for whole multibyte characters. $comment = $wgContLang->truncate($comment, 255); $oldid = $this->getArticleID(); $latest = $this->getLatestRevID(); $dbw = wfGetDB(DB_MASTER); if ($moveOverRedirect) { $rcts = $dbw->timestamp($nt->getEarliestRevTime()); $newid = $nt->getArticleID(); $newns = $nt->getNamespace(); $newdbk = $nt->getDBkey(); # Delete the old redirect. We don't save it to history since # by definition if we've got here it's rather uninteresting. # We have to remove it so that the next step doesn't trigger # a conflict on the unique namespace+title index... $dbw->delete('page', array('page_id' => $newid), __METHOD__); if (!$dbw->cascadingDeletes()) { $dbw->delete('revision', array('rev_page' => $newid), __METHOD__); global $wgUseTrackbacks; if ($wgUseTrackbacks) { $dbw->delete('trackbacks', array('tb_page' => $newid), __METHOD__); } $dbw->delete('pagelinks', array('pl_from' => $newid), __METHOD__); $dbw->delete('imagelinks', array('il_from' => $newid), __METHOD__); $dbw->delete('categorylinks', array('cl_from' => $newid), __METHOD__); $dbw->delete('templatelinks', array('tl_from' => $newid), __METHOD__); $dbw->delete('externallinks', array('el_from' => $newid), __METHOD__); $dbw->delete('langlinks', array('ll_from' => $newid), __METHOD__); $dbw->delete('iwlinks', array('iwl_from' => $newid), __METHOD__); $dbw->delete('redirect', array('rd_from' => $newid), __METHOD__); } // If the target page was recently created, it may have an entry in recentchanges still $dbw->delete('recentchanges', array('rc_timestamp' => $rcts, 'rc_namespace' => $newns, 'rc_title' => $newdbk, 'rc_new' => 1), __METHOD__); } # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true); if (!is_object($nullRevision)) { throw new MWException('No valid null revision produced in ' . __METHOD__); } $nullRevId = $nullRevision->insertOn($dbw); $now = wfTimestampNow(); # Change the name of the target page: $dbw->update('page', array('page_touched' => $dbw->timestamp($now), 'page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey(), 'page_latest' => $nullRevId), array('page_id' => $oldid), __METHOD__); $nt->resetArticleID($oldid); $article = new Article($nt); wfRunHooks('NewRevisionFromEditComplete', array($article, $nullRevision, $latest, $wgUser)); $article->setCachedLastEditTime($now); # Recreate the redirect, this time in the other direction. if ($createRedirect || !$wgUser->isAllowed('suppressredirect')) { $mwRedir = MagicWord::get('redirect'); $redirectText = $mwRedir->getSynonym(0) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = new Article($this); $newid = $redirectArticle->insertOn($dbw); $redirectRevision = new Revision(array('page' => $newid, 'comment' => $comment, 'text' => $redirectText)); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); wfRunHooks('NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser)); # Now, we record the link from the redirect to the new title. # It should have no other outgoing links... $dbw->delete('pagelinks', array('pl_from' => $newid), __METHOD__); $dbw->insert('pagelinks', array('pl_from' => $newid, 'pl_namespace' => $nt->getNamespace(), 'pl_title' => $nt->getDBkey()), __METHOD__); $redirectSuppressed = false; } else { $this->resetArticleID(0); $redirectSuppressed = true; } # Log the move $log = new LogPage('move'); $logType = $moveOverRedirect ? 'move_redir' : 'move'; $log->addEntry($logType, $this, $reason, array(1 => $nt->getPrefixedText(), 2 => $redirectSuppressed)); # Purge caches for old and new titles if ($moveOverRedirect) { # A simple purge is enough when moving over a redirect $nt->purgeSquid(); } else { # Purge caches as per article creation, including any pages that link to this title Article::onArticleCreate($nt); } $this->purgeSquid(); }
/** * @param Title $title * @param ForeignTitle $foreignTitle * @param int $revisionCount * @param int $successCount * @param array $pageInfo * @return void */ public function reportPage($title, $foreignTitle, $revisionCount, $successCount, $pageInfo) { $args = func_get_args(); call_user_func_array($this->mOriginalPageOutCallback, $args); if ($title === null) { # Invalid or non-importable title; a notice is already displayed return; } $this->mPageCount++; if ($successCount > 0) { $this->getOutput()->addHTML("<li>" . Linker::linkKnown($title) . " " . $this->msg('import-revision-count')->numParams($successCount)->escaped() . "</li>\n"); $logParams = ['4:number:count' => $successCount]; if ($this->mIsUpload) { $detail = $this->msg('import-logentry-upload-detail')->numParams($successCount)->inContentLanguage()->text(); $action = 'upload'; } else { $pageTitle = $foreignTitle->getFullText(); $fullInterwikiPrefix = $this->mInterwiki; Hooks::run('ImportLogInterwikiLink', [&$fullInterwikiPrefix, &$pageTitle]); $interwikiTitleStr = $fullInterwikiPrefix . ':' . $pageTitle; $interwiki = '[[:' . $interwikiTitleStr . ']]'; $detail = $this->msg('import-logentry-interwiki-detail')->numParams($successCount)->params($interwiki)->inContentLanguage()->text(); $action = 'interwiki'; $logParams['5:title-link:interwiki'] = $interwikiTitleStr; } if ($this->reason) { $detail .= $this->msg('colon-separator')->inContentLanguage()->text() . $this->reason; } $logEntry = new ManualLogEntry('import', $action); $logEntry->setTarget($title); $logEntry->setComment($this->reason); $logEntry->setPerformer($this->getUser()); $logEntry->setParameters($logParams); $logid = $logEntry->insert(); $logEntry->publish($logid); $comment = $detail; // quick $dbw = wfGetDB(DB_MASTER); $latest = $title->getLatestRevID(); $nullRevision = Revision::newNullRevision($dbw, $title->getArticleID(), $comment, true, $this->getUser()); if (!is_null($nullRevision)) { $nullRevision->insertOn($dbw); $page = WikiPage::factory($title); # Update page record $page->updateRevisionOn($dbw, $nullRevision); Hooks::run('NewRevisionFromEditComplete', [$page, $nullRevision, $latest, $this->getUser()]); } } else { $this->getOutput()->addHTML("<li>" . Linker::linkKnown($title) . " " . $this->msg('import-nonewrevisions')->escaped() . "</li>\n"); } }
/** * @param Title $title * @param Title $origTitle * @param int $revisionCount * @param $successCount * @param $pageInfo * @return void */ function reportPage($title, $origTitle, $revisionCount, $successCount, $pageInfo) { $args = func_get_args(); call_user_func_array($this->mOriginalPageOutCallback, $args); if ($title === null) { # Invalid or non-importable title; a notice is already displayed return; } $this->mPageCount++; if ($successCount > 0) { $this->getOutput()->addHTML("<li>" . Linker::linkKnown($title) . " " . $this->msg('import-revision-count')->numParams($successCount)->escaped() . "</li>\n"); $log = new LogPage('import'); if ($this->mIsUpload) { $detail = $this->msg('import-logentry-upload-detail')->numParams($successCount)->inContentLanguage()->text(); if ($this->reason) { $detail .= $this->msg('colon-separator')->inContentLanguage()->text() . $this->reason; } $log->addEntry('upload', $title, $detail); } else { $interwiki = '[[:' . $this->mInterwiki . ':' . $origTitle->getPrefixedText() . ']]'; $detail = $this->msg('import-logentry-interwiki-detail')->numParams($successCount)->params($interwiki)->inContentLanguage()->text(); if ($this->reason) { $detail .= $this->msg('colon-separator')->inContentLanguage()->text() . $this->reason; } $log->addEntry('interwiki', $title, $detail); } $comment = $detail; // quick $dbw = wfGetDB(DB_MASTER); $latest = $title->getLatestRevID(); $nullRevision = Revision::newNullRevision($dbw, $title->getArticleID(), $comment, true); if (!is_null($nullRevision)) { $nullRevision->insertOn($dbw); $page = WikiPage::factory($title); # Update page record $page->updateRevisionOn($dbw, $nullRevision); wfRunHooks('NewRevisionFromEditComplete', array($page, $nullRevision, $latest, $this->getUser())); } } else { $this->getOutput()->addHTML("<li>" . Linker::linkKnown($title) . " " . $this->msg('import-nonewrevisions')->escaped() . "</li>\n"); } }
/** * Move page to a title which is either a redirect to the * source page or nonexistent * * @param $nt Title the page to move to, which should be a redirect or nonexistent * @param $reason String The reason for the move * @param $createRedirect Bool Whether to leave a redirect at the old title. Ignored * if the user doesn't have the suppressredirect right */ private function moveToInternal(&$nt, $reason = '', $createRedirect = true) { global $wgUser, $wgContLang; if ($nt->exists()) { $moveOverRedirect = true; $logType = 'move_redir'; } else { $moveOverRedirect = false; $logType = 'move'; } $redirectSuppressed = !$createRedirect && $wgUser->isAllowed('suppressredirect'); $logEntry = new ManualLogEntry('move', $logType); $logEntry->setPerformer($wgUser); $logEntry->setTarget($this); $logEntry->setComment($reason); $logEntry->setParameters(array('4::target' => $nt->getPrefixedText(), '5::noredir' => $redirectSuppressed ? '1' : '0')); $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext(RequestContext::newExtraneousContext($this)); $comment = $formatter->getPlainActionText(); if ($reason) { $comment .= wfMsgForContent('colon-separator') . $reason; } # Truncate for whole multibyte characters. $comment = $wgContLang->truncate($comment, 255); $oldid = $this->getArticleID(); $latest = $this->getLatestRevID(); $dbw = wfGetDB(DB_MASTER); $newpage = WikiPage::factory($nt); if ($moveOverRedirect) { $newid = $nt->getArticleID(); # Delete the old redirect. We don't save it to history since # by definition if we've got here it's rather uninteresting. # We have to remove it so that the next step doesn't trigger # a conflict on the unique namespace+title index... $dbw->delete('page', array('page_id' => $newid), __METHOD__); $newpage->doDeleteUpdates($newid); } # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true); if (!is_object($nullRevision)) { throw new MWException('No valid null revision produced in ' . __METHOD__); } $nullRevId = $nullRevision->insertOn($dbw); # Change the name of the target page: $dbw->update('page', array('page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey()), array('page_id' => $oldid), __METHOD__); $this->resetArticleID(0); $nt->resetArticleID($oldid); $newpage->updateRevisionOn($dbw, $nullRevision); wfRunHooks('NewRevisionFromEditComplete', array($newpage, $nullRevision, $latest, $wgUser)); $newpage->doEditUpdates($nullRevision, $wgUser, array('changed' => false)); if (!$moveOverRedirect) { WikiPage::onArticleCreate($nt); } # Recreate the redirect, this time in the other direction. if ($redirectSuppressed) { WikiPage::onArticleDelete($this); } else { $mwRedir = MagicWord::get('redirect'); $redirectText = $mwRedir->getSynonym(0) . ' [[' . $nt->getPrefixedText() . "]]\n"; $redirectArticle = WikiPage::factory($this); $newid = $redirectArticle->insertOn($dbw); if ($newid) { // sanity $redirectRevision = new Revision(array('page' => $newid, 'comment' => $comment, 'text' => $redirectText)); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); wfRunHooks('NewRevisionFromEditComplete', array($redirectArticle, $redirectRevision, false, $wgUser)); $redirectArticle->doEditUpdates($redirectRevision, $wgUser, array('created' => true)); } } # Log the move $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * Record a file upload in the upload log and the image table * @param string $oldver * @param string $comment * @param string $pageText * @param bool|array $props * @param string|bool $timestamp * @param null|User $user * @return bool */ function recordUpload2($oldver, $comment, $pageText, $props = false, $timestamp = false, $user = null) { wfProfileIn(__METHOD__); if (is_null($user)) { global $wgUser; $user = $wgUser; } $dbw = $this->repo->getMasterDB(); $dbw->begin(__METHOD__); if (!$props) { wfProfileIn(__METHOD__ . '-getProps'); $props = $this->repo->getFileProps($this->getVirtualUrl()); wfProfileOut(__METHOD__ . '-getProps'); } if ($timestamp === false) { $timestamp = $dbw->timestamp(); } $props['description'] = $comment; $props['user'] = $user->getId(); $props['user_text'] = $user->getName(); $props['timestamp'] = wfTimestamp(TS_MW, $timestamp); // DB -> TS_MW $this->setProps($props); # Fail now if the file isn't there if (!$this->fileExists) { wfDebug(__METHOD__ . ": File " . $this->getRel() . " went missing!\n"); wfProfileOut(__METHOD__); return false; } $reupload = false; # Test to see if the row exists using INSERT IGNORE # This avoids race conditions by locking the row until the commit, and also # doesn't deadlock. SELECT FOR UPDATE causes a deadlock for every race condition. $dbw->insert('image', array('img_name' => $this->getName(), 'img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $dbw->encodeBlob($this->metadata), 'img_sha1' => $this->sha1), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() == 0) { # (bug 34993) Note: $oldver can be empty here, if the previous # version of the file was broken. Allow registration of the new # version to continue anyway, because that's better than having # an image that's not fixable by user operations. $reupload = true; # Collision, this is an update of a file # Insert previous contents into oldimage $dbw->insertSelect('oldimage', 'image', array('oi_name' => 'img_name', 'oi_archive_name' => $dbw->addQuotes($oldver), 'oi_size' => 'img_size', 'oi_width' => 'img_width', 'oi_height' => 'img_height', 'oi_bits' => 'img_bits', 'oi_timestamp' => 'img_timestamp', 'oi_description' => 'img_description', 'oi_user' => 'img_user', 'oi_user_text' => 'img_user_text', 'oi_metadata' => 'img_metadata', 'oi_media_type' => 'img_media_type', 'oi_major_mime' => 'img_major_mime', 'oi_minor_mime' => 'img_minor_mime', 'oi_sha1' => 'img_sha1'), array('img_name' => $this->getName()), __METHOD__); # Update the current image row $dbw->update('image', array('img_size' => $this->size, 'img_width' => intval($this->width), 'img_height' => intval($this->height), 'img_bits' => $this->bits, 'img_media_type' => $this->media_type, 'img_major_mime' => $this->major_mime, 'img_minor_mime' => $this->minor_mime, 'img_timestamp' => $timestamp, 'img_description' => $comment, 'img_user' => $user->getId(), 'img_user_text' => $user->getName(), 'img_metadata' => $dbw->encodeBlob($this->metadata), 'img_sha1' => $this->sha1), array('img_name' => $this->getName()), __METHOD__); } else { # This is a new file, so update the image count DeferredUpdates::addUpdate(SiteStatsUpdate::factory(array('images' => 1))); } $descTitle = $this->getTitle(); $wikiPage = new WikiFilePage($descTitle); $wikiPage->setFile($this); # Add the log entry $action = $reupload ? 'overwrite' : 'upload'; $logEntry = new ManualLogEntry('upload', $action); $logEntry->setPerformer($user); $logEntry->setComment($comment); $logEntry->setTarget($descTitle); // Allow people using the api to associate log entries with the upload. // Log has a timestamp, but sometimes different from upload timestamp. $logEntry->setParameters(array('img_sha1' => $this->sha1, 'img_timestamp' => $timestamp)); // Note we keep $logId around since during new image // creation, page doesn't exist yet, so log_page = 0 // but we want it to point to the page we're making, // so we later modify the log entry. // For a similar reason, we avoid making an RC entry // now and wait until the page exists. $logId = $logEntry->insert(); $exists = $descTitle->exists(); if ($exists) { // Page exists, do RC entry now (otherwise we wait for later). $logEntry->publish($logId); } wfProfileIn(__METHOD__ . '-edit'); if ($exists) { # Create a null revision $latest = $descTitle->getLatestRevID(); $editSummary = LogFormatter::newFromEntry($logEntry)->getPlainActionText(); $nullRevision = Revision::newNullRevision($dbw, $descTitle->getArticleID(), $editSummary, false); if (!is_null($nullRevision)) { $nullRevision->insertOn($dbw); wfRunHooks('NewRevisionFromEditComplete', array($wikiPage, $nullRevision, $latest, $user)); $wikiPage->updateRevisionOn($dbw, $nullRevision); } } # Commit the transaction now, in case something goes wrong later # The most important thing is that files don't get lost, especially archives # NOTE: once we have support for nested transactions, the commit may be moved # to after $wikiPage->doEdit has been called. $dbw->commit(__METHOD__); # Save to memcache. # We shall not saveToCache before the commit since otherwise # in case of a rollback there is an usable file from memcached # which in fact doesn't really exist (bug 24978) $this->saveToCache(); if ($exists) { # Invalidate the cache for the description page $descTitle->invalidateCache(); $descTitle->purgeSquid(); } else { # New file; create the description page. # There's already a log entry, so don't make a second RC entry # Squid and file cache for the description page are purged by doEditContent. $content = ContentHandler::makeContent($pageText, $descTitle); $status = $wikiPage->doEditContent($content, $comment, EDIT_NEW | EDIT_SUPPRESS_RC, false, $user); $dbw->begin(__METHOD__); // XXX; doEdit() uses a transaction // Now that the page exists, make an RC entry. $logEntry->publish($logId); if (isset($status->value['revision'])) { $dbw->update('logging', array('log_page' => $status->value['revision']->getPage()), array('log_id' => $logId), __METHOD__); } $dbw->commit(__METHOD__); // commit before anything bad can happen } wfProfileOut(__METHOD__ . '-edit'); if ($reupload) { # Delete old thumbnails wfProfileIn(__METHOD__ . '-purge'); $this->purgeThumbnails(); wfProfileOut(__METHOD__ . '-purge'); # Remove the old file from the squid cache SquidUpdate::purge(array($this->getURL())); } # Hooks, hooks, the magic of hooks... wfProfileIn(__METHOD__ . '-hooks'); wfRunHooks('FileUpload', array($this, $reupload, $descTitle->exists())); wfProfileOut(__METHOD__ . '-hooks'); # Invalidate cache for all pages using this file $update = new HTMLCacheUpdate($this->getTitle(), 'imagelinks'); $update->doUpdate(); if (!$reupload) { LinksUpdate::queueRecursiveJobsForTable($this->getTitle(), 'imagelinks'); } wfProfileOut(__METHOD__); return true; }
/** * Update the article's restriction field, and leave a log entry. * * @param array $limit set of restriction keys * @param string $reason * @return bool true on success */ function updateRestrictions($limit = array(), $reason = '') { global $wgUser, $wgRestrictionTypes, $wgContLang; $id = $this->mTitle->getArticleID(); if (!$wgUser->isAllowed('protect') || wfReadOnly() || $id == 0) { return false; } # FIXME: Same limitations as described in ProtectionForm.php (line 37); # we expect a single selection, but the schema allows otherwise. $current = array(); foreach ($wgRestrictionTypes as $action) { $current[$action] = implode('', $this->mTitle->getRestrictions($action)); } $current = Article::flattenRestrictions($current); $updated = Article::flattenRestrictions($limit); $changed = $current != $updated; $protect = $updated != ''; # If nothing's changed, do nothing if ($changed) { if (wfRunHooks('ArticleProtect', array(&$this, &$wgUser, $limit, $reason))) { $dbw =& wfGetDB(DB_MASTER); # Prepare a null revision to be added to the history $comment = $wgContLang->ucfirst(wfMsgForContent($protect ? 'protectedarticle' : 'unprotectedarticle', $this->mTitle->getPrefixedText())); if ($reason) { $comment .= ": {$reason}"; } if ($protect) { $comment .= " [{$updated}]"; } $nullRevision = Revision::newNullRevision($dbw, $id, $comment, true); $nullRevId = $nullRevision->insertOn($dbw); # Update page record $dbw->update('page', array('page_touched' => $dbw->timestamp(), 'page_restrictions' => $updated, 'page_latest' => $nullRevId), array('page_id' => $id), 'Article::protect'); wfRunHooks('ArticleProtectComplete', array(&$this, &$wgUser, $limit, $reason)); # Update the protection log $log = new LogPage('protect'); if ($protect) { $log->addEntry('protect', $this->mTitle, trim($reason . " [{$updated}]")); } else { $log->addEntry('unprotect', $this->mTitle, $reason); } } # End hook } # End "changed" check return true; }
/** * Insert a new null revision for this page. * * @param string $revCommentMsg comment message key for the revision * @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 * @return Revision|null on error */ public function insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason ) { global $wgContLang; $dbw = wfGetDB( DB_MASTER ); // Prepare a null revision to be added to the history $editComment = $wgContLang->ucfirst( wfMessage( $revCommentMsg, $this->mTitle->getPrefixedText() )->inContentLanguage()->text() ); if ( $reason ) { $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason; } $protectDescription = $this->protectDescription( $limit, $expiry ); if ( $protectDescription ) { $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text(); $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text(); } if ( $cascade ) { $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text(); $editComment .= wfMessage( 'brackets' )->params( wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text() )->inContentLanguage()->text(); } $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true ); if ( $nullRev ) { $nullRev->insertOn( $dbw ); // Update page record and touch page $oldLatest = $nullRev->getParentId(); $this->updateRevisionOn( $dbw, $nullRev, $oldLatest ); } return $nullRev; }
/** * Move page to a title which is either a redirect to the * source page or nonexistent * * @fixme This was basically directly moved from Title, it should be split into smaller functions * @param User $user the User doing the move * @param Title $nt The page to move to, which should be a redirect or non-existent * @param string $reason The reason for the move * @param bool $createRedirect Whether to leave a redirect at the old title. Does not check * if the user has the suppressredirect right * @return Revision the revision created by the move * @throws MWException */ private function moveToInternal(User $user, &$nt, $reason = '', $createRedirect = true) { global $wgContLang; if ($nt->exists()) { $moveOverRedirect = true; $logType = 'move_redir'; } else { $moveOverRedirect = false; $logType = 'move'; } if ($moveOverRedirect) { $overwriteMessage = wfMessage('delete_and_move_reason', $this->oldTitle->getPrefixedText())->text(); $newpage = WikiPage::factory($nt); $errs = []; $status = $newpage->doDeleteArticleReal($overwriteMessage, false, $nt->getArticleID(), false, $errs, $user); if (!$status->isGood()) { throw new MWException('Failed to delete page-move revision: ' . $status); } $nt->resetArticleID(false); } if ($createRedirect) { if ($this->oldTitle->getNamespace() == NS_CATEGORY && !wfMessage('category-move-redirect-override')->inContentLanguage()->isDisabled()) { $redirectContent = new WikitextContent(wfMessage('category-move-redirect-override')->params($nt->getPrefixedText())->inContentLanguage()->plain()); } else { $contentHandler = ContentHandler::getForTitle($this->oldTitle); $redirectContent = $contentHandler->makeRedirectContent($nt, wfMessage('move-redirect-text')->inContentLanguage()->plain()); } // NOTE: If this page's content model does not support redirects, $redirectContent will be null. } else { $redirectContent = null; } // Figure out whether the content model is no longer the default $oldDefault = ContentHandler::getDefaultModelFor($this->oldTitle); $contentModel = $this->oldTitle->getContentModel(); $newDefault = ContentHandler::getDefaultModelFor($nt); $defaultContentModelChanging = $oldDefault !== $newDefault && $oldDefault === $contentModel; // bug 57084: log_page should be the ID of the *moved* page $oldid = $this->oldTitle->getArticleID(); $logTitle = clone $this->oldTitle; $logEntry = new ManualLogEntry('move', $logType); $logEntry->setPerformer($user); $logEntry->setTarget($logTitle); $logEntry->setComment($reason); $logEntry->setParameters(['4::target' => $nt->getPrefixedText(), '5::noredir' => $redirectContent ? '0' : '1']); $formatter = LogFormatter::newFromEntry($logEntry); $formatter->setContext(RequestContext::newExtraneousContext($this->oldTitle)); $comment = $formatter->getPlainActionText(); if ($reason) { $comment .= wfMessage('colon-separator')->inContentLanguage()->text() . $reason; } # Truncate for whole multibyte characters. $comment = $wgContLang->truncate($comment, 255); $dbw = wfGetDB(DB_MASTER); $oldpage = WikiPage::factory($this->oldTitle); $oldcountable = $oldpage->isCountable(); $newpage = WikiPage::factory($nt); # Save a null revision in the page's history notifying of the move $nullRevision = Revision::newNullRevision($dbw, $oldid, $comment, true, $user); if (!is_object($nullRevision)) { throw new MWException('No valid null revision produced in ' . __METHOD__); } $nullRevision->insertOn($dbw); # Change the name of the target page: $dbw->update('page', ['page_namespace' => $nt->getNamespace(), 'page_title' => $nt->getDBkey()], ['page_id' => $oldid], __METHOD__); if (!$redirectContent) { // Clean up the old title *before* reset article id - bug 45348 WikiPage::onArticleDelete($this->oldTitle); } $this->oldTitle->resetArticleID(0); // 0 == non existing $nt->resetArticleID($oldid); $newpage->loadPageData(WikiPage::READ_LOCKING); // bug 46397 $newpage->updateRevisionOn($dbw, $nullRevision); Hooks::run('NewRevisionFromEditComplete', [$newpage, $nullRevision, $nullRevision->getParentId(), $user]); $newpage->doEditUpdates($nullRevision, $user, ['changed' => false, 'moved' => true, 'oldcountable' => $oldcountable]); // If the default content model changes, we need to populate rev_content_model if ($defaultContentModelChanging) { $dbw->update('revision', ['rev_content_model' => $contentModel], ['rev_page' => $nt->getArticleID(), 'rev_content_model IS NULL'], __METHOD__); } WikiPage::onArticleCreate($nt); # Recreate the redirect, this time in the other direction. if ($redirectContent) { $redirectArticle = WikiPage::factory($this->oldTitle); $redirectArticle->loadFromRow(false, WikiPage::READ_LOCKING); // bug 46397 $newid = $redirectArticle->insertOn($dbw); if ($newid) { // sanity $this->oldTitle->resetArticleID($newid); $redirectRevision = new Revision(['title' => $this->oldTitle, 'page' => $newid, 'user_text' => $user->getName(), 'user' => $user->getId(), 'comment' => $comment, 'content' => $redirectContent]); $redirectRevision->insertOn($dbw); $redirectArticle->updateRevisionOn($dbw, $redirectRevision, 0); Hooks::run('NewRevisionFromEditComplete', [$redirectArticle, $redirectRevision, false, $user]); $redirectArticle->doEditUpdates($redirectRevision, $user, ['created' => true]); } } # Log the move $logid = $logEntry->insert(); $logEntry->publish($logid); return $nullRevision; }