Difference between revisions of "MediaWikiDoc:Article.php"

From HypertWiki
Jump to navigation Jump to search
 
(more functions. still editing.)
Line 1: Line 1:
[[Techniques]]: [[MediaWiki]]: [[MediaWikiDoc:Article.php|Article.php]]
[[Techniques]]: [[MediaWiki]]: [[MediaWikiDoc:Article.php|Article.php]]
==Class Functions==
==Class Functions==
function Article( &$title ) {
function getTitle() {
function clear() {
function getRevisionText( $row, $prefix = 'old_' ) {
function compressRevisionText( &$text ) {
function getContent( $noredir ) {
function getSection($text,$section) {
function &getCurContentFields() {
function &getOldContentFields() {
function getOldID() {
function loadContent( $noredir = false ) {
function getContentWithoutUsingSoManyDamnGlobals( $oldid = 0, $noredir = false ) {
function forUpdate( $x = NULL ) {
function &getDB() {
function getSelectOptions( $options = '' ) {
function getID() {
function getCount() {
**Get the view count for this article
function isCountable( $text ) {
**Would the given text make this article a "good" article (i.e., suitable for including in the article count)?
*function isRedirect( $text = false ) {
**Tests if the article text represents a redirect
*function loadLastEdit() {
**Loads everything from cur except cur_text
**This isn't necessary for all uses, so it's only done if needed.
===Data Access===
*function getTimestamp() {
*function getUser() {
*function getUserText() {
*function getComment() {
*function getMinorEdit() {
===More Other Methods===
*function getContributors($limit = 0, $offset = 0) {
*function view() {
**This is the default action of the script: just view the page of the given title.
*function insertNewArticle( $text, $summary, $isminor, $watchthis ) {
**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.
*function getTextOfLastEditWithSectionReplacedOrAdded($section, $text, $summary = '', $edittime = NULL) {
**Side effects: loads last edit if $edittime is NULL
*function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = '' ) {
**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 showArticle( $text, $subtitle , $sectionanchor = '' ) {
**After we've either updated or inserted the article, update the link tables and redirect to the new page.
*function validate () {
**Validate article
*function markpatrolled() {
**Mark this particular edit as patrolled
*function watch() {
**Add this page to $wgUser's watchlist
*function unwatch() {
**Stop watching a page
*function protect( $limit = 'sysop' ) {
**protect a page
*function confirmProtect( $par, $reason, $limit = 'sysop'  ) {
**Output protection confirmation dialog
*function unprotect() {
**Unprotect the pages
*function delete() {
**UI entry point for page deletion
/**
* Output deletion confirmation dialog
*/
function confirmDelete( $par, $reason ) {
global $wgOut, $wgUser;
wfDebug( "Article::confirmDelete\n" );
$sub = htmlspecialchars( $this->mTitle->getPrefixedText() );
$wgOut->setSubtitle( wfMsg( 'deletesub', $sub ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
$wgOut->addWikiText( wfMsg( 'confirmdeletetext' ) );
$formaction = $this->mTitle->escapeLocalURL( 'action=delete' . $par );
$confirm = htmlspecialchars( wfMsg( 'confirm' ) );
$check = htmlspecialchars( wfMsg( 'confirmcheck' ) );
$delcom = htmlspecialchars( wfMsg( 'deletecomment' ) );
$token = htmlspecialchars( $wgUser->editToken() );
$wgOut->addHTML( "
<form id='deleteconfirm' method='post' action=\"{$formaction}\">
<table border='0'>
<tr>
<td align='right'>
<label for='wpReason'>{$delcom}:</label>
</td>
<td align='left'>
<input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" />
</td>
</tr>
<tr>
<td>&nbsp;</td>
</tr>
<tr>
<td align='right'>
<input type='checkbox' name='wpConfirm' value='1' id='wpConfirm' />
</td>
<td>
<label for='wpConfirm'>{$check}</label>
</td>
</tr>
<tr>
<td>&nbsp;</td>
<td>
<input type='submit' name='wpConfirmB' value=\"{$confirm}\" />
</td>
</tr>
</table>
<input type='hidden' name='wpEditToken' value=\"{$token}\" />
</form>\n" );
$wgOut->returnToMain( false );
}
/**
* Perform a deletion and output success or failure messages
*/
function doDelete( $reason ) {
global $wgOut, $wgUser, $wgContLang;
$fname = 'Article::doDelete';
wfDebug( $fname."\n" );
if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) {
if ( $this->doDeleteArticle( $reason ) ) {
$deleted = $this->mTitle->getPrefixedText();
$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
$sk = $wgUser->getSkin();
$loglink = $sk->makeKnownLink( $wgContLang->getNsText( NS_SPECIAL ) .
  ':Log/delete',
  wfMsg( 'deletionlog' ) );
$text = wfMsg( 'deletedtext', $deleted, $loglink );
$wgOut->addHTML( '<p>' . $text . "</p>\n" );
$wgOut->returnToMain( false );
wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason));
} else {
$wgOut->fatalError( wfMsg( 'cannotdelete' ) );
}
}
}
/**
* Back-end article deletion
* Deletes the article with database consistency, writes logs, purges caches
* Returns success
*/
function doDeleteArticle( $reason ) {
global $wgUser;
global  $wgUseSquid, $wgDeferredUpdateList, $wgInternalServer, $wgPostCommitUpdateList;
$fname = 'Article::doDeleteArticle';
wfDebug( $fname."\n" );
$dbw =& wfGetDB( DB_MASTER );
$ns = $this->mTitle->getNamespace();
$t = $this->mTitle->getDBkey();
$id = $this->mTitle->getArticleID();
if ( $t == '' || $id == 0 ) {
return false;
}
$u = new SiteStatsUpdate( 0, 1, -$this->isCountable( $this->getContent( true ) ), -1 );
array_push( $wgDeferredUpdateList, $u );
$linksTo = $this->mTitle->getLinksTo();
# Squid purging
if ( $wgUseSquid ) {
$urls = array(
$this->mTitle->getInternalURL(),
$this->mTitle->getInternalURL( 'history' )
);
foreach ( $linksTo as $linkTo ) {
$urls[] = $linkTo->getInternalURL();
}
$u = new SquidUpdate( $urls );
array_push( $wgPostCommitUpdateList, $u );
}
# Client and file cache invalidation
Title::touchArray( $linksTo );
# Move article and history to the "archive" table
$archiveTable = $dbw->tableName( 'archive' );
$oldTable = $dbw->tableName( 'old' );
$curTable = $dbw->tableName( 'cur' );
$recentchangesTable = $dbw->tableName( 'recentchanges' );
$linksTable = $dbw->tableName( 'links' );
$brokenlinksTable = $dbw->tableName( 'brokenlinks' );
# Archive cur row
$dbw->insertSelect( 'archive', 'cur',
array(
'ar_namespace' => 'cur_namespace',
'ar_title' => 'cur_title',
'ar_text' => 'cur_text',
'ar_comment' => 'cur_comment',
'ar_user' => 'cur_user',
'ar_user_text' => 'cur_user_text',
'ar_timestamp' => 'cur_timestamp',
'ar_minor_edit' => 'cur_minor_edit',
'ar_flags' => 0,
), array(
'cur_namespace' => $ns,
'cur_title' => $t,
), $fname
);
# Archive old rows
$res = $dbw->select( 'old', array( 'old_namespace', 'old_title', 'old_text', 'old_comment',
'old_user', 'old_user_text', 'old_timestamp', 'old_minor_edit', 'old_flags' ),
array( 'old_namespace' => $ns, 'old_title' => $t ), $fname );
while ( $row = $dbw->fetchObject( $res ) ) {
# Recompress block-compressed revisions
if ( $row->old_flags == 'object' ) {
$text = Article::getRevisionText( $row );
$flags = Article::compressRevisionText( $text );
} else {
$text = $row->old_text;
$flags = $row->old_flags;
}
$dbw->insert( 'archive',
array(
'ar_namespace' => $ns,
'ar_title' => $t,
'ar_text' => $text,
'ar_comment' => $row->old_comment,
'ar_user' => $row->old_user,
'ar_user_text' => $row->old_user_text,
'ar_timestamp' => $row->old_timestamp,
'ar_minor_edit' => $row->old_minor_edit,
'ar_flags' => $flags
), $fname
);
}
# Now that it's safely backed up, delete it
$dbw->delete( 'cur', array( 'cur_namespace' => $ns, 'cur_title' => $t ), $fname );
$dbw->delete( 'old', array( 'old_namespace' => $ns, 'old_title' => $t ), $fname );
$dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), $fname );
# Finally, clean up the link tables
$t = $this->mTitle->getPrefixedDBkey();
Article::onArticleDelete( $this->mTitle );
# Insert broken links
$brokenLinks = array();
foreach ( $linksTo as $titleObj ) {
# Get article ID. Efficient because it was loaded into the cache by getLinksTo().
$linkID = $titleObj->getArticleID();
$brokenLinks[] = array( 'bl_from' => $linkID, 'bl_to' => $t );
}
$dbw->insert( 'brokenlinks', $brokenLinks, $fname, 'IGNORE' );
# Delete live links
$dbw->delete( 'links', array( 'l_to' => $id ) );
$dbw->delete( 'links', array( 'l_from' => $id ) );
$dbw->delete( 'imagelinks', array( 'il_from' => $id ) );
$dbw->delete( 'brokenlinks', array( 'bl_from' => $id ) );
$dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );
# Log the deletion
$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 );
$this->mTitle->mArticleID = 0;
return true;
}
/**
* Revert a modification
*/
function rollback() {
global $wgUser, $wgOut, $wgRequest;
$fname = 'Article::rollback';
if ( ! $wgUser->isAllowed('rollback') ) {
$wgOut->sysopRequired();
return;
}
if ( wfReadOnly() ) {
$wgOut->readOnlyPage( $this->getContent( true ) );
return;
}
if( !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ),
array( $this->mTitle->getPrefixedText(),
$wgRequest->getVal( 'from' ) )  ) ) {
$wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
$wgOut->addWikiText( wfMsg( 'sessionfailure' ) );
return;
}
$dbw =& wfGetDB( DB_MASTER );
# Enhanced rollback, marks edits rc_bot=1
$bot = $wgRequest->getBool( 'bot' );
# Replace all this user's current edits with the next one down
$tt = $this->mTitle->getDBKey();
$n = $this->mTitle->getNamespace();
# Get the last editor, lock table exclusively
$s = $dbw->selectRow( 'cur',
array( 'cur_id','cur_user','cur_user_text','cur_comment' ),
array( 'cur_title' => $tt, 'cur_namespace' => $n ),
$fname, 'FOR UPDATE'
);
if( $s === false ) {
# Something wrong
$wgOut->addHTML( wfMsg( 'notanarticle' ) );
return;
}
$ut = $dbw->strencode( $s->cur_user_text );
$uid = $s->cur_user;
$pid = $s->cur_id;
$from = str_replace( '_', ' ', $wgRequest->getVal( 'from' ) );
if( $from != $s->cur_user_text ) {
$wgOut->setPageTitle(wfmsg('rollbackfailed'));
$wgOut->addWikiText( wfMsg( 'alreadyrolled',
htmlspecialchars( $this->mTitle->getPrefixedText()),
htmlspecialchars( $from ),
htmlspecialchars( $s->cur_user_text ) ) );
if($s->cur_comment != '') {
$wgOut->addHTML(
wfMsg('editcomment',
htmlspecialchars( $s->cur_comment ) ) );
}
return;
}
# Get the last edit not by this guy
$s = $dbw->selectRow( 'old',
array( 'old_text','old_user','old_user_text','old_timestamp','old_flags' ),
array(
'old_namespace' => $n,
'old_title' => $tt,
"old_user <> {$uid} OR old_user_text <> '{$ut}'"
), $fname, array( 'FOR UPDATE', 'USE INDEX' => 'name_title_timestamp' )
);
if( $s === false ) {
# Something wrong
$wgOut->setPageTitle(wfMsg('rollbackfailed'));
$wgOut->addHTML( wfMsg( 'cantrollback' ) );
return;
}
if ( $bot ) {
# Mark all reverted edits as bot
$dbw->update( 'recentchanges',
array( /* SET */
'rc_bot' => 1
), array( /* WHERE */
'rc_user' => $uid,
"rc_timestamp > '{$s->old_timestamp}'",
), $fname
);
}
# Save it!
$newcomment = wfMsg( 'revertpage', $s->old_user_text, $from );
$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) );
$wgOut->setRobotpolicy( 'noindex,nofollow' );
$wgOut->addHTML( '<h2>' . htmlspecialchars( $newcomment ) . "</h2>\n<hr />\n" );
$this->updateArticle( Article::getRevisionText( $s ), $newcomment, 1, $this->mTitle->userIsWatching(), $bot );
Article::onArticleEdit( $this->mTitle );
$wgOut->returnToMain( false );
}
/**
* Do standard deferred updates after page view
* @private
*/
function viewUpdates() {
global $wgDeferredUpdateList;
if ( 0 != $this->getID() ) {
global $wgDisableCounters;
if( !$wgDisableCounters ) {
Article::incViewCount( $this->getID() );
$u = new SiteStatsUpdate( 1, 0, 0 );
array_push( $wgDeferredUpdateList, $u );
}
}
# Update newtalk status if user is reading their own
# talk page
global $wgUser;
if ($this->mTitle->getNamespace() == NS_USER_TALK &&
$this->mTitle->getText() == $wgUser->getName())
{
$wgUser->setNewtalk(0);
$wgUser->saveNewtalk();
}
}
/**
* 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, $wgUser;
wfSeedRandom();
if ( 0 == mt_rand( 0, 999 ) ) {
# Periodically flush old entries from the recentchanges table.
global $wgRCMaxAge;
$dbw =& wfGetDB( DB_MASTER );
$cutoff = $dbw->timestamp( time() - $wgRCMaxAge );
$recentchanges = $dbw->tableName( 'recentchanges' );
$sql = "DELETE FROM $recentchanges WHERE rc_timestamp < '{$cutoff}'";
$dbw->query( $sql );
}
$id = $this->getID();
$title = $this->mTitle->getPrefixedDBkey();
$shortTitle = $this->mTitle->getDBkey();
if ( 0 != $id ) {
$u = new LinksUpdate( $id, $title );
array_push( $wgDeferredUpdateList, $u );
$u = new SiteStatsUpdate( 0, 1, $this->mGoodAdjustment, $this->mTotalAdjustment );
array_push( $wgDeferredUpdateList, $u );
$u = new SearchUpdate( $id, $title, $text );
array_push( $wgDeferredUpdateList, $u );
# If this is another user's talk page, save a
# newtalk notification for them
if ($this->mTitle->getNamespace() == NS_USER_TALK &&
$shortTitle != $wgUser->getName())
{
$other = User::newFromName($shortTitle);
$other->setNewtalk(1);
$other->saveNewtalk();
}
if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) {
$wgMessageCache->replace( $shortTitle, $text );
}
}
}
/**
* @todo document this function
* @private
* @param string $oldid Revision ID of this article revision
*/
function setOldSubtitle( $oldid=0 ) {
global $wgLang, $wgOut, $wgUser;
$td = $wgLang->timeanddate( $this->mTimestamp, true );
$sk = $wgUser->getSkin();
$lnk = $sk->makeKnownLinkObj ( $this->mTitle, wfMsg( 'currentrevisionlink' ) );
$prevlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'previousrevision' ), 'direction=prev&oldid='.$oldid );
$nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'nextrevision' ), 'direction=next&oldid='.$oldid );
$r = wfMsg( 'revisionasofwithlink', $td, $lnk, $prevlink, $nextlink );
$wgOut->setSubtitle( $r );
}
/**
* 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 ) );
}
/* 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)
*/
function tryFileCache() {
static $called = false;
if( $called ) {
wfDebug( " tryFileCache() -- called twice!?\n" );
return;
}
$called = true;
if($this->isFileCacheable()) {
$touched = $this->mTouched;
$cache = new CacheManager( $this->mTitle );
if($cache->isFileCacheGood( $touched )) {
global $wgOut;
wfDebug( " tryFileCache() - about to load\n" );
$cache->loadFromFileCache();
return true;
} else {
wfDebug( " tryFileCache() - starting buffer\n" );
ob_start( array(&$cache, 'saveToFileCache' ) );
}
} else {
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' ) );
return $wgUseFileCache
and (!$wgShowIPinHeader)
and ($this->getID() != 0)
and ($wgUser->getId() == 0)
and (!$wgUser->getNewtalk())
and ($this->mTitle->getNamespace() != NS_SPECIAL )
and (empty( $action ) || $action == 'view')
and (!isset($oldid))
and (!isset($diff))
and (!isset($redirect))
and (!isset($printable))
and (!$this->mRedirectedFrom);
}
/**
* Loads cur_touched and returns a value indicating if it should be used
*
*/
function checkTouched() {
$fname = 'Article::checkTouched';
$id = $this->getID();
$dbr =& $this->getDB();
$s = $dbr->selectRow( 'cur', array( 'cur_touched', 'cur_is_redirect' ),
array( 'cur_id' => $id ), $fname, $this->getSelectOptions() );
if( $s !== false ) {
$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
*
* @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;
$fname = 'Article::quickEdit';
wfProfileIn( $fname );
$dbw =& wfGetDB( DB_MASTER );
$ns = $this->mTitle->getNamespace();
$dbkey = $this->mTitle->getDBkey();
$encDbKey = $dbw->strencode( $dbkey );
$timestamp = $dbw->timestamp();
# Save to history
$dbw->insertSelect( 'old', 'cur',
array(
'old_namespace' => 'cur_namespace',
'old_title' => 'cur_title',
'old_text' => 'cur_text',
'old_comment' => 'cur_comment',
'old_user' => 'cur_user',
'old_user_text' => 'cur_user_text',
'old_timestamp' => 'cur_timestamp',
'inverse_timestamp' => '99999999999999-cur_timestamp',
), array(
'cur_namespace' => $ns,
'cur_title' => $dbkey,
), $fname
);
# Use the affected row count to determine if the article is new
$numRows = $dbw->affectedRows();
# Make an array of fields to be inserted
$fields = array(
'cur_text' => $text,
'cur_timestamp' => $dbw->timestamp($timestamp),
'cur_user' => $wgUser->getID(),
'cur_user_text' => $wgUser->getName(),
'inverse_timestamp' => wfInvertTimestamp( $timestamp ),
'cur_comment' => $comment,
'cur_is_redirect' => $this->isRedirect( $text ) ? 1 : 0,
'cur_minor_edit' => intval($minor),
'cur_touched' => $dbw->timestamp($timestamp),
);
if ( $numRows ) {
# Update article
$fields['cur_is_new'] = 0;
$dbw->update( 'cur', $fields, array( 'cur_namespace' => $ns, 'cur_title' => $dbkey ), $fname );
} else {
# Insert new article
$fields['cur_is_new'] = 1;
$fields['cur_namespace'] = $ns;
$fields['cur_title'] = $dbkey;
$fields['cur_random'] = $rand = wfRandom();
$dbw->insert( 'cur', $fields, $fname );
}
wfProfileOut( $fname );
}
/**
* Used to increment the view counter
*
* @static
* @param integer $id article id
*/
function incViewCount( $id ) {
$id = intval( $id );
global $wgHitcounterUpdateFreq;
$dbw =& wfGetDB( DB_MASTER );
$curTable = $dbw->tableName( 'cur' );
$hitcounterTable = $dbw->tableName( 'hitcounter' );
$acchitsTable = $dbw->tableName( 'acchits' );
if( $wgHitcounterUpdateFreq <= 1 ){ //
$dbw->query( "UPDATE $curTable SET cur_counter = cur_counter + 1 WHERE cur_id = $id" );
return;
}
# Not important enough to warrant an error page in case of failure
$oldignore = $dbw->ignoreErrors( true );
$dbw->query( "INSERT INTO $hitcounterTable (hc_id) VALUES ({$id})" );
$checkfreq = intval( $wgHitcounterUpdateFreq/25 + 1 );
if( (rand() % $checkfreq != 0) or ($dbw->lastErrno() != 0) ){
# Most of the time (or on SQL errors), skip row count check
$dbw->ignoreErrors( $oldignore );
return;
}
$res = $dbw->query("SELECT COUNT(*) as n FROM $hitcounterTable");
$row = $dbw->fetchObject( $res );
$rown = intval( $row->n );
if( $rown >= $wgHitcounterUpdateFreq ){
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 ".
"SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable ".
'GROUP BY hc_id');
$dbw->query("DELETE FROM $hitcounterTable");
$dbw->query('UNLOCK TABLES');
$dbw->query("UPDATE $curTable,$acchitsTable SET cur_counter=cur_counter + hc_n ".
'WHERE cur_id = hc_id');
$dbw->query("DROP TABLE $acchitsTable");
ignore_user_abort( $old_user_abort );
wfProfileOut( 'Article::incViewCount-collect' );
}
$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
* @param $title_obj a title object
*/
function onArticleCreate($title_obj) {
global $wgUseSquid, $wgPostCommitUpdateList;
$titles = $title_obj->getBrokenLinksTo();
# Purge squid
if ( $wgUseSquid ) {
$urls = $title_obj->getSquidURLs();
foreach ( $titles as $linkTitle ) {
$urls[] = $linkTitle->getInternalURL();
}
$u = new SquidUpdate( $urls );
array_push( $wgPostCommitUpdateList, $u );
}
# Clear persistent link cache
LinkCache::linksccClearBrokenLinksTo( $title_obj->getPrefixedDBkey() );
}
function onArticleDelete($title_obj) {
LinkCache::linksccClearLinksTo( $title_obj->getArticleID() );
}
function onArticleEdit($title_obj) {
LinkCache::linksccClearPage( $title_obj->getArticleID() );
}
/**#@-*/
/**
* Info about this page
*/
function info() {
global $wgUser, $wgTitle, $wgOut, $wgAllowPageInfo;
$fname = 'Article::info';
if ( !$wgAllowPageInfo ) {
$wgOut->errorpage( 'nosuchaction', 'nosuchactiontext' );
return;
}
$dbr =& $this->getDB();
$basenamespace = $wgTitle->getNamespace() & (~1);
$cur_clause = array( 'cur_title' => $wgTitle->getDBkey(), 'cur_namespace' => $basenamespace );
$old_clause = array( 'old_title' => $wgTitle->getDBkey(), 'old_namespace' => $basenamespace );
$wl_clause  = array( 'wl_title' => $wgTitle->getDBkey(), 'wl_namespace' => $basenamespace );
$fullTitle = $wgTitle->makeName($basenamespace, $wgTitle->getDBKey());
$wgOut->setPagetitle(  $fullTitle );
$wgOut->setSubtitle( wfMsg( 'infosubtitle' ));
# first, see if the page exists at all.
$exists = $dbr->selectField( 'cur', 'COUNT(*)', $cur_clause, $fname, $this->getSelectOptions() );
if ($exists < 1) {
$wgOut->addHTML( wfMsg('noarticletext') );
} else {
$numwatchers = $dbr->selectField( 'watchlist', 'COUNT(*)', $wl_clause, $fname,
$this->getSelectOptions() );
$wgOut->addHTML( "<ul><li>" . wfMsg("numwatchers", $numwatchers) . '</li>' );
$old = $dbr->selectField( 'old', 'COUNT(*)', $old_clause, $fname, $this->getSelectOptions() );
$wgOut->addHTML( "<li>" . wfMsg('numedits', $old + 1) . '</li>');
# to find number of distinct authors, we need to do some
# funny stuff because of the cur/old table split:
# - first, find the name of the 'cur' author
# - then, find the number of *other* authors in 'old'
# find 'cur' author
$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,
$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, $this->getSelectOptions() );
# number of edits
if ($exists > 0) {
$old = $dbr->selectField( 'old', 'COUNT(*)', $old_clause, $fname, $this->getSelectOptions() );
$wgOut->addHTML( '<li>' . wfMsg("numtalkedits", $old + 1) . '</li>');
}
$wgOut->addHTML( '<li>' . wfMsg("numauthors", $authors) . '</li>' );
# number of authors
if ($exists > 0) {
$cur_author = $dbr->selectField( 'cur', 'cur_user_text', $cur_clause, $fname,
$this->getSelectOptions() );
$authors = $dbr->selectField( 'old', 'COUNT(DISTINCT old_user_text)',
$old_clause + array( 'old_user_text<>' . $dbr->addQuotes( $cur_author ) ),
$fname, $this->getSelectOptions() );
$wgOut->addHTML( '<li>' . wfMsg('numtalkauthors', $authors) . '</li></ul>' );
}
}
}
}
?>
{{editing}}
{{editing}}

Revision as of 13:45, 16 June 2005

Techniques: MediaWiki: Article.php

Class Functions

function Article( &$title ) { function getTitle() { function clear() { function getRevisionText( $row, $prefix = 'old_' ) { function compressRevisionText( &$text ) { function getContent( $noredir ) { function getSection($text,$section) { function &getCurContentFields() { function &getOldContentFields() { function getOldID() { function loadContent( $noredir = false ) { function getContentWithoutUsingSoManyDamnGlobals( $oldid = 0, $noredir = false ) { function forUpdate( $x = NULL ) { function &getDB() { function getSelectOptions( $options = ) { function getID() { function getCount() {

    • Get the view count for this article

function isCountable( $text ) {

    • Would the given text make this article a "good" article (i.e., suitable for including in the article count)?
  • function isRedirect( $text = false ) {
    • Tests if the article text represents a redirect
  • function loadLastEdit() {
    • Loads everything from cur except cur_text
    • This isn't necessary for all uses, so it's only done if needed.

Data Access

  • function getTimestamp() {
  • function getUser() {
  • function getUserText() {
  • function getComment() {
  • function getMinorEdit() {

More Other Methods

  • function getContributors($limit = 0, $offset = 0) {
  • function view() {
    • This is the default action of the script: just view the page of the given title.
  • function insertNewArticle( $text, $summary, $isminor, $watchthis ) {
    • 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.
  • function getTextOfLastEditWithSectionReplacedOrAdded($section, $text, $summary = , $edittime = NULL) {
    • Side effects: loads last edit if $edittime is NULL
  • function updateArticle( $text, $summary, $minor, $watchthis, $forceBot = false, $sectionanchor = ) {
    • 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 showArticle( $text, $subtitle , $sectionanchor = ) {
    • After we've either updated or inserted the article, update the link tables and redirect to the new page.
  • function validate () {
    • Validate article
  • function markpatrolled() {
    • Mark this particular edit as patrolled
  • function watch() {
    • Add this page to $wgUser's watchlist
  • function unwatch() {
    • Stop watching a page
  • function protect( $limit = 'sysop' ) {
    • protect a page
  • function confirmProtect( $par, $reason, $limit = 'sysop' ) {
    • Output protection confirmation dialog
  • function unprotect() {
    • Unprotect the pages
  • function delete() {
    • UI entry point for page deletion

/** * Output deletion confirmation dialog */ function confirmDelete( $par, $reason ) { global $wgOut, $wgUser;

wfDebug( "Article::confirmDelete\n" );

$sub = htmlspecialchars( $this->mTitle->getPrefixedText() ); $wgOut->setSubtitle( wfMsg( 'deletesub', $sub ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' ); $wgOut->addWikiText( wfMsg( 'confirmdeletetext' ) );

$formaction = $this->mTitle->escapeLocalURL( 'action=delete' . $par );

$confirm = htmlspecialchars( wfMsg( 'confirm' ) ); $check = htmlspecialchars( wfMsg( 'confirmcheck' ) ); $delcom = htmlspecialchars( wfMsg( 'deletecomment' ) ); $token = htmlspecialchars( $wgUser->editToken() );

$wgOut->addHTML( " <form id='deleteconfirm' method='post' action=\"{$formaction}\">

<label for='wpReason'>{$delcom}:</label>

<input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" />

 

<input type='checkbox' name='wpConfirm' value='1' id='wpConfirm' />

<label for='wpConfirm'>{$check}</label>

 

<input type='submit' name='wpConfirmB' value=\"{$confirm}\" />

<input type='hidden' name='wpEditToken' value=\"{$token}\" /> </form>\n" );

$wgOut->returnToMain( false ); }


/** * Perform a deletion and output success or failure messages */ function doDelete( $reason ) { global $wgOut, $wgUser, $wgContLang; $fname = 'Article::doDelete'; wfDebug( $fname."\n" );

if (wfRunHooks('ArticleDelete', array(&$this, &$wgUser, &$reason))) { if ( $this->doDeleteArticle( $reason ) ) { $deleted = $this->mTitle->getPrefixedText();

$wgOut->setPagetitle( wfMsg( 'actioncomplete' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' );

$sk = $wgUser->getSkin(); $loglink = $sk->makeKnownLink( $wgContLang->getNsText( NS_SPECIAL ) . ':Log/delete', wfMsg( 'deletionlog' ) );

$text = wfMsg( 'deletedtext', $deleted, $loglink );

$wgOut->addHTML( '

' . $text . "

\n" );

$wgOut->returnToMain( false ); wfRunHooks('ArticleDeleteComplete', array(&$this, &$wgUser, $reason)); } else { $wgOut->fatalError( wfMsg( 'cannotdelete' ) ); } } }

/** * Back-end article deletion * Deletes the article with database consistency, writes logs, purges caches * Returns success */ function doDeleteArticle( $reason ) { global $wgUser; global $wgUseSquid, $wgDeferredUpdateList, $wgInternalServer, $wgPostCommitUpdateList;

$fname = 'Article::doDeleteArticle'; wfDebug( $fname."\n" );

$dbw =& wfGetDB( DB_MASTER ); $ns = $this->mTitle->getNamespace(); $t = $this->mTitle->getDBkey(); $id = $this->mTitle->getArticleID();

if ( $t == || $id == 0 ) { return false; }

$u = new SiteStatsUpdate( 0, 1, -$this->isCountable( $this->getContent( true ) ), -1 ); array_push( $wgDeferredUpdateList, $u );

$linksTo = $this->mTitle->getLinksTo();

# Squid purging if ( $wgUseSquid ) { $urls = array( $this->mTitle->getInternalURL(), $this->mTitle->getInternalURL( 'history' ) ); foreach ( $linksTo as $linkTo ) { $urls[] = $linkTo->getInternalURL(); }

$u = new SquidUpdate( $urls ); array_push( $wgPostCommitUpdateList, $u );

}

# Client and file cache invalidation Title::touchArray( $linksTo );

# Move article and history to the "archive" table $archiveTable = $dbw->tableName( 'archive' ); $oldTable = $dbw->tableName( 'old' ); $curTable = $dbw->tableName( 'cur' ); $recentchangesTable = $dbw->tableName( 'recentchanges' ); $linksTable = $dbw->tableName( 'links' ); $brokenlinksTable = $dbw->tableName( 'brokenlinks' );

# Archive cur row $dbw->insertSelect( 'archive', 'cur', array( 'ar_namespace' => 'cur_namespace', 'ar_title' => 'cur_title', 'ar_text' => 'cur_text', 'ar_comment' => 'cur_comment', 'ar_user' => 'cur_user', 'ar_user_text' => 'cur_user_text', 'ar_timestamp' => 'cur_timestamp', 'ar_minor_edit' => 'cur_minor_edit', 'ar_flags' => 0, ), array( 'cur_namespace' => $ns, 'cur_title' => $t, ), $fname );

# Archive old rows $res = $dbw->select( 'old', array( 'old_namespace', 'old_title', 'old_text', 'old_comment', 'old_user', 'old_user_text', 'old_timestamp', 'old_minor_edit', 'old_flags' ), array( 'old_namespace' => $ns, 'old_title' => $t ), $fname );

while ( $row = $dbw->fetchObject( $res ) ) { # Recompress block-compressed revisions if ( $row->old_flags == 'object' ) { $text = Article::getRevisionText( $row ); $flags = Article::compressRevisionText( $text ); } else { $text = $row->old_text; $flags = $row->old_flags; }

$dbw->insert( 'archive', array( 'ar_namespace' => $ns, 'ar_title' => $t, 'ar_text' => $text, 'ar_comment' => $row->old_comment, 'ar_user' => $row->old_user, 'ar_user_text' => $row->old_user_text, 'ar_timestamp' => $row->old_timestamp, 'ar_minor_edit' => $row->old_minor_edit, 'ar_flags' => $flags ), $fname ); }

# Now that it's safely backed up, delete it

$dbw->delete( 'cur', array( 'cur_namespace' => $ns, 'cur_title' => $t ), $fname ); $dbw->delete( 'old', array( 'old_namespace' => $ns, 'old_title' => $t ), $fname ); $dbw->delete( 'recentchanges', array( 'rc_namespace' => $ns, 'rc_title' => $t ), $fname );

# Finally, clean up the link tables $t = $this->mTitle->getPrefixedDBkey();

Article::onArticleDelete( $this->mTitle );

# Insert broken links $brokenLinks = array(); foreach ( $linksTo as $titleObj ) { # Get article ID. Efficient because it was loaded into the cache by getLinksTo(). $linkID = $titleObj->getArticleID(); $brokenLinks[] = array( 'bl_from' => $linkID, 'bl_to' => $t ); } $dbw->insert( 'brokenlinks', $brokenLinks, $fname, 'IGNORE' );

# Delete live links $dbw->delete( 'links', array( 'l_to' => $id ) ); $dbw->delete( 'links', array( 'l_from' => $id ) ); $dbw->delete( 'imagelinks', array( 'il_from' => $id ) ); $dbw->delete( 'brokenlinks', array( 'bl_from' => $id ) ); $dbw->delete( 'categorylinks', array( 'cl_from' => $id ) );

# Log the deletion $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 ); $this->mTitle->mArticleID = 0; return true; }

/** * Revert a modification */ function rollback() { global $wgUser, $wgOut, $wgRequest; $fname = 'Article::rollback';

if ( ! $wgUser->isAllowed('rollback') ) { $wgOut->sysopRequired(); return; } if ( wfReadOnly() ) { $wgOut->readOnlyPage( $this->getContent( true ) ); return; } if( !$wgUser->matchEditToken( $wgRequest->getVal( 'token' ), array( $this->mTitle->getPrefixedText(), $wgRequest->getVal( 'from' ) ) ) ) { $wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) ); $wgOut->addWikiText( wfMsg( 'sessionfailure' ) ); return; } $dbw =& wfGetDB( DB_MASTER );

# Enhanced rollback, marks edits rc_bot=1 $bot = $wgRequest->getBool( 'bot' );

# Replace all this user's current edits with the next one down $tt = $this->mTitle->getDBKey(); $n = $this->mTitle->getNamespace();

# Get the last editor, lock table exclusively $s = $dbw->selectRow( 'cur', array( 'cur_id','cur_user','cur_user_text','cur_comment' ), array( 'cur_title' => $tt, 'cur_namespace' => $n ), $fname, 'FOR UPDATE' ); if( $s === false ) { # Something wrong $wgOut->addHTML( wfMsg( 'notanarticle' ) ); return; } $ut = $dbw->strencode( $s->cur_user_text ); $uid = $s->cur_user; $pid = $s->cur_id;

$from = str_replace( '_', ' ', $wgRequest->getVal( 'from' ) ); if( $from != $s->cur_user_text ) { $wgOut->setPageTitle(wfmsg('rollbackfailed')); $wgOut->addWikiText( wfMsg( 'alreadyrolled', htmlspecialchars( $this->mTitle->getPrefixedText()), htmlspecialchars( $from ), htmlspecialchars( $s->cur_user_text ) ) ); if($s->cur_comment != ) { $wgOut->addHTML( wfMsg('editcomment', htmlspecialchars( $s->cur_comment ) ) ); } return; }

# Get the last edit not by this guy $s = $dbw->selectRow( 'old', array( 'old_text','old_user','old_user_text','old_timestamp','old_flags' ), array( 'old_namespace' => $n, 'old_title' => $tt, "old_user <> {$uid} OR old_user_text <> '{$ut}'" ), $fname, array( 'FOR UPDATE', 'USE INDEX' => 'name_title_timestamp' ) ); if( $s === false ) { # Something wrong $wgOut->setPageTitle(wfMsg('rollbackfailed')); $wgOut->addHTML( wfMsg( 'cantrollback' ) ); return; }

if ( $bot ) { # Mark all reverted edits as bot $dbw->update( 'recentchanges', array( /* SET */ 'rc_bot' => 1 ), array( /* WHERE */ 'rc_user' => $uid, "rc_timestamp > '{$s->old_timestamp}'", ), $fname ); }

# Save it! $newcomment = wfMsg( 'revertpage', $s->old_user_text, $from ); $wgOut->setPagetitle( wfMsg( 'actioncomplete' ) ); $wgOut->setRobotpolicy( 'noindex,nofollow' );

$wgOut->addHTML( '

' . htmlspecialchars( $newcomment ) . "

\n


\n" );

$this->updateArticle( Article::getRevisionText( $s ), $newcomment, 1, $this->mTitle->userIsWatching(), $bot ); Article::onArticleEdit( $this->mTitle ); $wgOut->returnToMain( false ); }


/** * Do standard deferred updates after page view * @private */ function viewUpdates() { global $wgDeferredUpdateList;

if ( 0 != $this->getID() ) { global $wgDisableCounters; if( !$wgDisableCounters ) { Article::incViewCount( $this->getID() ); $u = new SiteStatsUpdate( 1, 0, 0 ); array_push( $wgDeferredUpdateList, $u ); } }

# Update newtalk status if user is reading their own # talk page

global $wgUser;

if ($this->mTitle->getNamespace() == NS_USER_TALK && $this->mTitle->getText() == $wgUser->getName()) { $wgUser->setNewtalk(0); $wgUser->saveNewtalk(); } }

/** * 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, $wgUser;

wfSeedRandom(); if ( 0 == mt_rand( 0, 999 ) ) { # Periodically flush old entries from the recentchanges table. global $wgRCMaxAge; $dbw =& wfGetDB( DB_MASTER ); $cutoff = $dbw->timestamp( time() - $wgRCMaxAge ); $recentchanges = $dbw->tableName( 'recentchanges' ); $sql = "DELETE FROM $recentchanges WHERE rc_timestamp < '{$cutoff}'"; $dbw->query( $sql ); } $id = $this->getID(); $title = $this->mTitle->getPrefixedDBkey(); $shortTitle = $this->mTitle->getDBkey();

if ( 0 != $id ) { $u = new LinksUpdate( $id, $title ); array_push( $wgDeferredUpdateList, $u ); $u = new SiteStatsUpdate( 0, 1, $this->mGoodAdjustment, $this->mTotalAdjustment ); array_push( $wgDeferredUpdateList, $u ); $u = new SearchUpdate( $id, $title, $text ); array_push( $wgDeferredUpdateList, $u );

# If this is another user's talk page, save a # newtalk notification for them

if ($this->mTitle->getNamespace() == NS_USER_TALK && $shortTitle != $wgUser->getName()) { $other = User::newFromName($shortTitle); $other->setNewtalk(1); $other->saveNewtalk(); }

if ( $this->mTitle->getNamespace() == NS_MEDIAWIKI ) { $wgMessageCache->replace( $shortTitle, $text ); } } }

/** * @todo document this function * @private * @param string $oldid Revision ID of this article revision */ function setOldSubtitle( $oldid=0 ) { global $wgLang, $wgOut, $wgUser;

$td = $wgLang->timeanddate( $this->mTimestamp, true ); $sk = $wgUser->getSkin(); $lnk = $sk->makeKnownLinkObj ( $this->mTitle, wfMsg( 'currentrevisionlink' ) ); $prevlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'previousrevision' ), 'direction=prev&oldid='.$oldid ); $nextlink = $sk->makeKnownLinkObj( $this->mTitle, wfMsg( 'nextrevision' ), 'direction=next&oldid='.$oldid ); $r = wfMsg( 'revisionasofwithlink', $td, $lnk, $prevlink, $nextlink ); $wgOut->setSubtitle( $r ); }

/** * 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 ) ); }

/* 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) */ function tryFileCache() { static $called = false; if( $called ) { wfDebug( " tryFileCache() -- called twice!?\n" ); return; } $called = true; if($this->isFileCacheable()) { $touched = $this->mTouched; $cache = new CacheManager( $this->mTitle ); if($cache->isFileCacheGood( $touched )) { global $wgOut; wfDebug( " tryFileCache() - about to load\n" ); $cache->loadFromFileCache(); return true; } else { wfDebug( " tryFileCache() - starting buffer\n" ); ob_start( array(&$cache, 'saveToFileCache' ) ); } } else { 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' ) );

return $wgUseFileCache and (!$wgShowIPinHeader) and ($this->getID() != 0) and ($wgUser->getId() == 0) and (!$wgUser->getNewtalk()) and ($this->mTitle->getNamespace() != NS_SPECIAL ) and (empty( $action ) || $action == 'view') and (!isset($oldid)) and (!isset($diff)) and (!isset($redirect)) and (!isset($printable)) and (!$this->mRedirectedFrom); }

/** * Loads cur_touched and returns a value indicating if it should be used * */ function checkTouched() { $fname = 'Article::checkTouched'; $id = $this->getID(); $dbr =& $this->getDB(); $s = $dbr->selectRow( 'cur', array( 'cur_touched', 'cur_is_redirect' ), array( 'cur_id' => $id ), $fname, $this->getSelectOptions() ); if( $s !== false ) { $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 * * @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; $fname = 'Article::quickEdit'; wfProfileIn( $fname );

$dbw =& wfGetDB( DB_MASTER ); $ns = $this->mTitle->getNamespace(); $dbkey = $this->mTitle->getDBkey(); $encDbKey = $dbw->strencode( $dbkey ); $timestamp = $dbw->timestamp();

# Save to history $dbw->insertSelect( 'old', 'cur', array( 'old_namespace' => 'cur_namespace', 'old_title' => 'cur_title', 'old_text' => 'cur_text', 'old_comment' => 'cur_comment', 'old_user' => 'cur_user', 'old_user_text' => 'cur_user_text', 'old_timestamp' => 'cur_timestamp', 'inverse_timestamp' => '99999999999999-cur_timestamp', ), array( 'cur_namespace' => $ns, 'cur_title' => $dbkey, ), $fname );

# Use the affected row count to determine if the article is new $numRows = $dbw->affectedRows();

# Make an array of fields to be inserted $fields = array( 'cur_text' => $text, 'cur_timestamp' => $dbw->timestamp($timestamp), 'cur_user' => $wgUser->getID(), 'cur_user_text' => $wgUser->getName(), 'inverse_timestamp' => wfInvertTimestamp( $timestamp ), 'cur_comment' => $comment, 'cur_is_redirect' => $this->isRedirect( $text ) ? 1 : 0, 'cur_minor_edit' => intval($minor), 'cur_touched' => $dbw->timestamp($timestamp), );

if ( $numRows ) { # Update article $fields['cur_is_new'] = 0; $dbw->update( 'cur', $fields, array( 'cur_namespace' => $ns, 'cur_title' => $dbkey ), $fname ); } else { # Insert new article $fields['cur_is_new'] = 1; $fields['cur_namespace'] = $ns; $fields['cur_title'] = $dbkey; $fields['cur_random'] = $rand = wfRandom(); $dbw->insert( 'cur', $fields, $fname ); } wfProfileOut( $fname ); }

/** * Used to increment the view counter * * @static * @param integer $id article id */ function incViewCount( $id ) { $id = intval( $id ); global $wgHitcounterUpdateFreq;

$dbw =& wfGetDB( DB_MASTER ); $curTable = $dbw->tableName( 'cur' ); $hitcounterTable = $dbw->tableName( 'hitcounter' ); $acchitsTable = $dbw->tableName( 'acchits' );

if( $wgHitcounterUpdateFreq <= 1 ){ // $dbw->query( "UPDATE $curTable SET cur_counter = cur_counter + 1 WHERE cur_id = $id" ); return; }

# Not important enough to warrant an error page in case of failure $oldignore = $dbw->ignoreErrors( true );

$dbw->query( "INSERT INTO $hitcounterTable (hc_id) VALUES ({$id})" );

$checkfreq = intval( $wgHitcounterUpdateFreq/25 + 1 ); if( (rand() % $checkfreq != 0) or ($dbw->lastErrno() != 0) ){ # Most of the time (or on SQL errors), skip row count check $dbw->ignoreErrors( $oldignore ); return; }

$res = $dbw->query("SELECT COUNT(*) as n FROM $hitcounterTable"); $row = $dbw->fetchObject( $res ); $rown = intval( $row->n ); if( $rown >= $wgHitcounterUpdateFreq ){ 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 ". "SELECT hc_id,COUNT(*) AS hc_n FROM $hitcounterTable ". 'GROUP BY hc_id'); $dbw->query("DELETE FROM $hitcounterTable"); $dbw->query('UNLOCK TABLES'); $dbw->query("UPDATE $curTable,$acchitsTable SET cur_counter=cur_counter + hc_n ". 'WHERE cur_id = hc_id'); $dbw->query("DROP TABLE $acchitsTable");

ignore_user_abort( $old_user_abort ); wfProfileOut( 'Article::incViewCount-collect' ); } $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 * @param $title_obj a title object */

function onArticleCreate($title_obj) { global $wgUseSquid, $wgPostCommitUpdateList;

$titles = $title_obj->getBrokenLinksTo();

# Purge squid if ( $wgUseSquid ) { $urls = $title_obj->getSquidURLs(); foreach ( $titles as $linkTitle ) { $urls[] = $linkTitle->getInternalURL(); } $u = new SquidUpdate( $urls ); array_push( $wgPostCommitUpdateList, $u ); }

# Clear persistent link cache LinkCache::linksccClearBrokenLinksTo( $title_obj->getPrefixedDBkey() ); }

function onArticleDelete($title_obj) { LinkCache::linksccClearLinksTo( $title_obj->getArticleID() ); } function onArticleEdit($title_obj) { LinkCache::linksccClearPage( $title_obj->getArticleID() ); } /**#@-*/

/** * Info about this page */ function info() { global $wgUser, $wgTitle, $wgOut, $wgAllowPageInfo; $fname = 'Article::info';

if ( !$wgAllowPageInfo ) { $wgOut->errorpage( 'nosuchaction', 'nosuchactiontext' ); return; }

$dbr =& $this->getDB();

$basenamespace = $wgTitle->getNamespace() & (~1); $cur_clause = array( 'cur_title' => $wgTitle->getDBkey(), 'cur_namespace' => $basenamespace ); $old_clause = array( 'old_title' => $wgTitle->getDBkey(), 'old_namespace' => $basenamespace ); $wl_clause = array( 'wl_title' => $wgTitle->getDBkey(), 'wl_namespace' => $basenamespace ); $fullTitle = $wgTitle->makeName($basenamespace, $wgTitle->getDBKey()); $wgOut->setPagetitle( $fullTitle ); $wgOut->setSubtitle( wfMsg( 'infosubtitle' ));

# first, see if the page exists at all. $exists = $dbr->selectField( 'cur', 'COUNT(*)', $cur_clause, $fname, $this->getSelectOptions() ); if ($exists < 1) { $wgOut->addHTML( wfMsg('noarticletext') ); } else { $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: # - first, find the name of the 'cur' author # - then, find the number of *other* authors in 'old' # find 'cur' author $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, $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, $this->getSelectOptions() ); # number of edits if ($exists > 0) { $old = $dbr->selectField( 'old', 'COUNT(*)', $old_clause, $fname, $this->getSelectOptions() ); $wgOut->addHTML( '
  • ' . wfMsg("numtalkedits", $old + 1) . '
  • '); } $wgOut->addHTML( '
  • ' . wfMsg("numauthors", $authors) . '
  • ' ); # number of authors if ($exists > 0) { $cur_author = $dbr->selectField( 'cur', 'cur_user_text', $cur_clause, $fname, $this->getSelectOptions() ); $authors = $dbr->selectField( 'old', 'COUNT(DISTINCT old_user_text)', $old_clause + array( 'old_user_text<>' . $dbr->addQuotes( $cur_author ) ), $fname, $this->getSelectOptions() ); $wgOut->addHTML( '
  • ' . wfMsg('numtalkauthors', $authors) . '

' );

} } } }

?>


Editing is currently in progress on this article. Although editing is incomplete, the author or editor has saved their work to prevent loss. Please check back later by reloading the page, and do not edit while this message is still showing. Thank you.