/** * Insert a new empty page record for this article. * This *must* be followed up by creating a revision * and running $this->updateRevisionOn( ... ); * or else the record will be left in a funky state. * Best if all done inside a transaction. * * @param $dbw DatabaseBase * @return int The newly created page_id key, or false if the title already existed */ public function insertOn( $dbw ) { wfProfileIn( __METHOD__ ); $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' ); $dbw->insert( 'page', array( 'page_id' => $page_id, 'page_namespace' => $this->mTitle->getNamespace(), 'page_title' => $this->mTitle->getDBkey(), 'page_counter' => 0, 'page_restrictions' => '', 'page_is_redirect' => 0, // Will set this shortly... 'page_is_new' => 1, 'page_random' => wfRandom(), 'page_touched' => $dbw->timestamp(), 'page_latest' => 0, // Fill this in shortly... 'page_len' => 0, // Fill this in shortly... ), __METHOD__, 'IGNORE' ); $affected = $dbw->affectedRows(); if ( $affected ) { $newid = $dbw->insertId(); $this->mId = $newid; $this->mTitle->resetArticleID( $newid ); } wfProfileOut( __METHOD__ ); return $affected ? $newid : false; }
/** * * @return Title */ public function &getTitle() { if ($this->mTitle === false) { $this->mTitle = Title::makeTitle($this->mAttribs['rc_namespace'], $this->mAttribs['rc_title']); # Make sure the correct page ID is process cached $this->mTitle->resetArticleID($this->mAttribs['rc_cur_id']); } return $this->mTitle; }
/** * Insert a new empty page record for this article. * This *must* be followed up by creating a revision * and running $this->updateRevisionOn( ... ); * or else the record will be left in a funky state. * Best if all done inside a transaction. * * @param IDatabase $dbw * @return int|bool The newly created page_id key; false if the title already existed */ public function insertOn($dbw) { $dbw->insert('page', array('page_id' => $dbw->nextSequenceValue('page_page_id_seq'), 'page_namespace' => $this->mTitle->getNamespace(), 'page_title' => $this->mTitle->getDBkey(), 'page_restrictions' => '', 'page_is_redirect' => 0, 'page_is_new' => 1, 'page_random' => wfRandom(), 'page_touched' => $dbw->timestamp(), 'page_latest' => 0, 'page_len' => 0), __METHOD__, 'IGNORE'); if ($dbw->affectedRows() > 0) { $newid = $dbw->insertId(); $this->mId = $newid; $this->mTitle->resetArticleID($newid); return $newid; } else { return false; // nothing changed } }
/** * Do some database updates after deletion * * @param $id Int: page_id value of the page being deleted (B/C, currently unused) * @param $content Content: optional page content to be used when determining the required updates. * This may be needed because $this->getContent() may already return null when the page proper was deleted. */ public function doDeleteUpdates($id, Content $content = null) { # update site status DeferredUpdates::addUpdate(new SiteStatsUpdate(0, 1, -(int) $this->isCountable(), -1)); # remove secondary indexes, etc $updates = $this->getDeletionUpdates($content); DataUpdate::runUpdates($updates); # Clear caches WikiPage::onArticleDelete($this->mTitle); # Reset this object $this->clear(); # Clear the cached article id so the interface doesn't act like we exist $this->mTitle->resetArticleID(0); }
/** * * @return Title */ public function &getTitle() { if ($this->mTitle === false) { $this->mTitle = Title::makeTitle($this->mAttribs['rc_namespace'], $this->mAttribs['rc_title']); /* Wikia change begin - @author: garth */ # Don't reset here if the FastLinkCache is enabled. Calling # resetArticleID clears this memcached key when its likely we # just set it earlier in this request. BugID 42735 global $wgEnableFastLinkCache; if (empty($wgEnableFastLinkCache)) { # Make sure the correct page ID is process cached $this->mTitle->resetArticleID($this->mAttribs['rc_cur_id']); } /* Wikia change - end */ } return $this->mTitle; }
/** * Do some database updates after deletion * * @param $id Int: page_id value of the page being deleted */ public function doDeleteUpdates($id) { DeferredUpdates::addUpdate(new SiteStatsUpdate(0, 1, -(int) $this->isCountable(), -1)); $dbw = wfGetDB(DB_MASTER); # Delete restrictions for it $dbw->delete('page_restrictions', array('pr_page' => $id), __METHOD__); # Fix category table counts $cats = array(); $res = $dbw->select('categorylinks', 'cl_to', array('cl_from' => $id), __METHOD__); foreach ($res as $row) { $cats[] = $row->cl_to; } $this->updateCategoryCounts(array(), $cats); # If using cascading deletes, we can skip some explicit deletes if (!$dbw->cascadingDeletes()) { $dbw->delete('revision', array('rev_page' => $id), __METHOD__); # Delete outgoing links $dbw->delete('pagelinks', array('pl_from' => $id), __METHOD__); $dbw->delete('imagelinks', array('il_from' => $id), __METHOD__); $dbw->delete('categorylinks', array('cl_from' => $id), __METHOD__); $dbw->delete('templatelinks', array('tl_from' => $id), __METHOD__); $dbw->delete('externallinks', array('el_from' => $id), __METHOD__); $dbw->delete('langlinks', array('ll_from' => $id), __METHOD__); $dbw->delete('iwlinks', array('iwl_from' => $id), __METHOD__); $dbw->delete('redirect', array('rd_from' => $id), __METHOD__); $dbw->delete('page_props', array('pp_page' => $id), __METHOD__); } # If using cleanup triggers, we can skip some manual deletes if (!$dbw->cleanupTriggers()) { # Clean up recentchanges entries... $dbw->delete('recentchanges', array('rc_type != ' . RC_LOG, 'rc_namespace' => $this->mTitle->getNamespace(), 'rc_title' => $this->mTitle->getDBkey()), __METHOD__); $dbw->delete('recentchanges', array('rc_type != ' . RC_LOG, 'rc_cur_id' => $id), __METHOD__); } # Clear caches self::onArticleDelete($this->mTitle); # Clear the cached article id so the interface doesn't act like we exist $this->mTitle->resetArticleID(0); }
/** * Back-end article deletion * Deletes the article with database consistency, writes logs, purges caches * * @param $reason string delete reason for deletion log * @param suppress bitfield * Revision::DELETED_TEXT * Revision::DELETED_COMMENT * Revision::DELETED_USER * Revision::DELETED_RESTRICTED * @param $id int article ID * @param $commit boolean defaults to true, triggers transaction end * @param &$errors Array of errors to append to * @param $user User The relevant user * @return boolean true if successful */ public function doDeleteArticle($reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null) { global $wgDeferredUpdateList, $wgUseTrackbacks, $wgUser; $user = is_null($user) ? $wgUser : $user; wfDebug(__METHOD__ . "\n"); if (!wfRunHooks('ArticleDelete', array(&$this, &$user, &$reason, &$error))) { return false; } $dbw = wfGetDB(DB_MASTER); $t = $this->mTitle->getDBkey(); $id = $id ? $id : $this->mTitle->getArticleID(Title::GAID_FOR_UPDATE); if ($t === '' || $id == 0) { return false; } $u = new SiteStatsUpdate(0, 1, -(int) $this->isCountable(), -1); array_push($wgDeferredUpdateList, $u); // Bitfields to further suppress the content if ($suppress) { $bitfield = 0; // This should be 15... $bitfield |= Revision::DELETED_TEXT; $bitfield |= Revision::DELETED_COMMENT; $bitfield |= Revision::DELETED_USER; $bitfield |= Revision::DELETED_RESTRICTED; } else { $bitfield = 'rev_deleted'; } $dbw->begin(); // For now, shunt the revision data into the archive table. // Text is *not* removed from the text table; bulk storage // is left intact to avoid breaking block-compression or // immutable storage schemes. // // For backwards compatibility, note that some older archive // table entries will have ar_text and ar_flags fields still. // // In the future, we may keep revisions and mark them with // the rev_deleted field, which is reserved for this purpose. $dbw->insertSelect('archive', array('page', 'revision'), array('ar_namespace' => 'page_namespace', 'ar_title' => 'page_title', 'ar_comment' => 'rev_comment', 'ar_user' => 'rev_user', 'ar_user_text' => 'rev_user_text', 'ar_timestamp' => 'rev_timestamp', 'ar_minor_edit' => 'rev_minor_edit', 'ar_rev_id' => 'rev_id', 'ar_text_id' => 'rev_text_id', 'ar_text' => '\'\'', 'ar_flags' => '\'\'', 'ar_len' => 'rev_len', 'ar_page_id' => 'page_id', 'ar_deleted' => $bitfield), array('page_id' => $id, 'page_id = rev_page'), __METHOD__); # Delete restrictions for it $dbw->delete('page_restrictions', array('pr_page' => $id), __METHOD__); # Now that it's safely backed up, delete it $dbw->delete('page', array('page_id' => $id), __METHOD__); $ok = $dbw->affectedRows() > 0; // getArticleId() uses slave, could be laggy if (!$ok) { $dbw->rollback(); return false; } # Fix category table counts $cats = array(); $res = $dbw->select('categorylinks', 'cl_to', array('cl_from' => $id), __METHOD__); foreach ($res as $row) { $cats[] = $row->cl_to; } $this->updateCategoryCounts(array(), $cats); # If using cascading deletes, we can skip some explicit deletes if (!$dbw->cascadingDeletes()) { $dbw->delete('revision', array('rev_page' => $id), __METHOD__); if ($wgUseTrackbacks) { $dbw->delete('trackbacks', array('tb_page' => $id), __METHOD__); } # Delete outgoing links $dbw->delete('pagelinks', array('pl_from' => $id)); $dbw->delete('imagelinks', array('il_from' => $id)); $dbw->delete('categorylinks', array('cl_from' => $id)); $dbw->delete('templatelinks', array('tl_from' => $id)); $dbw->delete('externallinks', array('el_from' => $id)); $dbw->delete('langlinks', array('ll_from' => $id)); $dbw->delete('iwlinks', array('iwl_from' => $id)); $dbw->delete('redirect', array('rd_from' => $id)); } # If using cleanup triggers, we can skip some manual deletes if (!$dbw->cleanupTriggers()) { # Clean up recentchanges entries... $dbw->delete('recentchanges', array('rc_type != ' . RC_LOG, 'rc_namespace' => $this->mTitle->getNamespace(), 'rc_title' => $this->mTitle->getDBkey()), __METHOD__); $dbw->delete('recentchanges', array('rc_type != ' . RC_LOG, 'rc_cur_id' => $id), __METHOD__); } # Clear caches self::onArticleDelete($this->mTitle); # Clear the cached article id so the interface doesn't act like we exist $this->mTitle->resetArticleID(0); # Log the deletion, if the page was suppressed, log it at Oversight instead $logtype = $suppress ? 'suppress' : 'delete'; $log = new LogPage($logtype); # Make sure logging got through $log->addEntry('delete', $this->mTitle, $reason, array()); if ($commit) { $dbw->commit(); } wfRunHooks('ArticleDeleteComplete', array(&$this, &$user, $reason, $id)); return true; }
/** * 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 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(User $user, &$nt, $reason = '', $createRedirect = true) { global $wgContLang; if ($nt->exists()) { $moveOverRedirect = true; $logType = 'move_redir'; } else { $moveOverRedirect = false; $logType = 'move'; } 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(array('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); 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, $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', 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->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', array($newpage, $nullRevision, $nullRevision->getParentId(), $user)); $newpage->doEditUpdates($nullRevision, $user, array('changed' => false, 'moved' => true, 'oldcountable' => $oldcountable)); // If the default content model changes, we need to populate rev_content_model if ($defaultContentModelChanging) { $dbw->update('revision', array('rev_content_model' => $contentModel), array('rev_page' => $nt->getArticleID(), 'rev_content_model IS NULL'), __METHOD__); } if (!$moveOverRedirect) { 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(array('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', array($redirectArticle, $redirectRevision, false, $user)); $redirectArticle->doEditUpdates($redirectRevision, $user, array('created' => true)); } } # Log the move $logid = $logEntry->insert(); $logEntry->publish($logid); }
/** * Propagate an article move * Sets xml property and calls the abstract function propagateMoveData($newTitle) * @param Title $newTitle title article has been moved to * @return bool true if propagate succeeded */ public function propagateMove($newTitle) { global $wgUser, $wrBotUserID; $result = true; // we do propagate bot edits // if ($wgUser->getID() != $wrBotUserID) { // remove the title from the link cache and re-created it so it has the right page id //$linkCache =& LinkCache::singleton(); //$linkCache->clearBadLink($newTitle->getPrefixedDBkey()); //$newTitle = Title::makeTitle($newTitle->getNamespace(), $newTitle->getDBkey()); $newTitle->resetArticleID(0); // clears link cache $newTitle->getArticleID(GAID_FOR_UPDATE); // make sure you read the master db for the pageid $newTitleString = $newTitle->getText(); $newNs = $newTitle->getNamespace(); $revision = StructuredData::getRevision($newTitle, false, true); if (!$revision) { error_log("Revision being moved not found!\n"); return true; } $text =& $revision->getText(); $this->xml = StructuredData::getXml($this->tagName, $text); $textChanged = false; $result = $this->propagateMoveDeleteUndelete($newTitleString, $newNs, $text, $textChanged); if ($result && $textChanged) { // should change only if moving namespaces (which is currently only allowed for source -> repo) $article = new Article($newTitle, 0); PropagationManager::enablePropagation(false); $result = $article->doEdit($text, self::PROPAGATE_MOVE_MESSAGE, PROPAGATE_EDIT_FLAGS); PropagationManager::enablePropagation(true); } if (!$result) { error_log("ERROR propagateMove failed for {$this->title->getPrefixedText()} -> {$newTitle->getPrefixedText()}\n"); } // } return true; }