MediaWiki  REL1_22
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 $mCounter = null;
00090 
00095     public function __construct( Title $title ) {
00096         $this->mTitle = $title;
00097     }
00098 
00106     public static function factory( Title $title ) {
00107         $ns = $title->getNamespace();
00108 
00109         if ( $ns == NS_MEDIA ) {
00110             throw new MWException( "NS_MEDIA is a virtual namespace; use NS_FILE." );
00111         } elseif ( $ns < 0 ) {
00112             throw new MWException( "Invalid or virtual namespace $ns given." );
00113         }
00114 
00115         switch ( $ns ) {
00116             case NS_FILE:
00117                 $page = new WikiFilePage( $title );
00118                 break;
00119             case NS_CATEGORY:
00120                 $page = new WikiCategoryPage( $title );
00121                 break;
00122             default:
00123                 $page = new WikiPage( $title );
00124         }
00125 
00126         return $page;
00127     }
00128 
00139     public static function newFromID( $id, $from = 'fromdb' ) {
00140         $from = self::convertSelectType( $from );
00141         $db = wfGetDB( $from === self::READ_LATEST ? DB_MASTER : DB_SLAVE );
00142         $row = $db->selectRow( 'page', self::selectFields(), array( 'page_id' => $id ), __METHOD__ );
00143         if ( !$row ) {
00144             return null;
00145         }
00146         return self::newFromRow( $row, $from );
00147     }
00148 
00161     public static function newFromRow( $row, $from = 'fromdb' ) {
00162         $page = self::factory( Title::newFromRow( $row ) );
00163         $page->loadFromRow( $row, $from );
00164         return $page;
00165     }
00166 
00173     private static function convertSelectType( $type ) {
00174         switch ( $type ) {
00175         case 'fromdb':
00176             return self::READ_NORMAL;
00177         case 'fromdbmaster':
00178             return self::READ_LATEST;
00179         case 'forupdate':
00180             return self::READ_LOCKING;
00181         default:
00182             // It may already be an integer or whatever else
00183             return $type;
00184         }
00185     }
00186 
00197     public function getActionOverrides() {
00198         $content_handler = $this->getContentHandler();
00199         return $content_handler->getActionOverrides();
00200     }
00201 
00211     public function getContentHandler() {
00212         return ContentHandler::getForModelID( $this->getContentModel() );
00213     }
00214 
00219     public function getTitle() {
00220         return $this->mTitle;
00221     }
00222 
00227     public function clear() {
00228         $this->mDataLoaded = false;
00229         $this->mDataLoadedFrom = self::READ_NONE;
00230 
00231         $this->clearCacheFields();
00232     }
00233 
00238     protected function clearCacheFields() {
00239         $this->mId = null;
00240         $this->mCounter = null;
00241         $this->mRedirectTarget = null; // Title object if set
00242         $this->mLastRevision = null; // Latest revision
00243         $this->mTouched = '19700101000000';
00244         $this->mTimestamp = '';
00245         $this->mIsRedirect = false;
00246         $this->mLatest = false;
00247         // Bug 57026: do not clear mPreparedEdit since prepareTextForEdit() already checks
00248         // the requested rev ID and immutable content against the cached one.
00249         // Clearing it can cause extra parses on edit for no reason.
00250     }
00251 
00257     public function clearPreparedEdit() {
00258         $this->mPreparedEdit = false;
00259     }
00260 
00267     public static function selectFields() {
00268         global $wgContentHandlerUseDB;
00269 
00270         $fields = array(
00271             'page_id',
00272             'page_namespace',
00273             'page_title',
00274             'page_restrictions',
00275             'page_counter',
00276             'page_is_redirect',
00277             'page_is_new',
00278             'page_random',
00279             'page_touched',
00280             'page_latest',
00281             'page_len',
00282         );
00283 
00284         if ( $wgContentHandlerUseDB ) {
00285             $fields[] = 'page_content_model';
00286         }
00287 
00288         return $fields;
00289     }
00290 
00298     protected function pageData( $dbr, $conditions, $options = array() ) {
00299         $fields = self::selectFields();
00300 
00301         wfRunHooks( 'ArticlePageDataBefore', array( &$this, &$fields ) );
00302 
00303         $row = $dbr->selectRow( 'page', $fields, $conditions, __METHOD__, $options );
00304 
00305         wfRunHooks( 'ArticlePageDataAfter', array( &$this, &$row ) );
00306 
00307         return $row;
00308     }
00309 
00319     public function pageDataFromTitle( $dbr, $title, $options = array() ) {
00320         return $this->pageData( $dbr, array(
00321             'page_namespace' => $title->getNamespace(),
00322             'page_title' => $title->getDBkey() ), $options );
00323     }
00324 
00333     public function pageDataFromId( $dbr, $id, $options = array() ) {
00334         return $this->pageData( $dbr, array( 'page_id' => $id ), $options );
00335     }
00336 
00349     public function loadPageData( $from = 'fromdb' ) {
00350         $from = self::convertSelectType( $from );
00351         if ( is_int( $from ) && $from <= $this->mDataLoadedFrom ) {
00352             // We already have the data from the correct location, no need to load it twice.
00353             return;
00354         }
00355 
00356         if ( $from === self::READ_LOCKING ) {
00357             $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle, array( 'FOR UPDATE' ) );
00358         } elseif ( $from === self::READ_LATEST ) {
00359             $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
00360         } elseif ( $from === self::READ_NORMAL ) {
00361             $data = $this->pageDataFromTitle( wfGetDB( DB_SLAVE ), $this->mTitle );
00362             // Use a "last rev inserted" timestamp key to diminish the issue of slave lag.
00363             // Note that DB also stores the master position in the session and checks it.
00364             $touched = $this->getCachedLastEditTime();
00365             if ( $touched ) { // key set
00366                 if ( !$data || $touched > wfTimestamp( TS_MW, $data->page_touched ) ) {
00367                     $from = self::READ_LATEST;
00368                     $data = $this->pageDataFromTitle( wfGetDB( DB_MASTER ), $this->mTitle );
00369                 }
00370             }
00371         } else {
00372             // No idea from where the caller got this data, assume slave database.
00373             $data = $from;
00374             $from = self::READ_NORMAL;
00375         }
00376 
00377         $this->loadFromRow( $data, $from );
00378     }
00379 
00392     public function loadFromRow( $data, $from ) {
00393         $lc = LinkCache::singleton();
00394         $lc->clearLink( $this->mTitle );
00395 
00396         if ( $data ) {
00397             $lc->addGoodLinkObjFromRow( $this->mTitle, $data );
00398 
00399             $this->mTitle->loadFromRow( $data );
00400 
00401             // Old-fashioned restrictions
00402             $this->mTitle->loadRestrictions( $data->page_restrictions );
00403 
00404             $this->mId = intval( $data->page_id );
00405             $this->mCounter = intval( $data->page_counter );
00406             $this->mTouched = wfTimestamp( TS_MW, $data->page_touched );
00407             $this->mIsRedirect = intval( $data->page_is_redirect );
00408             $this->mLatest = intval( $data->page_latest );
00409             // Bug 37225: $latest may no longer match the cached latest Revision object.
00410             // Double-check the ID of any cached latest Revision object for consistency.
00411             if ( $this->mLastRevision && $this->mLastRevision->getId() != $this->mLatest ) {
00412                 $this->mLastRevision = null;
00413                 $this->mTimestamp = '';
00414             }
00415         } else {
00416             $lc->addBadLinkObj( $this->mTitle );
00417 
00418             $this->mTitle->loadFromRow( false );
00419 
00420             $this->clearCacheFields();
00421 
00422             $this->mId = 0;
00423         }
00424 
00425         $this->mDataLoaded = true;
00426         $this->mDataLoadedFrom = self::convertSelectType( $from );
00427     }
00428 
00432     public function getId() {
00433         if ( !$this->mDataLoaded ) {
00434             $this->loadPageData();
00435         }
00436         return $this->mId;
00437     }
00438 
00442     public function exists() {
00443         if ( !$this->mDataLoaded ) {
00444             $this->loadPageData();
00445         }
00446         return $this->mId > 0;
00447     }
00448 
00457     public function hasViewableContent() {
00458         return $this->exists() || $this->mTitle->isAlwaysKnown();
00459     }
00460 
00464     public function getCount() {
00465         if ( !$this->mDataLoaded ) {
00466             $this->loadPageData();
00467         }
00468 
00469         return $this->mCounter;
00470     }
00471 
00477     public function isRedirect() {
00478         $content = $this->getContent();
00479         if ( !$content ) {
00480             return false;
00481         }
00482 
00483         return $content->isRedirect();
00484     }
00485 
00496     public function getContentModel() {
00497         if ( $this->exists() ) {
00498             // look at the revision's actual content model
00499             $rev = $this->getRevision();
00500 
00501             if ( $rev !== null ) {
00502                 return $rev->getContentModel();
00503             } else {
00504                 $title = $this->mTitle->getPrefixedDBkey();
00505                 wfWarn( "Page $title exists but has no (visible) revisions!" );
00506             }
00507         }
00508 
00509         // use the default model for this page
00510         return $this->mTitle->getContentModel();
00511     }
00512 
00517     public function checkTouched() {
00518         if ( !$this->mDataLoaded ) {
00519             $this->loadPageData();
00520         }
00521         return !$this->mIsRedirect;
00522     }
00523 
00528     public function getTouched() {
00529         if ( !$this->mDataLoaded ) {
00530             $this->loadPageData();
00531         }
00532         return $this->mTouched;
00533     }
00534 
00539     public function getLatest() {
00540         if ( !$this->mDataLoaded ) {
00541             $this->loadPageData();
00542         }
00543         return (int)$this->mLatest;
00544     }
00545 
00550     public function getOldestRevision() {
00551         wfProfileIn( __METHOD__ );
00552 
00553         // Try using the slave database first, then try the master
00554         $continue = 2;
00555         $db = wfGetDB( DB_SLAVE );
00556         $revSelectFields = Revision::selectFields();
00557 
00558         $row = null;
00559         while ( $continue ) {
00560             $row = $db->selectRow(
00561                 array( 'page', 'revision' ),
00562                 $revSelectFields,
00563                 array(
00564                     'page_namespace' => $this->mTitle->getNamespace(),
00565                     'page_title' => $this->mTitle->getDBkey(),
00566                     'rev_page = page_id'
00567                 ),
00568                 __METHOD__,
00569                 array(
00570                     'ORDER BY' => 'rev_timestamp ASC'
00571                 )
00572             );
00573 
00574             if ( $row ) {
00575                 $continue = 0;
00576             } else {
00577                 $db = wfGetDB( DB_MASTER );
00578                 $continue--;
00579             }
00580         }
00581 
00582         wfProfileOut( __METHOD__ );
00583         return $row ? Revision::newFromRow( $row ) : null;
00584     }
00585 
00590     protected function loadLastEdit() {
00591         if ( $this->mLastRevision !== null ) {
00592             return; // already loaded
00593         }
00594 
00595         $latest = $this->getLatest();
00596         if ( !$latest ) {
00597             return; // page doesn't exist or is missing page_latest info
00598         }
00599 
00600         // Bug 37225: if session S1 loads the page row FOR UPDATE, the result always includes the
00601         // latest changes committed. This is true even within REPEATABLE-READ transactions, where
00602         // S1 normally only sees changes committed before the first S1 SELECT. Thus we need S1 to
00603         // also gets the revision row FOR UPDATE; otherwise, it may not find it since a page row
00604         // UPDATE and revision row INSERT by S2 may have happened after the first S1 SELECT.
00605         // http://dev.mysql.com/doc/refman/5.0/en/set-transaction.html#isolevel_repeatable-read.
00606         $flags = ( $this->mDataLoadedFrom == self::READ_LOCKING ) ? Revision::READ_LOCKING : 0;
00607         $revision = Revision::newFromPageId( $this->getId(), $latest, $flags );
00608         if ( $revision ) { // sanity
00609             $this->setLastEdit( $revision );
00610         }
00611     }
00612 
00616     protected function setLastEdit( Revision $revision ) {
00617         $this->mLastRevision = $revision;
00618         $this->mTimestamp = $revision->getTimestamp();
00619     }
00620 
00625     public function getRevision() {
00626         $this->loadLastEdit();
00627         if ( $this->mLastRevision ) {
00628             return $this->mLastRevision;
00629         }
00630         return null;
00631     }
00632 
00646     public function getContent( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00647         $this->loadLastEdit();
00648         if ( $this->mLastRevision ) {
00649             return $this->mLastRevision->getContent( $audience, $user );
00650         }
00651         return null;
00652     }
00653 
00666     public function getText( $audience = Revision::FOR_PUBLIC, User $user = null ) { // @todo deprecated, replace usage!
00667         ContentHandler::deprecated( __METHOD__, '1.21' );
00668 
00669         $this->loadLastEdit();
00670         if ( $this->mLastRevision ) {
00671             return $this->mLastRevision->getText( $audience, $user );
00672         }
00673         return false;
00674     }
00675 
00682     public function getRawText() {
00683         ContentHandler::deprecated( __METHOD__, '1.21' );
00684 
00685         return $this->getText( Revision::RAW );
00686     }
00687 
00691     public function getTimestamp() {
00692         // Check if the field has been filled by WikiPage::setTimestamp()
00693         if ( !$this->mTimestamp ) {
00694             $this->loadLastEdit();
00695         }
00696 
00697         return wfTimestamp( TS_MW, $this->mTimestamp );
00698     }
00699 
00705     public function setTimestamp( $ts ) {
00706         $this->mTimestamp = wfTimestamp( TS_MW, $ts );
00707     }
00708 
00718     public function getUser( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00719         $this->loadLastEdit();
00720         if ( $this->mLastRevision ) {
00721             return $this->mLastRevision->getUser( $audience, $user );
00722         } else {
00723             return -1;
00724         }
00725     }
00726 
00737     public function getCreator( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00738         $revision = $this->getOldestRevision();
00739         if ( $revision ) {
00740             $userName = $revision->getUserText( $audience, $user );
00741             return User::newFromName( $userName, false );
00742         } else {
00743             return null;
00744         }
00745     }
00746 
00756     public function getUserText( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00757         $this->loadLastEdit();
00758         if ( $this->mLastRevision ) {
00759             return $this->mLastRevision->getUserText( $audience, $user );
00760         } else {
00761             return '';
00762         }
00763     }
00764 
00774     public function getComment( $audience = Revision::FOR_PUBLIC, User $user = null ) {
00775         $this->loadLastEdit();
00776         if ( $this->mLastRevision ) {
00777             return $this->mLastRevision->getComment( $audience, $user );
00778         } else {
00779             return '';
00780         }
00781     }
00782 
00788     public function getMinorEdit() {
00789         $this->loadLastEdit();
00790         if ( $this->mLastRevision ) {
00791             return $this->mLastRevision->isMinor();
00792         } else {
00793             return false;
00794         }
00795     }
00796 
00802     protected function getCachedLastEditTime() {
00803         global $wgMemc;
00804         $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
00805         return $wgMemc->get( $key );
00806     }
00807 
00814     public function setCachedLastEditTime( $timestamp ) {
00815         global $wgMemc;
00816         $key = wfMemcKey( 'page-lastedit', md5( $this->mTitle->getPrefixedDBkey() ) );
00817         $wgMemc->set( $key, wfTimestamp( TS_MW, $timestamp ), 60 * 15 );
00818     }
00819 
00828     public function isCountable( $editInfo = false ) {
00829         global $wgArticleCountMethod;
00830 
00831         if ( !$this->mTitle->isContentPage() ) {
00832             return false;
00833         }
00834 
00835         if ( $editInfo ) {
00836             $content = $editInfo->pstContent;
00837         } else {
00838             $content = $this->getContent();
00839         }
00840 
00841         if ( !$content || $content->isRedirect() ) {
00842             return false;
00843         }
00844 
00845         $hasLinks = null;
00846 
00847         if ( $wgArticleCountMethod === 'link' ) {
00848             // nasty special case to avoid re-parsing to detect links
00849 
00850             if ( $editInfo ) {
00851                 // ParserOutput::getLinks() is a 2D array of page links, so
00852                 // to be really correct we would need to recurse in the array
00853                 // but the main array should only have items in it if there are
00854                 // links.
00855                 $hasLinks = (bool)count( $editInfo->output->getLinks() );
00856             } else {
00857                 $hasLinks = (bool)wfGetDB( DB_SLAVE )->selectField( 'pagelinks', 1,
00858                     array( 'pl_from' => $this->getId() ), __METHOD__ );
00859             }
00860         }
00861 
00862         return $content->isCountable( $hasLinks );
00863     }
00864 
00872     public function getRedirectTarget() {
00873         if ( !$this->mTitle->isRedirect() ) {
00874             return null;
00875         }
00876 
00877         if ( $this->mRedirectTarget !== null ) {
00878             return $this->mRedirectTarget;
00879         }
00880 
00881         // Query the redirect table
00882         $dbr = wfGetDB( DB_SLAVE );
00883         $row = $dbr->selectRow( 'redirect',
00884             array( 'rd_namespace', 'rd_title', 'rd_fragment', 'rd_interwiki' ),
00885             array( 'rd_from' => $this->getId() ),
00886             __METHOD__
00887         );
00888 
00889         // rd_fragment and rd_interwiki were added later, populate them if empty
00890         if ( $row && !is_null( $row->rd_fragment ) && !is_null( $row->rd_interwiki ) ) {
00891             return $this->mRedirectTarget = Title::makeTitle(
00892                 $row->rd_namespace, $row->rd_title,
00893                 $row->rd_fragment, $row->rd_interwiki );
00894         }
00895 
00896         // This page doesn't have an entry in the redirect table
00897         return $this->mRedirectTarget = $this->insertRedirect();
00898     }
00899 
00906     public function insertRedirect() {
00907         // recurse through to only get the final target
00908         $content = $this->getContent();
00909         $retval = $content ? $content->getUltimateRedirectTarget() : null;
00910         if ( !$retval ) {
00911             return null;
00912         }
00913         $this->insertRedirectEntry( $retval );
00914         return $retval;
00915     }
00916 
00922     public function insertRedirectEntry( $rt ) {
00923         $dbw = wfGetDB( DB_MASTER );
00924         $dbw->replace( 'redirect', array( 'rd_from' ),
00925             array(
00926                 'rd_from' => $this->getId(),
00927                 'rd_namespace' => $rt->getNamespace(),
00928                 'rd_title' => $rt->getDBkey(),
00929                 'rd_fragment' => $rt->getFragment(),
00930                 'rd_interwiki' => $rt->getInterwiki(),
00931             ),
00932             __METHOD__
00933         );
00934     }
00935 
00941     public function followRedirect() {
00942         return $this->getRedirectURL( $this->getRedirectTarget() );
00943     }
00944 
00952     public function getRedirectURL( $rt ) {
00953         if ( !$rt ) {
00954             return false;
00955         }
00956 
00957         if ( $rt->isExternal() ) {
00958             if ( $rt->isLocal() ) {
00959                 // Offsite wikis need an HTTP redirect.
00960                 //
00961                 // This can be hard to reverse and may produce loops,
00962                 // so they may be disabled in the site configuration.
00963                 $source = $this->mTitle->getFullURL( 'redirect=no' );
00964                 return $rt->getFullURL( array( 'rdfrom' => $source ) );
00965             } else {
00966                 // External pages pages without "local" bit set are not valid
00967                 // redirect targets
00968                 return false;
00969             }
00970         }
00971 
00972         if ( $rt->isSpecialPage() ) {
00973             // Gotta handle redirects to special pages differently:
00974             // Fill the HTTP response "Location" header and ignore
00975             // the rest of the page we're on.
00976             //
00977             // Some pages are not valid targets
00978             if ( $rt->isValidRedirectTarget() ) {
00979                 return $rt->getFullURL();
00980             } else {
00981                 return false;
00982             }
00983         }
00984 
00985         return $rt;
00986     }
00987 
00993     public function getContributors() {
00994         // @todo FIXME: This is expensive; cache this info somewhere.
00995 
00996         $dbr = wfGetDB( DB_SLAVE );
00997 
00998         if ( $dbr->implicitGroupby() ) {
00999             $realNameField = 'user_real_name';
01000         } else {
01001             $realNameField = 'MIN(user_real_name) AS user_real_name';
01002         }
01003 
01004         $tables = array( 'revision', 'user' );
01005 
01006         $fields = array(
01007             'user_id' => 'rev_user',
01008             'user_name' => 'rev_user_text',
01009             $realNameField,
01010             'timestamp' => 'MAX(rev_timestamp)',
01011         );
01012 
01013         $conds = array( 'rev_page' => $this->getId() );
01014 
01015         // The user who made the top revision gets credited as "this page was last edited by
01016         // John, based on contributions by Tom, Dick and Harry", so don't include them twice.
01017         $user = $this->getUser();
01018         if ( $user ) {
01019             $conds[] = "rev_user != $user";
01020         } else {
01021             $conds[] = "rev_user_text != {$dbr->addQuotes( $this->getUserText() )}";
01022         }
01023 
01024         $conds[] = "{$dbr->bitAnd( 'rev_deleted', Revision::DELETED_USER )} = 0"; // username hidden?
01025 
01026         $jconds = array(
01027             'user' => array( 'LEFT JOIN', 'rev_user = user_id' ),
01028         );
01029 
01030         $options = array(
01031             'GROUP BY' => array( 'rev_user', 'rev_user_text' ),
01032             'ORDER BY' => 'timestamp DESC',
01033         );
01034 
01035         $res = $dbr->select( $tables, $fields, $conds, __METHOD__, $options, $jconds );
01036         return new UserArrayFromResult( $res );
01037     }
01038 
01045     public function getLastNAuthors( $num, $revLatest = 0 ) {
01046         wfProfileIn( __METHOD__ );
01047         // First try the slave
01048         // If that doesn't have the latest revision, try the master
01049         $continue = 2;
01050         $db = wfGetDB( DB_SLAVE );
01051 
01052         do {
01053             $res = $db->select( array( 'page', 'revision' ),
01054                 array( 'rev_id', 'rev_user_text' ),
01055                 array(
01056                     'page_namespace' => $this->mTitle->getNamespace(),
01057                     'page_title' => $this->mTitle->getDBkey(),
01058                     'rev_page = page_id'
01059                 ), __METHOD__,
01060                 array(
01061                     'ORDER BY' => 'rev_timestamp DESC',
01062                     'LIMIT' => $num
01063                 )
01064             );
01065 
01066             if ( !$res ) {
01067                 wfProfileOut( __METHOD__ );
01068                 return array();
01069             }
01070 
01071             $row = $db->fetchObject( $res );
01072 
01073             if ( $continue == 2 && $revLatest && $row->rev_id != $revLatest ) {
01074                 $db = wfGetDB( DB_MASTER );
01075                 $continue--;
01076             } else {
01077                 $continue = 0;
01078             }
01079         } while ( $continue );
01080 
01081         $authors = array( $row->rev_user_text );
01082 
01083         foreach ( $res as $row ) {
01084             $authors[] = $row->rev_user_text;
01085         }
01086 
01087         wfProfileOut( __METHOD__ );
01088         return $authors;
01089     }
01090 
01098     public function isParserCacheUsed( ParserOptions $parserOptions, $oldid ) {
01099         global $wgEnableParserCache;
01100 
01101         return $wgEnableParserCache
01102             && $parserOptions->getStubThreshold() == 0
01103             && $this->exists()
01104             && ( $oldid === null || $oldid === 0 || $oldid === $this->getLatest() )
01105             && $this->getContentHandler()->isParserCacheSupported();
01106     }
01107 
01119     public function getParserOutput( ParserOptions $parserOptions, $oldid = null ) {
01120         wfProfileIn( __METHOD__ );
01121 
01122         $useParserCache = $this->isParserCacheUsed( $parserOptions, $oldid );
01123         wfDebug( __METHOD__ . ': using parser cache: ' . ( $useParserCache ? 'yes' : 'no' ) . "\n" );
01124         if ( $parserOptions->getStubThreshold() ) {
01125             wfIncrStats( 'pcache_miss_stub' );
01126         }
01127 
01128         if ( $useParserCache ) {
01129             $parserOutput = ParserCache::singleton()->get( $this, $parserOptions );
01130             if ( $parserOutput !== false ) {
01131                 wfProfileOut( __METHOD__ );
01132                 return $parserOutput;
01133             }
01134         }
01135 
01136         if ( $oldid === null || $oldid === 0 ) {
01137             $oldid = $this->getLatest();
01138         }
01139 
01140         $pool = new PoolWorkArticleView( $this, $parserOptions, $oldid, $useParserCache );
01141         $pool->execute();
01142 
01143         wfProfileOut( __METHOD__ );
01144 
01145         return $pool->getParserOutput();
01146     }
01147 
01152     public function doViewUpdates( User $user ) {
01153         global $wgDisableCounters;
01154         if ( wfReadOnly() ) {
01155             return;
01156         }
01157 
01158         // Don't update page view counters on views from bot users (bug 14044)
01159         if ( !$wgDisableCounters && !$user->isAllowed( 'bot' ) && $this->exists() ) {
01160             DeferredUpdates::addUpdate( new ViewCountUpdate( $this->getId() ) );
01161             DeferredUpdates::addUpdate( new SiteStatsUpdate( 1, 0, 0 ) );
01162         }
01163 
01164         // Update newtalk / watchlist notification status
01165         $user->clearNotification( $this->mTitle );
01166     }
01167 
01172     public function doPurge() {
01173         global $wgUseSquid;
01174 
01175         if ( !wfRunHooks( 'ArticlePurge', array( &$this ) ) ) {
01176             return false;
01177         }
01178 
01179         // Invalidate the cache
01180         $this->mTitle->invalidateCache();
01181 
01182         if ( $wgUseSquid ) {
01183             // Commit the transaction before the purge is sent
01184             $dbw = wfGetDB( DB_MASTER );
01185             $dbw->commit( __METHOD__ );
01186 
01187             // Send purge
01188             $update = SquidUpdate::newSimplePurge( $this->mTitle );
01189             $update->doUpdate();
01190         }
01191 
01192         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
01193             // @todo move this logic to MessageCache
01194 
01195             if ( $this->exists() ) {
01196                 // NOTE: use transclusion text for messages.
01197                 //       This is consistent with  MessageCache::getMsgFromNamespace()
01198 
01199                 $content = $this->getContent();
01200                 $text = $content === null ? null : $content->getWikitextForTransclusion();
01201 
01202                 if ( $text === null ) {
01203                     $text = false;
01204                 }
01205             } else {
01206                 $text = false;
01207             }
01208 
01209             MessageCache::singleton()->replace( $this->mTitle->getDBkey(), $text );
01210         }
01211         return true;
01212     }
01213 
01224     public function insertOn( $dbw ) {
01225         wfProfileIn( __METHOD__ );
01226 
01227         $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' );
01228         $dbw->insert( 'page', array(
01229             'page_id'           => $page_id,
01230             'page_namespace'    => $this->mTitle->getNamespace(),
01231             'page_title'        => $this->mTitle->getDBkey(),
01232             'page_counter'      => 0,
01233             'page_restrictions' => '',
01234             'page_is_redirect'  => 0, // Will set this shortly...
01235             'page_is_new'       => 1,
01236             'page_random'       => wfRandom(),
01237             'page_touched'      => $dbw->timestamp(),
01238             'page_latest'       => 0, // Fill this in shortly...
01239             'page_len'          => 0, // Fill this in shortly...
01240         ), __METHOD__, 'IGNORE' );
01241 
01242         $affected = $dbw->affectedRows();
01243 
01244         if ( $affected ) {
01245             $newid = $dbw->insertId();
01246             $this->mId = $newid;
01247             $this->mTitle->resetArticleID( $newid );
01248         }
01249         wfProfileOut( __METHOD__ );
01250 
01251         return $affected ? $newid : false;
01252     }
01253 
01269     public function updateRevisionOn( $dbw, $revision, $lastRevision = null, $lastRevIsRedirect = null ) {
01270         global $wgContentHandlerUseDB;
01271 
01272         wfProfileIn( __METHOD__ );
01273 
01274         $content = $revision->getContent();
01275         $len = $content ? $content->getSize() : 0;
01276         $rt = $content ? $content->getUltimateRedirectTarget() : null;
01277 
01278         $conditions = array( 'page_id' => $this->getId() );
01279 
01280         if ( !is_null( $lastRevision ) ) {
01281             // An extra check against threads stepping on each other
01282             $conditions['page_latest'] = $lastRevision;
01283         }
01284 
01285         $now = wfTimestampNow();
01286         $row = array( /* SET */
01287             'page_latest'      => $revision->getId(),
01288             'page_touched'     => $dbw->timestamp( $now ),
01289             'page_is_new'      => ( $lastRevision === 0 ) ? 1 : 0,
01290             'page_is_redirect' => $rt !== null ? 1 : 0,
01291             'page_len'         => $len,
01292         );
01293 
01294         if ( $wgContentHandlerUseDB ) {
01295             $row['page_content_model'] = $revision->getContentModel();
01296         }
01297 
01298         $dbw->update( 'page',
01299             $row,
01300             $conditions,
01301             __METHOD__ );
01302 
01303         $result = $dbw->affectedRows() > 0;
01304         if ( $result ) {
01305             $this->updateRedirectOn( $dbw, $rt, $lastRevIsRedirect );
01306             $this->setLastEdit( $revision );
01307             $this->setCachedLastEditTime( $now );
01308             $this->mLatest = $revision->getId();
01309             $this->mIsRedirect = (bool)$rt;
01310             // Update the LinkCache.
01311             LinkCache::singleton()->addGoodLinkObj( $this->getId(), $this->mTitle, $len, $this->mIsRedirect,
01312                                                     $this->mLatest, $revision->getContentModel() );
01313         }
01314 
01315         wfProfileOut( __METHOD__ );
01316         return $result;
01317     }
01318 
01330     public function updateRedirectOn( $dbw, $redirectTitle, $lastRevIsRedirect = null ) {
01331         // Always update redirects (target link might have changed)
01332         // Update/Insert if we don't know if the last revision was a redirect or not
01333         // Delete if changing from redirect to non-redirect
01334         $isRedirect = !is_null( $redirectTitle );
01335 
01336         if ( !$isRedirect && $lastRevIsRedirect === false ) {
01337             return true;
01338         }
01339 
01340         wfProfileIn( __METHOD__ );
01341         if ( $isRedirect ) {
01342             $this->insertRedirectEntry( $redirectTitle );
01343         } else {
01344             // This is not a redirect, remove row from redirect table
01345             $where = array( 'rd_from' => $this->getId() );
01346             $dbw->delete( 'redirect', $where, __METHOD__ );
01347         }
01348 
01349         if ( $this->getTitle()->getNamespace() == NS_FILE ) {
01350             RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $this->getTitle() );
01351         }
01352         wfProfileOut( __METHOD__ );
01353 
01354         return ( $dbw->affectedRows() != 0 );
01355     }
01356 
01365     public function updateIfNewerOn( $dbw, $revision ) {
01366         wfProfileIn( __METHOD__ );
01367 
01368         $row = $dbw->selectRow(
01369             array( 'revision', 'page' ),
01370             array( 'rev_id', 'rev_timestamp', 'page_is_redirect' ),
01371             array(
01372                 'page_id' => $this->getId(),
01373                 'page_latest=rev_id' ),
01374             __METHOD__ );
01375 
01376         if ( $row ) {
01377             if ( wfTimestamp( TS_MW, $row->rev_timestamp ) >= $revision->getTimestamp() ) {
01378                 wfProfileOut( __METHOD__ );
01379                 return false;
01380             }
01381             $prev = $row->rev_id;
01382             $lastRevIsRedirect = (bool)$row->page_is_redirect;
01383         } else {
01384             // No or missing previous revision; mark the page as new
01385             $prev = 0;
01386             $lastRevIsRedirect = null;
01387         }
01388 
01389         $ret = $this->updateRevisionOn( $dbw, $revision, $prev, $lastRevIsRedirect );
01390 
01391         wfProfileOut( __METHOD__ );
01392         return $ret;
01393     }
01394 
01405     public function getUndoContent( Revision $undo, Revision $undoafter = null ) {
01406         $handler = $undo->getContentHandler();
01407         return $handler->getUndoContent( $this->getRevision(), $undo, $undoafter );
01408     }
01409 
01419     public function getUndoText( Revision $undo, Revision $undoafter = null ) {
01420         ContentHandler::deprecated( __METHOD__, '1.21' );
01421 
01422         $this->loadLastEdit();
01423 
01424         if ( $this->mLastRevision ) {
01425             if ( is_null( $undoafter ) ) {
01426                 $undoafter = $undo->getPrevious();
01427             }
01428 
01429             $handler = $this->getContentHandler();
01430             $undone = $handler->getUndoContent( $this->mLastRevision, $undo, $undoafter );
01431 
01432             if ( !$undone ) {
01433                 return false;
01434             } else {
01435                 return ContentHandler::getContentText( $undone );
01436             }
01437         }
01438 
01439         return false;
01440     }
01441 
01452     public function replaceSection( $section, $text, $sectionTitle = '', $edittime = null ) {
01453         ContentHandler::deprecated( __METHOD__, '1.21' );
01454 
01455         if ( strval( $section ) == '' ) { //NOTE: keep condition in sync with condition in replaceSectionContent!
01456             // Whole-page edit; let the whole text through
01457             return $text;
01458         }
01459 
01460         if ( !$this->supportsSections() ) {
01461             throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
01462         }
01463 
01464         // could even make section title, but that's not required.
01465         $sectionContent = ContentHandler::makeContent( $text, $this->getTitle() );
01466 
01467         $newContent = $this->replaceSectionContent( $section, $sectionContent, $sectionTitle, $edittime );
01468 
01469         return ContentHandler::getContentText( $newContent );
01470     }
01471 
01480     public function supportsSections() {
01481         return $this->getContentHandler()->supportsSections();
01482     }
01483 
01495     public function replaceSectionContent( $section, Content $sectionContent, $sectionTitle = '', $edittime = null ) {
01496         wfProfileIn( __METHOD__ );
01497 
01498         if ( strval( $section ) == '' ) {
01499             // Whole-page edit; let the whole text through
01500             $newContent = $sectionContent;
01501         } else {
01502             if ( !$this->supportsSections() ) {
01503                 wfProfileOut( __METHOD__ );
01504                 throw new MWException( "sections not supported for content model " . $this->getContentHandler()->getModelID() );
01505             }
01506 
01507             // Bug 30711: always use current version when adding a new section
01508             if ( is_null( $edittime ) || $section == 'new' ) {
01509                 $oldContent = $this->getContent();
01510             } else {
01511                 $dbw = wfGetDB( DB_MASTER );
01512                 $rev = Revision::loadFromTimestamp( $dbw, $this->mTitle, $edittime );
01513 
01514                 if ( !$rev ) {
01515                     wfDebug( "WikiPage::replaceSection asked for bogus section (page: " .
01516                         $this->getId() . "; section: $section; edittime: $edittime)\n" );
01517                     wfProfileOut( __METHOD__ );
01518                     return null;
01519                 }
01520 
01521                 $oldContent = $rev->getContent();
01522             }
01523 
01524             if ( ! $oldContent ) {
01525                 wfDebug( __METHOD__ . ": no page text\n" );
01526                 wfProfileOut( __METHOD__ );
01527                 return null;
01528             }
01529 
01530             // FIXME: $oldContent might be null?
01531             $newContent = $oldContent->replaceSection( $section, $sectionContent, $sectionTitle );
01532         }
01533 
01534         wfProfileOut( __METHOD__ );
01535         return $newContent;
01536     }
01537 
01543     function checkFlags( $flags ) {
01544         if ( !( $flags & EDIT_NEW ) && !( $flags & EDIT_UPDATE ) ) {
01545             if ( $this->exists() ) {
01546                 $flags |= EDIT_UPDATE;
01547             } else {
01548                 $flags |= EDIT_NEW;
01549             }
01550         }
01551 
01552         return $flags;
01553     }
01554 
01604     public function doEdit( $text, $summary, $flags = 0, $baseRevId = false, $user = null ) {
01605         ContentHandler::deprecated( __METHOD__, '1.21' );
01606 
01607         $content = ContentHandler::makeContent( $text, $this->getTitle() );
01608 
01609         return $this->doEditContent( $content, $summary, $flags, $baseRevId, $user );
01610     }
01611 
01660     public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
01661                                    User $user = null, $serialisation_format = null ) {
01662         global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
01663 
01664         // Low-level sanity check
01665         if ( $this->mTitle->getText() === '' ) {
01666             throw new MWException( 'Something is trying to edit an article with an empty title' );
01667         }
01668 
01669         wfProfileIn( __METHOD__ );
01670 
01671         if ( !$content->getContentHandler()->canBeUsedOn( $this->getTitle() ) ) {
01672             wfProfileOut( __METHOD__ );
01673             return Status::newFatal( 'content-not-allowed-here',
01674                 ContentHandler::getLocalizedName( $content->getModel() ),
01675                 $this->getTitle()->getPrefixedText() );
01676         }
01677 
01678         $user = is_null( $user ) ? $wgUser : $user;
01679         $status = Status::newGood( array() );
01680 
01681         // Load the data from the master database if needed.
01682         // The caller may already loaded it from the master or even loaded it using
01683         // SELECT FOR UPDATE, so do not override that using clear().
01684         $this->loadPageData( 'fromdbmaster' );
01685 
01686         $flags = $this->checkFlags( $flags );
01687 
01688         // handle hook
01689         $hook_args = array( &$this, &$user, &$content, &$summary,
01690                             $flags & EDIT_MINOR, null, null, &$flags, &$status );
01691 
01692         if ( !wfRunHooks( 'PageContentSave', $hook_args )
01693             || !ContentHandler::runLegacyHooks( 'ArticleSave', $hook_args ) ) {
01694 
01695             wfDebug( __METHOD__ . ": ArticleSave or ArticleSaveContent hook aborted save!\n" );
01696 
01697             if ( $status->isOK() ) {
01698                 $status->fatal( 'edit-hook-aborted' );
01699             }
01700 
01701             wfProfileOut( __METHOD__ );
01702             return $status;
01703         }
01704 
01705         // Silently ignore EDIT_MINOR if not allowed
01706         $isminor = ( $flags & EDIT_MINOR ) && $user->isAllowed( 'minoredit' );
01707         $bot = $flags & EDIT_FORCE_BOT;
01708 
01709         $old_content = $this->getContent( Revision::RAW ); // current revision's content
01710 
01711         $oldsize = $old_content ? $old_content->getSize() : 0;
01712         $oldid = $this->getLatest();
01713         $oldIsRedirect = $this->isRedirect();
01714         $oldcountable = $this->isCountable();
01715 
01716         $handler = $content->getContentHandler();
01717 
01718         // Provide autosummaries if one is not provided and autosummaries are enabled.
01719         if ( $wgUseAutomaticEditSummaries && $flags & EDIT_AUTOSUMMARY && $summary == '' ) {
01720             if ( !$old_content ) {
01721                 $old_content = null;
01722             }
01723             $summary = $handler->getAutosummary( $old_content, $content, $flags );
01724         }
01725 
01726         $editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
01727         $serialized = $editInfo->pst;
01728 
01732         $content = $editInfo->pstContent;
01733         $newsize = $content->getSize();
01734 
01735         $dbw = wfGetDB( DB_MASTER );
01736         $now = wfTimestampNow();
01737         $this->mTimestamp = $now;
01738 
01739         if ( $flags & EDIT_UPDATE ) {
01740             // Update article, but only if changed.
01741             $status->value['new'] = false;
01742 
01743             if ( !$oldid ) {
01744                 // Article gone missing
01745                 wfDebug( __METHOD__ . ": EDIT_UPDATE specified but article doesn't exist\n" );
01746                 $status->fatal( 'edit-gone-missing' );
01747 
01748                 wfProfileOut( __METHOD__ );
01749                 return $status;
01750             } elseif ( !$old_content ) {
01751                 // Sanity check for bug 37225
01752                 wfProfileOut( __METHOD__ );
01753                 throw new MWException( "Could not find text for current revision {$oldid}." );
01754             }
01755 
01756             $revision = new Revision( array(
01757                 'page'       => $this->getId(),
01758                 'title'      => $this->getTitle(), // for determining the default content model
01759                 'comment'    => $summary,
01760                 'minor_edit' => $isminor,
01761                 'text'       => $serialized,
01762                 'len'        => $newsize,
01763                 'parent_id'  => $oldid,
01764                 'user'       => $user->getId(),
01765                 'user_text'  => $user->getName(),
01766                 'timestamp'  => $now,
01767                 'content_model' => $content->getModel(),
01768                 'content_format' => $serialisation_format,
01769             ) ); // XXX: pass content object?!
01770 
01771             $changed = !$content->equals( $old_content );
01772 
01773             if ( $changed ) {
01774                 if ( !$content->isValid() ) {
01775                     wfProfileOut( __METHOD__ );
01776                     throw new MWException( "New content failed validity check!" );
01777                 }
01778 
01779                 $dbw->begin( __METHOD__ );
01780 
01781                 $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
01782                 $status->merge( $prepStatus );
01783 
01784                 if ( !$status->isOK() ) {
01785                     $dbw->rollback( __METHOD__ );
01786 
01787                     wfProfileOut( __METHOD__ );
01788                     return $status;
01789                 }
01790 
01791                 $revisionId = $revision->insertOn( $dbw );
01792 
01793                 // Update page
01794                 //
01795                 // Note that we use $this->mLatest instead of fetching a value from the master DB
01796                 // during the course of this function. This makes sure that EditPage can detect
01797                 // edit conflicts reliably, either by $ok here, or by $article->getTimestamp()
01798                 // before this function is called. A previous function used a separate query, this
01799                 // creates a window where concurrent edits can cause an ignored edit conflict.
01800                 $ok = $this->updateRevisionOn( $dbw, $revision, $oldid, $oldIsRedirect );
01801 
01802                 if ( !$ok ) {
01803                     // Belated edit conflict! Run away!!
01804                     $status->fatal( 'edit-conflict' );
01805 
01806                     $dbw->rollback( __METHOD__ );
01807 
01808                     wfProfileOut( __METHOD__ );
01809                     return $status;
01810                 }
01811 
01812                 wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, $baseRevId, $user ) );
01813                 // Update recentchanges
01814                 if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
01815                     // Mark as patrolled if the user can do so
01816                     $patrolled = $wgUseRCPatrol && !count(
01817                         $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
01818                     // Add RC row to the DB
01819                     $rc = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $user, $summary,
01820                         $oldid, $this->getTimestamp(), $bot, '', $oldsize, $newsize,
01821                         $revisionId, $patrolled
01822                     );
01823 
01824                     // Log auto-patrolled edits
01825                     if ( $patrolled ) {
01826                         PatrolLog::record( $rc, true, $user );
01827                     }
01828                 }
01829                 $user->incEditCount();
01830                 $dbw->commit( __METHOD__ );
01831             } else {
01832                 // Bug 32948: revision ID must be set to page {{REVISIONID}} and
01833                 // related variables correctly
01834                 $revision->setId( $this->getLatest() );
01835             }
01836 
01837             // Update links tables, site stats, etc.
01838             $this->doEditUpdates(
01839                 $revision,
01840                 $user,
01841                 array(
01842                     'changed' => $changed,
01843                     'oldcountable' => $oldcountable
01844                 )
01845             );
01846 
01847             if ( !$changed ) {
01848                 $status->warning( 'edit-no-change' );
01849                 $revision = null;
01850                 // Update page_touched, this is usually implicit in the page update
01851                 // Other cache updates are done in onArticleEdit()
01852                 $this->mTitle->invalidateCache();
01853             }
01854         } else {
01855             // Create new article
01856             $status->value['new'] = true;
01857 
01858             $dbw->begin( __METHOD__ );
01859 
01860             $prepStatus = $content->prepareSave( $this, $flags, $baseRevId, $user );
01861             $status->merge( $prepStatus );
01862 
01863             if ( !$status->isOK() ) {
01864                 $dbw->rollback( __METHOD__ );
01865 
01866                 wfProfileOut( __METHOD__ );
01867                 return $status;
01868             }
01869 
01870             $status->merge( $prepStatus );
01871 
01872             // Add the page record; stake our claim on this title!
01873             // This will return false if the article already exists
01874             $newid = $this->insertOn( $dbw );
01875 
01876             if ( $newid === false ) {
01877                 $dbw->rollback( __METHOD__ );
01878                 $status->fatal( 'edit-already-exists' );
01879 
01880                 wfProfileOut( __METHOD__ );
01881                 return $status;
01882             }
01883 
01884             // Save the revision text...
01885             $revision = new Revision( array(
01886                 'page'       => $newid,
01887                 'title'      => $this->getTitle(), // for determining the default content model
01888                 'comment'    => $summary,
01889                 'minor_edit' => $isminor,
01890                 'text'       => $serialized,
01891                 'len'        => $newsize,
01892                 'user'       => $user->getId(),
01893                 'user_text'  => $user->getName(),
01894                 'timestamp'  => $now,
01895                 'content_model' => $content->getModel(),
01896                 'content_format' => $serialisation_format,
01897             ) );
01898             $revisionId = $revision->insertOn( $dbw );
01899 
01900             // Bug 37225: use accessor to get the text as Revision may trim it
01901             $content = $revision->getContent(); // sanity; get normalized version
01902 
01903             if ( $content ) {
01904                 $newsize = $content->getSize();
01905             }
01906 
01907             // Update the page record with revision data
01908             $this->updateRevisionOn( $dbw, $revision, 0 );
01909 
01910             wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
01911 
01912             // Update recentchanges
01913             if ( !( $flags & EDIT_SUPPRESS_RC ) ) {
01914                 // Mark as patrolled if the user can do so
01915                 $patrolled = ( $wgUseRCPatrol || $wgUseNPPatrol ) && !count(
01916                     $this->mTitle->getUserPermissionsErrors( 'autopatrol', $user ) );
01917                 // Add RC row to the DB
01918                 $rc = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $user, $summary, $bot,
01919                     '', $newsize, $revisionId, $patrolled );
01920 
01921                 // Log auto-patrolled edits
01922                 if ( $patrolled ) {
01923                     PatrolLog::record( $rc, true, $user );
01924                 }
01925             }
01926             $user->incEditCount();
01927             $dbw->commit( __METHOD__ );
01928 
01929             // Update links, etc.
01930             $this->doEditUpdates( $revision, $user, array( 'created' => true ) );
01931 
01932             $hook_args = array( &$this, &$user, $content, $summary,
01933                                 $flags & EDIT_MINOR, null, null, &$flags, $revision );
01934 
01935             ContentHandler::runLegacyHooks( 'ArticleInsertComplete', $hook_args );
01936             wfRunHooks( 'PageContentInsertComplete', $hook_args );
01937         }
01938 
01939         // Do updates right now unless deferral was requested
01940         if ( !( $flags & EDIT_DEFER_UPDATES ) ) {
01941             DeferredUpdates::doUpdates();
01942         }
01943 
01944         // Return the new revision (or null) to the caller
01945         $status->value['revision'] = $revision;
01946 
01947         $hook_args = array( &$this, &$user, $content, $summary,
01948                             $flags & EDIT_MINOR, null, null, &$flags, $revision, &$status, $baseRevId );
01949 
01950         ContentHandler::runLegacyHooks( 'ArticleSaveComplete', $hook_args );
01951         wfRunHooks( 'PageContentSaveComplete', $hook_args );
01952 
01953         // Promote user to any groups they meet the criteria for
01954         $user->addAutopromoteOnceGroups( 'onEdit' );
01955 
01956         wfProfileOut( __METHOD__ );
01957         return $status;
01958     }
01959 
01974     public function makeParserOptions( $context ) {
01975         $options = $this->getContentHandler()->makeParserOptions( $context );
01976 
01977         if ( $this->getTitle()->isConversionTable() ) {
01978             // @todo ConversionTable should become a separate content model, so we don't need special cases like this one.
01979             $options->disableContentConversion();
01980         }
01981 
01982         return $options;
01983     }
01984 
01991     public function prepareTextForEdit( $text, $revid = null, User $user = null ) {
01992         ContentHandler::deprecated( __METHOD__, '1.21' );
01993         $content = ContentHandler::makeContent( $text, $this->getTitle() );
01994         return $this->prepareContentForEdit( $content, $revid, $user );
01995     }
01996 
02010     public function prepareContentForEdit( Content $content, $revid = null, User $user = null,
02011         $serialization_format = null
02012     ) {
02013         global $wgContLang, $wgUser;
02014         $user = is_null( $user ) ? $wgUser : $user;
02015         //XXX: check $user->getId() here???
02016 
02017         if ( $this->mPreparedEdit
02018             && $this->mPreparedEdit->newContent
02019             && $this->mPreparedEdit->newContent->equals( $content )
02020             && $this->mPreparedEdit->revid == $revid
02021             && $this->mPreparedEdit->format == $serialization_format
02022             // XXX: also check $user here?
02023         ) {
02024             // Already prepared
02025             return $this->mPreparedEdit;
02026         }
02027 
02028         $popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
02029         wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
02030 
02031         $edit = (object)array();
02032         $edit->revid = $revid;
02033 
02034         $edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
02035 
02036         $edit->format = $serialization_format;
02037         $edit->popts = $this->makeParserOptions( 'canonical' );
02038         $edit->output = $edit->pstContent ? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts ) : null;
02039 
02040         $edit->newContent = $content;
02041         $edit->oldContent = $this->getContent( Revision::RAW );
02042 
02043         // NOTE: B/C for hooks! don't use these fields!
02044         $edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
02045         $edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
02046         $edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : '';
02047 
02048         $this->mPreparedEdit = $edit;
02049         return $edit;
02050     }
02051 
02068     public function doEditUpdates( Revision $revision, User $user, array $options = array() ) {
02069         global $wgEnableParserCache;
02070 
02071         wfProfileIn( __METHOD__ );
02072 
02073         $options += array( 'changed' => true, 'created' => false, 'oldcountable' => null );
02074         $content = $revision->getContent();
02075 
02076         // Parse the text
02077         // Be careful not to do pre-save transform twice: $text is usually
02078         // already pre-save transformed once.
02079         if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
02080             wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
02081             $editInfo = $this->prepareContentForEdit( $content, $revision->getId(), $user );
02082         } else {
02083             wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
02084             $editInfo = $this->mPreparedEdit;
02085         }
02086 
02087         // Save it to the parser cache
02088         if ( $wgEnableParserCache ) {
02089             $parserCache = ParserCache::singleton();
02090             $parserCache->save( $editInfo->output, $this, $editInfo->popts );
02091         }
02092 
02093         // Update the links tables and other secondary data
02094         if ( $content ) {
02095             $recursive = $options['changed']; // bug 50785
02096             $updates = $content->getSecondaryDataUpdates(
02097                 $this->getTitle(), null, $recursive, $editInfo->output );
02098             DataUpdate::runUpdates( $updates );
02099         }
02100 
02101         wfRunHooks( 'ArticleEditUpdates', array( &$this, &$editInfo, $options['changed'] ) );
02102 
02103         if ( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
02104             if ( 0 == mt_rand( 0, 99 ) ) {
02105                 // Flush old entries from the `recentchanges` table; we do this on
02106                 // random requests so as to avoid an increase in writes for no good reason
02107                 RecentChange::purgeExpiredChanges();
02108             }
02109         }
02110 
02111         if ( !$this->exists() ) {
02112             wfProfileOut( __METHOD__ );
02113             return;
02114         }
02115 
02116         $id = $this->getId();
02117         $title = $this->mTitle->getPrefixedDBkey();
02118         $shortTitle = $this->mTitle->getDBkey();
02119 
02120         if ( !$options['changed'] ) {
02121             $good = 0;
02122             $total = 0;
02123         } elseif ( $options['created'] ) {
02124             $good = (int)$this->isCountable( $editInfo );
02125             $total = 1;
02126         } elseif ( $options['oldcountable'] !== null ) {
02127             $good = (int)$this->isCountable( $editInfo ) - (int)$options['oldcountable'];
02128             $total = 0;
02129         } else {
02130             $good = 0;
02131             $total = 0;
02132         }
02133 
02134         DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, $good, $total ) );
02135         DeferredUpdates::addUpdate( new SearchUpdate( $id, $title, $content ) );
02136 
02137         // If this is another user's talk page, update newtalk.
02138         // Don't do this if $options['changed'] = false (null-edits) nor if
02139         // it's a minor edit and the user doesn't want notifications for those.
02140         if ( $options['changed']
02141             && $this->mTitle->getNamespace() == NS_USER_TALK
02142             && $shortTitle != $user->getTitleKey()
02143             && !( $revision->isMinor() && $user->isAllowed( 'nominornewtalk' ) )
02144         ) {
02145             $recipient = User::newFromName( $shortTitle, false );
02146             if ( !$recipient ) {
02147                 wfDebug( __METHOD__ . ": invalid username\n" );
02148             } else {
02149                 // Allow extensions to prevent user notification when a new message is added to their talk page
02150                 if ( wfRunHooks( 'ArticleEditUpdateNewTalk', array( &$this, $recipient ) ) ) {
02151                     if ( User::isIP( $shortTitle ) ) {
02152                         // An anonymous user
02153                         $recipient->setNewtalk( true, $revision );
02154                     } elseif ( $recipient->isLoggedIn() ) {
02155                         $recipient->setNewtalk( true, $revision );
02156                     } else {
02157                         wfDebug( __METHOD__ . ": don't need to notify a nonexistent user\n" );
02158                     }
02159                 }
02160             }
02161         }
02162 
02163         if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
02164             // XXX: could skip pseudo-messages like js/css here, based on content model.
02165             $msgtext = $content ? $content->getWikitextForTransclusion() : null;
02166             if ( $msgtext === false || $msgtext === null ) {
02167                 $msgtext = '';
02168             }
02169 
02170             MessageCache::singleton()->replace( $shortTitle, $msgtext );
02171         }
02172 
02173         if ( $options['created'] ) {
02174             self::onArticleCreate( $this->mTitle );
02175         } else {
02176             self::onArticleEdit( $this->mTitle );
02177         }
02178 
02179         wfProfileOut( __METHOD__ );
02180     }
02181 
02194     public function doQuickEdit( $text, User $user, $comment = '', $minor = 0 ) {
02195         ContentHandler::deprecated( __METHOD__, "1.21" );
02196 
02197         $content = ContentHandler::makeContent( $text, $this->getTitle() );
02198         $this->doQuickEditContent( $content, $user, $comment, $minor );
02199     }
02200 
02212     public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
02213         $serialisation_format = null
02214     ) {
02215         wfProfileIn( __METHOD__ );
02216 
02217         $serialized = $content->serialize( $serialisation_format );
02218 
02219         $dbw = wfGetDB( DB_MASTER );
02220         $revision = new Revision( array(
02221             'title'      => $this->getTitle(), // for determining the default content model
02222             'page'       => $this->getId(),
02223             'text'       => $serialized,
02224             'length'     => $content->getSize(),
02225             'comment'    => $comment,
02226             'minor_edit' => $minor ? 1 : 0,
02227         ) ); // XXX: set the content object?
02228         $revision->insertOn( $dbw );
02229         $this->updateRevisionOn( $dbw, $revision );
02230 
02231         wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $revision, false, $user ) );
02232 
02233         wfProfileOut( __METHOD__ );
02234     }
02235 
02247     public function doUpdateRestrictions( array $limit, array $expiry, &$cascade, $reason, User $user ) {
02248         global $wgCascadingRestrictionLevels;
02249 
02250         if ( wfReadOnly() ) {
02251             return Status::newFatal( 'readonlytext', wfReadOnlyReason() );
02252         }
02253 
02254         $restrictionTypes = $this->mTitle->getRestrictionTypes();
02255 
02256         $id = $this->getId();
02257 
02258         if ( !$cascade ) {
02259             $cascade = false;
02260         }
02261 
02262         // Take this opportunity to purge out expired restrictions
02263         Title::purgeExpiredRestrictions();
02264 
02265         // @todo FIXME: Same limitations as described in ProtectionForm.php (line 37);
02266         // we expect a single selection, but the schema allows otherwise.
02267         $isProtected = false;
02268         $protect = false;
02269         $changed = false;
02270 
02271         $dbw = wfGetDB( DB_MASTER );
02272 
02273         foreach ( $restrictionTypes as $action ) {
02274             if ( !isset( $expiry[$action] ) ) {
02275                 $expiry[$action] = $dbw->getInfinity();
02276             }
02277             if ( !isset( $limit[$action] ) ) {
02278                 $limit[$action] = '';
02279             } elseif ( $limit[$action] != '' ) {
02280                 $protect = true;
02281             }
02282 
02283             // Get current restrictions on $action
02284             $current = implode( '', $this->mTitle->getRestrictions( $action ) );
02285             if ( $current != '' ) {
02286                 $isProtected = true;
02287             }
02288 
02289             if ( $limit[$action] != $current ) {
02290                 $changed = true;
02291             } elseif ( $limit[$action] != '' ) {
02292                 // Only check expiry change if the action is actually being
02293                 // protected, since expiry does nothing on an not-protected
02294                 // action.
02295                 if ( $this->mTitle->getRestrictionExpiry( $action ) != $expiry[$action] ) {
02296                     $changed = true;
02297                 }
02298             }
02299         }
02300 
02301         if ( !$changed && $protect && $this->mTitle->areRestrictionsCascading() != $cascade ) {
02302             $changed = true;
02303         }
02304 
02305         // If nothing has changed, do nothing
02306         if ( !$changed ) {
02307             return Status::newGood();
02308         }
02309 
02310         if ( !$protect ) { // No protection at all means unprotection
02311             $revCommentMsg = 'unprotectedarticle';
02312             $logAction = 'unprotect';
02313         } elseif ( $isProtected ) {
02314             $revCommentMsg = 'modifiedarticleprotection';
02315             $logAction = 'modify';
02316         } else {
02317             $revCommentMsg = 'protectedarticle';
02318             $logAction = 'protect';
02319         }
02320 
02321         if ( $id ) { // Protection of existing page
02322             if ( !wfRunHooks( 'ArticleProtect', array( &$this, &$user, $limit, $reason ) ) ) {
02323                 return Status::newGood();
02324             }
02325 
02326             // Only certain restrictions can cascade...
02327             $editrestriction = isset( $limit['edit'] ) ? array( $limit['edit'] ) : $this->mTitle->getRestrictions( 'edit' );
02328             foreach ( array_keys( $editrestriction, 'sysop' ) as $key ) {
02329                 $editrestriction[$key] = 'editprotected'; // backwards compatibility
02330             }
02331             foreach ( array_keys( $editrestriction, 'autoconfirmed' ) as $key ) {
02332                 $editrestriction[$key] = 'editsemiprotected'; // backwards compatibility
02333             }
02334 
02335             $cascadingRestrictionLevels = $wgCascadingRestrictionLevels;
02336             foreach ( array_keys( $cascadingRestrictionLevels, 'sysop' ) as $key ) {
02337                 $cascadingRestrictionLevels[$key] = 'editprotected'; // backwards compatibility
02338             }
02339             foreach ( array_keys( $cascadingRestrictionLevels, 'autoconfirmed' ) as $key ) {
02340                 $cascadingRestrictionLevels[$key] = 'editsemiprotected'; // backwards compatibility
02341             }
02342 
02343             // The schema allows multiple restrictions
02344             if ( !array_intersect( $editrestriction, $cascadingRestrictionLevels ) ) {
02345                 $cascade = false;
02346             }
02347 
02348             // insert null revision to identify the page protection change as edit summary
02349             $latest = $this->getLatest();
02350             $nullRevision = $this->insertProtectNullRevision( $revCommentMsg, $limit, $expiry, $cascade, $reason );
02351             if ( $nullRevision === null ) {
02352                 return Status::newFatal( 'no-null-revision', $this->mTitle->getPrefixedText() );
02353             }
02354 
02355             // Update restrictions table
02356             foreach ( $limit as $action => $restrictions ) {
02357                 if ( $restrictions != '' ) {
02358                     $dbw->replace( 'page_restrictions', array( array( 'pr_page', 'pr_type' ) ),
02359                         array( 'pr_page' => $id,
02360                             'pr_type' => $action,
02361                             'pr_level' => $restrictions,
02362                             'pr_cascade' => ( $cascade && $action == 'edit' ) ? 1 : 0,
02363                             'pr_expiry' => $dbw->encodeExpiry( $expiry[$action] )
02364                         ),
02365                         __METHOD__
02366                     );
02367                 } else {
02368                     $dbw->delete( 'page_restrictions', array( 'pr_page' => $id,
02369                         'pr_type' => $action ), __METHOD__ );
02370                 }
02371             }
02372 
02373             // Clear out legacy restriction fields
02374             $dbw->update(
02375                 'page',
02376                 array( 'page_restrictions' => '' ),
02377                 array( 'page_id' => $id ),
02378                 __METHOD__
02379             );
02380 
02381             wfRunHooks( 'NewRevisionFromEditComplete', array( $this, $nullRevision, $latest, $user ) );
02382             wfRunHooks( 'ArticleProtectComplete', array( &$this, &$user, $limit, $reason ) );
02383         } else { // Protection of non-existing page (also known as "title protection")
02384             // Cascade protection is meaningless in this case
02385             $cascade = false;
02386 
02387             if ( $limit['create'] != '' ) {
02388                 $dbw->replace( 'protected_titles',
02389                     array( array( 'pt_namespace', 'pt_title' ) ),
02390                     array(
02391                         'pt_namespace' => $this->mTitle->getNamespace(),
02392                         'pt_title' => $this->mTitle->getDBkey(),
02393                         'pt_create_perm' => $limit['create'],
02394                         'pt_timestamp' => $dbw->encodeExpiry( wfTimestampNow() ),
02395                         'pt_expiry' => $dbw->encodeExpiry( $expiry['create'] ),
02396                         'pt_user' => $user->getId(),
02397                         'pt_reason' => $reason,
02398                     ), __METHOD__
02399                 );
02400             } else {
02401                 $dbw->delete( 'protected_titles',
02402                     array(
02403                         'pt_namespace' => $this->mTitle->getNamespace(),
02404                         'pt_title' => $this->mTitle->getDBkey()
02405                     ), __METHOD__
02406                 );
02407             }
02408         }
02409 
02410         $this->mTitle->flushRestrictions();
02411         InfoAction::invalidateCache( $this->mTitle );
02412 
02413         if ( $logAction == 'unprotect' ) {
02414             $params = array();
02415         } else {
02416             $protectDescriptionLog = $this->protectDescriptionLog( $limit, $expiry );
02417             $params = array( $protectDescriptionLog, $cascade ? 'cascade' : '' );
02418         }
02419 
02420         // Update the protection log
02421         $log = new LogPage( 'protect' );
02422         $log->addEntry( $logAction, $this->mTitle, trim( $reason ), $params, $user );
02423 
02424         return Status::newGood();
02425     }
02426 
02437     public function insertProtectNullRevision( $revCommentMsg, array $limit, array $expiry, $cascade, $reason ) {
02438         global $wgContLang;
02439         $dbw = wfGetDB( DB_MASTER );
02440 
02441         // Prepare a null revision to be added to the history
02442         $editComment = $wgContLang->ucfirst(
02443             wfMessage(
02444                 $revCommentMsg,
02445                 $this->mTitle->getPrefixedText()
02446             )->inContentLanguage()->text()
02447         );
02448         if ( $reason ) {
02449             $editComment .= wfMessage( 'colon-separator' )->inContentLanguage()->text() . $reason;
02450         }
02451         $protectDescription = $this->protectDescription( $limit, $expiry );
02452         if ( $protectDescription ) {
02453             $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
02454             $editComment .= wfMessage( 'parentheses' )->params( $protectDescription )->inContentLanguage()->text();
02455         }
02456         if ( $cascade ) {
02457             $editComment .= wfMessage( 'word-separator' )->inContentLanguage()->text();
02458             $editComment .= wfMessage( 'brackets' )->params(
02459                 wfMessage( 'protect-summary-cascade' )->inContentLanguage()->text()
02460             )->inContentLanguage()->text();
02461         }
02462 
02463         $nullRev = Revision::newNullRevision( $dbw, $this->getId(), $editComment, true );
02464         if ( $nullRev ) {
02465             $nullRev->insertOn( $dbw );
02466 
02467             // Update page record and touch page
02468             $oldLatest = $nullRev->getParentId();
02469             $this->updateRevisionOn( $dbw, $nullRev, $oldLatest );
02470         }
02471 
02472         return $nullRev;
02473     }
02474 
02479     protected function formatExpiry( $expiry ) {
02480         global $wgContLang;
02481         $dbr = wfGetDB( DB_SLAVE );
02482 
02483         $encodedExpiry = $dbr->encodeExpiry( $expiry );
02484         if ( $encodedExpiry != 'infinity' ) {
02485             return wfMessage(
02486                 'protect-expiring',
02487                 $wgContLang->timeanddate( $expiry, false, false ),
02488                 $wgContLang->date( $expiry, false, false ),
02489                 $wgContLang->time( $expiry, false, false )
02490             )->inContentLanguage()->text();
02491         } else {
02492             return wfMessage( 'protect-expiry-indefinite' )
02493                 ->inContentLanguage()->text();
02494         }
02495     }
02496 
02504     public function protectDescription( array $limit, array $expiry ) {
02505         $protectDescription = '';
02506 
02507         foreach ( array_filter( $limit ) as $action => $restrictions ) {
02508             # $action is one of $wgRestrictionTypes = array( 'create', 'edit', 'move', 'upload' ).
02509             # All possible message keys are listed here for easier grepping:
02510             # * restriction-create
02511             # * restriction-edit
02512             # * restriction-move
02513             # * restriction-upload
02514             $actionText = wfMessage( 'restriction-' . $action )->inContentLanguage()->text();
02515             # $restrictions is one of $wgRestrictionLevels = array( '', 'autoconfirmed', 'sysop' ),
02516             # with '' filtered out. All possible message keys are listed below:
02517             # * protect-level-autoconfirmed
02518             # * protect-level-sysop
02519             $restrictionsText = wfMessage( 'protect-level-' . $restrictions )->inContentLanguage()->text();
02520 
02521             $expiryText = $this->formatExpiry( $expiry[$action] );
02522 
02523             if ( $protectDescription !== '' ) {
02524                 $protectDescription .= wfMessage( 'word-separator' )->inContentLanguage()->text();
02525             }
02526             $protectDescription .= wfMessage( 'protect-summary-desc' )
02527                 ->params( $actionText, $restrictionsText, $expiryText )
02528                 ->inContentLanguage()->text();
02529         }
02530 
02531         return $protectDescription;
02532     }
02533 
02545     public function protectDescriptionLog( array $limit, array $expiry ) {
02546         global $wgContLang;
02547 
02548         $protectDescriptionLog = '';
02549 
02550         foreach ( array_filter( $limit ) as $action => $restrictions ) {
02551             $expiryText = $this->formatExpiry( $expiry[$action] );
02552             $protectDescriptionLog .= $wgContLang->getDirMark() . "[$action=$restrictions] ($expiryText)";
02553         }
02554 
02555         return trim( $protectDescriptionLog );
02556     }
02557 
02565     protected static function flattenRestrictions( $limit ) {
02566         if ( !is_array( $limit ) ) {
02567             throw new MWException( 'WikiPage::flattenRestrictions given non-array restriction set' );
02568         }
02569 
02570         $bits = array();
02571         ksort( $limit );
02572 
02573         foreach ( array_filter( $limit ) as $action => $restrictions ) {
02574             $bits[] = "$action=$restrictions";
02575         }
02576 
02577         return implode( ':', $bits );
02578     }
02579 
02596     public function doDeleteArticle(
02597         $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
02598     ) {
02599         $status = $this->doDeleteArticleReal( $reason, $suppress, $id, $commit, $error, $user );
02600         return $status->isGood();
02601     }
02602 
02620     public function doDeleteArticleReal(
02621         $reason, $suppress = false, $id = 0, $commit = true, &$error = '', User $user = null
02622     ) {
02623         global $wgUser, $wgContentHandlerUseDB;
02624 
02625         wfDebug( __METHOD__ . "\n" );
02626 
02627         $status = Status::newGood();
02628 
02629         if ( $this->mTitle->getDBkey() === '' ) {
02630             $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
02631             return $status;
02632         }
02633 
02634         $user = is_null( $user ) ? $wgUser : $user;
02635         if ( ! wfRunHooks( 'ArticleDelete', array( &$this, &$user, &$reason, &$error, &$status ) ) ) {
02636             if ( $status->isOK() ) {
02637                 // Hook aborted but didn't set a fatal status
02638                 $status->fatal( 'delete-hook-aborted' );
02639             }
02640             return $status;
02641         }
02642 
02643         if ( $id == 0 ) {
02644             $this->loadPageData( 'forupdate' );
02645             $id = $this->getID();
02646             if ( $id == 0 ) {
02647                 $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
02648                 return $status;
02649             }
02650         }
02651 
02652         // Bitfields to further suppress the content
02653         if ( $suppress ) {
02654             $bitfield = 0;
02655             // This should be 15...
02656             $bitfield |= Revision::DELETED_TEXT;
02657             $bitfield |= Revision::DELETED_COMMENT;
02658             $bitfield |= Revision::DELETED_USER;
02659             $bitfield |= Revision::DELETED_RESTRICTED;
02660         } else {
02661             $bitfield = 'rev_deleted';
02662         }
02663 
02664         // we need to remember the old content so we can use it to generate all deletion updates.
02665         $content = $this->getContent( Revision::RAW );
02666 
02667         $dbw = wfGetDB( DB_MASTER );
02668         $dbw->begin( __METHOD__ );
02669         // For now, shunt the revision data into the archive table.
02670         // Text is *not* removed from the text table; bulk storage
02671         // is left intact to avoid breaking block-compression or
02672         // immutable storage schemes.
02673         //
02674         // For backwards compatibility, note that some older archive
02675         // table entries will have ar_text and ar_flags fields still.
02676         //
02677         // In the future, we may keep revisions and mark them with
02678         // the rev_deleted field, which is reserved for this purpose.
02679 
02680         $row = array(
02681             'ar_namespace'  => 'page_namespace',
02682             'ar_title'      => 'page_title',
02683             'ar_comment'    => 'rev_comment',
02684             'ar_user'       => 'rev_user',
02685             'ar_user_text'  => 'rev_user_text',
02686             'ar_timestamp'  => 'rev_timestamp',
02687             'ar_minor_edit' => 'rev_minor_edit',
02688             'ar_rev_id'     => 'rev_id',
02689             'ar_parent_id'  => 'rev_parent_id',
02690             'ar_text_id'    => 'rev_text_id',
02691             'ar_text'       => '\'\'', // Be explicit to appease
02692             'ar_flags'      => '\'\'', // MySQL's "strict mode"...
02693             'ar_len'        => 'rev_len',
02694             'ar_page_id'    => 'page_id',
02695             'ar_deleted'    => $bitfield,
02696             'ar_sha1'       => 'rev_sha1',
02697         );
02698 
02699         if ( $wgContentHandlerUseDB ) {
02700             $row['ar_content_model'] = 'rev_content_model';
02701             $row['ar_content_format'] = 'rev_content_format';
02702         }
02703 
02704         $dbw->insertSelect( 'archive', array( 'page', 'revision' ),
02705             $row,
02706             array(
02707                 'page_id' => $id,
02708                 'page_id = rev_page'
02709             ), __METHOD__
02710         );
02711 
02712         // Now that it's safely backed up, delete it
02713         $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__ );
02714         $ok = ( $dbw->affectedRows() > 0 ); // $id could be laggy
02715 
02716         if ( !$ok ) {
02717             $dbw->rollback( __METHOD__ );
02718             $status->error( 'cannotdelete', wfEscapeWikiText( $this->getTitle()->getPrefixedText() ) );
02719             return $status;
02720         }
02721 
02722         if ( !$dbw->cascadingDeletes() ) {
02723             $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ );
02724         }
02725 
02726         $this->doDeleteUpdates( $id, $content );
02727 
02728         // Log the deletion, if the page was suppressed, log it at Oversight instead
02729         $logtype = $suppress ? 'suppress' : 'delete';
02730 
02731         $logEntry = new ManualLogEntry( $logtype, 'delete' );
02732         $logEntry->setPerformer( $user );
02733         $logEntry->setTarget( $this->mTitle );
02734         $logEntry->setComment( $reason );
02735         $logid = $logEntry->insert();
02736         $logEntry->publish( $logid );
02737 
02738         if ( $commit ) {
02739             $dbw->commit( __METHOD__ );
02740         }
02741 
02742         wfRunHooks( 'ArticleDeleteComplete', array( &$this, &$user, $reason, $id, $content, $logEntry ) );
02743         $status->value = $logid;
02744         return $status;
02745     }
02746 
02754     public function doDeleteUpdates( $id, Content $content = null ) {
02755         // update site status
02756         DeferredUpdates::addUpdate( new SiteStatsUpdate( 0, 1, - (int)$this->isCountable(), -1 ) );
02757 
02758         // remove secondary indexes, etc
02759         $updates = $this->getDeletionUpdates( $content );
02760         DataUpdate::runUpdates( $updates );
02761 
02762         // Clear caches
02763         WikiPage::onArticleDelete( $this->mTitle );
02764 
02765         // Reset this object and the Title object
02766         $this->loadFromRow( false, self::READ_LATEST );
02767 
02768         // Search engine
02769         DeferredUpdates::addUpdate( new SearchUpdate( $id, $this->mTitle ) );
02770     }
02771 
02796     public function doRollback(
02797         $fromP, $summary, $token, $bot, &$resultDetails, User $user
02798     ) {
02799         $resultDetails = null;
02800 
02801         // Check permissions
02802         $editErrors = $this->mTitle->getUserPermissionsErrors( 'edit', $user );
02803         $rollbackErrors = $this->mTitle->getUserPermissionsErrors( 'rollback', $user );
02804         $errors = array_merge( $editErrors, wfArrayDiff2( $rollbackErrors, $editErrors ) );
02805 
02806         if ( !$user->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) ) {
02807             $errors[] = array( 'sessionfailure' );
02808         }
02809 
02810         if ( $user->pingLimiter( 'rollback' ) || $user->pingLimiter() ) {
02811             $errors[] = array( 'actionthrottledtext' );
02812         }
02813 
02814         // If there were errors, bail out now
02815         if ( !empty( $errors ) ) {
02816             return $errors;
02817         }
02818 
02819         return $this->commitRollback( $fromP, $summary, $bot, $resultDetails, $user );
02820     }
02821 
02838     public function commitRollback( $fromP, $summary, $bot, &$resultDetails, User $guser ) {
02839         global $wgUseRCPatrol, $wgContLang;
02840 
02841         $dbw = wfGetDB( DB_MASTER );
02842 
02843         if ( wfReadOnly() ) {
02844             return array( array( 'readonlytext' ) );
02845         }
02846 
02847         // Get the last editor
02848         $current = $this->getRevision();
02849         if ( is_null( $current ) ) {
02850             // Something wrong... no page?
02851             return array( array( 'notanarticle' ) );
02852         }
02853 
02854         $from = str_replace( '_', ' ', $fromP );
02855         // User name given should match up with the top revision.
02856         // If the user was deleted then $from should be empty.
02857         if ( $from != $current->getUserText() ) {
02858             $resultDetails = array( 'current' => $current );
02859             return array( array( 'alreadyrolled',
02860                 htmlspecialchars( $this->mTitle->getPrefixedText() ),
02861                 htmlspecialchars( $fromP ),
02862                 htmlspecialchars( $current->getUserText() )
02863             ) );
02864         }
02865 
02866         // Get the last edit not by this guy...
02867         // Note: these may not be public values
02868         $user = intval( $current->getRawUser() );
02869         $user_text = $dbw->addQuotes( $current->getRawUserText() );
02870         $s = $dbw->selectRow( 'revision',
02871             array( 'rev_id', 'rev_timestamp', 'rev_deleted' ),
02872             array( 'rev_page' => $current->getPage(),
02873                 "rev_user != {$user} OR rev_user_text != {$user_text}"
02874             ), __METHOD__,
02875             array( 'USE INDEX' => 'page_timestamp',
02876                 'ORDER BY' => 'rev_timestamp DESC' )
02877             );
02878         if ( $s === false ) {
02879             // No one else ever edited this page
02880             return array( array( 'cantrollback' ) );
02881         } elseif ( $s->rev_deleted & Revision::DELETED_TEXT || $s->rev_deleted & Revision::DELETED_USER ) {
02882             // Only admins can see this text
02883             return array( array( 'notvisiblerev' ) );
02884         }
02885 
02886         $set = array();
02887         if ( $bot && $guser->isAllowed( 'markbotedits' ) ) {
02888             // Mark all reverted edits as bot
02889             $set['rc_bot'] = 1;
02890         }
02891 
02892         if ( $wgUseRCPatrol ) {
02893             // Mark all reverted edits as patrolled
02894             $set['rc_patrolled'] = 1;
02895         }
02896 
02897         if ( count( $set ) ) {
02898             $dbw->update( 'recentchanges', $set,
02899                 array( /* WHERE */
02900                     'rc_cur_id' => $current->getPage(),
02901                     'rc_user_text' => $current->getUserText(),
02902                     'rc_timestamp > ' . $dbw->addQuotes( $s->rev_timestamp ),
02903                 ), __METHOD__
02904             );
02905         }
02906 
02907         // Generate the edit summary if necessary
02908         $target = Revision::newFromId( $s->rev_id );
02909         if ( empty( $summary ) ) {
02910             if ( $from == '' ) { // no public user name
02911                 $summary = wfMessage( 'revertpage-nouser' );
02912             } else {
02913                 $summary = wfMessage( 'revertpage' );
02914             }
02915         }
02916 
02917         // Allow the custom summary to use the same args as the default message
02918         $args = array(
02919             $target->getUserText(), $from, $s->rev_id,
02920             $wgContLang->timeanddate( wfTimestamp( TS_MW, $s->rev_timestamp ) ),
02921             $current->getId(), $wgContLang->timeanddate( $current->getTimestamp() )
02922         );
02923         if ( $summary instanceof Message ) {
02924             $summary = $summary->params( $args )->inContentLanguage()->text();
02925         } else {
02926             $summary = wfMsgReplaceArgs( $summary, $args );
02927         }
02928 
02929         // Trim spaces on user supplied text
02930         $summary = trim( $summary );
02931 
02932         // Truncate for whole multibyte characters.
02933         $summary = $wgContLang->truncate( $summary, 255 );
02934 
02935         // Save
02936         $flags = EDIT_UPDATE;
02937 
02938         if ( $guser->isAllowed( 'minoredit' ) ) {
02939             $flags |= EDIT_MINOR;
02940         }
02941 
02942         if ( $bot && ( $guser->isAllowedAny( 'markbotedits', 'bot' ) ) ) {
02943             $flags |= EDIT_FORCE_BOT;
02944         }
02945 
02946         // Actually store the edit
02947         $status = $this->doEditContent( $target->getContent(), $summary, $flags, $target->getId(), $guser );
02948 
02949         if ( !$status->isOK() ) {
02950             return $status->getErrorsArray();
02951         }
02952 
02953         if ( !empty( $status->value['revision'] ) ) {
02954             $revId = $status->value['revision']->getId();
02955         } else {
02956             $revId = false;
02957         }
02958 
02959         wfRunHooks( 'ArticleRollbackComplete', array( $this, $guser, $target, $current ) );
02960 
02961         $resultDetails = array(
02962             'summary' => $summary,
02963             'current' => $current,
02964             'target' => $target,
02965             'newid' => $revId
02966         );
02967 
02968         return array();
02969     }
02970 
02982     public static function onArticleCreate( $title ) {
02983         // Update existence markers on article/talk tabs...
02984         if ( $title->isTalkPage() ) {
02985             $other = $title->getSubjectPage();
02986         } else {
02987             $other = $title->getTalkPage();
02988         }
02989 
02990         $other->invalidateCache();
02991         $other->purgeSquid();
02992 
02993         $title->touchLinks();
02994         $title->purgeSquid();
02995         $title->deleteTitleProtection();
02996     }
02997 
03003     public static function onArticleDelete( $title ) {
03004         // Update existence markers on article/talk tabs...
03005         if ( $title->isTalkPage() ) {
03006             $other = $title->getSubjectPage();
03007         } else {
03008             $other = $title->getTalkPage();
03009         }
03010 
03011         $other->invalidateCache();
03012         $other->purgeSquid();
03013 
03014         $title->touchLinks();
03015         $title->purgeSquid();
03016 
03017         // File cache
03018         HTMLFileCache::clearFileCache( $title );
03019         InfoAction::invalidateCache( $title );
03020 
03021         // Messages
03022         if ( $title->getNamespace() == NS_MEDIAWIKI ) {
03023             MessageCache::singleton()->replace( $title->getDBkey(), false );
03024         }
03025 
03026         // Images
03027         if ( $title->getNamespace() == NS_FILE ) {
03028             $update = new HTMLCacheUpdate( $title, 'imagelinks' );
03029             $update->doUpdate();
03030         }
03031 
03032         // User talk pages
03033         if ( $title->getNamespace() == NS_USER_TALK ) {
03034             $user = User::newFromName( $title->getText(), false );
03035             if ( $user ) {
03036                 $user->setNewtalk( false );
03037             }
03038         }
03039 
03040         // Image redirects
03041         RepoGroup::singleton()->getLocalRepo()->invalidateImageRedirect( $title );
03042     }
03043 
03050     public static function onArticleEdit( $title ) {
03051         // Invalidate caches of articles which include this page
03052         DeferredUpdates::addHTMLCacheUpdate( $title, 'templatelinks' );
03053 
03054         // Invalidate the caches of all pages which redirect here
03055         DeferredUpdates::addHTMLCacheUpdate( $title, 'redirect' );
03056 
03057         // Purge squid for this page only
03058         $title->purgeSquid();
03059 
03060         // Clear file cache for this page only
03061         HTMLFileCache::clearFileCache( $title );
03062         InfoAction::invalidateCache( $title );
03063     }
03064 
03073     public function getCategories() {
03074         $id = $this->getId();
03075         if ( $id == 0 ) {
03076             return TitleArray::newFromResult( new FakeResultWrapper( array() ) );
03077         }
03078 
03079         $dbr = wfGetDB( DB_SLAVE );
03080         $res = $dbr->select( 'categorylinks',
03081             array( 'cl_to AS page_title, ' . NS_CATEGORY . ' AS page_namespace' ),
03082             // Have to do that since DatabaseBase::fieldNamesWithAlias treats numeric indexes
03083             // as not being aliases, and NS_CATEGORY is numeric
03084             array( 'cl_from' => $id ),
03085             __METHOD__ );
03086 
03087         return TitleArray::newFromResult( $res );
03088     }
03089 
03096     public function getHiddenCategories() {
03097         $result = array();
03098         $id = $this->getId();
03099 
03100         if ( $id == 0 ) {
03101             return array();
03102         }
03103 
03104         $dbr = wfGetDB( DB_SLAVE );
03105         $res = $dbr->select( array( 'categorylinks', 'page_props', 'page' ),
03106             array( 'cl_to' ),
03107             array( 'cl_from' => $id, 'pp_page=page_id', 'pp_propname' => 'hiddencat',
03108                 'page_namespace' => NS_CATEGORY, 'page_title=cl_to' ),
03109             __METHOD__ );
03110 
03111         if ( $res !== false ) {
03112             foreach ( $res as $row ) {
03113                 $result[] = Title::makeTitle( NS_CATEGORY, $row->cl_to );
03114             }
03115         }
03116 
03117         return $result;
03118     }
03119 
03129     public static function getAutosummary( $oldtext, $newtext, $flags ) {
03130         // NOTE: stub for backwards-compatibility. assumes the given text is wikitext. will break horribly if it isn't.
03131 
03132         ContentHandler::deprecated( __METHOD__, '1.21' );
03133 
03134         $handler = ContentHandler::getForModelID( CONTENT_MODEL_WIKITEXT );
03135         $oldContent = is_null( $oldtext ) ? null : $handler->unserializeContent( $oldtext );
03136         $newContent = is_null( $newtext ) ? null : $handler->unserializeContent( $newtext );
03137 
03138         return $handler->getAutosummary( $oldContent, $newContent, $flags );
03139     }
03140 
03148     public function getAutoDeleteReason( &$hasHistory ) {
03149         return $this->getContentHandler()->getAutoDeleteReason( $this->getTitle(), $hasHistory );
03150     }
03151 
03159     public function updateCategoryCounts( array $added, array $deleted ) {
03160         $that = $this;
03161         $method = __METHOD__;
03162         $dbw = wfGetDB( DB_MASTER );
03163 
03164         // Do this at the end of the commit to reduce lock wait timeouts
03165         $dbw->onTransactionPreCommitOrIdle(
03166             function() use ( $dbw, $that, $method, $added, $deleted ) {
03167                 $ns = $that->getTitle()->getNamespace();
03168 
03169                 $addFields = array( 'cat_pages = cat_pages + 1' );
03170                 $removeFields = array( 'cat_pages = cat_pages - 1' );
03171                 if ( $ns == NS_CATEGORY ) {
03172                     $addFields[] = 'cat_subcats = cat_subcats + 1';
03173                     $removeFields[] = 'cat_subcats = cat_subcats - 1';
03174                 } elseif ( $ns == NS_FILE ) {
03175                     $addFields[] = 'cat_files = cat_files + 1';
03176                     $removeFields[] = 'cat_files = cat_files - 1';
03177                 }
03178 
03179                 if ( count( $added ) ) {
03180                     $insertRows = array();
03181                     foreach ( $added as $cat ) {
03182                         $insertRows[] = array(
03183                             'cat_title'   => $cat,
03184                             'cat_pages'   => 1,
03185                             'cat_subcats' => ( $ns == NS_CATEGORY ) ? 1 : 0,
03186                             'cat_files'   => ( $ns == NS_FILE ) ? 1 : 0,
03187                         );
03188                     }
03189                     $dbw->upsert(
03190                         'category',
03191                         $insertRows,
03192                         array( 'cat_title' ),
03193                         $addFields,
03194                         $method
03195                     );
03196                 }
03197 
03198                 if ( count( $deleted ) ) {
03199                     $dbw->update(
03200                         'category',
03201                         $removeFields,
03202                         array( 'cat_title' => $deleted ),
03203                         $method
03204                     );
03205                 }
03206 
03207                 foreach ( $added as $catName ) {
03208                     $cat = Category::newFromName( $catName );
03209                     wfRunHooks( 'CategoryAfterPageAdded', array( $cat, $that ) );
03210                 }
03211 
03212                 foreach ( $deleted as $catName ) {
03213                     $cat = Category::newFromName( $catName );
03214                     wfRunHooks( 'CategoryAfterPageRemoved', array( $cat, $that ) );
03215                 }
03216             }
03217         );
03218     }
03219 
03225     public function doCascadeProtectionUpdates( ParserOutput $parserOutput ) {
03226         if ( wfReadOnly() || !$this->mTitle->areRestrictionsCascading() ) {
03227             return;
03228         }
03229 
03230         // templatelinks table may have become out of sync,
03231         // especially if using variable-based transclusions.
03232         // For paranoia, check if things have changed and if
03233         // so apply updates to the database. This will ensure
03234         // that cascaded protections apply as soon as the changes
03235         // are visible.
03236 
03237         // Get templates from templatelinks
03238         $id = $this->getId();
03239 
03240         $tlTemplates = array();
03241 
03242         $dbr = wfGetDB( DB_SLAVE );
03243         $res = $dbr->select( array( 'templatelinks' ),
03244             array( 'tl_namespace', 'tl_title' ),
03245             array( 'tl_from' => $id ),
03246             __METHOD__
03247         );
03248 
03249         foreach ( $res as $row ) {
03250             $tlTemplates["{$row->tl_namespace}:{$row->tl_title}"] = true;
03251         }
03252 
03253         // Get templates from parser output.
03254         $poTemplates = array();
03255         foreach ( $parserOutput->getTemplates() as $ns => $templates ) {
03256             foreach ( $templates as $dbk => $id ) {
03257                 $poTemplates["$ns:$dbk"] = true;
03258             }
03259         }
03260 
03261         // Get the diff
03262         $templates_diff = array_diff_key( $poTemplates, $tlTemplates );
03263 
03264         if ( count( $templates_diff ) > 0 ) {
03265             // Whee, link updates time.
03266             // Note: we are only interested in links here. We don't need to get other DataUpdate items from the parser output.
03267             $u = new LinksUpdate( $this->mTitle, $parserOutput, false );
03268             $u->doUpdate();
03269         }
03270     }
03271 
03279     public function getUsedTemplates() {
03280         return $this->mTitle->getTemplateLinksFrom();
03281     }
03282 
03293     public function createUpdates( $rev ) {
03294         wfDeprecated( __METHOD__, '1.18' );
03295         global $wgUser;
03296         $this->doEditUpdates( $rev, $wgUser, array( 'created' => true ) );
03297     }
03298 
03311     public function preSaveTransform( $text, User $user = null, ParserOptions $popts = null ) {
03312         global $wgParser, $wgUser;
03313 
03314         wfDeprecated( __METHOD__, '1.19' );
03315 
03316         $user = is_null( $user ) ? $wgUser : $user;
03317 
03318         if ( $popts === null ) {
03319             $popts = ParserOptions::newFromUser( $user );
03320         }
03321 
03322         return $wgParser->preSaveTransform( $text, $this->mTitle, $user, $popts );
03323     }
03324 
03331     public function isBigDeletion() {
03332         wfDeprecated( __METHOD__, '1.19' );
03333         return $this->mTitle->isBigDeletion();
03334     }
03335 
03342     public function estimateRevisionCount() {
03343         wfDeprecated( __METHOD__, '1.19' );
03344         return $this->mTitle->estimateRevisionCount();
03345     }
03346 
03358     public function updateRestrictions(
03359         $limit = array(), $reason = '', &$cascade = 0, $expiry = array(), User $user = null
03360     ) {
03361         global $wgUser;
03362 
03363         $user = is_null( $user ) ? $wgUser : $user;
03364 
03365         return $this->doUpdateRestrictions( $limit, $expiry, $cascade, $reason, $user )->isOK();
03366     }
03367 
03371     public function quickEdit( $text, $comment = '', $minor = 0 ) {
03372         wfDeprecated( __METHOD__, '1.18' );
03373         global $wgUser;
03374         $this->doQuickEdit( $text, $wgUser, $comment, $minor );
03375     }
03376 
03380     public function viewUpdates() {
03381         wfDeprecated( __METHOD__, '1.18' );
03382         global $wgUser;
03383         $this->doViewUpdates( $wgUser );
03384     }
03385 
03391     public function useParserCache( $oldid ) {
03392         wfDeprecated( __METHOD__, '1.18' );
03393         global $wgUser;
03394         return $this->isParserCacheUsed( ParserOptions::newFromUser( $wgUser ), $oldid );
03395     }
03396 
03404     public function getDeletionUpdates( Content $content = null ) {
03405         if ( !$content ) {
03406             // load content object, which may be used to determine the necessary updates
03407             // XXX: the content may not be needed to determine the updates, then this would be overhead.
03408             $content = $this->getContent( Revision::RAW );
03409         }
03410 
03411         if ( !$content ) {
03412             $updates = array();
03413         } else {
03414             $updates = $content->getDeletionUpdates( $this );
03415         }
03416 
03417         wfRunHooks( 'WikiPageDeletionUpdates', array( $this, $content, &$updates ) );
03418         return $updates;
03419     }
03420 
03421 }
03422 
03423 class PoolWorkArticleView extends PoolCounterWork {
03424 
03428     private $page;
03429 
03433     private $cacheKey;
03434 
03438     private $revid;
03439 
03443     private $parserOptions;
03444 
03448     private $content = null;
03449 
03453     private $parserOutput = false;
03454 
03458     private $isDirty = false;
03459 
03463     private $error = false;
03464 
03474     function __construct( Page $page, ParserOptions $parserOptions, $revid, $useParserCache, $content = null ) {
03475         if ( is_string( $content ) ) { // BC: old style call
03476             $modelId = $page->getRevision()->getContentModel();
03477             $format = $page->getRevision()->getContentFormat();
03478             $content = ContentHandler::makeContent( $content, $page->getTitle(), $modelId, $format );
03479         }
03480 
03481         $this->page = $page;
03482         $this->revid = $revid;
03483         $this->cacheable = $useParserCache;
03484         $this->parserOptions = $parserOptions;
03485         $this->content = $content;
03486         $this->cacheKey = ParserCache::singleton()->getKey( $page, $parserOptions );
03487         parent::__construct( 'ArticleView', $this->cacheKey . ':revid:' . $revid );
03488     }
03489 
03495     public function getParserOutput() {
03496         return $this->parserOutput;
03497     }
03498 
03504     public function getIsDirty() {
03505         return $this->isDirty;
03506     }
03507 
03513     public function getError() {
03514         return $this->error;
03515     }
03516 
03520     function doWork() {
03521         global $wgUseFileCache;
03522 
03523         // @todo several of the methods called on $this->page are not declared in Page, but present
03524         //        in WikiPage and delegated by Article.
03525 
03526         $isCurrent = $this->revid === $this->page->getLatest();
03527 
03528         if ( $this->content !== null ) {
03529             $content = $this->content;
03530         } elseif ( $isCurrent ) {
03531             // XXX: why use RAW audience here, and PUBLIC (default) below?
03532             $content = $this->page->getContent( Revision::RAW );
03533         } else {
03534             $rev = Revision::newFromTitle( $this->page->getTitle(), $this->revid );
03535 
03536             if ( $rev === null ) {
03537                 $content = null;
03538             } else {
03539                 // XXX: why use PUBLIC audience here (default), and RAW above?
03540                 $content = $rev->getContent();
03541             }
03542         }
03543 
03544         if ( $content === null ) {
03545             return false;
03546         }
03547 
03548         // Reduce effects of race conditions for slow parses (bug 46014)
03549         $cacheTime = wfTimestampNow();
03550 
03551         $time = - microtime( true );
03552         $this->parserOutput = $content->getParserOutput( $this->page->getTitle(), $this->revid, $this->parserOptions );
03553         $time += microtime( true );
03554 
03555         // Timing hack
03556         if ( $time > 3 ) {
03557             wfDebugLog( 'slow-parse', sprintf( "%-5.2f %s", $time,
03558                 $this->page->getTitle()->getPrefixedDBkey() ) );
03559         }
03560 
03561         if ( $this->cacheable && $this->parserOutput->isCacheable() ) {
03562             ParserCache::singleton()->save(
03563                 $this->parserOutput, $this->page, $this->parserOptions, $cacheTime );
03564         }
03565 
03566         // Make sure file cache is not used on uncacheable content.
03567         // Output that has magic words in it can still use the parser cache
03568         // (if enabled), though it will generally expire sooner.
03569         if ( !$this->parserOutput->isCacheable() || $this->parserOutput->containsOldMagic() ) {
03570             $wgUseFileCache = false;
03571         }
03572 
03573         if ( $isCurrent ) {
03574             $this->page->doCascadeProtectionUpdates( $this->parserOutput );
03575         }
03576 
03577         return true;
03578     }
03579 
03583     function getCachedWork() {
03584         $this->parserOutput = ParserCache::singleton()->get( $this->page, $this->parserOptions );
03585 
03586         if ( $this->parserOutput === false ) {
03587             wfDebug( __METHOD__ . ": parser cache miss\n" );
03588             return false;
03589         } else {
03590             wfDebug( __METHOD__ . ": parser cache hit\n" );
03591             return true;
03592         }
03593     }
03594 
03598     function fallback() {
03599         $this->parserOutput = ParserCache::singleton()->getDirty( $this->page, $this->parserOptions );
03600 
03601         if ( $this->parserOutput === false ) {
03602             wfDebugLog( 'dirty', "dirty missing\n" );
03603             wfDebug( __METHOD__ . ": no dirty cache\n" );
03604             return false;
03605         } else {
03606             wfDebug( __METHOD__ . ": sending dirty output\n" );
03607             wfDebugLog( 'dirty', "dirty output {$this->cacheKey}\n" );
03608             $this->isDirty = true;
03609             return true;
03610         }
03611     }
03612 
03617     function error( $status ) {
03618         $this->error = $status;
03619         return false;
03620     }
03621 }