'DeferredUpdates' => 'includes/DeferredUpdates.php',
'DeprecatedGlobal' => 'includes/DeprecatedGlobal.php',
'DerivativeRequest' => 'includes/WebRequest.php',
+ 'DeviceDetection' => 'includes/DeviceDetection.php',
'DiffHistoryBlob' => 'includes/HistoryBlob.php',
'DoubleReplacer' => 'includes/StringUtils.php',
'Linker' => 'includes/Linker.php',
'LinkFilter' => 'includes/LinkFilter.php',
'LinksUpdate' => 'includes/LinksUpdate.php',
+ 'LinksDeletionUpdate' => 'includes/LinksUpdate.php',
'LocalisationCache' => 'includes/LocalisationCache.php',
'LocalisationCache_BulkLoad' => 'includes/LocalisationCache.php',
'MagicWord' => 'includes/MagicWord.php',
'RevisionList' => 'includes/RevisionList.php',
'RSSFeed' => 'includes/Feed.php',
'Sanitizer' => 'includes/Sanitizer.php',
+ 'DataUpdate' => 'includes/DataUpdate.php',
+ 'SqlDataUpdate' => 'includes/SqlDataUpdate.php',
'ScopedPHPTimeout' => 'includes/ScopedPHPTimeout.php',
'SiteConfiguration' => 'includes/SiteConfiguration.php',
'SiteStats' => 'includes/SiteStats.php',
'FileBackendStoreShardDirIterator' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendStoreShardFileIterator' => 'includes/filerepo/backend/FileBackendStore.php',
'FileBackendMultiWrite' => 'includes/filerepo/backend/FileBackendMultiWrite.php',
+ 'FileBackendStoreOpHandle' => 'includes/filerepo/backend/FileBackendStore.php',
'FSFileBackend' => 'includes/filerepo/backend/FSFileBackend.php',
'FSFileBackendList' => 'includes/filerepo/backend/FSFileBackend.php',
'FSFileBackendDirList' => 'includes/filerepo/backend/FSFileBackend.php',
'FSFileBackendFileList' => 'includes/filerepo/backend/FSFileBackend.php',
+ 'FSFileOpHandle' => 'includes/filerepo/backend/FSFileBackend.php',
'SwiftFileBackend' => 'includes/filerepo/backend/SwiftFileBackend.php',
'SwiftFileBackendList' => 'includes/filerepo/backend/SwiftFileBackend.php',
'SwiftFileBackendDirList' => 'includes/filerepo/backend/SwiftFileBackend.php',
'SwiftFileBackendFileList' => 'includes/filerepo/backend/SwiftFileBackend.php',
+ 'SwiftFileOpHandle' => 'includes/filerepo/backend/SwiftFileBackend.php',
'FileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
'DBFileJournal' => 'includes/filerepo/backend/filejournal/DBFileJournal.php',
'NullFileJournal' => 'includes/filerepo/backend/filejournal/FileJournal.php',
'MySqlLockManager'=> 'includes/filerepo/backend/lockmanager/DBLockManager.php',
'NullLockManager' => 'includes/filerepo/backend/lockmanager/LockManager.php',
'FileOp' => 'includes/filerepo/backend/FileOp.php',
+ 'FileOpBatch' => 'includes/filerepo/backend/FileOpBatch.php',
'StoreFileOp' => 'includes/filerepo/backend/FileOp.php',
'CopyFileOp' => 'includes/filerepo/backend/FileOp.php',
'MoveFileOp' => 'includes/filerepo/backend/FileOp.php',
<?php
/**
- * See docs/deferred.txt
+ * Updater for link tracking tables after a page edit.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
* http://www.gnu.org/copyleft/gpl.html
*
+ * @file
+ */
+
+/**
+ * See docs/deferred.txt
+ *
* @todo document (e.g. one-sentence top-level class description).
*/
- class LinksUpdate {
+ class LinksUpdate extends SqlDataUpdate {
- /**@{{
- * @private
- */
- var $mId, //!< Page ID of the article linked from
+ // @todo: make members protected, but make sure extensions don't break
+
+ public $mId, //!< Page ID of the article linked from
$mTitle, //!< Title object of the article linked from
$mParserOutput, //!< Parser output
$mLinks, //!< Map of title strings to IDs for the links in the document
$mDb, //!< Database connection reference
$mOptions, //!< SELECT options to be used (array)
$mRecursive; //!< Whether to queue jobs for recursive updates
- /**@}}*/
/**
* Constructor
* @param $recursive Boolean: queue jobs for recursive updates?
*/
function __construct( $title, $parserOutput, $recursive = true ) {
- global $wgAntiLockFlags;
+ parent::__construct( );
- if ( $wgAntiLockFlags & ALF_NO_LINK_LOCK ) {
- $this->mOptions = array();
- } else {
- $this->mOptions = array( 'FOR UPDATE' );
+ if ( !( $title instanceof Title ) ) {
+ throw new MWException( "The calling convention to LinksUpdate::LinksUpdate() has changed. " .
+ "Please see Article::editUpdates() for an invocation example.\n" );
}
- $this->mDb = wfGetDB( DB_MASTER );
- if ( !is_object( $title ) ) {
+ if ( !( $parserOutput instanceof ParserOutput ) ) {
throw new MWException( "The calling convention to LinksUpdate::__construct() has changed. " .
"Please see WikiPage::doEditUpdates() for an invocation example.\n" );
}
+
$this->mTitle = $title;
$this->mId = $title->getArticleID();
wfProfileOut( __METHOD__ );
}
- /**
- * Invalidate the cache of a list of pages from a single namespace
- *
- * @param $namespace Integer
- * @param $dbkeys Array
- */
- function invalidatePages( $namespace, $dbkeys ) {
- if ( !count( $dbkeys ) ) {
- return;
- }
-
- /**
- * Determine which pages need to be updated
- * This is necessary to prevent the job queue from smashing the DB with
- * large numbers of concurrent invalidations of the same page
- */
- $now = $this->mDb->timestamp();
- $ids = array();
- $res = $this->mDb->select( 'page', array( 'page_id' ),
- array(
- 'page_namespace' => $namespace,
- 'page_title IN (' . $this->mDb->makeList( $dbkeys ) . ')',
- 'page_touched < ' . $this->mDb->addQuotes( $now )
- ), __METHOD__
- );
- foreach ( $res as $row ) {
- $ids[] = $row->page_id;
- }
- if ( !count( $ids ) ) {
- return;
- }
-
- /**
- * Do the update
- * We still need the page_touched condition, in case the row has changed since
- * the non-locking select above.
- */
- $this->mDb->update( 'page', array( 'page_touched' => $now ),
- array(
- 'page_id IN (' . $this->mDb->makeList( $ids ) . ')',
- 'page_touched < ' . $this->mDb->addQuotes( $now )
- ), __METHOD__
- );
- }
-
/**
* @param $cats
*/
}
}
}
-}
+
+ /**
+ * Update object handling the cleanup of links tables after a page was deleted.
+ **/
+ class LinksDeletionUpdate extends SqlDataUpdate {
+
+ protected $mPage; //!< WikiPage the wikipage that was deleted
+
+ /**
+ * Constructor
+ *
+ * @param $title Title of the page we're updating
+ * @param $parserOutput ParserOutput: output from a full parse of this page
+ * @param $recursive Boolean: queue jobs for recursive updates?
+ */
+ function __construct( WikiPage $page ) {
+ parent::__construct( );
+
+ $this->mPage = $page;
+ }
+
+ /**
+ * Do some database updates after deletion
+ */
+ public function doUpdate() {
+ $title = $this->mPage->getTitle();
+ $id = $this->mPage->getId();
+
+ # Delete restrictions for it
+ $this->mDb->delete( 'page_restrictions', array ( 'pr_page' => $id ), __METHOD__ );
+
+ # Fix category table counts
+ $cats = array();
+ $res = $this->mDb->select( 'categorylinks', 'cl_to', array( 'cl_from' => $id ), __METHOD__ );
+
+ foreach ( $res as $row ) {
+ $cats [] = $row->cl_to;
+ }
+
+ $this->mPage->updateCategoryCounts( array(), $cats );
+
+ # If using cascading deletes, we can skip some explicit deletes
+ if ( !$this->mDb->cascadingDeletes() ) {
+ $this->mDb->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
+
+ # Delete outgoing links
+ $this->mDb->delete( 'pagelinks', array( 'pl_from' => $id ), __METHOD__ );
+ $this->mDb->delete( 'imagelinks', array( 'il_from' => $id ), __METHOD__ );
+ $this->mDb->delete( 'categorylinks', array( 'cl_from' => $id ), __METHOD__ );
+ $this->mDb->delete( 'templatelinks', array( 'tl_from' => $id ), __METHOD__ );
+ $this->mDb->delete( 'externallinks', array( 'el_from' => $id ), __METHOD__ );
+ $this->mDb->delete( 'langlinks', array( 'll_from' => $id ), __METHOD__ );
+ $this->mDb->delete( 'iwlinks', array( 'iwl_from' => $id ), __METHOD__ );
+ $this->mDb->delete( 'redirect', array( 'rd_from' => $id ), __METHOD__ );
+ $this->mDb->delete( 'page_props', array( 'pp_page' => $id ), __METHOD__ );
+ }
+
+ # If using cleanup triggers, we can skip some manual deletes
+ if ( !$this->mDb->cleanupTriggers() ) {
+ # Clean up recentchanges entries...
+ $this->mDb->delete( 'recentchanges',
+ array( 'rc_type != ' . RC_LOG,
+ 'rc_namespace' => $title->getNamespace(),
+ 'rc_title' => $title->getDBkey() ),
+ __METHOD__ );
+ $this->mDb->delete( 'recentchanges',
+ array( 'rc_type != ' . RC_LOG, 'rc_cur_id' => $id ),
+ __METHOD__ );
+ }
+ }
++}
<?php
+/**
+ * Base representation for a MediaWiki page.
+ *
+ * This program is free software; you can redistribute it and/or modify
+ * it under the terms of the GNU General Public License as published by
+ * the Free Software Foundation; either version 2 of the License, or
+ * (at your option) any later version.
+ *
+ * This program is distributed in the hope that it will be useful,
+ * but WITHOUT ANY WARRANTY; without even the implied warranty of
+ * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
+ * GNU General Public License for more details.
+ *
+ * You should have received a copy of the GNU General Public License along
+ * with this program; if not, write to the Free Software Foundation, Inc.,
+ * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
+ * http://www.gnu.org/copyleft/gpl.html
+ *
+ * @file
+ */
+
/**
* Abstract class for type hinting (accepts WikiPage, Article, ImagePage, CategoryPage)
*/
return (int)$this->mLatest;
}
+ /**
+ * Get the Revision object of the oldest revision
+ * @return Revision|null
+ */
+ public function getOldestRevision() {
+ wfProfileIn( __METHOD__ );
+
+ // Try using the slave database first, then try the master
+ $continue = 2;
+ $db = wfGetDB( DB_SLAVE );
+ $revSelectFields = Revision::selectFields();
+
+ while ( $continue ) {
+ $row = $db->selectRow(
+ array( 'page', 'revision' ),
+ $revSelectFields,
+ array(
+ 'page_namespace' => $this->mTitle->getNamespace(),
+ 'page_title' => $this->mTitle->getDBkey(),
+ 'rev_page = page_id'
+ ),
+ __METHOD__,
+ array(
+ 'ORDER BY' => 'rev_timestamp ASC'
+ )
+ );
+
+ if ( $row ) {
+ $continue = 0;
+ } else {
+ $db = wfGetDB( DB_MASTER );
+ $continue--;
+ }
+ }
+
+ wfProfileOut( __METHOD__ );
+ if ( $row ) {
+ return Revision::newFromRow( $row );
+ } else {
+ return null;
+ }
+ }
+
/**
* Loads everything except the text
* This isn't necessary for all uses, so it's only done if needed.
}
}
+ /**
+ * Get the User object of the user who created the page
+ * @param $audience Integer: one of:
+ * Revision::FOR_PUBLIC to be displayed to all users
+ * Revision::FOR_THIS_USER to be displayed to $wgUser
+ * Revision::RAW get the text regardless of permissions
+ * @return User|null
+ */
+ public function getCreator( $audience = Revision::FOR_PUBLIC ) {
+ $revision = $this->getOldestRevision();
+ if ( $revision ) {
+ $userName = $revision->getUserText( $audience );
+ return User::newFromName( $userName, false );
+ } else {
+ return null;
+ }
+ }
+
/**
* @param $audience Integer: one of:
* Revision::FOR_PUBLIC to be displayed to all users
$parserCache->save( $editInfo->output, $this, $editInfo->popts );
}
- # Update the links tables
- $u = new LinksUpdate( $this->mTitle, $editInfo->output );
- $u->doUpdate();
+ # Update the links tables and other secondary data
+ $updates = $editInfo->output->getSecondaryDataUpdates( $this->mTitle );
+ DataUpdate::runUpdates( $updates );
wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
return WikiPage::DELETE_NO_REVISIONS;
}
- $this->doDeleteUpdates( $id );
+ # update site status
+ DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
+
+ # remove secondary indexes, etc
+ $updates = $this->getDeletionUpdates( );
+ 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 );
# Log the deletion, if the page was suppressed, log it at Oversight instead
$logtype = $suppress ? 'suppress' : 'delete';
return WikiPage::DELETE_SUCCESS;
}
- /**
- * 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 );
-
- # Reset this object
- $this->clear();
-
- # Clear the cached article id so the interface doesn't act like we exist
- $this->mTitle->resetArticleID( 0 );
- }
-
/**
* Roll back the most recent consecutive set of edits to a page
* from the same user; fails if there are no eligible edits to
if ( count( $templates_diff ) > 0 ) {
# Whee, link updates time.
+ # Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output.
$u = new LinksUpdate( $this->mTitle, $parserOutput, false );
$u->doUpdate();
}
global $wgUser;
return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
}
+
+ public function getDeletionUpdates() {
+ $updates = array(
+ new LinksDeletionUpdate( $this ),
+ );
+
+ //@todo: make a hook to add update objects
+ //NOTE: deletion updates will be determined by the ContentHandler in the future
+ return $updates;
+ }
}
class PoolWorkArticleView extends PoolCounterWork {
global $wgParser, $wgEnableParserCache;
$popts = ParserOptions::newFromContext( $this->getContext() );
+ $popts->setTidy( true );
$p_result = $wgParser->parse( $page->getRawText(), $title, $popts,
true, true, $page->getLatest() );
# Update the links tables
- $u = new LinksUpdate( $title, $p_result );
- $u->doUpdate();
+ $updates = $p_result->getSecondaryDataUpdates( $title );
+ DataUpdate::runUpdates( $updates );
$r['linkupdate'] = '';