X-Git-Url: http://git.heureux-cyclage.org/?a=blobdiff_plain;f=includes%2FArticle.php;h=402ca8146851c67a459013fc11c9519e1d47edfd;hb=382e2b0b34717913302fec41b72237cf4e28e228;hp=a00e5aa201d8509e5404b8bd1ee232ac3dcfc12e;hpb=f11481e96871ee4d0f3cf92bcce260720ac19240;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/Article.php b/includes/Article.php index a00e5aa201..402ca81468 100644 --- a/includes/Article.php +++ b/includes/Article.php @@ -1,49 +1,80 @@ mTitle =& $title; $this->clear(); } - /* private */ function clear() { + /** + * Clear the object + * @private + */ + function clear() { $this->mContentLoaded = false; $this->mCurID = $this->mUser = $this->mCounter = -1; # Not loaded $this->mRedirectedFrom = $this->mUserText = $this->mTimestamp = $this->mComment = $this->mFileCache = ''; $this->mCountAdjustment = 0; $this->mTouched = '19700101000000'; + $this->mForUpdate = false; } - # Get revision text associated with an old or archive row - # $row is usually an object from wfFetchRow(), both the flags and the text field must be included - /* static */ function getRevisionText( $row, $prefix = 'old_' ) { + /** + * Get revision text associated with an old or archive row + * $row is usually an object from wfFetchRow(), both the flags and the text + * field must be included + * @static + * @param integer $row Id of a row + * @param string $prefix table prefix (default 'old_') + * @return string $text|false the text requested + */ + function getRevisionText( $row, $prefix = 'old_' ) { # Get data $textField = $prefix . 'text'; $flagsField = $prefix . 'flags'; if ( isset( $row->$flagsField ) ) { - $flags = explode( ",", $row->$flagsField ); + $flags = explode( ',', $row->$flagsField ); } else { $flags = array(); } @@ -66,7 +97,13 @@ class Article { return $text; } - /* static */ function compressRevisionText( &$text ) { + /** + * If $wgCompressRevisions is enabled, we will compress datas + * @static + * @param mixed $text reference to a text + * @return string 'gzip' if it get compressed, '' overwise + */ + function compressRevisionText( &$text ) { global $wgCompressRevisions; if( !$wgCompressRevisions ) { return ''; @@ -79,8 +116,13 @@ class Article { return 'gzip'; } - # Returns the text associated with a "link" type old table row - /* static */ function followLink( $link ) { + /** + * Returns the text associated with a "link" type old table row + * @static + * @param mixed $link + * @return string $text|false + */ + function followLink( $link ) { # Split the link into fields and values $lines = explode( '\n', $link ); $hash = ''; @@ -118,7 +160,12 @@ class Article { return $text; } - /* static */ function fetchFromLocation( $location, $hash ) { + /** + * @static + * @param $location + * @param $hash + */ + function fetchFromLocation( $location, $hash ) { global $wgLoadBalancer; $fname = 'fetchFromLocation'; wfProfileIn( $fname ); @@ -143,7 +190,7 @@ class Article { $index = $matches[4]; if ( $machineID == 0 ) { # Current connection - $db =& wfGetDB( DB_SLAVE ); + $db =& $this->getDB(); } else { # Alternate connection $db =& $wgLoadBalancer->getConnection( $machineID ); @@ -157,7 +204,8 @@ class Article { } if ( $db->isOpen() ) { $index = $db->strencode( $index ); - $res = $db->query( "SELECT blob_data FROM $dbName.$tblName WHERE blob_index='$index'", $fname ); + $res = $db->query( "SELECT blob_data FROM $dbName.$tblName " . + "WHERE blob_index='$index' " . $this->getSelectOptions(), $fname ); $row = $db->fetchObject( $res ); $text = $row->text_data; } @@ -193,10 +241,13 @@ class Article { return $text; } - # Note that getContent/loadContent may follow redirects if - # not told otherwise, and so may cause a change to mTitle. - - # Return the text of this revision + /** + * Note that getContent/loadContent may follow redirects if + * not told otherwise, and so may cause a change to mTitle. + * + * @param $noredir + * @return Return the text of this revision + */ function getContent( $noredir ) { global $wgRequest; @@ -244,12 +295,17 @@ class Article { } } - # 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 - # the first section before any such heading (section 0). - # - # If a section contains subsections, these are also returned. - # + /** + * 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 + * 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 + * @return string text of the requested section + */ function getSection($text,$section) { # strip NOWIKI etc. to avoid confusion (true-parameter causes HTML @@ -263,14 +319,14 @@ class Article { # split it up by section $secs = preg_split( - '/(^=+.*?=+|^.*?<\/h[1-6].*?' . '>)/mi', + '/(^=+.+?=+|^.*?<\/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].*?' . '>/mi',$headline,$matches); + preg_match( '/^(=+).+?=+|^.*?<\/h[1-6].*?' . '>(?!\S)/mi',$headline,$matches); $hlevel=$matches[1]; # translate wiki heading into level @@ -285,7 +341,7 @@ class Article { while(!empty($secs[$count*2-1]) && !$break) { $subheadline=$secs[$count*2-1]; - preg_match( '/^(=+).*?=+|^.*?<\/h[1-6].*?' . '>/mi',$subheadline,$matches); + preg_match( '/^(=+).+?=+|^.*?<\/h[1-6].*?' . '>(?!\S)/mi',$subheadline,$matches); $subhlevel=$matches[1]; if(strpos($subhlevel,'=')!==false) { $subhlevel=strlen($subhlevel); @@ -308,7 +364,9 @@ class Article { } - # Return an array of the columns of the "cur"-table + /** + * Return an array of the columns of the "cur"-table + */ function &getCurContentFields() { global $wgArticleCurContentFields; if ( !$wgArticleCurContentFields ) { @@ -318,7 +376,9 @@ class Article { return $wgArticleCurContentFields; } - # Return an array of the columns of the "old"-table + /** + * Return an array of the columns of the "old"-table + */ function &getOldContentFields() { global $wgArticleOldContentFields; if ( !$wgArticleOldContentFields ) { @@ -328,16 +388,19 @@ class Article { return $wgArticleOldContentFields; } - # Load the revision (including cur_text) into this object + /** + * Load the revision (including cur_text) into this object + */ function loadContent( $noredir = false ) { - global $wgOut, $wgMwRedir, $wgRequest; + global $wgOut, $wgRequest; - $dbr =& wfGetDB( DB_SLAVE ); + if ( $this->mContentLoaded ) return; + + $dbr =& $this->getDB(); # Query variables :P $oldid = $wgRequest->getVal( 'oldid' ); $redirect = $wgRequest->getVal( 'redirect' ); - if ( $this->mContentLoaded ) return; $fname = 'Article::loadContent'; # Pre-fill content with error message so that if something @@ -346,11 +409,11 @@ class Article { $t = $this->mTitle->getPrefixedText(); if ( isset( $oldid ) ) { $oldid = IntVal( $oldid ); - $t .= ",oldid={$oldid}"; + $t .= ',oldid='.$oldid; } if ( isset( $redirect ) ) { $redirect = ($redirect == 'no') ? 'no' : 'yes'; - $t .= ",redirect={$redirect}"; + $t .= ',redirect='.$redirect; } $this->mContent = wfMsg( 'missingarticle', $t ); @@ -358,7 +421,8 @@ class Article { $id = $this->getID(); if ( 0 == $id ) return; - $s = $dbr->getArray( 'cur', $this->getCurContentFields(), array( 'cur_id' => $id ), $fname ); + $s = $dbr->selectRow( 'cur', $this->getCurContentFields(), array( 'cur_id' => $id ), $fname, + $this->getSelectOptions() ); if ( $s === false ) { return; } @@ -367,6 +431,7 @@ class Article { # not to by either the function parameter or the query if ( ( 'no' != $redirect ) && ( false == $noredir ) ) { $rt = Title::newFromRedirect( $s->cur_text ); + # process if title object is valid and not special:userlogout if ( $rt && ! ( $rt->getNamespace() == NS_SPECIAL && $rt->getText() == 'Userlogout' ) ) { # Gotta hand redirects to special pages differently: # Fill the HTTP response "Location" header and ignore @@ -382,7 +447,8 @@ class Article { } $rid = $rt->getArticleID(); if ( 0 != $rid ) { - $redirRow = $dbr->getArray( 'cur', $this->getCurContentFields(), array( 'cur_id' => $rid ), $fname ); + $redirRow = $dbr->selectRow( 'cur', $this->getCurContentFields(), + array( 'cur_id' => $rid ), $fname, $this->getSelectOptions() ); if ( $redirRow !== false ) { $this->mRedirectedFrom = $this->mTitle->getPrefixedText(); @@ -398,12 +464,13 @@ class Article { $this->mUserText = $s->cur_user_text; $this->mComment = $s->cur_comment; $this->mCounter = $s->cur_counter; - $this->mTimestamp = $s->cur_timestamp; - $this->mTouched = $s->cur_touched; + $this->mTimestamp = wfTimestamp(TS_MW,$s->cur_timestamp); + $this->mTouched = wfTimestamp(TS_MW,$s->cur_touched); $this->mTitle->mRestrictions = explode( ',', trim( $s->cur_restrictions ) ); $this->mTitle->mRestrictionsLoaded = true; } else { # oldid set, retrieve historical version - $s = $dbr->getArray( 'old', $this->getOldContentFields(), array( 'old_id' => $oldid ), $fname ); + $s = $dbr->getArray( 'old', $this->getOldContentFields(), array( 'old_id' => $oldid ), + $fname, $this->getSelectOptions() ); if ( $s === false ) { return; } @@ -419,24 +486,26 @@ class Article { $this->mUserText = $s->old_user_text; $this->mComment = $s->old_comment; $this->mCounter = 0; - $this->mTimestamp = $s->old_timestamp; + $this->mTimestamp = wfTimestamp(TS_MW,$s->old_timestamp); } $this->mContentLoaded = true; return $this->mContent; } - # Gets the article text without using so many damn globals - # Returns false on error + /** + * Gets the article text without using so many damn globals + * Returns false on error + * + * @param integer $oldid + */ function getContentWithoutUsingSoManyDamnGlobals( $oldid = 0, $noredir = false ) { - global $wgMwRedir; - if ( $this->mContentLoaded ) { return $this->mContent; } $this->mContent = false; $fname = 'Article::getContentWithout'; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr =& $this->getDB(); if ( ! $oldid ) { # Retrieve current version $id = $this->getID(); @@ -444,7 +513,8 @@ class Article { return false; } - $s = $dbr->getArray( 'cur', $this->getCurContentFields(), array( 'cur_id' => $id ), $fname ); + $s = $dbr->selectRow( 'cur', $this->getCurContentFields(), array( 'cur_id' => $id ), + $fname, $this->getSelectOptions() ); if ( $s === false ) { return false; } @@ -456,7 +526,8 @@ class Article { if( $rt && $rt->getInterwiki() == '' && $rt->getNamespace() != NS_SPECIAL ) { $rid = $rt->getArticleID(); if ( 0 != $rid ) { - $redirRow = $dbr->getArray( 'cur', $this->getCurContentFields(), array( 'cur_id' => $rid ), $fname ); + $redirRow = $dbr->selectRow( 'cur', $this->getCurContentFields(), + array( 'cur_id' => $rid ), $fname, $this->getSelectOptions() ); if ( $redirRow !== false ) { $this->mRedirectedFrom = $this->mTitle->getPrefixedText(); @@ -472,12 +543,13 @@ class Article { $this->mUserText = $s->cur_user_text; $this->mComment = $s->cur_comment; $this->mCounter = $s->cur_counter; - $this->mTimestamp = $s->cur_timestamp; - $this->mTouched = $s->cur_touched; - $this->mTitle->mRestrictions = explode( ",", trim( $s->cur_restrictions ) ); + $this->mTimestamp = wfTimestamp(TS_MW,$s->cur_timestamp); + $this->mTouched = wfTimestamp(TS_MW,$s->cur_touched); + $this->mTitle->mRestrictions = explode( ',', trim( $s->cur_restrictions ) ); $this->mTitle->mRestrictionsLoaded = true; } else { # oldid set, retrieve historical version - $s = $dbr->getArray( 'old', $this->getOldContentFields(), array( 'old_id' => $oldid ) ); + $s = $dbr->selectRow( 'old', $this->getOldContentFields(), array( 'old_id' => $oldid ), + $fname, $this->getSelectOptions() ); if ( $s === false ) { return false; } @@ -486,12 +558,48 @@ class Article { $this->mUserText = $s->old_user_text; $this->mComment = $s->old_comment; $this->mCounter = 0; - $this->mTimestamp = $s->old_timestamp; + $this->mTimestamp = wfTimestamp(TS_MW,$s->old_timestamp); } $this->mContentLoaded = true; return $this->mContent; } + /** + * Read/write accessor to select FOR UPDATE + */ + function forUpdate( $x = NULL ) { + return wfSetVar( $this->mForUpdate, $x ); + } + + /** + * Get the database which should be used for reads + */ + function &getDB() { + if ( $this->mForUpdate ) { + return wfGetDB( DB_MASTER ); + } else { + return wfGetDB( DB_SLAVE ); + } + } + + /** + * Get options for all SELECT statements + * Can pass an option array, to which the class-wide options will be appended + */ + function getSelectOptions( $options = '' ) { + if ( $this->mForUpdate ) { + if ( $options ) { + $options[] = 'FOR UPDATE'; + } else { + $options = 'FOR UPDATE'; + } + } + return $options; + } + + /** + * Return the Article ID + */ function getID() { if( $this->mTitle ) { return $this->mTitle->getArticleID(); @@ -500,44 +608,66 @@ class Article { } } + /** + * Get the view count for this article + */ function getCount() { if ( -1 == $this->mCounter ) { $id = $this->getID(); - $dbr =& wfGetDB( DB_SLAVE ); - $this->mCounter = $dbr->getField( 'cur', 'cur_counter', "cur_id={$id}" ); + $dbr =& $this->getDB(); + $this->mCounter = $dbr->selectField( 'cur', 'cur_counter', 'cur_id='.$id, + 'Article::getCount', $this->getSelectOptions() ); } return $this->mCounter; } - # Would the given text make this article a "good" article (i.e., - # suitable for including in the article count)? + /** + * Would the given text make this article a "good" article (i.e., + * suitable for including in the article count)? + */ function isCountable( $text ) { - global $wgUseCommaCount, $wgMwRedir; + global $wgUseCommaCount; if ( 0 != $this->mTitle->getNamespace() ) { return 0; } - if ( $wgMwRedir->matchStart( $text ) ) { return 0; } + if ( $this->isRedirect( $text ) ) { return 0; } $token = ($wgUseCommaCount ? ',' : '[[' ); if ( false === strstr( $text, $token ) ) { return 0; } return 1; } - # Loads everything from cur except cur_text - # This isn't necessary for all uses, so it's only done if needed. - /* private */ function loadLastEdit() { + /** + * Tests if the article text represents a redirect + */ + function isRedirect( $text = false ) { + if ( $text === false ) { + $this->loadContent(); + $titleObj = Title::newFromRedirect( $this->mText ); + } else { + $titleObj = Title::newFromRedirect( $text ); + } + return $titleObj !== NULL; + } + + /** + * Loads everything from cur except cur_text + * This isn't necessary for all uses, so it's only done if needed. + * @private + */ + function loadLastEdit() { global $wgOut; if ( -1 != $this->mUser ) return; $fname = 'Article::loadLastEdit'; - $dbr =& wfGetDB( DB_SLAVE ); + $dbr =& $this->getDB(); $s = $dbr->getArray( 'cur', array( 'cur_user','cur_user_text','cur_timestamp', 'cur_comment','cur_minor_edit' ), - array( 'cur_id' => $this->getID() ), $fname ); + array( 'cur_id' => $this->getID() ), $fname, $this->getSelectOptions() ); if ( $s !== false ) { $this->mUser = $s->cur_user; $this->mUserText = $s->cur_user_text; - $this->mTimestamp = $s->cur_timestamp; + $this->mTimestamp = wfTimestamp(TS_MW,$s->cur_timestamp); $this->mComment = $s->cur_comment; $this->mMinorEdit = $s->cur_minor_edit; } @@ -575,10 +705,10 @@ class Article { $title = $this->mTitle; $contribs = array(); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr =& $this->getDB(); $oldTable = $dbr->tableName( 'old' ); $userTable = $dbr->tableName( 'user' ); - $encDBkey = $dbr->strencode( $title->getDBkey() ); + $encDBkey = $dbr->addQuotes( $title->getDBkey() ); $ns = $title->getNamespace(); $user = $this->getUser(); @@ -587,10 +717,11 @@ class Article { WHERE old_namespace = $user AND old_title = $encDBkey AND old_user != $user - GROUP BY old_user + GROUP BY old_user, old_user_text, user_real_name ORDER BY timestamp DESC"; if ($limit > 0) { $sql .= ' LIMIT '.$limit; } + $sql .= ' '. $this->getSelectOptions(); $res = $dbr->query($sql, $fname); @@ -602,17 +733,17 @@ class Article { return $contribs; } - # This is the default action of the script: just view the page of - # the given title. - + /** + * This is the default action of the script: just view the page of + * the given title. + */ function view() { - global $wgUser, $wgOut, $wgLang, $wgRequest, $wgMwRedir, $wgOnlySysopsCanPatrol; + global $wgUser, $wgOut, $wgLang, $wgRequest, $wgOnlySysopsCanPatrol; global $wgLinkCache, $IP, $wgEnableParserCache, $wgStylePath, $wgUseRCPatrol; $sk = $wgUser->getSkin(); $fname = 'Article::view'; wfProfileIn( $fname ); - # Get variables from query string $oldid = $wgRequest->getVal( 'oldid' ); $diff = $wgRequest->getVal( 'diff' ); @@ -620,13 +751,13 @@ class Article { $wgOut->setArticleFlag( true ); $wgOut->setRobotpolicy( 'index,follow' ); - # If we got diff and oldid in the query, we want to see a # diff page instead of the article. if ( !is_null( $diff ) ) { + require_once( 'DifferenceEngine.php' ); $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); - $de = new DifferenceEngine( intval($oldid), intval($diff), intval($rcid) ); + $de = new DifferenceEngine( $oldid, $diff, $rcid ); $de->showDiffPage(); wfProfileOut( $fname ); if( $diff == 0 ) { @@ -635,7 +766,6 @@ class Article { } return; } - if ( empty( $oldid ) && $this->checkTouched() ) { if( $wgOut->checkLastModified( $this->mTouched ) ){ return; @@ -646,7 +776,6 @@ class Article { return; } } - # Should the parser cache be used? if ( $wgEnableParserCache && intval($wgUser->getOption( 'stubthreshold' )) == 0 && empty( $oldid ) ) { $pcache = true; @@ -660,7 +789,6 @@ class Article { $outputDone = true; } } - if ( !$outputDone ) { $text = $this->getContent( false ); # May change mTitle by following a redirect @@ -699,22 +827,21 @@ class Article { $wgOut->addHTML( '
'.htmlspecialchars($this->mContent)."\n
" ); } else if ( $rt = Title::newFromRedirect( $text ) ) { # Display redirect - $imageUrl = "$wgStylePath/images/redirect.png"; + $imageUrl = $wgStylePath.'/common/images/redirect.png'; $targetUrl = $rt->escapeLocalURL(); $titleText = htmlspecialchars( $rt->getPrefixedText() ); $link = $sk->makeLinkObj( $rt ); - $wgOut->addHTML( "" . - "$link" ); + $wgOut->addHTML( '' . + ''.$link.'' ); } else if ( $pcache ) { # Display content and save to parser cache - $wgOut->addWikiText( $text, true, $this ); + $wgOut->addPrimaryWikiText( $text, $this ); } else { # Display content, don't attempt to save to parser cache $wgOut->addWikiText( $text ); } } $wgOut->setPageTitle( $this->mTitle->getPrefixedText() ); - # If we have been passed an &rcid= parameter, we want to give the user a # chance to mark this new article as patrolled. if ( $wgUseRCPatrol && !is_null ( $rcid ) && $rcid != 0 && $wgUser->getID() != 0 && @@ -722,12 +849,13 @@ class Article { { $wgOut->addHTML( wfMsg ( 'markaspatrolledlink', $sk->makeKnownLinkObj ( $this->mTitle, wfMsg ( 'markaspatrolledtext' ), - "action=markpatrolled&rcid={$rcid}" ) + 'action=markpatrolled&rcid='.$rcid ) ) ); } # Put link titles into the link cache - $wgOut->replaceLinkHolders(); + $wgOut->transformBuffer(); + # Add link titles as META keywords $wgOut->addMetaTags() ; @@ -735,13 +863,15 @@ class Article { wfProfileOut( $fname ); } - # Theoretically we could defer these whole insert and update - # functions for after display, but that's taking a big leap - # of faith, and we want to be able to report database - # errors at some point. - - /* private */ function insertNewArticle( $text, $summary, $isminor, $watchthis ) { - global $wgOut, $wgUser, $wgMwRedir; + /** + * Theoretically we could defer these whole insert and update + * functions for after display, but that's taking a big leap + * of faith, and we want to be able to report database + * errors at some point. + * @private + */ + function insertNewArticle( $text, $summary, $isminor, $watchthis ) { + global $wgOut, $wgUser; global $wgUseSquid, $wgDeferredUpdateList, $wgInternalServer; $fname = 'Article::insertNewArticle'; @@ -751,7 +881,7 @@ class Article { $ns = $this->mTitle->getNamespace(); $ttl = $this->mTitle->getDBkey(); $text = $this->preSaveTransform( $text ); - if ( $wgMwRedir->matchStart( $text ) ) { $redir = 1; } + if ( $this->isRedirect( $text ) ) { $redir = 1; } else { $redir = 0; } $now = wfTimestampNow(); @@ -771,7 +901,7 @@ class Article { 'cur_text' => $text, 'cur_comment' => $summary, 'cur_user' => $wgUser->getID(), - 'cur_timestamp' => $now, + 'cur_timestamp' => $dbw->timestamp($now), 'cur_minor_edit' => $isminor, 'cur_counter' => 0, 'cur_restrictions' => '', @@ -779,7 +909,7 @@ class Article { 'cur_is_redirect' => $redir, 'cur_is_new' => 1, 'cur_random' => $rand, - 'cur_touched' => $now, + 'cur_touched' => $dbw->timestamp($now), 'inverse_timestamp' => $won, ), $fname ); @@ -799,7 +929,8 @@ class Article { # The talk page isn't in the regular link tables, so we need to update manually: $talkns = $ns ^ 1; # talk -> normal; normal -> talk - $dbw->updateArray( 'cur', array( 'cur_touched' => $now ), array( 'cur_namespace' => $talkns, 'cur_title' => $ttl ), $fname ); + $dbw->updateArray( 'cur', array('cur_touched' => $dbw->timestamp($now) ), + array( 'cur_namespace' => $talkns, 'cur_title' => $ttl ), $fname ); # standard deferred updates $this->editUpdates( $text ); @@ -808,10 +939,25 @@ class Article { } - /* Side effects: loads last edit */ - function getTextOfLastEditWithSectionReplacedOrAdded($section, $text, $summary = '') { - $this->loadLastEdit(); - $oldtext = $this->getContent( true ); + /** + * Side effects: loads last edit if $edittime is NULL + */ + function getTextOfLastEditWithSectionReplacedOrAdded($section, $text, $summary = '', $edittime = NULL) { + $fname = 'Article::getTextOfLastEditWithSectionReplacedOrAdded'; + if(is_null($edittime)) { + $this->loadLastEdit(); + $oldtext = $this->getContent( true ); + } else { + $dbw =& wfGetDB( DB_MASTER ); + $ns = $this->mTitle->getNamespace(); + $title = $this->mTitle->getDBkey(); + $obj = $dbw->getArray( 'old', + array( 'old_text','old_flags'), + array( 'old_namespace' => $ns, 'old_title' => $title, + 'old_timestamp' => $dbw->timestamp($edittime)), + $fname ); + $oldtext = Article::getRevisionText( $obj ); + } if ($section != '') { if($section=='new') { if($summary) $subject="== {$summary} ==\n\n"; @@ -829,7 +975,7 @@ class Article { # 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].*?' . '>)/mi', + $secs=preg_split('/(^=+.+?=+|^.*?<\/h[1-6].*?' . '>)(?!\S)/mi', $oldtext,-1,PREG_SPLIT_DELIM_CAPTURE); $secs[$section*2]=$text."\n\n"; // replace with edited @@ -841,7 +987,7 @@ class Article { # 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].*?' . '>/mi',$headline,$matches); + preg_match( '/^(=+).+?=+|^.*?<\/h[1-6].*?' . '>(?!\S)/mi',$headline,$matches); $hlevel=$matches[1]; # determine headline level for wikimarkup headings @@ -856,7 +1002,7 @@ class Article { $subheadline=$secs[$count*2-1]; preg_match( - '/^(=+).*?=+|^.*?<\/h[1-6].*?' . '>/mi',$subheadline,$matches); + '/^(=+).+?=+|^.*?<\/h[1-6].*?' . '>(?!\S)/mi',$subheadline,$matches); $subhlevel=$matches[1]; if(strpos($subhlevel,'=')!==false) { $subhlevel=strlen($subhlevel); @@ -884,6 +1030,13 @@ class Article { return $text; } + /** + * 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. + * + * 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. + */ function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) { global $wgOut, $wgUser; global $wgDBtransactions, $wgMwRedir; @@ -894,9 +1047,15 @@ class Article { if ( $this->mMinorEdit ) { $me1 = 1; } else { $me1 = 0; } if ( $minor && $wgUser->getID() ) { $me2 = 1; } else { $me2 = 0; } - if ( preg_match( "/^((" . $wgMwRedir->getBaseRegex() . ')[^\\n]+)/i', $text, $m ) ) { - $redir = 1; - $text = $m[1] . "\n"; # Remove all content but redirect + if ( $this->isRedirect( $text ) ) { + # Remove all content but redirect + # This could be done by reconstructing the redirect from a title given by + # Title::newFromRedirect(), but then we wouldn't know which synonym the user + # wants to see + if ( preg_match( "/^((" . $wgMwRedir->getBaseRegex() . ')[^\\n]+)/i', $text, $m ) ) { + $redir = 1; + $text = $m[1] . "\n"; + } } else { $redir = 0; } @@ -929,15 +1088,15 @@ class Article { 'cur_comment' => $summary, 'cur_minor_edit' => $me2, 'cur_user' => $wgUser->getID(), - 'cur_timestamp' => $now, + 'cur_timestamp' => $dbw->timestamp($now), 'cur_user_text' => $wgUser->getName(), 'cur_is_redirect' => $redir, 'cur_is_new' => 0, - 'cur_touched' => $now, + 'cur_touched' => $dbw->timestamp($now), 'inverse_timestamp' => $won ), array( /* WHERE */ 'cur_id' => $this->getID(), - 'cur_timestamp' => $this->getTimestamp() + 'cur_timestamp' => $dbw->timestamp($this->getTimestamp()) ), $fname ); @@ -959,7 +1118,7 @@ class Article { 'old_comment' => $this->getComment(), 'old_user' => $this->getUser(), 'old_user_text' => $this->getUserText(), - 'old_timestamp' => $this->getTimestamp(), + 'old_timestamp' => $dbw->timestamp($this->getTimestamp()), 'old_minor_edit' => $me1, 'inverse_timestamp' => wfInvertTimestamp( $this->getTimestamp() ), 'old_flags' => $flags, @@ -1018,12 +1177,12 @@ class Article { return $good; } - # After we've either updated or inserted the article, update - # the link tables and redirect to the new page. - + /** + * After we've either updated or inserted the article, update + * the link tables and redirect to the new page. + */ function showArticle( $text, $subtitle , $sectionanchor = '' ) { global $wgOut, $wgUser, $wgLinkCache; - global $wgMwRedir; $wgLinkCache = new LinkCache(); # Select for update @@ -1038,21 +1197,24 @@ class Article { $wgOut->addWikiText( $text ); # Look up the links in the DB and add them to the link cache - $wgOut->replaceLinkHolders( RLH_FOR_UPDATE ); + $wgOut->transformBuffer( RLH_FOR_UPDATE ); - if( $wgMwRedir->matchStart( $text ) ) + if( $this->isRedirect( $text ) ) $r = 'redirect=no'; else $r = ''; $wgOut->redirect( $this->mTitle->getFullURL( $r ).$sectionanchor ); } - # Validate article - + /** + * Validate article + * @todo document this function a bit more + */ function validate () { global $wgOut, $wgUseValidation; if( $wgUseValidation ) { - $wgOut->setPagetitle( wfMsg( 'validate' ) . ": " . $this->mTitle->getPrefixedText() ); + require_once ( 'SpecialValidate.php' ) ; + $wgOut->setPagetitle( wfMsg( 'validate' ) . ': ' . $this->mTitle->getPrefixedText() ); $wgOut->setRobotpolicy( 'noindex,follow' ); if( $this->mTitle->getNamespace() != 0 ) { $wgOut->addHTML( wfMsg( 'val_validate_article_namespace_only' ) ); @@ -1061,11 +1223,13 @@ class Article { $v = new Validation; $v->validate_form( $this->mTitle->getDBkey() ); } else { - $wgOut->errorpage( "nosuchaction", "nosuchactiontext" ); + $wgOut->errorpage( 'nosuchaction', 'nosuchactiontext' ); } } - # Mark this particular edit as patrolled + /** + * Mark this particular edit as patrolled + */ function markpatrolled() { global $wgOut, $wgRequest, $wgOnlySysopsCanPatrol, $wgUseRCPatrol, $wgUser; $wgOut->setRobotpolicy( 'noindex,follow' ); @@ -1100,8 +1264,9 @@ class Article { } - # Add this page to my watchlist - + /** + * Add or remove this page to my watchlist based on value of $add + */ function watch( $add = true ) { global $wgUser, $wgOut, $wgLang; global $wgDeferredUpdateList; @@ -1137,12 +1302,16 @@ class Article { $wgOut->returnToMain( true, $this->mTitle->getPrefixedText() ); } + /** + * Stop watching a page, it act just like a call to watch(false) + */ function unwatch() { $this->watch( false ); } - # protect a page - + /** + * protect a page + */ function protect( $limit = 'sysop' ) { global $wgUser, $wgOut, $wgRequest; @@ -1167,18 +1336,18 @@ class Article { $dbw =& wfGetDB( DB_MASTER ); $dbw->updateArray( 'cur', array( /* SET */ - 'cur_touched' => wfTimestampNow(), + 'cur_touched' => $dbw->timestamp(), 'cur_restrictions' => (string)$limit ), array( /* WHERE */ 'cur_id' => $id ), 'Article::protect' ); - $log = new LogPage( wfMsg( 'protectlogpage' ), wfMsg( 'protectlogtext' ) ); - if ( $limit === "" ) { - $log->addEntry( wfMsg( 'unprotectedarticle', $this->mTitle->getPrefixedText() ), $reason ); + $log = new LogPage( 'protect' ); + if ( $limit === '' ) { + $log->addEntry( 'unprotect', $this->mTitle, $reason ); } else { - $log->addEntry( wfMsg( 'protectedarticle', $this->mTitle->getPrefixedText() ), $reason ); + $log->addEntry( 'protect', $this->mTitle, $reason ); } $wgOut->redirect( $this->mTitle->getFullURL() ); return; @@ -1188,8 +1357,9 @@ class Article { } } - # Output protection confirmation dialog - + /** + * Output protection confirmation dialog + */ function confirmProtect( $par, $reason, $limit = 'sysop' ) { global $wgOut; @@ -1253,12 +1423,16 @@ class Article { $wgOut->returnToMain( false ); } + /** + * Unprotect the pages + */ function unprotect() { return $this->protect( '' ); } - # UI entry point for page deletion - + /* + * UI entry point for page deletion + */ function delete() { global $wgUser, $wgOut, $wgMessageCache, $wgRequest; $fname = 'Article::delete'; @@ -1293,7 +1467,7 @@ class Article { # determine whether this page has earlier revisions # and insert a warning if it does # we select the text because it might be useful below - $dbr =& wfGetDB( DB_SLAVE ); + $dbr =& $this->getDB(); $ns = $this->mTitle->getNamespace(); $title = $this->mTitle->getDBkey(); $old = $dbr->getArray( 'old', @@ -1301,7 +1475,7 @@ class Article { array( 'old_namespace' => $ns, 'old_title' => $title, - ), $fname, array( 'ORDER BY' => 'inverse_timestamp' ) + ), $fname, $this->getSelectOptions( array( 'ORDER BY' => 'inverse_timestamp' ) ) ); if( $old !== false && !$confirm ) { @@ -1316,7 +1490,7 @@ class Article { array( 'cur_namespace' => $ns, 'cur_title' => $title, - ), $fname + ), $fname, $this->getSelectOptions() ); if( $s !== false ) { @@ -1365,8 +1539,9 @@ class Article { return $this->confirmDelete( '', $reason ); } - # Output deletion confirmation dialog - + /** + * Output deletion confirmation dialog + */ function confirmDelete( $par, $reason ) { global $wgOut; @@ -1418,12 +1593,13 @@ class Article { } - # Perform a deletion and output success or failure messages - + /** + * Perform a deletion and output success or failure messages + */ function doDelete( $reason ) { global $wgOut, $wgUser, $wgLang; $fname = 'Article::doDelete'; - wfDebug( "$fname\n" ); + wfDebug( $fname."\n" ); if ( $this->doDeleteArticle( $reason ) ) { $deleted = $this->mTitle->getPrefixedText(); @@ -1432,10 +1608,10 @@ class Article { $wgOut->setRobotpolicy( 'noindex,nofollow' ); $sk = $wgUser->getSkin(); - $loglink = $sk->makeKnownLink( $wgLang->getNsText( NS_WIKIPEDIA ) . + $loglink = $sk->makeKnownLink( $wgLang->getNsText( NS_PROJECT ) . ':' . wfMsg( 'dellogpage' ), wfMsg( 'deletionlog' ) ); - $text = wfMsg( "deletedtext", $deleted, $loglink ); + $text = wfMsg( 'deletedtext', $deleted, $loglink ); $wgOut->addHTML( '

' . $text . "

\n" ); $wgOut->returnToMain( false ); @@ -1444,9 +1620,11 @@ class Article { } } - # Back-end article deletion - # Deletes the article with database consistency, writes logs, purges caches - # Returns success + /** + * Back-end article deletion + * Deletes the article with database consistency, writes logs, purges caches + * Returns success + */ function doDeleteArticle( $reason ) { global $wgUser, $wgLang; global $wgUseSquid, $wgDeferredUpdateList, $wgInternalServer; @@ -1546,7 +1724,7 @@ class Article { $linkID = $titleObj->getArticleID(); $brokenLinks[] = array( 'bl_from' => $linkID, 'bl_to' => $t ); } - $dbw->insert( 'brokenlinks', $brokenLinks, $fname, 'IGNORE' ); + $dbw->insertArray( 'brokenlinks', $brokenLinks, $fname, 'IGNORE' ); # Delete live links $dbw->delete( 'links', array( 'l_to' => $id ) ); @@ -1556,9 +1734,8 @@ class Article { $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) ); # Log the deletion - $log = new LogPage( wfMsg( 'dellogpage' ), wfMsg( 'dellogpagetext' ) ); - $art = $this->mTitle->getPrefixedText(); - $log->addEntry( wfMsg( 'deletedarticle', $art ), $reason ); + $log = new LogPage( 'delete' ); + $log->addEntry( 'delete', $this->mTitle, $reason ); # Clear the cached article id so the interface doesn't act like we exist $this->mTitle->resetArticleID( 0 ); @@ -1566,9 +1743,12 @@ class Article { return true; } + /** + * Revert a modification + */ function rollback() { global $wgUser, $wgLang, $wgOut, $wgRequest; - $fname = "Article::rollback"; + $fname = 'Article::rollback'; if ( ! $wgUser->isSysop() ) { $wgOut->sysopRequired(); @@ -1656,9 +1836,11 @@ class Article { } - # Do standard deferred updates after page view - - /* private */ function viewUpdates() { + /** + * Do standard deferred updates after page view + * @private + */ + function viewUpdates() { global $wgDeferredUpdateList; if ( 0 != $this->getID() ) { global $wgDisableCounters; @@ -1673,17 +1855,20 @@ class Article { array_push( $wgDeferredUpdateList, $u ); } - # Do standard deferred updates after page edit. - # Every 1000th edit, prune the recent changes table. - - /* private */ function editUpdates( $text ) { + /** + * Do standard deferred updates after page edit. + * Every 1000th edit, prune the recent changes table. + * @private + * @param string $text + */ + function editUpdates( $text ) { global $wgDeferredUpdateList, $wgDBname, $wgMemc; global $wgMessageCache; wfSeedRandom(); if ( 0 == mt_rand( 0, 999 ) ) { $dbw =& wfGetDB( DB_MASTER ); - $cutoff = wfUnix2Timestamp( time() - ( 7 * 86400 ) ); + $cutoff = $dbw->timestamp( time() - ( 7 * 86400 ) ); $sql = "DELETE FROM recentchanges WHERE rc_timestamp < '{$cutoff}'"; $dbw->query( $sql ); } @@ -1710,17 +1895,26 @@ class Article { } } - /* private */ function setOldSubtitle() { - global $wgLang, $wgOut; + /** + * @todo document this function + * @private + */ + function setOldSubtitle() { + global $wgLang, $wgOut, $wgUser; $td = $wgLang->timeanddate( $this->mTimestamp, true ); - $r = wfMsg( 'revisionasof', $td ); - $wgOut->setSubtitle( "({$r})" ); + $sk = $wgUser->getSkin(); + $lnk = $sk->makeKnownLinkObj ( $this->mTitle, wfMsg( 'currentrevisionlink' ) ); + $r = wfMsg( 'revisionasofwithlink', $td, $lnk ); + $wgOut->setSubtitle( $r ); } - # This function is called right before saving the wikitext, - # so we can do things like signatures and links-in-context. - + /** + * This function is called right before saving the wikitext, + * so we can do things like signatures and links-in-context. + * + * @param string $text + */ function preSaveTransform( $text ) { global $wgParser, $wgUser; return $wgParser->preSaveTransform( $text, $this->mTitle, $wgUser, ParserOptions::newFromUser( $wgUser ) ); @@ -1728,9 +1922,11 @@ class Article { /* Caching functions */ - # checkLastModified returns true if it has taken care of all - # output to the client that is necessary for this request. - # (that is, it has sent a cached version of the page) + /** + * checkLastModified returns true if it has taken care of all + * output to the client that is necessary for this request. + * (that is, it has sent a cached version of the page) + */ function tryFileCache() { static $called = false; if( $called ) { @@ -1759,7 +1955,11 @@ class Article { wfDebug( " tryFileCache() - not cacheable\n" ); } } - + + /** + * Check if the page can be cached + * @return bool + */ function isFileCacheable() { global $wgUser, $wgUseFileCache, $wgShowIPinHeader, $wgRequest; extract( $wgRequest->getValues( 'action', 'oldid', 'diff', 'redirect', 'printable' ) ); @@ -1778,25 +1978,33 @@ class Article { and (!$this->mRedirectedFrom); } - # Loads cur_touched and returns a value indicating if it should be used + /** + * Loads cur_touched and returns a value indicating if it should be used + * + */ function checkTouched() { $fname = 'Article::checkTouched'; - $id = $this->getID(); - $dbr =& wfGetDB( DB_SLAVE ); + $dbr =& $this->getDB(); $s = $dbr->getArray( 'cur', array( 'cur_touched', 'cur_is_redirect' ), - array( 'cur_id' => $id ), $fname ); + array( 'cur_id' => $id ), $fname, $this->getSelectOptions() ); if( $s !== false ) { - $this->mTouched = $s->cur_touched; + $this->mTouched = wfTimestamp(TS_MW,$s->cur_touched); return !$s->cur_is_redirect; } else { return false; } } - # Edit an article without doing all that other stuff + /** + * Edit an article without doing all that other stuff + * + * @param string $text text submitted + * @param string $comment comment submitted + * @param integer $minor whereas it's a minor modification + */ function quickEdit( $text, $comment = '', $minor = 0 ) { - global $wgUser, $wgMwRedir; + global $wgUser; $fname = 'Article::quickEdit'; wfProfileIn( $fname ); @@ -1834,9 +2042,9 @@ class Article { 'cur_user_text' => $wgUser->getName(), 'inverse_timestamp' => wfInvertTimestamp( $timestamp ), 'cur_comment' => $comment, - 'cur_is_redirect' => $wgMwRedir->matchStart( $text ) ? 1 : 0, + 'cur_is_redirect' => $this->isRedirect( $text ) ? 1 : 0, 'cur_minor_edit' => intval($minor), - 'cur_touched' => $timestamp, + 'cur_touched' => $dbw->timestamp($timestamp), ); if ( $numRows ) { @@ -1854,7 +2062,13 @@ class Article { wfProfileOut( $fname ); } - /* static */ function incViewCount( $id ) { + /** + * Used to increment the view counter + * + * @static + * @param integer $id article id + */ + function incViewCount( $id ) { $id = intval( $id ); global $wgHitcounterUpdateFreq; @@ -1903,14 +2117,19 @@ class Article { $dbw->ignoreErrors( $oldignore ); } - # The onArticle*() functions are supposed to be a kind of hooks - # which should be called whenever any of the specified actions - # are done. - # - # This is a good place to put code to clear caches, for instance. - - # This is called on page move and undelete, as well as edit - /* static */ function onArticleCreate($title_obj) { + /**#@+ + * The onArticle*() functions are supposed to be a kind of hooks + * which should be called whenever any of the specified actions + * are done. + * + * This is a good place to put code to clear caches, for instance. + * + * This is called on page move and undelete, as well as edit + * @static + * @param $title_obj a title object + */ + + function onArticleCreate($title_obj) { global $wgUseSquid, $wgDeferredUpdateList; $titles = $title_obj->getBrokenLinksTo(); @@ -1929,27 +2148,27 @@ class Article { LinkCache::linksccClearBrokenLinksTo( $title_obj->getPrefixedDBkey() ); } - /* static */ function onArticleDelete($title_obj) { + function onArticleDelete($title_obj) { LinkCache::linksccClearLinksTo( $title_obj->getArticleID() ); } - - /* static */ function onArticleEdit($title_obj) { + function onArticleEdit($title_obj) { LinkCache::linksccClearPage( $title_obj->getArticleID() ); } + /**#@-*/ - - # Info about this page - + /** + * Info about this page + */ function info() { global $wgUser, $wgTitle, $wgOut, $wgLang, $wgAllowPageInfo; $fname = 'Article::info'; if ( !$wgAllowPageInfo ) { - $wgOut->errorpage( "nosuchaction", "nosuchactiontext" ); + $wgOut->errorpage( 'nosuchaction', 'nosuchactiontext' ); return; } - $dbr =& wfGetDB( DB_SLAVE ); + $dbr =& $this->getDB(); $basenamespace = $wgTitle->getNamespace() & (~1); $cur_clause = array( 'cur_title' => $wgTitle->getDBkey(), 'cur_namespace' => $basenamespace ); @@ -1957,17 +2176,18 @@ class Article { $wl_clause = array( 'wl_title' => $wgTitle->getDBkey(), 'wl_namespace' => $basenamespace ); $fullTitle = $wgTitle->makeName($basenamespace, $wgTitle->getDBKey()); $wgOut->setPagetitle( $fullTitle ); - $wgOut->setSubtitle( wfMsg( "infosubtitle" )); + $wgOut->setSubtitle( wfMsg( 'infosubtitle' )); # first, see if the page exists at all. - $exists = $dbr->selectField( 'cur', 'COUNT(*)', $cur_clause, $fname ); + $exists = $dbr->selectField( 'cur', 'COUNT(*)', $cur_clause, $fname, $this->getSelectOptions() ); if ($exists < 1) { - $wgOut->addHTML( wfMsg("noarticletext") ); + $wgOut->addHTML( wfMsg('noarticletext') ); } else { - $numwatchers = $dbr->selectField( 'watchlist', 'COUNT(*)', $wl_clause, $fname ); - $wgOut->addHTML( "
  • " . wfMsg("numwatchers", $numwatchers) . "
  • " ); - $old = $dbr->selectField( 'old', 'COUNT(*)', $old_clause, $fname ); - $wgOut->addHTML( "
  • " . wfMsg("numedits", $old + 1) . "
  • "); + $numwatchers = $dbr->selectField( 'watchlist', 'COUNT(*)', $wl_clause, $fname, + $this->getSelectOptions() ); + $wgOut->addHTML( "
    • " . wfMsg("numwatchers", $numwatchers) . '
    • ' ); + $old = $dbr->selectField( 'old', 'COUNT(*)', $old_clause, $fname, $this->getSelectOptions() ); + $wgOut->addHTML( "
    • " . wfMsg('numedits', $old + 1) . '
    • '); # to find number of distinct authors, we need to do some # funny stuff because of the cur/old table split: @@ -1975,33 +2195,37 @@ class Article { # - then, find the number of *other* authors in 'old' # find 'cur' author - $cur_author = $dbr->selectField( 'cur', 'cur_user_text', $cur_clause, $fname ); + $cur_author = $dbr->selectField( 'cur', 'cur_user_text', $cur_clause, $fname, + $this->getSelectOptions() ); # find number of 'old' authors excluding 'cur' author $authors = $dbr->selectField( 'old', 'COUNT(DISTINCT old_user_text)', - $old_clause + array( 'old_user_text<>' . $dbr->addQuotes( $cur_author ) ), $fname ) + 1; + $old_clause + array( 'old_user_text<>' . $dbr->addQuotes( $cur_author ) ), $fname, + $this->getSelectOptions() ) + 1; # now for the Talk page ... $cur_clause = array( 'cur_title' => $wgTitle->getDBkey(), 'cur_namespace' => $basenamespace+1 ); $old_clause = array( 'old_title' => $wgTitle->getDBkey(), 'old_namespace' => $basenamespace+1 ); # does it exist? - $exists = $dbr->selectField( 'cur', 'COUNT(*)', $cur_clause, $fname ); + $exists = $dbr->selectField( 'cur', 'COUNT(*)', $cur_clause, $fname, $this->getSelectOptions() ); # number of edits if ($exists > 0) { - $old = $dbr->selectField( 'old', 'COUNT(*)', $old_clause, $fname ); - $wgOut->addHTML( "
    • " . wfMsg("numtalkedits", $old + 1) . "
    • "); + $old = $dbr->selectField( 'old', 'COUNT(*)', $old_clause, $fname, $this->getSelectOptions() ); + $wgOut->addHTML( '
    • ' . wfMsg("numtalkedits", $old + 1) . '
    • '); } - $wgOut->addHTML( "
    • " . wfMsg("numauthors", $authors) . "
    • " ); + $wgOut->addHTML( '
    • ' . wfMsg("numauthors", $authors) . '
    • ' ); # number of authors if ($exists > 0) { - $cur_author = $dbr->selectField( 'cur', 'cur_user_text', $cur_clause, $fname ); + $cur_author = $dbr->selectField( 'cur', 'cur_user_text', $cur_clause, $fname, + $this->getSelectOptions() ); $authors = $dbr->selectField( 'cur', 'COUNT(DISTINCT old_user_text)', - $old_clause + array( 'old_user_text<>' . $dbr->addQuotes( $cur_author ) ), $fname ); + $old_clause + array( 'old_user_text<>' . $dbr->addQuotes( $cur_author ) ), + $fname, $this->getSelectOptions() ); - $wgOut->addHTML( "
    • " . wfMsg("numtalkauthors", $authors) . "
    " ); + $wgOut->addHTML( '
  • ' . wfMsg('numtalkauthors', $authors) . '
' ); } } }