MediaWiki  REL1_24
WikiPage.php
Go to the documentation of this file.
00001 <?php
00026 interface Page {
00027 }
00028 
00037 class WikiPage implements Page, IDBAccessObject {
00038     // Constants for $mDataLoadedFrom and related
00039 
00043     public $mTitle = null;
00044 
00048     public $mDataLoaded = false;         // !< Boolean
00049     public $mIsRedirect = false;         // !< Boolean
00050     public $mLatest = false;             // !< Integer (false means "not loaded")
00054     public $mPreparedEdit = false;
00055 
00059     protected $mId = null;
00060 
00064     protected $mDataLoadedFrom = self::READ_NONE;
00065 
00069     protected $mRedirectTarget = null;
00070 
00074     protected $mLastRevision = null;
00075 
00079     protected $mTimestamp = '';
00080 
00084     protected $mTouched = '19700101000000';
00085 
00089     protected $mLinksUpdated = '19700101000000';
00090 
00094     protected $mCounter = null;
00095 
00100     public function __construct( Title $title ) {
00101         $this->mTitle = $title;
00102     }
00103 
00112     public static function factory( Title $title ) {
00113         $ns = $title->getNamespace();
00114 
00115         if ( $ns == NS_MEDIA ) {
00116             throw new MWException( "NS_MEDIA is a virtual namespace; use NS_FILE." );
00117         } elseif ( $ns < 0 ) {
00118             throw new MWException( "Invalid or virtual namespace $ns given." );
00119         }
00120 
00121         switch ( $ns ) {
00122             case NS_FILE:
00123                 $page = new WikiFilePage( $title );
00124                 break;
00125             case NS_CATEGORY:
00126                 $page = new WikiCategoryPage( $title );
00127                 break;
00128             default:
00129                 $page = new WikiPage( $title );
00130         }
00131 
00132         return $page;
00133     }
00134 
00145     public static function newFromID( $id, $from = 'fromdb' ) {
00146         // page id's are never 0 or negative, see bug 61166
00147         if ( $id < 1 ) {
00148             return null;
00149         }
00150 
00151         $from = self::convertSelectType( $from );
00152         $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
00153         $row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
00154         if ( !$row ) {
00155             return null;
00156         }
00157         return self::newFromRow( $row, $from );
00158     }
00159 
00171     public static function newFromRow( $row, $from = 'fromdb' ) {
00172         $page = self::factory( Title::newFromRow( $row ) );
00173         $page->loadFromRow( $row, $from );
00174         return $page;
00175     }
00176 
00183     private static function convertSelectType( $type ) {
00184         switch ( $type ) {
00185         case 'fromdb':
00186             return self::READ_NORMAL;
00187         case 'fromdbmaster':
00188             return self::READ_LATEST;
00189         case 'forupdate':
00190             return self::READ_LOCKING;
00191         default:
00192             // It may already be an integer or whatever else
00193             return $type;
00194         }
00195     }
00196 
00207     public function getActionOverrides() {
00208         $content_handler = $this->getContentHandler();
00209         return $content_handler->getActionOverrides();
00210     }
00211 
00221     public function getContentHandler() {
00222         return ContentHandler::getForModelID( $this->getContentModel() );
00223     }
00224 
00229     public function getTitle() {
00230         return $this->mTitle;
00231     }
00232 
00237     public function clear() {
00238         $this->mDataLoaded = false;
00239         $this->mDataLoadedFrom = self::READ_NONE;
00240 
00241         $this->clearCacheFields();
00242     }
00243 
00248     protected function clearCacheFields() {
00249         $this->mId = null;
00250         $this->mCounter = null;
00251         $this->mRedirectTarget = null; // Title object if set
00252         $this->mLastRevision = null; // Latest revision
00253         $this->mTouched = '19700101000000';
00254         $this->mLinksUpdated = '19700101000000';
00255         $this->mTimestamp = '';
00256         $this->mIsRedirect = false;
00257         $this->mLatest = false;
00258         // Bug 57026: do not clear mPreparedEdit since prepareTextForEdit() already checks
00259         // the requested rev ID and content against the cached one for equality. For most
00260         // content types, the output should not change during the lifetime of this cache.
00261         // Clearing it can cause extra parses on edit for no reason.
00262     }
00263 
00269     public function clearPreparedEdit() {
00270         $this->mPreparedEdit = false;
00271     }
00272 
00279     public static function selectFields() {
00280         global $wgContentHandlerUseDB, $wgPageLanguageUseDB;
00281 
00282         $fields = array(
00283             'page_id',
00284             'page_namespace',
00285             'page_title',
00286             'page_restrictions',
00287             'page_counter',
00288             'page_is_redirect',
00289             'page_is_new',
00290             'page_random',
00291             'page_touched',
00292             'page_links_updated',
00293             'page_latest',
00294             'page_len',
00295         );
00296 
00297         if ( $wgContentHandlerUseDB ) {
00298             $fields[] = 'page_content_model';
00299         }
00300 
00301         if ( $wgPageLanguageUseDB ) {
00302             $fields[] = 'page_lang';
00303         }
00304 
00305         return $fields;
00306     }
00307 
00315     protected function pageData( $dbr, $conditions, $options = array() ) {
00316         $fields = self::selectFields();
00317 
00318         wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
00319 
00320         $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options );
00321 
00322         wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
00323 
00324         return $row;
00325     }
00326 
00336     public function pageDataFromTitle( $dbr, $title, $options = array() ) {
00337         return $this->pageData( $dbr, array(
00338             'page_namespace' => $title->getNamespace(),
00339             'page_title' => $title->getDBkey() ), $options );
00340     }
00341 
00350     public function pageDataFromId( $dbr, $id, $options = array() ) {
00351         return $this->pageData( $dbr, array( 'page_id' => $id ), $options );
00352     }
00353 
00367     public function loadPageData( $from = 'fromdb' ) {
00368         $from = self::convertSelectType( $from );
00369         if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
00370             // We already have the data from the correct location, no need to load it twice.
00371             return;
00372         }
00373 
00374         if ( $from === self::READ_LOCKING ) {
00375             $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle, array( 'FOR UPDATE' ) );
00376         } elseif ( $from === self::READ_LATEST ) {
00377             $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
00378         } elseif ( $from === self::READ_NORMAL ) {
00379             $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
00380             // Use a "last rev inserted" timestamp key to diminish the issue of slave lag.
00381             // Note that DB also stores the master position in the session and checks it.
00382             $touched = $this->getCachedLastEditTime();
00383             if ( $touched ) { // key set
00384                 if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
00385                     $from = self::READ_LATEST;
00386                     $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
00387                 }
00388             }
00389         } else {
00390             // No idea from where the caller got this data, assume slave database.
00391             $data = $from;
00392             $from = self::READ_NORMAL;
00393         }
00394 
00395         $this->loadFromRow( $data, $from );
00396     }
00397 
00409     public function loadFromRow( $data, $from ) {
00410         $lc = LinkCache::singleton();
00411         $lc->clearLink( $this->mTitle );
00412 
00413         if ( $data ) {
00414             $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
00415 
00416             $this->mTitle->loadFromRow( $data );
00417 
00418             // Old-fashioned restrictions
00419             $this->mTitle->loadRestrictions( $data->page_restrictions );
00420 
00421             $this->mId = intval( $data->page_id );
00422             $this->mCounter = intval( $data->page_counter );
00423             $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
00424             $this->mLinksUpdated = wfTimestampOrNull( TS_MW, $data->page_links_updated );
00425             $this->mIsRedirect = intval( $data->page_is_redirect );
00426             $this->mLatest = intval( $data->page_latest );
00427             // Bug 37225: $latest may no longer match the cached latest Revision object.
00428             // Double-check the ID of any cached latest Revision object for consistency.
00429             if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
00430                 $this->mLastRevision = null;
00431                 $this->mTimestamp = '';
00432             }
00433         } else {
00434             $lc->addBadLinkObj( $this->mTitle );
00435 
00436             $this->mTitle->loadFromRow( false );
00437 
00438             $this->clearCacheFields();
00439 
00440             $this->mId = 0;
00441         }
00442 
00443         $this->mDataLoaded = true;
00444         $this->mDataLoadedFrom = self::convertSelectType( $from );
00445     }
00446 
00450     public function getId() {
00451         if ( !$this->mDataLoaded ) {
00452             $this->loadPageData();
00453         }
00454         return $this->mId;
00455     }
00456 
00460     public function exists() {
00461         if ( !$this->mDataLoaded ) {
00462             $this->loadPageData();
00463         }
00464         return $this->mId > 0;
00465     }
00466 
00475     public function hasViewableContent() {
00476         return $this->exists() || $this->mTitle->isAlwaysKnown();
00477     }
00478 
00482     public function getCount() {
00483         if ( !$this->mDataLoaded ) {
00484             $this->loadPageData();
00485         }
00486 
00487         return $this->mCounter;
00488     }
00489 
00495     public function isRedirect() {
00496         $content = $this->getContent();
00497         if ( !$content ) {
00498             return false;
00499         }
00500 
00501         return $content->isRedirect();
00502     }
00503 
00514     public function getContentModel() {
00515         if ( $this->exists() ) {
00516             // look at the revision's actual content model
00517             $rev = $this->getRevision();
00518 
00519             if ( $rev !== null ) {
00520                 return $rev->getContentModel();
00521             } else {
00522                 $title = $this->mTitle->getPrefixedDBkey();
00523                 wfWarn( "Page $title exists but has no (visible) revisions!" );
00524             }
00525         }
00526 
00527         // use the default model for this page
00528         return $this->mTitle->getContentModel();
00529     }
00530 
00535     public function checkTouched() {
00536         if ( !$this->mDataLoaded ) {
00537             $this->loadPageData();
00538         }
00539         return !$this->mIsRedirect;
00540     }
00541 
00546     public function getTouched() {
00547         if ( !$this->mDataLoaded ) {
00548             $this->loadPageData();
00549         }
00550         return $this->mTouched;
00551     }
00552 
00557     public function getLinksTimestamp() {
00558         if ( !$this->mDataLoaded ) {
00559             $this->loadPageData();
00560         }
00561         return $this->mLinksUpdated;
00562     }
00563 
00568     public function getLatest() {
00569         if ( !$this->mDataLoaded ) {
00570             $this->loadPageData();
00571         }
00572         return (int)$this->mLatest;
00573     }
00574 
00579     public function getOldestRevision() {
00580         wfProfileIn( __METHOD__ );
00581 
00582         // Try using the slave database first, then try the master
00583         $continue = 2;
00584         $db = wfGetDB( DB_SLAVE );
00585         $revSelectFields = Revision::selectFields();
00586 
00587         $row = null;
00588         while ( $continue ) {
00589             $row = $db->selectRow(
00590                 array( 'page', 'revision' ),
00591                 $revSelectFields,
00592                 array(
00593                     'page_namespace' => $this->mTitle->getNamespace(),
00594                     'page_title' => $this->mTitle->getDBkey(),
00595                     'rev_page = page_id'
00596                 ),
00597                 __METHOD__,
00598                 array(
00599                     'ORDER BY' => 'rev_timestamp ASC'
00600                 )
00601             );
00602 
00603             if ( $row ) {
00604                 $continue = 0;
00605             } else {
00606                 $db = wfGetDB( DB_MASTER );
00607                 $continue--;
00608             }
00609         }
00610 
00611         wfProfileOut( __METHOD__ );
00612         return $row ? Revision::newFromRow( $row ) : null;
00613     }
00614 
00619     protected function loadLastEdit() {
00620         if ( $this->mLastRevision !== null ) {
00621             return; // already loaded
00622         }
00623 
00624         $latest = $this->getLatest();
00625         if ( !$latest ) {
00626             return; // page doesn't exist or is missing page_latest info
00627         }
00628 
00629         // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the
00630         // latest changes committed. This is true even within REPEATABLE-READ transactions, where
00631         // S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to
00632         // also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row
00633         // UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT.
00634         // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
00635         $flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0;
00636         $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
00637         if ( $revision ) { // sanity
00638             $this->setLastEdit( $revision );
00639         }
00640     }
00641 
00646     protected function setLastEdit( Revision $revision ) {
00647         $this->mLastRevision = $revision;
00648         $this->mTimestamp = $revision->getTimestamp();
00649     }
00650 
00655     public function getRevision() {
00656         $this->loadLastEdit();
00657         if ( $this->mLastRevision ) {
00658             return $this->mLastRevision;
00659         }
00660         return null;
00661     }
00662 
00676     public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00677         $this->loadLastEdit();
00678         if ( $this->mLastRevision ) {
00679             return $this->mLastRevision->getContent( $audience, $user );
00680         }
00681         return null;
00682     }
00683 
00696     public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00697         ContentHandler::deprecated( __METHOD__, '1.21' );
00698 
00699         $this->loadLastEdit();
00700         if ( $this->mLastRevision ) {
00701             return $this->mLastRevision->getText( $audience, $user );
00702         }
00703         return false;
00704     }
00705 
00712     public function getRawText() {
00713         ContentHandler::deprecated( __METHOD__, '1.21' );
00714 
00715         return $this->getText( Revision::RAW );
00716     }
00717 
00721     public function getTimestamp() {
00722         // Check if the field has been filled by WikiPage::setTimestamp()
00723         if ( !$this->mTimestamp ) {
00724             $this->loadLastEdit();
00725         }
00726 
00727         return wfTimestamp( TS_MW, $this->mTimestamp );
00728     }
00729 
00735     public function setTimestamp( $ts ) {
00736         $this->mTimestamp = wfTimestamp( TS_MW, $ts );
00737     }
00738 
00748     public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00749         $this->loadLastEdit();
00750         if ( $this->mLastRevision ) {
00751             return $this->mLastRevision->getUser( $audience, $user );
00752         } else {
00753             return -1;
00754         }
00755     }
00756 
00767     public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00768         $revision = $this->getOldestRevision();
00769         if ( $revision ) {
00770             $userName = $revision->getUserText( $audience, $user );
00771             return User::newFromName( $userName, false );
00772         } else {
00773             return null;
00774         }
00775     }
00776 
00786     public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00787         $this->loadLastEdit();
00788         if ( $this->mLastRevision ) {
00789             return $this->mLastRevision->getUserText( $audience, $user );
00790         } else {
00791             return '';
00792         }
00793     }
00794 
00804     public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00805         $this->loadLastEdit();
00806         if ( $this->mLastRevision ) {
00807             return $this->mLastRevision->getComment( $audience, $user );
00808         } else {
00809             return '';
00810         }
00811     }
00812 
00818     public function getMinorEdit() {
00819         $this->loadLastEdit();
00820         if ( $this->mLastRevision ) {
00821             return $this->mLastRevision->isMinor();
00822         } else {
00823             return false;
00824         }
00825     }
00826 
00832     protected function getCachedLastEditTime() {
00833         global $wgMemc;
00834         $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
00835         return $wgMemc->get( $key );
00836     }
00837 
00844     public function setCachedLastEditTime( $timestamp ) {
00845         global $wgMemc;
00846         $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
00847         $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60 * 15 );
00848     }
00849 
00858     public function isCountable( $editInfo = false ) {
00859         global $wgArticleCountMethod;
00860 
00861         if ( !$this->mTitle->isContentPage() ) {
00862             return false;
00863         }
00864 
00865         if ( $editInfo ) {
00866             $content = $editInfo->pstContent;
00867         } else {
00868             $content = $this->getContent();
00869         }
00870 
00871         if ( !$content || $content->isRedirect() ) {
00872             return false;
00873         }
00874 
00875         $hasLinks = null;
00876 
00877         if ( $wgArticleCountMethod === 'link' ) {
00878             // nasty special case to avoid re-parsing to detect links
00879 
00880             if ( $editInfo ) {
00881                 // ParserOutput::getLinks() is a 2D array of page links, so
00882                 // to be really correct we would need to recurse in the array
00883                 // but the main array should only have items in it if there are
00884                 // links.
00885                 $hasLinks = (bool)count( $editInfo->output->getLinks() );
00886             } else {
00887                 $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
00888                     array( 'pl_from' => $this->getId() ), __METHOD__ );
00889             }
00890         }
00891 
00892         return $content->isCountable( $hasLinks );
00893     }
00894 
00902     public function getRedirectTarget() {
00903         if ( !$this->mTitle->isRedirect() ) {
00904             return null;
00905         }
00906 
00907         if ( $this->mRedirectTarget !== null ) {
00908             return $this->mRedirectTarget;
00909         }
00910 
00911         // Query the redirect table
00912         $dbr = wfGetDB( DB_SLAVE );
00913         $row = $dbr->selectRow( 'redirect',
00914             array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
00915             array( 'rd_from' => $this->getId() ),
00916             __METHOD__
00917         );
00918 
00919         // rd_fragment and rd_interwiki were added later, populate them if empty
00920         if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
00921             $this->mRedirectTarget = Title::makeTitle(
00922                 $row->rd_namespace, $row->rd_title,
00923                 $row->rd_fragment, $row->rd_interwiki );
00924             return $this->mRedirectTarget;
00925         }
00926 
00927         // This page doesn't have an entry in the redirect table
00928         $this->mRedirectTarget = $this->insertRedirect();
00929         return $this->mRedirectTarget;
00930     }
00931 
00938     public function insertRedirect() {
00939         // recurse through to only get the final target
00940         $content = $this->getContent();
00941         $retval = $content ? $content->getUltimateRedirectTarget() : null;
00942         if ( !$retval ) {
00943             return null;
00944         }
00945         $this->insertRedirectEntry( $retval );
00946         return $retval;
00947     }
00948 
00954     public function insertRedirectEntry( $rt ) {
00955         $dbw = wfGetDB( DB_MASTER );
00956         $dbw->replace( 'redirect', array( 'rd_from' ),
00957             array(
00958                 'rd_from' => $this->getId(),
00959                 'rd_namespace' => $rt->getNamespace(),
00960                 'rd_title' => $rt->getDBkey(),
00961                 'rd_fragment' => $rt->getFragment(),
00962                 'rd_interwiki' => $rt->getInterwiki(),
00963             ),
00964             __METHOD__
00965         );
00966     }
00967 
00973     public function followRedirect() {
00974         return $this->getRedirectURL( $this->getRedirectTarget() );
00975     }
00976 
00984     public function getRedirectURL( $rt ) {
00985         if ( !$rt ) {
00986             return false;
00987         }
00988 
00989         if ( $rt->isExternal() ) {
00990             if ( $rt->isLocal() ) {
00991                 // Offsite wikis need an HTTP redirect.
00992                 //
00993                 // This can be hard to reverse and may produce loops,
00994                 // so they may be disabled in the site configuration.
00995                 $source = $this->mTitle->getFullURL( 'redirect=no' );
00996                 return $rt->getFullURL( array( 'rdfrom' => $source ) );
00997             } else {
00998                 // External pages pages without "local" bit set are not valid
00999                 // redirect targets
01000                 return false;
01001             }
01002         }
01003 
01004         if ( $rt->isSpecialPage() ) {
01005             // Gotta handle redirects to special pages differently:
01006             // Fill the HTTP response "Location" header and ignore
01007             // the rest of the page we're on.
01008             //
01009             // Some pages are not valid targets
01010             if ( $rt->isValidRedirectTarget() ) {
01011                 return $rt->getFullURL();
01012             } else {
01013                 return false;
01014             }
01015         }
01016 
01017         return $rt;
01018     }
01019 
01025     public function getContributors() {
01026         // @todo FIXME: This is expensive; cache this info somewhere.
01027 
01028         $dbr = wfGetDB( DB_SLAVE );
01029 
01030         if ( $dbr->implicitGroupby() ) {
01031             $realNameField = 'user_real_name';
01032         } else {
01033             $realNameField = 'MIN(user_real_name) AS user_real_name';
01034         }
01035 
01036         $tables = array( 'revision', 'user' );
01037 
01038         $fields = array(
01039             'user_id' => 'rev_user',
01040             'user_name' => 'rev_user_text',
01041             $realNameField,
01042             'timestamp' => 'MAX(rev_timestamp)',
01043         );
01044 
01045         $conds = array( 'rev_page' => $this->getId() );
01046 
01047         // The user who made the top revision gets credited as "this page was last edited by
01048         // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
01049         $user = $this->getUser();
01050         if ( $user ) {
01051             $conds[] = "rev_user != $user";
01052         } else {
01053             $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
01054         }
01055 
01056         $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
01057 
01058         $jconds = array(
01059             'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
01060         );
01061 
01062         $options = array(
01063             'GROUP BY' => array( 'rev_user', 'rev_user_text' ),
01064             'ORDER BY' => 'timestamp DESC',
01065         );
01066 
01067         $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
01068         return new UserArrayFromResult( $res );
01069     }
01070 
01077     public function getLastNAuthors( $num, $revLatest = 0 ) {
01078         wfProfileIn( __METHOD__ );
01079         // First try the slave
01080         // If that doesn't have the latest revision, try the master
01081         $continue = 2;
01082         $db = wfGetDB( DB_SLAVE );
01083 
01084         do {
01085             $res = $db->select( array( 'page', 'revision' ),
01086                 array( 'rev_id', 'rev_user_text' ),
01087                 array(
01088                     'page_namespace' => $this->mTitle->getNamespace(),
01089                     'page_title' => $this->mTitle->getDBkey(),
01090                     'rev_page = page_id'
01091                 ), __METHOD__,
01092                 array(
01093                     'ORDER BY' => 'rev_timestamp DESC',
01094                     'LIMIT' => $num
01095                 )
01096             );
01097 
01098             if ( !$res ) {
01099                 wfProfileOut( __METHOD__ );
01100                 return array();
01101             }
01102 
01103             $row = $db->fetchObject( $res );
01104 
01105             if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
01106                 $db = wfGetDB( DB_MASTER );
01107                 $continue--;
01108             } else {
01109                 $continue = 0;
01110             }
01111         } while ( $continue );
01112 
01113         $authors = array( $row->rev_user_text );
01114 
01115         foreach ( $res as $row ) {
01116             $authors[] = $row->rev_user_text;
01117         }
01118 
01119         wfProfileOut( __METHOD__ );
01120         return $authors;
01121     }
01122 
01130     public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
01131         global $wgEnableParserCache;
01132 
01133         return $wgEnableParserCache
01134             && $parserOptions->getStubThreshold() == 0
01135             && $this->exists()
01136             && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
01137             && $this->getContentHandler()->isParserCacheSupported();
01138     }
01139 
01151     public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
01152         wfProfileIn( __METHOD__ );
01153 
01154         $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
01155         wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
01156         if ( $parserOptions->getStubThreshold() ) {
01157             wfIncrStats( 'pcache_miss_stub' );
01158         }
01159 
01160         if ( $useParserCache ) {
01161             $parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
01162             if ( $parserOutput !== false ) {
01163                 wfProfileOut( __METHOD__ );
01164                 return $parserOutput;
01165             }
01166         }
01167 
01168         if ( $oldid === null || $oldid === 0 ) {
01169             $oldid = $this->getLatest();
01170         }
01171 
01172         $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
01173         $pool->execute();
01174 
01175         wfProfileOut( __METHOD__ );
01176 
01177         return $pool->getParserOutput();
01178     }
01179 
01185     public function doViewUpdates( User $user, $oldid = 0 ) {
01186         global $wgDisableCounters;
01187         if ( wfReadOnly() ) {
01188             return;
01189         }
01190 
01191         // Don't update page view counters on views from bot users (bug 14044)
01192         if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->exists() ) {
01193             DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
01194             DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
01195         }
01196 
01197         // Update newtalk / watchlist notification status
01198         $user->clearNotification( $this->mTitle, $oldid );
01199     }
01200 
01205     public function doPurge() {
01206         global $wgUseSquid;
01207 
01208         if ( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
01209             return false;
01210         }
01211 
01212         // Invalidate the cache
01213         $this->mTitle->invalidateCache();
01214 
01215         if ( $wgUseSquid ) {
01216             // Commit the transaction before the purge is sent
01217             $dbw = wfGetDB( DB_MASTER );
01218             $dbw->commit( __METHOD__ );
01219 
01220             // Send purge
01221             $update = SquidUpdate::newSimplePurge( $this->mTitle );
01222             $update->doUpdate();
01223         }
01224 
01225         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
01226             // @todo move this logic to MessageCache
01227 
01228             if ( $this->exists() ) {
01229                 // NOTE: use transclusion text for messages.
01230                 //       This is consistent with  MessageCache::getMsgFromNamespace()
01231 
01232                 $content = $this->getContent();
01233                 $text = $content === null ? null : $content->getWikitextForTransclusion();
01234 
01235                 if ( $text === null ) {
01236                     $text = false;
01237                 }
01238             } else {
01239                 $text = false;
01240             }
01241 
01242             MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
01243         }
01244         return true;
01245     }
01246 
01257     public function insertOn( $dbw ) {
01258         wfProfileIn( __METHOD__ );
01259 
01260         $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
01261         $dbw->insert( 'page', array(
01262             'page_id'           => $page_id,
01263             'page_namespace'    => $this->mTitle->getNamespace(),
01264             'page_title'        => $this->mTitle->getDBkey(),
01265             'page_counter'      => 0,
01266             'page_restrictions' => '',
01267             'page_is_redirect'  => 0, // Will set this shortly...
01268             'page_is_new'       => 1,
01269             'page_random'       => wfRandom(),
01270             'page_touched'      => $dbw->timestamp(),
01271             'page_latest'       => 0, // Fill this in shortly...
01272             'page_len'          => 0, // Fill this in shortly...
01273         ), __METHOD__, 'IGNORE' );
01274 
01275         $affected = $dbw->affectedRows();
01276 
01277         if ( $affected ) {
01278             $newid = $dbw->insertId();
01279             $this->mId = $newid;
01280             $this->mTitle->resetArticleID( $newid );
01281         }
01282         wfProfileOut( __METHOD__ );
01283 
01284         return $affected ? $newid : false;
01285     }
01286 
01300     public function updateRevisionOn( $dbw, $revision, $lastRevision = null,
01301         $lastRevIsRedirect = null
01302     ) {
01303         global $wgContentHandlerUseDB;
01304 
01305         wfProfileIn( __METHOD__ );
01306 
01307         $content = $revision->getContent();
01308         $len = $content ? $content->getSize() : 0;
01309         $rt = $content ? $content->getUltimateRedirectTarget() : null;
01310 
01311         $conditions = array( 'page_id' => $this->getId() );
01312 
01313         if ( !is_null( $lastRevision ) ) {
01314             // An extra check against threads stepping on each other
01315             $conditions['page_latest'] = $lastRevision;
01316         }
01317 
01318         $now = wfTimestampNow();
01319         $row = array( /* SET */
01320             'page_latest'      => $revision->getId(),
01321             'page_touched'     => $dbw->timestamp( $now ),
01322             'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
01323             'page_is_redirect' => $rt !== null ? 1 : 0,
01324             'page_len'         => $len,
01325         );
01326 
01327         if ( $wgContentHandlerUseDB ) {
01328             $row['page_content_model'] = $revision->getContentModel();
01329         }
01330 
01331         $dbw->update( 'page',
01332             $row,
01333             $conditions,
01334             __METHOD__ );
01335 
01336         $result = $dbw->affectedRows() > 0;
01337         if ( $result ) {
01338             $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
01339             $this->setLastEdit( $revision );
01340             $this->setCachedLastEditTime( $now );
01341             $this->mLatest = $revision->getId();
01342             $this->mIsRedirect = (bool)$rt;
01343             // Update the LinkCache.
01344             LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
01345                                                     $this->mLatest, $revision->getContentModel() );
01346         }
01347 
01348         wfProfileOut( __METHOD__ );
01349         return $result;
01350     }
01351 
01363     public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
01364         // Always update redirects (target link might have changed)
01365         // Update/Insert if we don't know if the last revision was a redirect or not
01366         // Delete if changing from redirect to non-redirect
01367         $isRedirect = !is_null( $redirectTitle );
01368 
01369         if ( !$isRedirect && $lastRevIsRedirect === false ) {
01370             return true;
01371         }
01372 
01373         wfProfileIn( __METHOD__ );
01374         if ( $isRedirect ) {
01375             $this->insertRedirectEntry( $redirectTitle );
01376         } else {
01377             // This is not a redirect, remove row from redirect table
01378             $where = array( 'rd_from' => $this->getId() );
01379             $dbw->delete( 'redirect', $where, __METHOD__ );
01380         }
01381 
01382         if ( $this->getTitle()->getNamespace() == NS_FILE ) {
01383             RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
01384         }
01385         wfProfileOut( __METHOD__ );
01386 
01387         return ( $dbw->affectedRows() != 0 );
01388     }
01389 
01400     public function updateIfNewerOn( $dbw, $revision ) {
01401         wfProfileIn( __METHOD__ );
01402 
01403         $row = $dbw->selectRow(
01404             array( 'revision', 'page' ),
01405             array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
01406             array(
01407                 'page_id' => $this->getId(),
01408                 'page_latest=rev_id' ),
01409             __METHOD__ );
01410 
01411         if ( $row ) {
01412             if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
01413                 wfProfileOut( __METHOD__ );
01414                 return false;
01415             }
01416             $prev = $row->rev_id;
01417             $lastRevIsRedirect = (bool)$row->page_is_redirect;
01418         } else {
01419             // No or missing previous revision; mark the page as new
01420             $prev = 0;
01421             $lastRevIsRedirect = null;
01422         }
01423 
01424         $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
01425 
01426         wfProfileOut( __METHOD__ );
01427         return $ret;
01428     }
01429 
01440     public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
01441         $handler = $undo->getContentHandler();
01442         return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
01443     }
01444 
01454     public function getUndoText( Revision $undo, Revision $undoafter = null ) {
01455         ContentHandler::deprecated( __METHOD__, '1.21' );
01456 
01457         $this->loadLastEdit();
01458 
01459         if ( $this->mLastRevision ) {
01460             if ( is_null( $undoafter ) ) {
01461                 $undoafter = $undo->getPrevious();
01462             }
01463 
01464             $handler = $this->getContentHandler();
01465             $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
01466 
01467             if ( !$undone ) {
01468                 return false;
01469             } else {
01470                 return ContentHandler::getContentText( $undone );
01471             }
01472         }
01473 
01474         return false;
01475     }
01476 
01490     public function replaceSection( $sectionId, $text, $sectionTitle = '',
01491         $edittime = null
01492     ) {
01493         ContentHandler::deprecated( __METHOD__, '1.21' );
01494 
01495         //NOTE: keep condition in sync with condition in replaceSectionContent!
01496         if ( strval( $sectionId ) === '' ) {
01497             // Whole-page edit; let the whole text through
01498             return $text;
01499         }
01500 
01501         if ( !$this->supportsSections() ) {
01502             throw new MWException( "sections not supported for content model " .
01503                 $this->getContentHandler()->getModelID() );
01504         }
01505 
01506         // could even make section title, but that's not required.
01507         $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
01508 
01509         $newContent = $this->replaceSectionContent( $sectionId, $sectionContent, $sectionTitle,
01510             $edittime );
01511 
01512         return ContentHandler::getContentText( $newContent );
01513     }
01514 
01525     public function supportsSections() {
01526         return $this->getContentHandler()->supportsSections();
01527     }
01528 
01543     public function replaceSectionContent( $sectionId, Content $sectionContent, $sectionTitle = '',
01544         $edittime = null ) {
01545         wfProfileIn( __METHOD__ );
01546 
01547         $baseRevId = null;
01548         if ( $edittime && $sectionId !== 'new' ) {
01549             $dbw = wfGetDB( DB_MASTER );
01550             $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
01551             if ( $rev ) {
01552                 $baseRevId = $rev->getId();
01553             }
01554         }
01555 
01556         wfProfileOut( __METHOD__ );
01557         return $this->replaceSectionAtRev( $sectionId, $sectionContent, $sectionTitle, $baseRevId );
01558     }
01559 
01573     public function replaceSectionAtRev( $sectionId, Content $sectionContent,
01574         $sectionTitle = '', $baseRevId = null
01575     ) {
01576         wfProfileIn( __METHOD__ );
01577 
01578         if ( strval( $sectionId ) === '' ) {
01579             // Whole-page edit; let the whole text through
01580             $newContent = $sectionContent;
01581         } else {
01582             if ( !$this->supportsSections() ) {
01583                 wfProfileOut( __METHOD__ );
01584                 throw new MWException( "sections not supported for content model " .
01585                     $this->getContentHandler()->getModelID() );
01586             }
01587 
01588             // Bug 30711: always use current version when adding a new section
01589             if ( is_null( $baseRevId ) || $sectionId === 'new' ) {
01590                 $oldContent = $this->getContent();
01591             } else {
01592                 // TODO: try DB_SLAVE first
01593                 $dbw = wfGetDB( DB_MASTER );
01594                 $rev = Revision::loadFromId( $dbw, $baseRevId );
01595 
01596                 if ( !$rev ) {
01597                     wfDebug( __METHOD__ . " asked for bogus section (page: " .
01598                         $this->getId() . "; section: $sectionId)\n" );
01599                     wfProfileOut( __METHOD__ );
01600                     return null;
01601                 }
01602 
01603                 $oldContent = $rev->getContent();
01604             }
01605 
01606             if ( !$oldContent ) {
01607                 wfDebug( __METHOD__ . ": no page text\n" );
01608                 wfProfileOut( __METHOD__ );
01609                 return null;
01610             }
01611 
01612             $newContent = $oldContent->replaceSection( $sectionId, $sectionContent, $sectionTitle );
01613         }
01614 
01615         wfProfileOut( __METHOD__ );
01616         return $newContent;
01617     }
01618 
01624     public function checkFlags( $flags ) {
01625         if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
01626             if ( $this->exists() ) {
01627                 $flags |= EDIT_UPDATE;
01628             } else {
01629                 $flags |= EDIT_NEW;
01630             }
01631         }
01632 
01633         return $flags;
01634     }
01635 
01688     public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
01689         ContentHandler::deprecated( __METHOD__, '1.21' );
01690 
01691         $content = ContentHandler::makeContent( $text, $this->getTitle() );
01692 
01693         return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
01694     }
01695 
01747     public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
01748         User $user = null, $serialisation_format = null
01749     ) {
01750         global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
01751 
01752         // Low-level sanity check
01753         if ( $this->mTitle->getText() === '' ) {
01754             throw new MWException( 'Something is trying to edit an article with an empty title' );
01755         }
01756 
01757         wfProfileIn( __METHOD__ );
01758 
01759         if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
01760             wfProfileOut( __METHOD__ );
01761             return Status::newFatal( 'content-not-allowed-here',
01762                 ContentHandler::getLocalizedName( $content->getModel() ),
01763                 $this->getTitle()->getPrefixedText() );
01764         }
01765 
01766         $user = is_null( $user ) ? $wgUser : $user;
01767         $status = Status::newGood( array() );
01768 
01769         // Load the data from the master database if needed.
01770         // The caller may already loaded it from the master or even loaded it using
01771         // SELECT FOR UPDATE, so do not override that using clear().
01772         $this->loadPageData( 'fromdbmaster' );
01773 
01774         $flags = $this->checkFlags( $flags );
01775 
01776         // handle hook
01777         $hook_args = array( &$this, &$user, &$content, &$summary,
01778                             $flags & EDIT_MINOR, null, null, &$flags, &$status );
01779 
01780         if ( !wfRunHooks( 'PageContentSave', $hook_args )
01781             || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
01782 
01783             wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
01784 
01785             if ( $status->isOK() ) {
01786                 $status->fatal( 'edit-hook-aborted' );
01787             }
01788 
01789             wfProfileOut( __METHOD__ );
01790             return $status;
01791         }
01792 
01793         // Silently ignore EDIT_MINOR if not allowed
01794         $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
01795         $bot = $flags & EDIT_FORCE_BOT;
01796 
01797         $old_content = $this->getContent( Revision::RAW ); // current revision's content
01798 
01799         $oldsize = $old_content ? $old_content->getSize() : 0;
01800         $oldid = $this->getLatest();
01801         $oldIsRedirect = $this->isRedirect();
01802         $oldcountable = $this->isCountable();
01803 
01804         $handler = $content->getContentHandler();
01805 
01806         // Provide autosummaries if one is not provided and autosummaries are enabled.
01807         if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
01808             if ( !$old_content ) {
01809                 $old_content = null;
01810             }
01811             $summary = $handler->getAutosummary( $old_content, $content, $flags );
01812         }
01813 
01814         $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
01815         $serialized = $editInfo->pst;
01816 
01820         $content = $editInfo->pstContent;
01821         $newsize = $content->getSize();
01822 
01823         $dbw = wfGetDB( DB_MASTER );
01824         $now = wfTimestampNow();
01825         $this->mTimestamp = $now;
01826 
01827         if ( $flags & EDIT_UPDATE ) {
01828             // Update article, but only if changed.
01829             $status->value['new'] = false;
01830 
01831             if ( !$oldid ) {
01832                 // Article gone missing
01833                 wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
01834                 $status->fatal( 'edit-gone-missing' );
01835 
01836                 wfProfileOut( __METHOD__ );
01837                 return $status;
01838             } elseif ( !$old_content ) {
01839                 // Sanity check for bug 37225
01840                 wfProfileOut( __METHOD__ );
01841                 throw new MWException( "Could not find text for current revision {$oldid}." );
01842             }
01843 
01844             $revision = new Revision( array(
01845                 'page'       => $this->getId(),
01846                 'title'      => $this->getTitle(), // for determining the default content model
01847                 'comment'    => $summary,
01848                 'minor_edit' => $isminor,
01849                 'text'       => $serialized,
01850                 'len'        => $newsize,
01851                 'parent_id'  => $oldid,
01852                 'user'       => $user->getId(),
01853                 'user_text'  => $user->getName(),
01854                 'timestamp'  => $now,
01855                 'content_model' => $content->getModel(),
01856                 'content_format' => $serialisation_format,
01857             ) ); // XXX: pass content object?!
01858 
01859             $changed = !$content->equals( $old_content );
01860 
01861             if ( $changed ) {
01862                 if ( !$content->isValid() ) {
01863                     wfProfileOut( __METHOD__ );
01864                     throw new MWException( "New content failed validity check!" );
01865                 }
01866 
01867                 $dbw->begin( __METHOD__ );
01868                 try {
01869 
01870                     $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
01871                     $status->merge( $prepStatus );
01872 
01873                     if ( !$status->isOK() ) {
01874                         $dbw->rollback( __METHOD__ );
01875 
01876                         wfProfileOut( __METHOD__ );
01877                         return $status;
01878                     }
01879                     $revisionId = $revision->insertOn( $dbw );
01880 
01881                     // Update page
01882                     //
01883                     // We check for conflicts by comparing $oldid with the current latest revision ID.
01884                     $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
01885 
01886                     if ( !$ok ) {
01887                         // Belated edit conflict! Run away!!
01888                         $status->fatal( 'edit-conflict' );
01889 
01890                         $dbw->rollback( __METHOD__ );
01891 
01892                         wfProfileOut( __METHOD__ );
01893                         return $status;
01894                     }
01895 
01896                     wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
01897                     // Update recentchanges
01898                     if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
01899                         // Mark as patrolled if the user can do so
01900                         $patrolled = $wgUseRCPatrol && !count(
01901                         $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
01902                         // Add RC row to the DB
01903                         $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
01904                             $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
01905                             $revisionId, $patrolled
01906                         );
01907 
01908                         // Log auto-patrolled edits
01909                         if ( $patrolled ) {
01910                             PatrolLog::record( $rc, true, $user );
01911                         }
01912                     }
01913                     $user->incEditCount();
01914                 } catch ( MWException $e ) {
01915                     $dbw->rollback( __METHOD__ );
01916                     // Question: Would it perhaps be better if this method turned all
01917                     // exceptions into $status's?
01918                     throw $e;
01919                 }
01920                 $dbw->commit( __METHOD__ );
01921             } else {
01922                 // Bug 32948: revision ID must be set to page {{REVISIONID}} and
01923                 // related variables correctly
01924                 $revision->setId( $this->getLatest() );
01925             }
01926 
01927             // Update links tables, site stats, etc.
01928             $this->doEditUpdates(
01929                 $revision,
01930                 $user,
01931                 array(
01932                     'changed' => $changed,
01933                     'oldcountable' => $oldcountable
01934                 )
01935             );
01936 
01937             if ( !$changed ) {
01938                 $status->warning( 'edit-no-change' );
01939                 $revision = null;
01940                 // Update page_touched, this is usually implicit in the page update
01941                 // Other cache updates are done in onArticleEdit()
01942                 $this->mTitle->invalidateCache();
01943             }
01944         } else {
01945             // Create new article
01946             $status->value['new'] = true;
01947 
01948             $dbw->begin( __METHOD__ );
01949             try {
01950 
01951                 $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
01952                 $status->merge( $prepStatus );
01953 
01954                 if ( !$status->isOK() ) {
01955                     $dbw->rollback( __METHOD__ );
01956 
01957                     wfProfileOut( __METHOD__ );
01958                     return $status;
01959                 }
01960 
01961                 $status->merge( $prepStatus );
01962 
01963                 // Add the page record; stake our claim on this title!
01964                 // This will return false if the article already exists
01965                 $newid = $this->insertOn( $dbw );
01966 
01967                 if ( $newid === false ) {
01968                     $dbw->rollback( __METHOD__ );
01969                     $status->fatal( 'edit-already-exists' );
01970 
01971                     wfProfileOut( __METHOD__ );
01972                     return $status;
01973                 }
01974 
01975                 // Save the revision text...
01976                 $revision = new Revision( array(
01977                     'page'       => $newid,
01978                     'title'      => $this->getTitle(), // for determining the default content model
01979                     'comment'    => $summary,
01980                     'minor_edit' => $isminor,
01981                     'text'       => $serialized,
01982                     'len'        => $newsize,
01983                     'user'       => $user->getId(),
01984                     'user_text'  => $user->getName(),
01985                     'timestamp'  => $now,
01986                     'content_model' => $content->getModel(),
01987                     'content_format' => $serialisation_format,
01988                 ) );
01989                 $revisionId = $revision->insertOn( $dbw );
01990 
01991                 // Bug 37225: use accessor to get the text as Revision may trim it
01992                 $content = $revision->getContent(); // sanity; get normalized version
01993 
01994                 if ( $content ) {
01995                     $newsize = $content->getSize();
01996                 }
01997 
01998                 // Update the page record with revision data
01999                 $this->updateRevisionOn( $dbw, $revision, 0 );
02000 
02001                 wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
02002 
02003                 // Update recentchanges
02004                 if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
02005                     // Mark as patrolled if the user can do so
02006                     $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
02007                         $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
02008                     // Add RC row to the DB
02009                     $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
02010                         '', $newsize, $revisionId, $patrolled );
02011 
02012                     // Log auto-patrolled edits
02013                     if ( $patrolled ) {
02014                         PatrolLog::record( $rc, true, $user );
02015                     }
02016                 }
02017                 $user->incEditCount();
02018 
02019             } catch ( MWException $e ) {
02020                 $dbw->rollback( __METHOD__ );
02021                 throw $e;
02022             }
02023             $dbw->commit( __METHOD__ );
02024 
02025             // Update links, etc.
02026             $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
02027 
02028             $hook_args = array( &$this, &$user, $content, $summary,
02029                                 $flags & EDIT_MINOR, null, null, &$flags, $revision );
02030 
02031             ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
02032             wfRunHooks( 'PageContentInsertComplete', $hook_args );
02033         }
02034 
02035         // Do updates right now unless deferral was requested
02036         if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
02037             DeferredUpdates::doUpdates();
02038         }
02039 
02040         // Return the new revision (or null) to the caller
02041         $status->value['revision'] = $revision;
02042 
02043         $hook_args = array( &$this, &$user, $content, $summary,
02044                             $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
02045 
02046         ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
02047         wfRunHooks( 'PageContentSaveComplete', $hook_args );
02048 
02049         // Promote user to any groups they meet the criteria for
02050         $dbw->onTransactionIdle( function () use ( $user ) {
02051             $user->addAutopromoteOnceGroups( 'onEdit' );
02052         } );
02053 
02054         wfProfileOut( __METHOD__ );
02055         return $status;
02056     }
02057 
02072     public function makeParserOptions( $context ) {
02073         $options = $this->getContentHandler()->makeParserOptions( $context );
02074 
02075         if ( $this->getTitle()->isConversionTable() ) {
02076             // @todo ConversionTable should become a separate content model, so
02077             // we don't need special cases like this one.
02078             $options->disableContentConversion();
02079         }
02080 
02081         return $options;
02082     }
02083 
02091     public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
02092         ContentHandler::deprecated( __METHOD__, '1.21' );
02093         $content = ContentHandler::makeContent( $text, $this->getTitle() );
02094         return $this->prepareContentForEdit( $content, $revid, $user );
02095     }
02096 
02110     public function prepareContentForEdit( Content $content, $revid = null, User $user = null,
02111         $serialization_format = null
02112     ) {
02113         global $wgContLang, $wgUser;
02114         $user = is_null( $user ) ? $wgUser : $user;
02115         //XXX: check $user->getId() here???
02116 
02117         // Use a sane default for $serialization_format, see bug 57026
02118         if ( $serialization_format === null ) {
02119             $serialization_format = $content->getContentHandler()->getDefaultFormat();
02120         }
02121 
02122         if ( $this->mPreparedEdit
02123             && $this->mPreparedEdit->newContent
02124             && $this->mPreparedEdit->newContent->equals( $content )
02125             && $this->mPreparedEdit->revid == $revid
02126             && $this->mPreparedEdit->format == $serialization_format
02127             // XXX: also check $user here?
02128         ) {
02129             // Already prepared
02130             return $this->mPreparedEdit;
02131         }
02132 
02133         $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
02134         wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
02135 
02136         $edit = (object)array();
02137         $edit->revid = $revid;
02138         $edit->timestamp = wfTimestampNow();
02139 
02140         $edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
02141 
02142         $edit->format = $serialization_format;
02143         $edit->popts = $this->makeParserOptions( 'canonical' );
02144         $edit->output = $edit->pstContent
02145             ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
02146             : null;
02147 
02148         $edit->newContent = $content;
02149         $edit->oldContent = $this->getContent( Revision::RAW );
02150 
02151         // NOTE: B/C for hooks! don't use these fields!
02152         $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
02153         $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
02154         $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : '';
02155 
02156         $this->mPreparedEdit = $edit;
02157         return $edit;
02158     }
02159 
02176     public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
02177         global $wgEnableParserCache;
02178 
02179         wfProfileIn( __METHOD__ );
02180 
02181         $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
02182         $content = $revision->getContent();
02183 
02184         // Parse the text
02185         // Be careful not to do pre-save transform twice: $text is usually
02186         // already pre-save transformed once.
02187         if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
02188             wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
02189             $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
02190         } else {
02191             wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
02192             $editInfo = $this->mPreparedEdit;
02193         }
02194 
02195         // Save it to the parser cache
02196         if ( $wgEnableParserCache ) {
02197             $parserCache = ParserCache::singleton();
02198             $parserCache->save(
02199                 $editInfo->output, $this, $editInfo->popts, $editInfo->timestamp, $editInfo->revid
02200             );
02201         }
02202 
02203         // Update the links tables and other secondary data
02204         if ( $content ) {
02205             $recursive = $options['changed']; // bug 50785
02206             $updates = $content->getSecondaryDataUpdates(
02207                 $this->getTitle(), null, $recursive, $editInfo->output );
02208             DataUpdate::runUpdates( $updates );
02209         }
02210 
02211         wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
02212 
02213         if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
02214             if ( 0 == mt_rand( 0, 99 ) ) {
02215                 // Flush old entries from the `recentchanges` table; we do this on
02216                 // random requests so as to avoid an increase in writes for no good reason
02217                 RecentChange::purgeExpiredChanges();
02218             }
02219         }
02220 
02221         if ( !$this->exists() ) {
02222             wfProfileOut( __METHOD__ );
02223             return;
02224         }
02225 
02226         $id = $this->getId();
02227         $title = $this->mTitle->getPrefixedDBkey();
02228         $shortTitle = $this->mTitle->getDBkey();
02229 
02230         if ( !$options['changed'] ) {
02231             $good = 0;
02232         } elseif ( $options['created'] ) {
02233             $good = (int)$this->isCountable( $editInfo );
02234         } elseif ( $options['oldcountable'] !== null ) {
02235             $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
02236         } else {
02237             $good = 0;
02238         }
02239         $edits = $options['changed'] ? 1 : 0;
02240         $total = $options['created'] ? 1 : 0;
02241 
02242         DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, $edits, $good, $total ) );
02243         DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );
02244 
02245         // If this is another user's talk page, update newtalk.
02246         // Don't do this if $options['changed'] = false (null-edits) nor if
02247         // it's a minor edit and the user doesn't want notifications for those.
02248         if ( $options['changed']
02249             && $this->mTitle->getNamespace() == NS_USER_TALK
02250             && $shortTitle != $user->getTitleKey()
02251             && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
02252         ) {
02253             $recipient = User::newFromName( $shortTitle, false );
02254             if ( !$recipient ) {
02255                 wfDebug( __METHOD__ . ": invalid username\n" );
02256             } else {
02257                 // Allow extensions to prevent user notification when a new message is added to their talk page
02258                 if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
02259                     if ( User::isIP( $shortTitle ) ) {
02260                         // An anonymous user
02261                         $recipient->setNewtalk( true, $revision );
02262                     } elseif ( $recipient->isLoggedIn() ) {
02263                         $recipient->setNewtalk( true, $revision );
02264                     } else {
02265                         wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
02266                     }
02267                 }
02268             }
02269         }
02270 
02271         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
02272             // XXX: could skip pseudo-messages like js/css here, based on content model.
02273             $msgtext = $content ? $content->getWikitextForTransclusion() : null;
02274             if ( $msgtext === false || $msgtext === null ) {
02275                 $msgtext = '';
02276             }
02277 
02278             MessageCache::singleton()->replace( $shortTitle, $msgtext );
02279         }
02280 
02281         if ( $options['created'] ) {
02282             self::onArticleCreate( $this->mTitle );
02283         } elseif ( $options['changed'] ) { // bug 50785
02284             self::onArticleEdit( $this->mTitle );
02285         }
02286 
02287         wfProfileOut( __METHOD__ );
02288     }
02289 
02302     public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
02303         ContentHandler::deprecated( __METHOD__, "1.21" );
02304 
02305         $content = ContentHandler::makeContent( $text, $this->getTitle() );
02306         $this->doQuickEditContent( $content, $user, $comment, $minor );
02307     }
02308 
02320     public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
02321         $serialisation_format = null
02322     ) {
02323         wfProfileIn( __METHOD__ );
02324 
02325         $serialized = $content->serialize( $serialisation_format );
02326 
02327         $dbw = wfGetDB( DB_MASTER );
02328         $revision = new Revision( array(
02329             'title'      => $this->getTitle(), // for determining the default content model
02330             'page'       => $this->getId(),
02331             'user_text'  => $user->getName(),
02332             'user'       => $user->getId(),
02333             'text'       => $serialized,
02334             'length'     => $content->getSize(),
02335             'comment'    => $comment,
02336             'minor_edit' => $minor ? 1 : 0,
02337         ) ); // XXX: set the content object?
02338         $revision->insertOn( $dbw );
02339         $this->updateRevisionOn( $dbw, $revision );
02340 
02341         wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
02342 
02343         wfProfileOut( __METHOD__ );
02344     }
02345 
02357     public function doUpdateRestrictions( array $limit, array $expiry,
02358         &$cascade, $reason, User $user
02359     ) {
02360         global $wgCascadingRestrictionLevels, $wgContLang;
02361 
02362         if ( wfReadOnly() ) {
02363             return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
02364         }
02365 
02366         $this->loadPageData( 'fromdbmaster' );
02367         $restrictionTypes = $this->mTitle->getRestrictionTypes();
02368         $id = $this->getId();
02369 
02370         if ( !$cascade ) {
02371             $cascade = false;
02372         }
02373 
02374         // Take this opportunity to purge out expired restrictions
02375         Title::purgeExpiredRestrictions();
02376 
02377         // @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
02378         // we expect a single selection, but the schema allows otherwise.
02379         $isProtected = false;
02380         $protect = false;
02381         $changed = false;
02382 
02383         $dbw = wfGetDB( DB_MASTER );
02384 
02385         foreach ( $restrictionTypes as $action ) {
02386             if ( !isset( $expiry[$action] ) ) {
02387                 $expiry[$action] = $dbw->getInfinity();
02388             }
02389             if ( !isset( $limit[$action] ) ) {
02390                 $limit[$action] = '';
02391             } elseif ( $limit[$action] != '' ) {
02392                 $protect = true;
02393             }
02394 
02395             // Get current restrictions on $action
02396             $current = implode( '', $this->mTitle->getRestrictions( $action ) );
02397             if ( $current != '' ) {
02398                 $isProtected = true;
02399             }
02400 
02401             if ( $limit[$action] != $current ) {
02402                 $changed = true;
02403             } elseif ( $limit[$action] != '' ) {
02404                 // Only check expiry change if the action is actually being
02405                 // protected, since expiry does nothing on an not-protected
02406                 // action.
02407                 if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
02408                     $changed = true;
02409                 }
02410             }
02411         }
02412 
02413         if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
02414             $changed = true;
02415         }
02416 
02417         // If nothing has changed, do nothing
02418         if ( !$changed ) {
02419             return Status::newGood();
02420         }
02421 
02422         if ( !$protect ) { // No protection at all means unprotection
02423             $revCommentMsg = 'unprotectedarticle';
02424             $logAction = 'unprotect';
02425         } elseif ( $isProtected ) {
02426             $revCommentMsg = 'modifiedarticleprotection';
02427             $logAction = 'modify';
02428         } else {
02429             $revCommentMsg = 'protectedarticle';
02430             $logAction = 'protect';
02431         }
02432 
02433         // Truncate for whole multibyte characters
02434         $reason = $wgContLang->truncate( $reason, 255 );
02435 
02436         $logRelationsValues = array();
02437         $logRelationsField = null;
02438 
02439         if ( $id ) { // Protection of existing page
02440             if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
02441                 return Status::newGood();
02442             }
02443 
02444             // Only certain restrictions can cascade...
02445             $editrestriction = isset( $limit['edit'] )
02446                 ? array( $limit['edit'] )
02447                 : $this->mTitle->getRestrictions( 'edit' );
02448             foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
02449                 $editrestriction[$key] = 'editprotected'; // backwards compatibility
02450             }
02451             foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
02452                 $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
02453             }
02454 
02455             $cascadingRestrictionLevels = $wgCascadingRestrictionLevels;
02456             foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
02457                 $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
02458             }
02459             foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
02460                 $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
02461             }
02462 
02463             // The schema allows multiple restrictions
02464             if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
02465                 $cascade = false;
02466             }
02467 
02468             // insert null revision to identify the page protection change as edit summary
02469             $latest = $this->getLatest();
02470             $nullRevision = $this->insertProtectNullRevision(
02471                 $revCommentMsg,
02472                 $limit,
02473                 $expiry,
02474                 $cascade,
02475                 $reason,
02476                 $user
02477             );
02478 
02479             if ( $nullRevision === null ) {
02480                 return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
02481             }
02482 
02483             $logRelationsField = 'pr_id';
02484 
02485             // Update restrictions table
02486             foreach ( $limit as $action => $restrictions ) {
02487                 $dbw->delete(
02488                     'page_restrictions',
02489                     array(
02490                         'pr_page' => $id,
02491                         'pr_type' => $action
02492                     ),
02493                     __METHOD__
02494                 );
02495                 if ( $restrictions != '' ) {
02496                     $dbw->insert(
02497                         'page_restrictions',
02498                         array(
02499                             'pr_id' => $dbw->nextSequenceValue( 'page_restrictions_pr_id_seq' ),
02500                             'pr_page' => $id,
02501                             'pr_type' => $action,
02502                             'pr_level' => $restrictions,
02503                             'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
02504                             'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
02505                         ),
02506                         __METHOD__
02507                     );
02508                     $logRelationsValues[] = $dbw->insertId();
02509                 }
02510             }
02511 
02512             // Clear out legacy restriction fields
02513             $dbw->update(
02514                 'page',
02515                 array( 'page_restrictions' => '' ),
02516                 array( 'page_id' => $id ),
02517                 __METHOD__
02518             );
02519 
02520             wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
02521             wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
02522         } else { // Protection of non-existing page (also known as "title protection")
02523             // Cascade protection is meaningless in this case
02524             $cascade = false;
02525 
02526             if ( $limit['create'] != '' ) {
02527                 $dbw->replace( 'protected_titles',
02528                     array( array( 'pt_namespace', 'pt_title' ) ),
02529                     array(
02530                         'pt_namespace' => $this->mTitle->getNamespace(),
02531                         'pt_title' => $this->mTitle->getDBkey(),
02532                         'pt_create_perm' => $limit['create'],
02533                         'pt_timestamp' => $dbw->timestamp(),
02534                         'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
02535                         'pt_user' => $user->getId(),
02536                         'pt_reason' => $reason,
02537                     ), __METHOD__
02538                 );
02539             } else {
02540                 $dbw->delete( 'protected_titles',
02541                     array(
02542                         'pt_namespace' => $this->mTitle->getNamespace(),
02543                         'pt_title' => $this->mTitle->getDBkey()
02544                     ), __METHOD__
02545                 );
02546             }
02547         }
02548 
02549         $this->mTitle->flushRestrictions();
02550         InfoAction::invalidateCache( $this->mTitle );
02551 
02552         if ( $logAction == 'unprotect' ) {
02553             $params = array();
02554         } else {
02555             $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
02556             $params = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
02557         }
02558 
02559         // Update the protection log
02560         $log = new LogPage( 'protect' );
02561         $logId = $log->addEntry( $logAction, $this->mTitle, $reason, $params, $user );
02562         if ( $logRelationsField !== null && count( $logRelationsValues ) ) {
02563             $log->addRelations( $logRelationsField, $logRelationsValues, $logId );
02564         }
02565 
02566         return Status::newGood();
02567     }
02568 
02580     public function insertProtectNullRevision( $revCommentMsg, array $limit,
02581         array $expiry, $cascade, $reason, $user = null
02582     ) {
02583         global $wgContLang;
02584         $dbw = wfGetDB( DB_MASTER );
02585 
02586         // Prepare a null revision to be added to the history
02587         $editComment = $wgContLang->ucfirst(
02588             wfMessage(
02589                 $revCommentMsg,
02590                 $this->mTitle->getPrefixedText()
02591             )->inContentLanguage()->text()
02592         );
02593         if ( $reason ) {
02594             $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
02595         }
02596         $protectDescription = $this->protectDescription( $limit, $expiry );
02597         if ( $protectDescription ) {
02598             $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
02599             $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )
02600                 ->inContentLanguage()->text();
02601         }
02602         if ( $cascade ) {
02603             $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
02604             $editComment .= wfMessage( 'brackets' )->params(
02605                 wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
02606             )->inContentLanguage()->text();
02607         }
02608 
02609         $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true, $user );
02610         if ( $nullRev ) {
02611             $nullRev->insertOn( $dbw );
02612 
02613             // Update page record and touch page
02614             $oldLatest = $nullRev->getParentId();
02615             $this->updateRevisionOn( $dbw, $nullRev, $oldLatest );
02616         }
02617 
02618         return $nullRev;
02619     }
02620 
02625     protected function formatExpiry( $expiry ) {
02626         global $wgContLang;
02627         $dbr = wfGetDB( DB_SLAVE );
02628 
02629         $encodedExpiry = $dbr->encodeExpiry( $expiry );
02630         if ( $encodedExpiry != 'infinity' ) {
02631             return wfMessage(
02632                 'protect-expiring',
02633                 $wgContLang->timeanddate( $expiry, false, false ),
02634                 $wgContLang->date( $expiry, false, false ),
02635                 $wgContLang->time( $expiry, false, false )
02636             )->inContentLanguage()->text();
02637         } else {
02638             return wfMessage( 'protect-expiry-indefinite' )
02639                 ->inContentLanguage()->text();
02640         }
02641     }
02642 
02650     public function protectDescription( array $limit, array $expiry ) {
02651         $protectDescription = '';
02652 
02653         foreach ( array_filter( $limit ) as $action => $restrictions ) {
02654             # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
02655             # All possible message keys are listed here for easier grepping:
02656             # * restriction-create
02657             # * restriction-edit
02658             # * restriction-move
02659             # * restriction-upload
02660             $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
02661             # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
02662             # with '' filtered out. All possible message keys are listed below:
02663             # * protect-level-autoconfirmed
02664             # * protect-level-sysop
02665             $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
02666 
02667             $expiryText = $this->formatExpiry( $expiry[$action] );
02668 
02669             if ( $protectDescription !== '' ) {
02670                 $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
02671             }
02672             $protectDescription .= wfMessage( 'protect-summary-desc' )
02673                 ->params( $actionText, $restrictionsText, $expiryText )
02674                 ->inContentLanguage()->text();
02675         }
02676 
02677         return $protectDescription;
02678     }
02679 
02691     public function protectDescriptionLog( array $limit, array $expiry ) {
02692         global $wgContLang;
02693 
02694         $protectDescriptionLog = '';
02695 
02696         foreach ( array_filter( $limit ) as $action => $restrictions ) {
02697             $expiryText = $this->formatExpiry( $expiry[$action] );
02698             $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
02699         }
02700 
02701         return trim( $protectDescriptionLog );
02702     }
02703 
02713     protected static function flattenRestrictions( $limit ) {
02714         if ( !is_array( $limit ) ) {
02715             throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
02716         }
02717 
02718         $bits = array();
02719         ksort( $limit );
02720 
02721         foreach ( array_filter( $limit ) as $action => $restrictions ) {
02722             $bits[] = "$action=$restrictions";
02723         }
02724 
02725         return implode( ':', $bits );
02726     }
02727 
02744     public function doDeleteArticle(
02745         $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
02746     ) {
02747         $status = $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user );
02748         return $status->isGood();
02749     }
02750 
02768     public function doDeleteArticleReal(
02769         $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
02770     ) {
02771         global $wgUser, $wgContentHandlerUseDB;
02772 
02773         wfDebug( __METHOD__ . "\n" );
02774 
02775         $status = Status::newGood();
02776 
02777         if ( $this->mTitle->getDBkey() === '' ) {
02778             $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
02779             return $status;
02780         }
02781 
02782         $user = is_null( $user ) ? $wgUser : $user;
02783         if ( !wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
02784             if ( $status->isOK() ) {
02785                 // Hook aborted but didn't set a fatal status
02786                 $status->fatal( 'delete-hook-aborted' );
02787             }
02788             return $status;
02789         }
02790 
02791         $dbw = wfGetDB( DB_MASTER );
02792         $dbw->begin( __METHOD__ );
02793 
02794         if ( $id == 0 ) {
02795             $this->loadPageData( 'forupdate' );
02796             $id = $this->getID();
02797             if ( $id == 0 ) {
02798                 $dbw->rollback( __METHOD__ );
02799                 $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
02800                 return $status;
02801             }
02802         }
02803 
02804         // we need to remember the old content so we can use it to generate all deletion updates.
02805         $content = $this->getContent( Revision::RAW );
02806 
02807         // Bitfields to further suppress the content
02808         if ( $suppress ) {
02809             $bitfield = 0;
02810             // This should be 15...
02811             $bitfield |= Revision::DELETED_TEXT;
02812             $bitfield |= Revision::DELETED_COMMENT;
02813             $bitfield |= Revision::DELETED_USER;
02814             $bitfield |= Revision::DELETED_RESTRICTED;
02815         } else {
02816             $bitfield = 'rev_deleted';
02817         }
02818 
02819         // For now, shunt the revision data into the archive table.
02820         // Text is *not* removed from the text table; bulk storage
02821         // is left intact to avoid breaking block-compression or
02822         // immutable storage schemes.
02823         //
02824         // For backwards compatibility, note that some older archive
02825         // table entries will have ar_text and ar_flags fields still.
02826         //
02827         // In the future, we may keep revisions and mark them with
02828         // the rev_deleted field, which is reserved for this purpose.
02829 
02830         $row = array(
02831             'ar_namespace'  => 'page_namespace',
02832             'ar_title'      => 'page_title',
02833             'ar_comment'    => 'rev_comment',
02834             'ar_user'       => 'rev_user',
02835             'ar_user_text'  => 'rev_user_text',
02836             'ar_timestamp'  => 'rev_timestamp',
02837             'ar_minor_edit' => 'rev_minor_edit',
02838             'ar_rev_id'     => 'rev_id',
02839             'ar_parent_id'  => 'rev_parent_id',
02840             'ar_text_id'    => 'rev_text_id',
02841             'ar_text'       => '\'\'', // Be explicit to appease
02842             'ar_flags'      => '\'\'', // MySQL's "strict mode"...
02843             'ar_len'        => 'rev_len',
02844             'ar_page_id'    => 'page_id',
02845             'ar_deleted'    => $bitfield,
02846             'ar_sha1'       => 'rev_sha1',
02847         );
02848 
02849         if ( $wgContentHandlerUseDB ) {
02850             $row['ar_content_model'] = 'rev_content_model';
02851             $row['ar_content_format'] = 'rev_content_format';
02852         }
02853 
02854         $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
02855             $row,
02856             array(
02857                 'page_id' => $id,
02858                 'page_id = rev_page'
02859             ), __METHOD__
02860         );
02861 
02862         // Now that it's safely backed up, delete it
02863         $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
02864         $ok = ( $dbw->affectedRows() > 0 ); // $id could be laggy
02865 
02866         if ( !$ok ) {
02867             $dbw->rollback( __METHOD__ );
02868             $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
02869             return $status;
02870         }
02871 
02872         if ( !$dbw->cascadingDeletes() ) {
02873             $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
02874         }
02875 
02876         // Clone the title, so we have the information we need when we log
02877         $logTitle = clone $this->mTitle;
02878 
02879         // Log the deletion, if the page was suppressed, log it at Oversight instead
02880         $logtype = $suppress ? 'suppress' : 'delete';
02881 
02882         $logEntry = new ManualLogEntry( $logtype, 'delete' );
02883         $logEntry->setPerformer( $user );
02884         $logEntry->setTarget( $logTitle );
02885         $logEntry->setComment( $reason );
02886         $logid = $logEntry->insert();
02887 
02888         $dbw->onTransactionPreCommitOrIdle( function () use ( $dbw, $logEntry, $logid ) {
02889             // Bug 56776: avoid deadlocks (especially from FileDeleteForm)
02890             $logEntry->publish( $logid );
02891         } );
02892 
02893         if ( $commit ) {
02894             $dbw->commit( __METHOD__ );
02895         }
02896 
02897         $this->doDeleteUpdates( $id, $content );
02898 
02899         wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
02900         $status->value = $logid;
02901         return $status;
02902     }
02903 
02912     public function doDeleteUpdates( $id, Content $content = null ) {
02913         // update site status
02914         DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
02915 
02916         // remove secondary indexes, etc
02917         $updates = $this->getDeletionUpdates( $content );
02918         DataUpdate::runUpdates( $updates );
02919 
02920         // Reparse any pages transcluding this page
02921         LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'templatelinks' );
02922 
02923         // Reparse any pages including this image
02924         if ( $this->mTitle->getNamespace() == NS_FILE ) {
02925             LinksUpdate::queueRecursiveJobsForTable( $this->mTitle, 'imagelinks' );
02926         }
02927 
02928         // Clear caches
02929         WikiPage::onArticleDelete( $this->mTitle );
02930 
02931         // Reset this object and the Title object
02932         $this->loadFromRow( false, self::READ_LATEST );
02933 
02934         // Search engine
02935         DeferredUpdates::addUpdate( new SearchUpdate( $id, $this->mTitle ) );
02936     }
02937 
02962     public function doRollback(
02963         $fromP, $summary, $token, $bot, &$resultDetails, User $user
02964     ) {
02965         $resultDetails = null;
02966 
02967         // Check permissions
02968         $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user );
02969         $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $user );
02970         $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
02971 
02972         if ( !$user->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) ) {
02973             $errors[] = array( 'sessionfailure' );
02974         }
02975 
02976         if ( $user->pingLimiter( 'rollback' ) || $user->pingLimiter() ) {
02977             $errors[] = array( 'actionthrottledtext' );
02978         }
02979 
02980         // If there were errors, bail out now
02981         if ( !empty( $errors ) ) {
02982             return $errors;
02983         }
02984 
02985         return $this->commitRollback( $fromP, $summary, $bot, $resultDetails, $user );
02986     }
02987 
03004     public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
03005         global $wgUseRCPatrol, $wgContLang;
03006 
03007         $dbw = wfGetDB( DB_MASTER );
03008 
03009         if ( wfReadOnly() ) {
03010             return array( array( 'readonlytext' ) );
03011         }
03012 
03013         // Get the last editor
03014         $current = $this->getRevision();
03015         if ( is_null( $current ) ) {
03016             // Something wrong... no page?
03017             return array( array( 'notanarticle' ) );
03018         }
03019 
03020         $from = str_replace( '_', ' ', $fromP );
03021         // User name given should match up with the top revision.
03022         // If the user was deleted then $from should be empty.
03023         if ( $from != $current->getUserText() ) {
03024             $resultDetails = array( 'current' => $current );
03025             return array( array( 'alreadyrolled',
03026                 htmlspecialchars( $this->mTitle->getPrefixedText() ),
03027                 htmlspecialchars( $fromP ),
03028                 htmlspecialchars( $current->getUserText() )
03029             ) );
03030         }
03031 
03032         // Get the last edit not by this guy...
03033         // Note: these may not be public values
03034         $user = intval( $current->getRawUser() );
03035         $user_text = $dbw->addQuotes( $current->getRawUserText() );
03036         $s = $dbw->selectRow( 'revision',
03037             array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
03038             array( 'rev_page' => $current->getPage(),
03039                 "rev_user != {$user} OR rev_user_text != {$user_text}"
03040             ), __METHOD__,
03041             array( 'USE INDEX' => 'page_timestamp',
03042                 'ORDER BY' => 'rev_timestamp DESC' )
03043             );
03044         if ( $s === false ) {
03045             // No one else ever edited this page
03046             return array( array( 'cantrollback' ) );
03047         } elseif ( $s->rev_deleted & Revision::DELETED_TEXT
03048             || $s->rev_deleted & Revision::DELETED_USER
03049         ) {
03050             // Only admins can see this text
03051             return array( array( 'notvisiblerev' ) );
03052         }
03053 
03054         // Set patrolling and bot flag on the edits, which gets rollbacked.
03055         // This is done before the rollback edit to have patrolling also on failure (bug 62157).
03056         $set = array();
03057         if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
03058             // Mark all reverted edits as bot
03059             $set['rc_bot'] = 1;
03060         }
03061 
03062         if ( $wgUseRCPatrol ) {
03063             // Mark all reverted edits as patrolled
03064             $set['rc_patrolled'] = 1;
03065         }
03066 
03067         if ( count( $set ) ) {
03068             $dbw->update( 'recentchanges', $set,
03069                 array( /* WHERE */
03070                     'rc_cur_id' => $current->getPage(),
03071                     'rc_user_text' => $current->getUserText(),
03072                     'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ),
03073                 ), __METHOD__
03074             );
03075         }
03076 
03077         // Generate the edit summary if necessary
03078         $target = Revision::newFromId( $s->rev_id );
03079         if ( empty( $summary ) ) {
03080             if ( $from == '' ) { // no public user name
03081                 $summary = wfMessage( 'revertpage-nouser' );
03082             } else {
03083                 $summary = wfMessage( 'revertpage' );
03084             }
03085         }
03086 
03087         // Allow the custom summary to use the same args as the default message
03088         $args = array(
03089             $target->getUserText(), $from, $s->rev_id,
03090             $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
03091             $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
03092         );
03093         if ( $summary instanceof Message ) {
03094             $summary = $summary->params( $args )->inContentLanguage()->text();
03095         } else {
03096             $summary = wfMsgReplaceArgs( $summary, $args );
03097         }
03098 
03099         // Trim spaces on user supplied text
03100         $summary = trim( $summary );
03101 
03102         // Truncate for whole multibyte characters.
03103         $summary = $wgContLang->truncate( $summary, 255 );
03104 
03105         // Save
03106         $flags = EDIT_UPDATE;
03107 
03108         if ( $guser->isAllowed( 'minoredit' ) ) {
03109             $flags |= EDIT_MINOR;
03110         }
03111 
03112         if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
03113             $flags |= EDIT_FORCE_BOT;
03114         }
03115 
03116         // Actually store the edit
03117         $status = $this->doEditContent(
03118             $target->getContent(),
03119             $summary,
03120             $flags,
03121             $target->getId(),
03122             $guser
03123         );
03124 
03125         if ( !$status->isOK() ) {
03126             return $status->getErrorsArray();
03127         }
03128 
03129         // raise error, when the edit is an edit without a new version
03130         if ( empty( $status->value['revision'] ) ) {
03131             $resultDetails = array( 'current' => $current );
03132             return array( array( 'alreadyrolled',
03133                     htmlspecialchars( $this->mTitle->getPrefixedText() ),
03134                     htmlspecialchars( $fromP ),
03135                     htmlspecialchars( $current->getUserText() )
03136             ) );
03137         }
03138 
03139         $revId = $status->value['revision']->getId();
03140 
03141         wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
03142 
03143         $resultDetails = array(
03144             'summary' => $summary,
03145             'current' => $current,
03146             'target' => $target,
03147             'newid' => $revId
03148         );
03149 
03150         return array();
03151     }
03152 
03164     public static function onArticleCreate( $title ) {
03165         // Update existence markers on article/talk tabs...
03166         if ( $title->isTalkPage() ) {
03167             $other = $title->getSubjectPage();
03168         } else {
03169             $other = $title->getTalkPage();
03170         }
03171 
03172         $other->invalidateCache();
03173         $other->purgeSquid();
03174 
03175         $title->touchLinks();
03176         $title->purgeSquid();
03177         $title->deleteTitleProtection();
03178     }
03179 
03185     public static function onArticleDelete( $title ) {
03186         // Update existence markers on article/talk tabs...
03187         if ( $title->isTalkPage() ) {
03188             $other = $title->getSubjectPage();
03189         } else {
03190             $other = $title->getTalkPage();
03191         }
03192 
03193         $other->invalidateCache();
03194         $other->purgeSquid();
03195 
03196         $title->touchLinks();
03197         $title->purgeSquid();
03198 
03199         // File cache
03200         HTMLFileCache::clearFileCache( $title );
03201         InfoAction::invalidateCache( $title );
03202 
03203         // Messages
03204         if ( $title->getNamespace() == NS_MEDIAWIKI ) {
03205             MessageCache::singleton()->replace( $title->getDBkey(), false );
03206         }
03207 
03208         // Images
03209         if ( $title->getNamespace() == NS_FILE ) {
03210             $update = new HTMLCacheUpdate( $title, 'imagelinks' );
03211             $update->doUpdate();
03212         }
03213 
03214         // User talk pages
03215         if ( $title->getNamespace() == NS_USER_TALK ) {
03216             $user = User::newFromName( $title->getText(), false );
03217             if ( $user ) {
03218                 $user->setNewtalk( false );
03219             }
03220         }
03221 
03222         // Image redirects
03223         RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
03224     }
03225 
03233     public static function onArticleEdit( $title ) {
03234         // Invalidate caches of articles which include this page
03235         DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
03236 
03237         // Invalidate the caches of all pages which redirect here
03238         DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' );
03239 
03240         // Purge squid for this page only
03241         $title->purgeSquid();
03242 
03243         // Clear file cache for this page only
03244         HTMLFileCache::clearFileCache( $title );
03245         InfoAction::invalidateCache( $title );
03246     }
03247 
03256     public function getCategories() {
03257         $id = $this->getId();
03258         if ( $id == 0 ) {
03259             return TitleArray::newFromResult( new FakeResultWrapper( array() ) );
03260         }
03261 
03262         $dbr = wfGetDB( DB_SLAVE );
03263         $res = $dbr->select( 'categorylinks',
03264             array( 'cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace' ),
03265             // Have to do that since DatabaseBase::fieldNamesWithAlias treats numeric indexes
03266             // as not being aliases, and NS_CATEGORY is numeric
03267             array( 'cl_from' => $id ),
03268             __METHOD__ );
03269 
03270         return TitleArray::newFromResult( $res );
03271     }
03272 
03279     public function getHiddenCategories() {
03280         $result = array();
03281         $id = $this->getId();
03282 
03283         if ( $id == 0 ) {
03284             return array();
03285         }
03286 
03287         $dbr = wfGetDB( DB_SLAVE );
03288         $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
03289             array( 'cl_to' ),
03290             array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
03291                 'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ),
03292             __METHOD__ );
03293 
03294         if ( $res !== false ) {
03295             foreach ( $res as $row ) {
03296                 $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
03297             }
03298         }
03299 
03300         return $result;
03301     }
03302 
03312     public static function getAutosummary( $oldtext, $newtext, $flags ) {
03313         // NOTE: stub for backwards-compatibility. assumes the given text is
03314         // wikitext. will break horribly if it isn't.
03315 
03316         ContentHandler::deprecated( __METHOD__, '1.21' );
03317 
03318         $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
03319         $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
03320         $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
03321 
03322         return $handler->getAutosummary( $oldContent, $newContent, $flags );
03323     }
03324 
03332     public function getAutoDeleteReason( &$hasHistory ) {
03333         return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
03334     }
03335 
03343     public function updateCategoryCounts( array $added, array $deleted ) {
03344         $that = $this;
03345         $method = __METHOD__;
03346         $dbw = wfGetDB( DB_MASTER );
03347 
03348         // Do this at the end of the commit to reduce lock wait timeouts
03349         $dbw->onTransactionPreCommitOrIdle(
03350             function () use ( $dbw, $that, $method, $added, $deleted ) {
03351                 $ns = $that->getTitle()->getNamespace();
03352 
03353                 $addFields = array( 'cat_pages = cat_pages + 1' );
03354                 $removeFields = array( 'cat_pages = cat_pages - 1' );
03355                 if ( $ns == NS_CATEGORY ) {
03356                     $addFields[] = 'cat_subcats = cat_subcats + 1';
03357                     $removeFields[] = 'cat_subcats = cat_subcats - 1';
03358                 } elseif ( $ns == NS_FILE ) {
03359                     $addFields[] = 'cat_files = cat_files + 1';
03360                     $removeFields[] = 'cat_files = cat_files - 1';
03361                 }
03362 
03363                 if ( count( $added ) ) {
03364                     $insertRows = array();
03365                     foreach ( $added as $cat ) {
03366                         $insertRows[] = array(
03367                             'cat_title'   => $cat,
03368                             'cat_pages'   => 1,
03369                             'cat_subcats' => ( $ns == NS_CATEGORY ) ? 1 : 0,
03370                             'cat_files'   => ( $ns == NS_FILE ) ? 1 : 0,
03371                         );
03372                     }
03373                     $dbw->upsert(
03374                         'category',
03375                         $insertRows,
03376                         array( 'cat_title' ),
03377                         $addFields,
03378                         $method
03379                     );
03380                 }
03381 
03382                 if ( count( $deleted ) ) {
03383                     $dbw->update(
03384                         'category',
03385                         $removeFields,
03386                         array( 'cat_title' => $deleted ),
03387                         $method
03388                     );
03389                 }
03390 
03391                 foreach ( $added as $catName ) {
03392                     $cat = Category::newFromName( $catName );
03393                     wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $that ) );
03394                 }
03395 
03396                 foreach ( $deleted as $catName ) {
03397                     $cat = Category::newFromName( $catName );
03398                     wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $that ) );
03399                 }
03400             }
03401         );
03402     }
03403 
03409     public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
03410         if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
03411             return;
03412         }
03413 
03414         // templatelinks or imagelinks tables may have become out of sync,
03415         // especially if using variable-based transclusions.
03416         // For paranoia, check if things have changed and if
03417         // so apply updates to the database. This will ensure
03418         // that cascaded protections apply as soon as the changes
03419         // are visible.
03420 
03421         // Get templates from templatelinks and images from imagelinks
03422         $id = $this->getId();
03423 
03424         $dbLinks = array();
03425 
03426         $dbr = wfGetDB( DB_SLAVE );
03427         $res = $dbr->select( array( 'templatelinks' ),
03428             array( 'tl_namespace', 'tl_title' ),
03429             array( 'tl_from' => $id ),
03430             __METHOD__
03431         );
03432 
03433         foreach ( $res as $row ) {
03434             $dbLinks["{$row->tl_namespace}:{$row->tl_title}"] = true;
03435         }
03436 
03437         $dbr = wfGetDB( DB_SLAVE );
03438         $res = $dbr->select( array( 'imagelinks' ),
03439             array( 'il_to' ),
03440             array( 'il_from' => $id ),
03441             __METHOD__
03442         );
03443 
03444         foreach ( $res as $row ) {
03445             $dbLinks[NS_FILE . ":{$row->il_to}"] = true;
03446         }
03447 
03448         // Get templates and images from parser output.
03449         $poLinks = array();
03450         foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
03451             foreach ( $templates as $dbk => $id ) {
03452                 $poLinks["$ns:$dbk"] = true;
03453             }
03454         }
03455         foreach ( $parserOutput->getImages() as $dbk => $id ) {
03456             $poLinks[NS_FILE . ":$dbk"] = true;
03457         }
03458 
03459         // Get the diff
03460         $links_diff = array_diff_key( $poLinks, $dbLinks );
03461 
03462         if ( count( $links_diff ) > 0 ) {
03463             // Whee, link updates time.
03464             // Note: we are only interested in links here. We don't need to get
03465             // other DataUpdate items from the parser output.
03466             $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
03467             $u->doUpdate();
03468         }
03469     }
03470 
03478     public function getUsedTemplates() {
03479         return $this->mTitle->getTemplateLinksFrom();
03480     }
03481 
03494     public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
03495         global $wgParser, $wgUser;
03496 
03497         wfDeprecated( __METHOD__, '1.19' );
03498 
03499         $user = is_null( $user ) ? $wgUser : $user;
03500 
03501         if ( $popts === null ) {
03502             $popts = ParserOptions::newFromUser( $user );
03503         }
03504 
03505         return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
03506     }
03507 
03519     public function updateRestrictions(
03520         $limit = array(), $reason = '', &$cascade = 0, $expiry = array(), User $user = null
03521     ) {
03522         global $wgUser;
03523 
03524         $user = is_null( $user ) ? $wgUser : $user;
03525 
03526         return $this->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user )->isOK();
03527     }
03528 
03538     public function getDeletionUpdates( Content $content = null ) {
03539         if ( !$content ) {
03540             // load content object, which may be used to determine the necessary updates
03541             // XXX: the content may not be needed to determine the updates, then this would be overhead.
03542             $content = $this->getContent( Revision::RAW );
03543         }
03544 
03545         if ( !$content ) {
03546             $updates = array();
03547         } else {
03548             $updates = $content->getDeletionUpdates( $this );
03549         }
03550 
03551         wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
03552         return $updates;
03553     }
03554 }