MediaWiki
REL1_22
|
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 }