X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FArticle.php;h=368210f4ffb958b86d0be88f48d8e044a5374ad9;hb=da174ad7c49abb560a9a571de2580ea2d59d9a99;hp=4ede20f3ec6965b245b2de62089ef0990dc5d608;hpb=c0d6b5adf924e9dbc90a8d8dcdb1f9178166c883;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Article.php b/includes/Article.php index 4ede20f3ec..368210f4ff 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -8,10 +8,6 @@ * Need the CacheManager to be loaded */ require_once( 'CacheManager.php' ); -require_once( 'Revision.php' ); - -$wgArticleCurContentFields = false; -$wgArticleOldContentFields = false; /** * Class representing a MediaWiki article and history. @@ -23,27 +19,34 @@ $wgArticleOldContentFields = false; * @package MediaWiki */ class Article { - /**#@+ - * @access private - */ - var $mContent, $mContentLoaded; - var $mUser, $mTimestamp, $mUserText; - var $mCounter, $mComment, $mGoodAdjustment, $mTotalAdjustment; - var $mMinorEdit, $mRedirectedFrom; - var $mTouched, $mFileCache, $mTitle; - var $mId, $mTable; - var $mForUpdate; - var $mOldId; - var $mRevIdFetched; - var $mRevision; - var $mRedirectUrl; - var $mLatest; - /**#@-*/ + /**@{{ + * @private + */ + var $mComment; //!< + var $mContent; //!< + var $mContentLoaded; //!< + var $mCounter; //!< + var $mForUpdate; //!< + var $mGoodAdjustment; //!< + var $mLatest; //!< + var $mMinorEdit; //!< + var $mOldId; //!< + var $mRedirectedFrom; //!< + var $mRedirectUrl; //!< + var $mRevIdFetched; //!< + var $mRevision; //!< + var $mTimestamp; //!< + var $mTitle; //!< + var $mTotalAdjustment; //!< + var $mTouched; //!< + var $mUser; //!< + var $mUserText; //!< + /**@}}*/ /** * Constructor and clear the article - * @param Title &$title - * @param integer $oldId Revision ID, null to fetch from request, zero for current + * @param $title Reference to a Title object. + * @param $oldId Integer revision ID, null to fetch from request, zero for current */ function Article( &$title, $oldId = null ) { $this->mTitle =& $title; @@ -54,7 +57,7 @@ class Article { /** * Tell the page view functions that this view was redirected * from another page on the wiki. - * @param Title $from + * @param $from Title object. */ function setRedirectedFrom( $from ) { $this->mRedirectedFrom = $from; @@ -110,7 +113,7 @@ class Article { /** * Clear the object - * @access private + * @private */ function clear() { $this->mDataLoaded = false; @@ -119,7 +122,7 @@ class Article { $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded $this->mRedirectedFrom = null; # Title object if set $this->mUserText = - $this->mTimestamp = $this->mComment = $this->mFileCache = ''; + $this->mTimestamp = $this->mComment = ''; $this->mGoodAdjustment = $this->mTotalAdjustment = 0; $this->mTouched = '19700101000000'; $this->mForUpdate = false; @@ -133,8 +136,8 @@ class Article { * Note that getContent/loadContent do not follow redirects anymore. * If you need to fetch redirectable content easily, try * the shortcut in Article::followContent() - * - * @fixme There are still side-effects in this! + * FIXME + * @todo There are still side-effects in this! * In general, you should use the Revision class, not Article, * to fetch text for purposes other than page views. * @@ -143,27 +146,10 @@ class Article { function getContent() { global $wgRequest, $wgUser, $wgOut; - # Get variables from query string :P - $action = $wgRequest->getText( 'action', 'view' ); - $section = $wgRequest->getText( 'section' ); - $preload = $wgRequest->getText( 'preload' ); - - $fname = 'Article::getContent'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); if ( 0 == $this->getID() ) { - if ( 'edit' == $action ) { - wfProfileOut( $fname ); - - # If requested, preload some text. - $text=$this->getPreloadedText($preload); - - # We used to put MediaWiki:Newarticletext here if - # $text was empty at this point. - # This is now shown above the edit box instead. - return $text; - } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { @@ -175,119 +161,26 @@ class Article { return "
$ret
"; } else { $this->loadContent(); - if($action=='edit') { - if($section!='') { - if($section=='new') { - wfProfileOut( $fname ); - $text=$this->getPreloadedText($preload); - return $text; - } - - # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML - # comments to be stripped as well) - $rv=$this->getSection($this->mContent,$section); - wfProfileOut( $fname ); - return $rv; - } - } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $this->mContent; } } - /** - * Get the contents of a page from its title and remove includeonly tags - * - * @param string The title of the page - * @return string The contents of the page - */ - function getPreloadedText($preload) { - if ( $preload === '' ) - return ''; - else { - $preloadTitle = Title::newFromText( $preload ); - if ( isset( $preloadTitle ) && $preloadTitle->userCanRead() ) { - $rev=Revision::newFromTitle($preloadTitle); - if ( is_object( $rev ) ) { - $text = $rev->getText(); - // TODO FIXME: AAAAAAAAAAA, this shouldn't be implementing - // its own mini-parser! -ævar - $text = preg_replace( '~~', '', $text ); - return $text; - } else - return ''; - } - } - } - /** * This function returns the text of a section, specified by a number ($section). - * A section is text under a heading like == Heading == or

Heading

, or + * A section is text under a heading like == Heading == or \Heading\, or * the first section before any such heading (section 0). * * If a section contains subsections, these are also returned. * - * @param string $text text to look in - * @param integer $section section number + * @param $text String: text to look in + * @param $section Integer: section number * @return string text of the requested section + * @deprecated */ function getSection($text,$section) { - - # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML - # comments to be stripped as well) - $striparray=array(); - $parser=new Parser(); - $parser->mOutputType=OT_WIKI; - $parser->mOptions = new ParserOptions(); - $striptext=$parser->strip($text, $striparray, true); - - # now that we can be sure that no pseudo-sections are in the source, - # split it up by section - $secs = - preg_split( - '/(^=+.+?=+|^.*?<\/h[1-6].*?>)(?!\S)/mi', - $striptext, -1, - PREG_SPLIT_DELIM_CAPTURE); - if($section==0) { - $rv=$secs[0]; - } else { - $headline=$secs[$section*2-1]; - preg_match( '/^(=+).+?=+|^.*?<\/h[1-6].*?>(?!\S)/mi',$headline,$matches); - $hlevel=$matches[1]; - - # translate wiki heading into level - if(strpos($hlevel,'=')!==false) { - $hlevel=strlen($hlevel); - } - - $rv=$headline. $secs[$section*2]; - $count=$section+1; - - $break=false; - while(!empty($secs[$count*2-1]) && !$break) { - - $subheadline=$secs[$count*2-1]; - preg_match( '/^(=+).+?=+|^.*?<\/h[1-6].*?>(?!\S)/mi',$subheadline,$matches); - $subhlevel=$matches[1]; - if(strpos($subhlevel,'=')!==false) { - $subhlevel=strlen($subhlevel); - } - if($subhlevel > $hlevel) { - $rv.=$subheadline.$secs[$count*2]; - } - if($subhlevel <= $hlevel) { - $break=true; - } - $count++; - - } - } - # reinsert stripped tags - $rv=$parser->unstrip($rv,$striparray); - $rv=$parser->unstripNoWiki($rv,$striparray); - $rv=trim($rv); - return $rv; - + global $wgParser; + return $wgParser->getSection( $text, $section ); } /** @@ -327,8 +220,10 @@ class Article { # TODO } } - $lastid = $oldid; + # unused: + # $lastid = $oldid; } + if ( !$oldid ) { $oldid = 0; } @@ -344,8 +239,6 @@ class Article { # Query variables :P $oldid = $this->getOldID(); - $fname = 'Article::loadContent'; - # Pre-fill content with error message so that if something # fails we'll have something telling us what we intended. @@ -360,7 +253,7 @@ class Article { * Fetch a page record with the given conditions * @param Database $dbr * @param array $conditions - * @access private + * @private */ function pageData( &$dbr, $conditions ) { $fields = array( @@ -407,7 +300,7 @@ class Article { * some source. * * @param object $data - * @access private + * @private */ function loadPageData( $data = 'fromdb' ) { if ( $data === 'fromdb' ) { @@ -449,7 +342,6 @@ class Article { } $dbr =& $this->getDB(); - $fname = 'Article::fetchContent'; # Pre-fill content with error message so that if something # fails we'll have something telling us what we intended. @@ -462,12 +354,12 @@ class Article { if( $oldid ) { $revision = Revision::newFromId( $oldid ); if( is_null( $revision ) ) { - wfDebug( "$fname failed to retrieve specified revision, id $oldid\n" ); + wfDebug( __METHOD__." failed to retrieve specified revision, id $oldid\n" ); return false; } $data = $this->pageDataFromId( $dbr, $revision->getPage() ); if( !$data ) { - wfDebug( "$fname failed to get page data linked to revision id $oldid\n" ); + wfDebug( __METHOD__." failed to get page data linked to revision id $oldid\n" ); return false; } $this->mTitle = Title::makeTitle( $data->page_namespace, $data->page_title ); @@ -476,21 +368,21 @@ class Article { if( !$this->mDataLoaded ) { $data = $this->pageDataFromTitle( $dbr, $this->mTitle ); if( !$data ) { - wfDebug( "$fname failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" ); + wfDebug( __METHOD__." failed to find page data for title " . $this->mTitle->getPrefixedText() . "\n" ); return false; } $this->loadPageData( $data ); } $revision = Revision::newFromId( $this->mLatest ); if( is_null( $revision ) ) { - wfDebug( "$fname failed to retrieve current page, rev_id {$data->page_latest}\n" ); + wfDebug( __METHOD__." failed to retrieve current page, rev_id {$data->page_latest}\n" ); return false; } } // FIXME: Horrible, horrible! This content-loading interface just plain sucks. // We should instead work with the Revision object when we need it... - $this->mContent = $revision->userCan( MW_REV_DELETED_TEXT ) ? $revision->getRawText() : ""; + $this->mContent = $revision->userCan( Revision::DELETED_TEXT ) ? $revision->getRawText() : ""; //$this->mContent = $revision->getText(); $this->mUser = $revision->getUser(); @@ -510,7 +402,7 @@ class Article { /** * Read/write accessor to select FOR UPDATE * - * @param mixed $x + * @param $x Mixed: FIXME */ function forUpdate( $x = NULL ) { return wfSetVar( $this->mForUpdate, $x ); @@ -529,9 +421,9 @@ class Article { /** * Get options for all SELECT statements * - * @param array $options an optional options array which'll be appended to + * @param $options Array: an optional options array which'll be appended to * the default - * @return array Options + * @return Array: options */ function getSelectOptions( $options = '' ) { if ( $this->mForUpdate ) { @@ -583,15 +475,15 @@ class Article { * Determine whether a page would be suitable for being counted as an * article in the site_stats table based on the title & its content * - * @param string $text Text to analyze + * @param $text String: text to analyze * @return bool */ function isCountable( $text ) { - global $wgUseCommaCount; + global $wgUseCommaCount, $wgContentNamespaces; $token = $wgUseCommaCount ? ',' : '[['; return - $this->mTitle->getNamespace() == NS_MAIN + array_search( $this->mTitle->getNamespace(), $wgContentNamespaces ) !== false && ! $this->isRedirect( $text ) && in_string( $token, $text ); } @@ -599,7 +491,7 @@ class Article { /** * Tests if the article text represents a redirect * - * @param string $text + * @param $text String: FIXME * @return bool */ function isRedirect( $text = false ) { @@ -626,7 +518,7 @@ class Article { /** * Loads everything except the text * This isn't necessary for all uses, so it's only done if needed. - * @access private + * @private */ function loadLastEdit() { if ( -1 != $this->mUser ) @@ -680,9 +572,12 @@ class Article { return $this->mRevIdFetched; } + /** + * @todo Document, fixme $offset never used. + * @param $limit Integer: default 0. + * @param $offset Integer: default 0. + */ function getContributors($limit = 0, $offset = 0) { - $fname = 'Article::getContributors'; - # XXX: this is expensive; cache this info somewhere. $title = $this->mTitle; @@ -705,7 +600,7 @@ class Article { if ($limit > 0) { $sql .= ' LIMIT '.$limit; } $sql .= ' '. $this->getSelectOptions(); - $res = $dbr->query($sql, $fname); + $res = $dbr->query($sql, __METHOD__); while ( $line = $dbr->fetchObject( $res ) ) { $contribs[] = array($line->rev_user, $line->rev_user_text, $line->user_real_name); @@ -722,19 +617,21 @@ class Article { function view() { global $wgUser, $wgOut, $wgRequest, $wgContLang; global $wgEnableParserCache, $wgStylePath, $wgUseRCPatrol, $wgParser; - global $wgUseTrackbacks; + global $wgUseTrackbacks, $wgNamespaceRobotPolicies; $sk = $wgUser->getSkin(); - $fname = 'Article::view'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); + $parserCache =& ParserCache::singleton(); + $ns = $this->mTitle->getNamespace(); # shortcut + # Get variables from query string $oldid = $this->getOldID(); # getOldID may want us to redirect somewhere else if ( $this->mRedirectUrl ) { $wgOut->redirect( $this->mRedirectUrl ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return; } @@ -743,7 +640,13 @@ class Article { $rdfrom = $wgRequest->getVal( 'rdfrom' ); $wgOut->setArticleFlag( true ); - $wgOut->setRobotpolicy( 'index,follow' ); + if ( isset( $wgNamespaceRobotPolicies[$ns] ) ) { + $policy = $wgNamespaceRobotPolicies[$ns]; + } else { + $policy = 'index,follow'; + } + $wgOut->setRobotpolicy( $policy ); + # If we got diff and oldid in the query, we want to see a # diff page instead of the article. @@ -760,7 +663,7 @@ class Article { # Run view updates for current revision only $this->viewUpdates(); } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return; } @@ -768,16 +671,17 @@ class Article { $wgOut->setETag($parserCache->getETag($this, $wgUser)); if( $wgOut->checkLastModified( $this->mTouched ) ){ - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return; } else if ( $this->tryFileCache() ) { # tell wgOut that output is taken care of $wgOut->disable(); $this->viewUpdates(); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return; } } + # Should the parser cache be used? $pcache = $wgEnableParserCache && intval( $wgUser->getOption( 'stubthreshold' ) ) == 0 && @@ -815,6 +719,7 @@ class Article { $outputDone = false; if ( $pcache ) { if ( $wgOut->tryParserCache( $this, $wgUser ) ) { + wfRunHooks( 'ArticleViewHeader', array( &$this ) ); $outputDone = true; } } @@ -846,8 +751,8 @@ class Article { // FIXME: This would be a nice place to load the 'no such page' text. } else { $this->setOldSubtitle( isset($this->mOldId) ? $this->mOldId : $oldid ); - if( $this->mRevision->isDeleted( MW_REV_DELETED_TEXT ) ) { - if( !$this->mRevision->userCan( MW_REV_DELETED_TEXT ) ) { + if( $this->mRevision->isDeleted( Revision::DELETED_TEXT ) ) { + if( !$this->mRevision->userCan( Revision::DELETED_TEXT ) ) { $wgOut->addWikiText( wfMsg( 'rev-deleted-text-permission' ) ); $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); return; @@ -870,7 +775,7 @@ class Article { # wrap user css and user js in pre and don't parse # XXX: use $this->mTitle->usCssJsSubpage() when php is fixed/ a workaround is found if ( - $this->mTitle->getNamespace() == NS_USER && + $ns == NS_USER && preg_match('/\\/[\\w]+\\.(css|js)$/', $this->mTitle->getDBkey()) ) { $wgOut->addWikiText( wfMsg('clearyourcache')); @@ -879,10 +784,12 @@ class Article { # Display redirect $imageDir = $wgContLang->isRTL() ? 'rtl' : 'ltr'; $imageUrl = $wgStylePath.'/common/images/redirect' . $imageDir . '.png'; - if( !$wasRedirected ) { + # Don't overwrite the subtitle if this was an old revision + if( !$wasRedirected && $this->isCurrent() ) { $wgOut->setSubtitle( wfMsgHtml( 'redirectpagesub' ) ); } $targetUrl = $rt->escapeLocalURL(); + # fixme unused $titleText : $titleText = htmlspecialchars( $rt->getPrefixedText() ); $link = $sk->makeLinkObj( $rt ); @@ -896,16 +803,15 @@ class Article { $wgOut->addPrimaryWikiText( $text, $this ); } else { # Display content, don't attempt to save to parser cache - # Don't show section-edit links on old revisions... this way lies madness. if( !$this->isCurrent() ) { - $oldEditSectionSetting = $wgOut->mParserOptions->setEditSection( false ); + $oldEditSectionSetting = $wgOut->parserOptions()->setEditSection( false ); } # Display content and don't save to parser cache $wgOut->addPrimaryWikiText( $text, $this, false ); if( !$this->isCurrent() ) { - $wgOut->mParserOptions->setEditSection( $oldEditSectionSetting ); + $wgOut->parserOptions()->setEditSection( $oldEditSectionSetting ); } } } @@ -914,9 +820,9 @@ class Article { if( empty( $t ) ) { $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); } - + # check if we're displaying a [[User talk:x.x.x.x]] anonymous talk page - if( $this->mTitle->getNamespace() == NS_USER_TALK && + if( $ns == NS_USER_TALK && User::isIP( $this->mTitle->getText() ) ) { $wgOut->addWikiText( wfMsg('anontalkpagetext') ); } @@ -938,7 +844,7 @@ class Article { $this->addTrackbacks(); $this->viewUpdates(); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); } function addTrackbacks() { @@ -957,7 +863,7 @@ class Article { $tbtext = ""; while ($o = $dbr->fetchObject($tbs)) { $rmvtxt = ""; - if ($wgUser->isSysop()) { + if ($wgUser->isAllowed( 'trackback' )) { $delurl = $this->mTitle->getFullURL("action=deletetrackback&tbid=" . $o->tb_id . "&token=" . $wgUser->editToken()); $rmvtxt = wfMsg('trackbackremove', $delurl); @@ -981,7 +887,7 @@ class Article { } if ((!$wgUser->isAllowed('delete'))) { - $wgOut->sysopRequired(); + $wgOut->permissionRequired( 'delete' ); return; } @@ -1058,11 +964,10 @@ class Article { * @param Database $dbw * @param string $restrictions * @return int The newly created page_id key - * @access private + * @private */ function insertOn( &$dbw, $restrictions = '' ) { - $fname = 'Article::insertOn'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); $page_id = $dbw->nextSequenceValue( 'page_page_id_seq' ); $dbw->insert( 'page', array( @@ -1077,12 +982,12 @@ class Article { 'page_touched' => $dbw->timestamp(), 'page_latest' => 0, # Fill this in shortly... 'page_len' => 0, # Fill this in shortly... - ), $fname ); + ), __METHOD__ ); $newid = $dbw->insertId(); $this->mTitle->resetArticleId( $newid ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $newid; } @@ -1097,11 +1002,10 @@ class Article { * Giving 0 indicates the new page flag should * be set on. * @return bool true on success, false on failure - * @access private + * @private */ function updateRevisionOn( &$dbw, $revision, $lastRevision = null ) { - $fname = 'Article::updateToRevision'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); $conditions = array( 'page_id' => $this->getId() ); if( !is_null( $lastRevision ) ) { @@ -1119,9 +1023,9 @@ class Article { 'page_len' => strlen( $text ), ), $conditions, - $fname ); + __METHOD__ ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return ( $dbw->affectedRows() != 0 ); } @@ -1133,8 +1037,7 @@ class Article { * @param Revision $revision */ function updateIfNewerOn( &$dbw, $revision ) { - $fname = 'Article::updateIfNewerOn'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); $row = $dbw->selectRow( array( 'revision', 'page' ), @@ -1142,10 +1045,10 @@ class Article { array( 'page_id' => $this->getId(), 'page_latest=rev_id' ), - $fname ); + __METHOD__ ); if( $row ) { if( wfTimestamp(TS_MW, $row->rev_timestamp) >= $revision->getTimestamp() ) { - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return false; } $prev = $row->rev_id; @@ -1155,120 +1058,19 @@ class Article { } $ret = $this->updateRevisionOn( $dbw, $revision, $prev ); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $ret; } - /** - * Insert a new article into the database - * @access private - */ - function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) { - global $wgUser; - - $fname = 'Article::insertNewArticle'; - wfProfileIn( $fname ); - - if( !wfRunHooks( 'ArticleSave', array( &$this, &$wgUser, &$text, - &$summary, &$isminor, &$watchthis, NULL ) ) ) { - wfDebug( "$fname: ArticleSave hook aborted save!\n" ); - wfProfileOut( $fname ); - return false; - } - - $ns = $this->mTitle->getNamespace(); - $ttl = $this->mTitle->getDBkey(); - - # If this is a comment, add the summary as headline - if($comment && $summary!="") { - $text="== {$summary} ==\n\n".$text; - } - $text = $this->preSaveTransform( $text ); - - - # Set statistics members - # We work out if it's countable after PST to avoid counter drift - # when articles are created with {{subst:}} - $this->mGoodAdjustment = (int)$this->isCountable( $text ); - $this->mTotalAdjustment = 1; - - /* Silently ignore minoredit if not allowed */ - $isminor = $isminor && $wgUser->isAllowed('minoredit'); - $now = wfTimestampNow(); - - $dbw =& wfGetDB( DB_MASTER ); - - # Add the page record; stake our claim on this title! - $newid = $this->insertOn( $dbw ); - - # Save the revision text... - $revision = new Revision( array( - 'page' => $newid, - 'comment' => $summary, - 'minor_edit' => $isminor, - 'text' => $text - ) ); - $revisionId = $revision->insertOn( $dbw ); - - $this->mTitle->resetArticleID( $newid ); - - # Update the page record with revision data - $this->updateRevisionOn( $dbw, $revision, 0 ); - - Article::onArticleCreate( $this->mTitle ); - if(!$suppressRC) { - require_once( 'RecentChange.php' ); - $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, 'default', - '', strlen( $text ), $revisionId ); - # Mark as patrolled if the user can and has the option set - if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) { - RecentChange::markPatrolled( $rcid ); - } - } - - if ($watchthis) { - if(!$this->mTitle->userIsWatching()) $this->doWatch(); - } else { - if ( $this->mTitle->userIsWatching() ) { - $this->doUnwatch(); - } - } - - # The talk page isn't in the regular link tables, so we need to update manually: - $talkns = $ns ^ 1; # talk -> normal; normal -> talk - $dbw->update( 'page', - array( 'page_touched' => $dbw->timestamp($now) ), - array( 'page_namespace' => $talkns, - 'page_title' => $ttl ), - $fname ); - - # standard deferred updates - $this->editUpdates( $text, $summary, $isminor, $now, $revisionId ); - - $oldid = 0; # new article - $this->showArticle( $text, wfMsg( 'newarticle' ), false, $isminor, $now, $summary, $oldid ); - - wfRunHooks( 'ArticleInsertComplete', array( &$this, &$wgUser, $text, - $summary, $isminor, - $watchthis, NULL ) ); - wfRunHooks( 'ArticleSaveComplete', array( &$this, &$wgUser, $text, - $summary, $isminor, - $watchthis, NULL ) ); - wfProfileOut( $fname ); - } - - function getTextOfLastEditWithSectionReplacedOrAdded($section, $text, $summary = '', $edittime = NULL) { - $this->replaceSection( $section, $text, $summary, $edittime ); - } - /** * @return string Complete article text, or null if error */ function replaceSection($section, $text, $summary = '', $edittime = NULL) { - $fname = 'Article::replaceSection'; - wfProfileIn( $fname ); - - if ($section != '') { + wfProfileIn( __METHOD__ ); + + if( $section == '' ) { + // Whole-page edit; let the text through unmolested. + } else { if( is_null( $edittime ) ) { $rev = Revision::newFromTitle( $this->mTitle ); } else { @@ -1286,256 +1088,316 @@ class Article { if($summary) $subject="== {$summary} ==\n\n"; $text=$oldtext."\n\n".$subject.$text; } else { + global $wgParser; + $text = $wgParser->replaceSection( $oldtext, $section, $text ); + } + } - # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML - # comments to be stripped as well) - $striparray=array(); - $parser=new Parser(); - $parser->mOutputType=OT_WIKI; - $parser->mOptions = new ParserOptions(); - $oldtext=$parser->strip($oldtext, $striparray, true); - - # now that we can be sure that no pseudo-sections are in the source, - # split it up - # Unfortunately we can't simply do a preg_replace because that might - # replace the wrong section, so we have to use the section counter instead - $secs=preg_split('/(^=+.+?=+|^.*?<\/h[1-6].*?>)(?!\S)/mi', - $oldtext,-1,PREG_SPLIT_DELIM_CAPTURE); - $secs[$section*2]=$text."\n\n"; // replace with edited - - # section 0 is top (intro) section - if($section!=0) { - - # headline of old section - we need to go through this section - # to determine if there are any subsections that now need to - # be erased, as the mother section has been replaced with - # the text of all subsections. - $headline=$secs[$section*2-1]; - preg_match( '/^(=+).+?=+|^.*?<\/h[1-6].*?>(?!\S)/mi',$headline,$matches); - $hlevel=$matches[1]; - - # determine headline level for wikimarkup headings - if(strpos($hlevel,'=')!==false) { - $hlevel=strlen($hlevel); - } + wfProfileOut( __METHOD__ ); + return $text; + } - $secs[$section*2-1]=''; // erase old headline - $count=$section+1; - $break=false; - while(!empty($secs[$count*2-1]) && !$break) { - - $subheadline=$secs[$count*2-1]; - preg_match( - '/^(=+).+?=+|^.*?<\/h[1-6].*?>(?!\S)/mi',$subheadline,$matches); - $subhlevel=$matches[1]; - if(strpos($subhlevel,'=')!==false) { - $subhlevel=strlen($subhlevel); - } - if($subhlevel > $hlevel) { - // erase old subsections - $secs[$count*2-1]=''; - $secs[$count*2]=''; - } - if($subhlevel <= $hlevel) { - $break=true; - } - $count++; + /** + * @deprecated use Article::doEdit() + */ + function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) { + $flags = EDIT_NEW | EDIT_DEFER_UPDATES | + ( $isminor ? EDIT_MINOR : 0 ) | + ( $suppressRC ? EDIT_SUPPRESS_RC : 0 ); - } + # If this is a comment, add the summary as headline + if ( $comment && $summary != "" ) { + $text = "== {$summary} ==\n\n".$text; + } + + $this->doEdit( $text, $summary, $flags ); + $dbw =& wfGetDB( DB_MASTER ); + if ($watchthis) { + if (!$this->mTitle->userIsWatching()) { + $dbw->begin(); + $this->doWatch(); + $dbw->commit(); + } + } else { + if ( $this->mTitle->userIsWatching() ) { + $dbw->begin(); + $this->doUnwatch(); + $dbw->commit(); + } + } + $this->doRedirect( $this->isRedirect( $text ) ); + } + + /** + * @deprecated use Article::doEdit() + */ + function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) { + $flags = EDIT_UPDATE | EDIT_DEFER_UPDATES | + ( $minor ? EDIT_MINOR : 0 ) | + ( $forceBot ? EDIT_FORCE_BOT : 0 ); + + $good = $this->doEdit( $text, $summary, $flags ); + if ( $good ) { + $dbw =& wfGetDB( DB_MASTER ); + if ($watchthis) { + if (!$this->mTitle->userIsWatching()) { + $dbw->begin(); + $this->doWatch(); + $dbw->commit(); + } + } else { + if ( $this->mTitle->userIsWatching() ) { + $dbw->begin(); + $this->doUnwatch(); + $dbw->commit(); } - $text=join('',$secs); - # reinsert the stuff that we stripped out earlier - $text=$parser->unstrip($text,$striparray); - $text=$parser->unstripNoWiki($text,$striparray); } + $this->doRedirect( $this->isRedirect( $text ), $sectionanchor ); } - wfProfileOut( $fname ); - return $text; + return $good; } /** - * Change an existing article. Puts the previous version back into the old table, updates RC - * and all necessary caches, mostly via the deferred update array. + * Article::doEdit() + * + * Change an existing article or create a new article. Updates RC and all necessary caches, + * optionally via the deferred update array. + * + * $wgUser must be set before calling this function. * - * It is possible to call this function from a command-line script, but note that you should - * first set $wgUser, and clean up $wgDeferredUpdates after each edit. + * @param string $text New text + * @param string $summary Edit summary + * @param integer $flags bitfield: + * EDIT_NEW + * Article is known or assumed to be non-existent, create a new one + * EDIT_UPDATE + * Article is known or assumed to be pre-existing, update it + * EDIT_MINOR + * Mark this edit minor, if the user is allowed to do so + * EDIT_SUPPRESS_RC + * Do not log the change in recentchanges + * EDIT_FORCE_BOT + * Mark the edit a "bot" edit regardless of user rights + * EDIT_DEFER_UPDATES + * Defer some of the updates until the end of index.php + * + * If neither EDIT_NEW nor EDIT_UPDATE is specified, the status of the article will be detected. + * If EDIT_UPDATE is specified and the article doesn't exist, the function will return false. If + * EDIT_NEW is specified and the article does exist, a duplicate key error will cause an exception + * to be thrown from the Database. These two conditions are also possible with auto-detection due + * to MediaWiki's performance-optimised locking strategy. + * + * @return bool success */ - function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) { - global $wgUser, $wgDBtransactions, $wgUseSquid; - global $wgPostCommitUpdateList, $wgUseFileCache; + function doEdit( $text, $summary, $flags = 0 ) { + global $wgUser, $wgDBtransactions; - $fname = 'Article::updateArticle'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); $good = true; + if ( !($flags & EDIT_NEW) && !($flags & EDIT_UPDATE) ) { + $aid = $this->mTitle->getArticleID( GAID_FOR_UPDATE ); + if ( $aid ) { + $flags |= EDIT_UPDATE; + } else { + $flags |= EDIT_NEW; + } + } + if( !wfRunHooks( 'ArticleSave', array( &$this, &$wgUser, &$text, - &$summary, &$minor, - &$watchthis, &$sectionanchor ) ) ) { - wfDebug( "$fname: ArticleSave hook aborted save!\n" ); - wfProfileOut( $fname ); + &$summary, $flags & EDIT_MINOR, + null, null, &$flags ) ) ) + { + wfDebug( __METHOD__ . ": ArticleSave hook aborted save!\n" ); + wfProfileOut( __METHOD__ ); return false; } - $isminor = $minor && $wgUser->isAllowed('minoredit'); - $redir = (int)$this->isRedirect( $text ); + # Silently ignore EDIT_MINOR if not allowed + $isminor = ( $flags & EDIT_MINOR ) && $wgUser->isAllowed('minoredit'); + $bot = $wgUser->isAllowed( 'bot' ) || ( $flags & EDIT_FORCE_BOT ); $text = $this->preSaveTransform( $text ); + $dbw =& wfGetDB( DB_MASTER ); $now = wfTimestampNow(); + + if ( $flags & EDIT_UPDATE ) { + # Update article, but only if changed. - # Update article, but only if changed. + # Make sure the revision is either completely inserted or not inserted at all + if( !$wgDBtransactions ) { + $userAbort = ignore_user_abort( true ); + } - # It's important that we either rollback or complete, otherwise an attacker could - # overwrite cur entries by sending precisely timed user aborts. Random bored users - # could conceivably have the same effect, especially if cur is locked for long periods. - if( !$wgDBtransactions ) { - $userAbort = ignore_user_abort( true ); - } + $oldtext = $this->getContent(); + $oldsize = strlen( $oldtext ); + $newsize = strlen( $text ); + $lastRevision = 0; + $revisionId = 0; + + if ( 0 != strcmp( $text, $oldtext ) ) { + $this->mGoodAdjustment = (int)$this->isCountable( $text ) + - (int)$this->isCountable( $oldtext ); + $this->mTotalAdjustment = 0; + + $lastRevision = $dbw->selectField( + 'page', 'page_latest', array( 'page_id' => $this->getId() ) ); - $oldtext = $this->getContent(); - $oldsize = strlen( $oldtext ); - $newsize = strlen( $text ); - $lastRevision = 0; - $revisionId = 0; + if ( !$lastRevision ) { + # Article gone missing + wfDebug( __METHOD__.": EDIT_UPDATE specified but article doesn't exist\n" ); + wfProfileOut( __METHOD__ ); + return false; + } + + $revision = new Revision( array( + 'page' => $this->getId(), + 'comment' => $summary, + 'minor_edit' => $isminor, + 'text' => $text + ) ); + + $dbw->begin(); + $revisionId = $revision->insertOn( $dbw ); + + # Update page + $ok = $this->updateRevisionOn( $dbw, $revision, $lastRevision ); + + if( !$ok ) { + /* Belated edit conflict! Run away!! */ + $good = false; + $dbw->rollback(); + } else { + # Update recentchanges + if( !( $flags & EDIT_SUPPRESS_RC ) ) { + $rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary, + $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize, + $revisionId ); + + # Mark as patrolled if the user can do so and has it set in their options + if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) { + RecentChange::markPatrolled( $rcid ); + } + } + $dbw->commit(); + } + } else { + // Keep the same revision ID, but do some updates on it + $revisionId = $this->getRevIdFetched(); + // Update page_touched, this is usually implicit in the page update + // Other cache updates are done in onArticleEdit() + $this->mTitle->invalidateCache(); + } + + if( !$wgDBtransactions ) { + ignore_user_abort( $userAbort ); + } + + if ( $good ) { + # Invalidate cache of this article and all pages using this article + # as a template. Partly deferred. + Article::onArticleEdit( $this->mTitle ); + + # Update links tables, site stats, etc. + $changed = ( strcmp( $oldtext, $text ) != 0 ); + $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, $changed ); + } + } else { + # Create new article + + # Set statistics members + # We work out if it's countable after PST to avoid counter drift + # when articles are created with {{subst:}} + $this->mGoodAdjustment = (int)$this->isCountable( $text ); + $this->mTotalAdjustment = 1; - if ( 0 != strcmp( $text, $oldtext ) ) { - $this->mGoodAdjustment = (int)$this->isCountable( $text ) - - (int)$this->isCountable( $oldtext ); - $this->mTotalAdjustment = 0; - $now = wfTimestampNow(); + $dbw->begin(); - $lastRevision = $dbw->selectField( - 'page', 'page_latest', array( 'page_id' => $this->getId() ) ); + # Add the page record; stake our claim on this title! + # This will fail with a database query exception if the article already exists + $newid = $this->insertOn( $dbw ); + # Save the revision text... $revision = new Revision( array( - 'page' => $this->getId(), + 'page' => $newid, 'comment' => $summary, 'minor_edit' => $isminor, 'text' => $text ) ); - - $dbw->immediateCommit(); - $dbw->begin(); $revisionId = $revision->insertOn( $dbw ); - # Update page - $ok = $this->updateRevisionOn( $dbw, $revision, $lastRevision ); + $this->mTitle->resetArticleID( $newid ); - if( !$ok ) { - /* Belated edit conflict! Run away!! */ - $good = false; - $dbw->rollback(); - } else { - # Update recentchanges and purge cache and whatnot - require_once( 'RecentChange.php' ); - $bot = (int)($wgUser->isBot() || $forceBot); - $rcid = RecentChange::notifyEdit( $now, $this->mTitle, $isminor, $wgUser, $summary, - $lastRevision, $this->getTimestamp(), $bot, '', $oldsize, $newsize, - $revisionId ); - - # Mark as patrolled if the user can do so and has it set in their options + # Update the page record with revision data + $this->updateRevisionOn( $dbw, $revision, 0 ); + + if( !( $flags & EDIT_SUPPRESS_RC ) ) { + $rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot, + '', strlen( $text ), $revisionId ); + # Mark as patrolled if the user can and has the option set if( $wgUser->isAllowed( 'patrol' ) && $wgUser->getOption( 'autopatrol' ) ) { RecentChange::markPatrolled( $rcid ); } - - $dbw->commit(); - - // Update caches outside the main transaction - Article::onArticleEdit( $this->mTitle ); } - } else { - // Keep the same revision ID, but do some updates on it - $revisionId = $this->getRevIdFetched(); - } + $dbw->commit(); - if( !$wgDBtransactions ) { - ignore_user_abort( $userAbort ); - } - - if ( $good ) { - if ($watchthis) { - if (!$this->mTitle->userIsWatching()) { - $dbw->immediateCommit(); - $dbw->begin(); - $this->doWatch(); - $dbw->commit(); - } - } else { - if ( $this->mTitle->userIsWatching() ) { - $dbw->immediateCommit(); - $dbw->begin(); - $this->doUnwatch(); - $dbw->commit(); - } - } - # standard deferred updates - $this->editUpdates( $text, $summary, $minor, $now, $revisionId ); + # Update links, etc. + $this->editUpdates( $text, $summary, $isminor, $now, $revisionId, true ); + # Clear caches + Article::onArticleCreate( $this->mTitle ); - $urls = array(); - # Invalidate caches of all articles using this article as a template - - # Template namespace - # Purge all articles linking here - $titles = $this->mTitle->getTemplateLinksTo(); - Title::touchArray( $titles ); - if ( $wgUseSquid ) { - foreach ( $titles as $title ) { - $urls[] = $title->getInternalURL(); - } - } - - # Squid updates - if ( $wgUseSquid ) { - $urls = array_merge( $urls, $this->mTitle->getSquidURLs() ); - $u = new SquidUpdate( $urls ); - array_push( $wgPostCommitUpdateList, $u ); - } - - # File cache - if ( $wgUseFileCache ) { - $cm = new CacheManager($this->mTitle); - @unlink($cm->fileCacheName()); - } + wfRunHooks( 'ArticleInsertComplete', array( &$this, &$wgUser, $text, + $summary, $flags & EDIT_MINOR, + null, null, &$flags ) ); + } - $this->showArticle( $text, wfMsg( 'updated' ), $sectionanchor, $isminor, $now, $summary, $lastRevision ); + if ( $good && !( $flags & EDIT_DEFER_UPDATES ) ) { + wfDoUpdates(); } + wfRunHooks( 'ArticleSaveComplete', array( &$this, &$wgUser, $text, - $summary, $minor, - $watchthis, $sectionanchor ) ); - wfProfileOut( $fname ); + $summary, $flags & EDIT_MINOR, + null, null, &$flags ) ); + + wfProfileOut( __METHOD__ ); return $good; } /** - * After we've either updated or inserted the article, update - * the link tables and redirect to the new page. + * @deprecated wrapper for doRedirect */ function showArticle( $text, $subtitle , $sectionanchor = '', $me2, $now, $summary, $oldid ) { - global $wgOut; - - $fname = 'Article::showArticle'; - wfProfileIn( $fname ); - - # Output the redirect - if( $this->isRedirect( $text ) ) - $r = 'redirect=no'; - else - $r = ''; - $wgOut->redirect( $this->mTitle->getFullURL( $r ).$sectionanchor ); - - wfProfileOut( $fname ); + $this->doRedirect( $this->isRedirect( $text ), $sectionanchor ); } + /** + * Output a redirect back to the article. + * This is typically used after an edit. + * + * @param boolean $noRedir Add redirect=no + * @param string $sectionAnchor section to redirect to, including "#" + */ + function doRedirect( $noRedir = false, $sectionAnchor = '' ) { + global $wgOut; + if ( $noRedir ) { + $query = 'redirect=no'; + } else { + $query = ''; + } + $wgOut->redirect( $this->mTitle->getFullURL( $query ) . $sectionAnchor ); + } + /** * Mark this particular edit as patrolled */ function markpatrolled() { global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUser; - $wgOut->setRobotpolicy( 'noindex,follow' ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); # Check RC patrol config. option if( !$wgUseRCPatrol ) { @@ -1552,7 +1414,6 @@ class Article { $rcid = $wgRequest->getVal( 'rcid' ); if ( !is_null ( $rcid ) ) { if( wfRunHooks( 'MarkPatrolled', array( &$rcid, &$wgUser, false ) ) ) { - require_once( 'RecentChange.php' ); RecentChange::markPatrolled( $rcid ); wfRunHooks( 'MarkPatrolledComplete', array( &$rcid, &$wgUser, false ) ); $wgOut->setPagetitle( wfMsg( 'markedaspatrolled' ) ); @@ -1562,7 +1423,7 @@ class Article { $wgOut->returnToMain( false, $rcTitle->getPrefixedText() ); } else { - $wgOut->errorpage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); + $wgOut->showErrorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' ); } } @@ -1575,7 +1436,7 @@ class Article { global $wgUser, $wgOut; if ( $wgUser->isAnon() ) { - $wgOut->errorpage( 'watchnologin', 'watchnologintext' ); + $wgOut->showErrorPage( 'watchnologin', 'watchnologintext' ); return; } if ( wfReadOnly() ) { @@ -1585,9 +1446,9 @@ class Article { if( $this->doWatch() ) { $wgOut->setPagetitle( wfMsg( 'addedwatch' ) ); - $wgOut->setRobotpolicy( 'noindex,follow' ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $link = $this->mTitle->getPrefixedText(); + $link = wfEscapeWikiText( $this->mTitle->getPrefixedText() ); $text = wfMsg( 'addedwatchtext', $link ); $wgOut->addWikiText( $text ); } @@ -1623,7 +1484,7 @@ class Article { global $wgUser, $wgOut; if ( $wgUser->isAnon() ) { - $wgOut->errorpage( 'watchnologin', 'watchnologintext' ); + $wgOut->showErrorPage( 'watchnologin', 'watchnologintext' ); return; } if ( wfReadOnly() ) { @@ -1633,9 +1494,9 @@ class Article { if( $this->doUnwatch() ) { $wgOut->setPagetitle( wfMsg( 'removedwatch' ) ); - $wgOut->setRobotpolicy( 'noindex,follow' ); + $wgOut->setRobotpolicy( 'noindex,nofollow' ); - $link = $this->mTitle->getPrefixedText(); + $link = wfEscapeWikiText( $this->mTitle->getPrefixedText() ); $text = wfMsg( 'removedwatchtext', $link ); $wgOut->addWikiText( $text ); } @@ -1752,11 +1613,11 @@ class Article { * suitable for insertion into the page_restrictions field. * @param array $limit * @return string - * @access private + * @private */ function flattenRestrictions( $limit ) { if( !is_array( $limit ) ) { - wfDebugDieBacktrace( 'Article::flattenRestrictions given non-array restriction set' ); + throw new MWException( 'Article::flattenRestrictions given non-array restriction set' ); } $bits = array(); ksort( $limit ); @@ -1773,7 +1634,6 @@ class Article { */ function delete() { global $wgUser, $wgOut, $wgRequest; - $fname = 'Article::delete'; $confirm = $wgRequest->wasPosted() && $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) ); $reason = $wgRequest->getText( 'wpReason' ); @@ -1782,7 +1642,7 @@ class Article { # Check permissions if( $wgUser->isAllowed( 'delete' ) ) { - if( $wgUser->isBlocked() ) { + if( $wgUser->isBlocked( !$confirm ) ) { $wgOut->blockedPage(); return; } @@ -1801,9 +1661,9 @@ class Article { # Better double-check that it hasn't been deleted yet! $dbw =& wfGetDB( DB_MASTER ); $conds = $this->mTitle->pageCond(); - $latest = $dbw->selectField( 'page', 'page_latest', $conds, $fname ); + $latest = $dbw->selectField( 'page', 'page_latest', $conds, __METHOD__ ); if ( $latest === false ) { - $wgOut->fatalError( wfMsg( 'cannotdelete' ) ); + $wgOut->showFatalError( wfMsg( 'cannotdelete' ) ); return; } @@ -1891,8 +1751,7 @@ class Article { * @return array Array of authors, duplicates not removed */ function getLastNAuthors( $num, $revLatest = 0 ) { - $fname = 'Article::getLastNAuthors'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); // First try the slave // If that doesn't have the latest revision, try the master @@ -1905,13 +1764,13 @@ class Article { 'page_namespace' => $this->mTitle->getNamespace(), 'page_title' => $this->mTitle->getDBkey(), 'rev_page = page_id' - ), $fname, $this->getSelectOptions( array( + ), __METHOD__, $this->getSelectOptions( array( 'ORDER BY' => 'rev_timestamp DESC', 'LIMIT' => $num ) ) ); if ( !$res ) { - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return array(); } $row = $db->fetchObject( $res ); @@ -1927,7 +1786,7 @@ class Article { while ( $row = $db->fetchObject( $res ) ) { $authors[] = $row->rev_user_text; } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return $authors; } @@ -1980,12 +1839,11 @@ class Article { */ function doDelete( $reason ) { global $wgOut, $wgUser; - $fname = 'Article::doDelete'; - wfDebug( $fname."\n" ); + wfDebug( __METHOD__."\n" ); if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) { if ( $this->doDeleteArticle( $reason ) ) { - $deleted = $this->mTitle->getPrefixedText(); + $deleted = wfEscapeWikiText( $this->mTitle->getPrefixedText() ); $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); @@ -1997,7 +1855,7 @@ class Article { $wgOut->returnToMain( false ); wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason)); } else { - $wgOut->fatalError( wfMsg( 'cannotdelete' ) ); + $wgOut->showFatalError( wfMsg( 'cannotdelete' ) ); } } } @@ -2011,8 +1869,7 @@ class Article { global $wgUseSquid, $wgDeferredUpdateList; global $wgPostCommitUpdateList, $wgUseTrackbacks; - $fname = 'Article::doDeleteArticle'; - wfDebug( $fname."\n" ); + wfDebug( __METHOD__."\n" ); $dbw =& wfGetDB( DB_MASTER ); $ns = $this->mTitle->getNamespace(); @@ -2026,24 +1883,6 @@ class Article { $u = new SiteStatsUpdate( 0, 1, -(int)$this->isCountable( $this->getContent() ), -1 ); array_push( $wgDeferredUpdateList, $u ); - $linksTo = $this->mTitle->getLinksTo(); - - # Squid purging - if ( $wgUseSquid ) { - $urls = array( - $this->mTitle->getInternalURL(), - $this->mTitle->getInternalURL( 'history' ) - ); - - $u = SquidUpdate::newFromTitles( $linksTo, $urls ); - array_push( $wgPostCommitUpdateList, $u ); - - } - - # Client and file cache invalidation - Title::touchArray( $linksTo ); - - // For now, shunt the revision data into the archive table. // Text is *not* removed from the text table; bulk storage // is left intact to avoid breaking block-compression or @@ -2068,30 +1907,38 @@ class Article { ), array( 'page_id' => $id, 'page_id = rev_page' - ), $fname + ), __METHOD__ ); # Now that it's safely backed up, delete it - $dbw->delete( 'revision', array( 'rev_page' => $id ), $fname ); - $dbw->delete( 'page', array( 'page_id' => $id ), $fname); + $dbw->delete( 'page', array( 'page_id' => $id ), __METHOD__); - if ($wgUseTrackbacks) - $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), $fname ); + # If using cascading deletes, we can skip some explicit deletes + if ( !$dbw->cascadingDeletes() ) { - # Clean up recentchanges entries... - $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), $fname ); + $dbw->delete( 'revision', array( 'rev_page' => $id ), __METHOD__ ); - # Finally, clean up the link tables - $t = $this->mTitle->getPrefixedDBkey(); + if ($wgUseTrackbacks) + $dbw->delete( 'trackbacks', array( 'tb_page' => $id ), __METHOD__ ); - Article::onArticleDelete( $this->mTitle ); + # Delete outgoing links + $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) ); + $dbw->delete( 'imagelinks', array( 'il_from' => $id ) ); + $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) ); + $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) ); + $dbw->delete( 'externallinks', array( 'el_from' => $id ) ); + $dbw->delete( 'langlinks', array( 'll_from' => $id ) ); + } + + # If using cleanup triggers, we can skip some manual deletes + if ( !$dbw->cleanupTriggers() ) { - # Delete outgoing links - $dbw->delete( 'pagelinks', array( 'pl_from' => $id ) ); - $dbw->delete( 'imagelinks', array( 'il_from' => $id ) ); - $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) ); - $dbw->delete( 'templatelinks', array( 'tl_from' => $id ) ); - $dbw->delete( 'externallinks', array( 'el_from' => $id ) ); + # Clean up recentchanges entries... + $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), __METHOD__ ); + } + + # Clear caches + Article::onArticleDelete( $this->mTitle ); # Log the deletion $log = new LogPage( 'delete' ); @@ -2108,7 +1955,6 @@ class Article { */ function rollback() { global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol; - $fname = 'Article::rollback'; if( $wgUser->isAllowed( 'rollback' ) ) { if( $wgUser->isBlocked() ) { @@ -2140,12 +1986,10 @@ class Article { $tt = $this->mTitle->getDBKey(); $n = $this->mTitle->getNamespace(); - # Get the last editor, lock table exclusively - $dbw->begin(); + # Get the last editor $current = Revision::newFromTitle( $this->mTitle ); if( is_null( $current ) ) { # Something wrong... no page? - $dbw->rollback(); $wgOut->addHTML( wfMsg( 'notanarticle' ) ); return; } @@ -2173,14 +2017,13 @@ class Article { array( 'rev_page' => $current->getPage(), "rev_user <> {$user} OR rev_user_text <> {$user_text}" - ), $fname, + ), __METHOD__, array( 'USE INDEX' => 'page_timestamp', 'ORDER BY' => 'rev_timestamp DESC' ) ); if( $s === false ) { # Something wrong - $dbw->rollback(); $wgOut->setPageTitle(wfMsg('rollbackfailed')); $wgOut->addHTML( wfMsg( 'cantrollback' ) ); return; @@ -2202,7 +2045,7 @@ class Article { 'rc_cur_id' => $current->getPage(), 'rc_user_text' => $current->getUserText(), "rc_timestamp > '{$s->rev_timestamp}'", - ), $fname + ), __METHOD__ ); } @@ -2217,16 +2060,14 @@ class Article { $wgOut->addHTML( '

' . htmlspecialchars( $newComment ) . "

\n
\n" ); $this->updateArticle( $target->getText(), $newComment, 1, $this->mTitle->userIsWatching(), $bot ); - Article::onArticleEdit( $this->mTitle ); - $dbw->commit(); $wgOut->returnToMain( false ); } /** * Do standard deferred updates after page view - * @access private + * @private */ function viewUpdates() { global $wgDeferredUpdateList; @@ -2247,15 +2088,21 @@ class Article { /** * Do standard deferred updates after page edit. + * Update links tables, site stats, search index and message cache. * Every 1000th edit, prune the recent changes table. - * @access private - * @param string $text - */ - function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange, $newid) { + * + * @private + * @param $text New text of the article + * @param $summary Edit summary + * @param $minoredit Minor edit + * @param $timestamp_of_pagechange Timestamp associated with the page change + * @param $newid rev_id value of the new revision + * @param $changed Whether or not the content actually changed + */ + function editUpdates( $text, $summary, $minoredit, $timestamp_of_pagechange, $newid, $changed = true ) { global $wgDeferredUpdateList, $wgMessageCache, $wgUser, $wgParser; - $fname = 'Article::editUpdates'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); # Parse the text $options = new ParserOptions; @@ -2289,7 +2136,7 @@ class Article { $shortTitle = $this->mTitle->getDBkey(); if ( 0 == $id ) { - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); return; } @@ -2299,8 +2146,9 @@ class Article { array_push( $wgDeferredUpdateList, $u ); # If this is another user's talk page, update newtalk - - if ($this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getName()) { + # Don't do this if $changed = false otherwise some idiot can null-edit a + # load of user talk pages and piss people off + if( $this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getTitleKey() && $changed ) { if (wfRunHooks('ArticleEditUpdateNewTalk', array(&$this)) ) { $other = User::newFromName( $shortTitle ); if( is_null( $other ) && User::isIP( $shortTitle ) ) { @@ -2318,21 +2166,27 @@ class Article { $wgMessageCache->replace( $shortTitle, $text ); } - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); } /** * Generate the navigation links when browsing through an article revisions * It shows the information as: - * Revision as of ; view current revision - * <- Previous version | Next Version -> + * Revision as of \; view current revision + * \<- Previous version | Next Version -\> * - * @access private + * @private * @param string $oldid Revision ID of this article revision */ function setOldSubtitle( $oldid=0 ) { global $wgLang, $wgOut, $wgUser; + if ( !wfRunHooks( 'DisplayOldSubtitle', array(&$this, &$oldid) ) ) { + return; + } + + $revision = Revision::newFromId( $oldid ); + $current = ( $oldid == $this->mLatest ); $td = $wgLang->timeanddate( $this->mTimestamp, true ); $sk = $wgUser->getSkin(); @@ -2343,10 +2197,20 @@ class Article { $prevlink = $prev ? $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'previousrevision' ), 'direction=prev&oldid='.$oldid ) : wfMsg( 'previousrevision' ); + $prevdiff = $prev + ? $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=prev&oldid='.$oldid ) + : wfMsg( 'diff' ); $nextlink = $current ? wfMsg( 'nextrevision' ) : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'nextrevision' ), 'direction=next&oldid='.$oldid ); - $r = wfMsg( 'revisionasofwithlink', $td, $lnk, $prevlink, $nextlink ); + $nextdiff = $current + ? wfMsg( 'diff' ) + : $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'diff' ), 'diff=next&oldid='.$oldid ); + + $userlinks = $sk->userLink( $revision->getUser(), $revision->getUserText() ) + . $sk->userToolLinks( $revision->getUser(), $revision->getUserText() ); + + $r = wfMsg( 'old-revision-navigation', $td, $lnk, $prevlink, $nextlink, $userlinks, $prevdiff, $nextdiff ); $wgOut->setSubtitle( $r ); } @@ -2371,7 +2235,7 @@ class Article { function tryFileCache() { static $called = false; if( $called ) { - wfDebug( " tryFileCache() -- called twice!?\n" ); + wfDebug( "Article::tryFileCache(): called twice!?\n" ); return; } $called = true; @@ -2379,15 +2243,15 @@ class Article { $touched = $this->mTouched; $cache = new CacheManager( $this->mTitle ); if($cache->isFileCacheGood( $touched )) { - wfDebug( " tryFileCache() - about to load\n" ); + wfDebug( "Article::tryFileCache(): about to load file\n" ); $cache->loadFromFileCache(); return true; } else { - wfDebug( " tryFileCache() - starting buffer\n" ); + wfDebug( "Article::tryFileCache(): starting buffer\n" ); ob_start( array(&$cache, 'saveToFileCache' ) ); } } else { - wfDebug( " tryFileCache() - not cacheable\n" ); + wfDebug( "Article::tryFileCache(): not cacheable\n" ); } } @@ -2418,7 +2282,6 @@ class Article { * */ function checkTouched() { - $fname = 'Article::checkTouched'; if( !$this->mDataLoaded ) { $this->loadPageData(); } @@ -2456,8 +2319,7 @@ class Article { * @param bool $minor whereas it's a minor modification */ function quickEdit( $text, $comment = '', $minor = 0 ) { - $fname = 'Article::quickEdit'; - wfProfileIn( $fname ); + wfProfileIn( __METHOD__ ); $dbw =& wfGetDB( DB_MASTER ); $dbw->begin(); @@ -2467,11 +2329,12 @@ class Article { 'comment' => $comment, 'minor_edit' => $minor ? 1 : 0, ) ); + # fixme : $revisionId never used $revisionId = $revision->insertOn( $dbw ); $this->updateRevisionOn( $dbw, $revision ); $dbw->commit(); - wfProfileOut( $fname ); + wfProfileOut( __METHOD__ ); } /** @@ -2482,7 +2345,7 @@ class Article { */ function incViewCount( $id ) { $id = intval( $id ); - global $wgHitcounterUpdateFreq; + global $wgHitcounterUpdateFreq, $wgDBtype; $dbw =& wfGetDB( DB_MASTER ); $pageTable = $dbw->tableName( 'page' ); @@ -2513,12 +2376,15 @@ class Article { wfProfileIn( 'Article::incViewCount-collect' ); $old_user_abort = ignore_user_abort( true ); - $dbw->query("LOCK TABLES $hitcounterTable WRITE"); - $dbw->query("CREATE TEMPORARY TABLE $acchitsTable TYPE=HEAP ". + if ($wgDBtype == 'mysql') + $dbw->query("LOCK TABLES $hitcounterTable WRITE"); + $tabletype = $wgDBtype == 'mysql' ? "ENGINE=HEAP " : ''; + $dbw->query("CREATE TEMPORARY TABLE $acchitsTable $tabletype". "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable ". 'GROUP BY hc_id'); $dbw->query("DELETE FROM $hitcounterTable"); - $dbw->query('UNLOCK TABLES'); + if ($wgDBtype == 'mysql') + $dbw->query('UNLOCK TABLES'); $dbw->query("UPDATE $pageTable,$acchitsTable SET page_counter=page_counter + hc_n ". 'WHERE page_id = hc_id'); $dbw->query("DROP TABLE $acchitsTable"); @@ -2541,27 +2407,31 @@ class Article { * @param $title_obj a title object */ - function onArticleCreate($title_obj) { - global $wgUseSquid, $wgPostCommitUpdateList; - - $title_obj->touchLinks(); - $titles = $title_obj->getLinksTo(); - - # Purge squid - if ( $wgUseSquid ) { - $urls = $title_obj->getSquidURLs(); - foreach ( $titles as $linkTitle ) { - $urls[] = $linkTitle->getInternalURL(); - } - $u = new SquidUpdate( $urls ); - array_push( $wgPostCommitUpdateList, $u ); + static function onArticleCreate($title) { + # The talk page isn't in the regular link tables, so we need to update manually: + if ( $title->isTalkPage() ) { + $other = $title->getSubjectPage(); + } else { + $other = $title->getTalkPage(); } + $other->invalidateCache(); + $other->purgeSquid(); + + $title->touchLinks(); + $title->purgeSquid(); } - function onArticleDelete( $title ) { - global $wgMessageCache; + static function onArticleDelete( $title ) { + global $wgUseFileCache, $wgMessageCache; $title->touchLinks(); + $title->purgeSquid(); + + # File cache + if ( $wgUseFileCache ) { + $cm = new CacheManager( $title ); + @unlink( $cm->fileCacheName() ); + } if( $title->getNamespace() == NS_MEDIAWIKI) { $wgMessageCache->replace( $title->getDBkey(), false ); @@ -2571,31 +2441,19 @@ class Article { /** * Purge caches on page update etc */ - function onArticleEdit( $title ) { - global $wgUseSquid, $wgPostCommitUpdateList, $wgUseFileCache; + static function onArticleEdit( $title ) { + global $wgDeferredUpdateList, $wgUseFileCache; $urls = array(); - // Template namespace? Purge all articles linking here. - // FIXME: When a templatelinks table arrives, use it for all includes. - if ( $title->getNamespace() == NS_TEMPLATE) { - $titles = $title->getLinksTo(); - Title::touchArray( $titles ); - if ( $wgUseSquid ) { - foreach ( $titles as $link ) { - $urls[] = $link->getInternalURL(); - } - } - } + // Invalidate caches of articles which include this page + $update = new HTMLCacheUpdate( $title, 'templatelinks' ); + $wgDeferredUpdateList[] = $update; - # Squid updates - if ( $wgUseSquid ) { - $urls = array_merge( $urls, $title->getSquidURLs() ); - $u = new SquidUpdate( $urls ); - array_push( $wgPostCommitUpdateList, $u ); - } + # Purge squid for this page only + $title->purgeSquid(); - # File cache + # Clear file cache if ( $wgUseFileCache ) { $cm = new CacheManager( $title ); @unlink( $cm->fileCacheName() ); @@ -2608,14 +2466,13 @@ class Article { * Info about this page * Called for ?action=info when $wgAllowPageInfo is on. * - * @access public + * @public */ function info() { global $wgLang, $wgOut, $wgAllowPageInfo, $wgUser; - $fname = 'Article::info'; if ( !$wgAllowPageInfo ) { - $wgOut->errorpage( 'nosuchaction', 'nosuchactiontext' ); + $wgOut->showErrorPage( 'nosuchaction', 'nosuchactiontext' ); return; } @@ -2641,7 +2498,7 @@ class Article { 'watchlist', 'COUNT(*)', $wl_clause, - $fname, + __METHOD__, $this->getSelectOptions() ); $pageInfo = $this->pageCountInfo( $page ); @@ -2667,7 +2524,7 @@ class Article { * * @param Title $title * @return array - * @access private + * @private */ function pageCountInfo( $title ) { $id = $title->getArticleId(); @@ -2678,20 +2535,19 @@ class Article { $dbr =& wfGetDB( DB_SLAVE ); $rev_clause = array( 'rev_page' => $id ); - $fname = 'Article::pageCountInfo'; $edits = $dbr->selectField( 'revision', 'COUNT(rev_page)', $rev_clause, - $fname, + __METHOD__, $this->getSelectOptions() ); $authors = $dbr->selectField( 'revision', 'COUNT(DISTINCT rev_user_text)', $rev_clause, - $fname, + __METHOD__, $this->getSelectOptions() ); return array( 'edits' => $edits, 'authors' => $authors );