const BAD_TITLE = 5; // $this is not a valid Article
const ALREADY_ROLLED = 6; // Someone else already rolled this back. $from and $summary will be set
const ONLY_AUTHOR = 7; // User is the only author of the page
+ const RATE_LIMITED = 8;
/**
* Constructor and clear the article
$this->mRevIdFetched = 0;
$this->mRedirectUrl = false;
$this->mLatest = false;
+ $this->mPreparedEdit = false;
}
/**
*/
function view() {
global $wgUser, $wgOut, $wgRequest, $wgContLang;
- global $wgEnableParserCache, $wgStylePath, $wgUseRCPatrol, $wgParser;
+ global $wgEnableParserCache, $wgStylePath, $wgParser;
global $wgUseTrackbacks, $wgNamespaceRobotPolicies, $wgArticleRobotPolicies;
$sk = $wgUser->getSkin();
$rcid = $wgRequest->getVal( 'rcid' );
$rdfrom = $wgRequest->getVal( 'rdfrom' );
$diffOnly = $wgRequest->getBool( 'diffonly', $wgUser->getOption( 'diffonly' ) );
+ $purge = $wgRequest->getVal( 'action' ) == 'purge';
$wgOut->setArticleFlag( true );
if ( !is_null( $diff ) ) {
$wgOut->setPageTitle( $this->mTitle->getPrefixedText() );
- $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid );
+ $de = new DifferenceEngine( $this->mTitle, $oldid, $diff, $rcid, $purge );
// DifferenceEngine directly fetched the revision:
$this->mRevIdFetched = $de->mNewid;
$de->showDiffPage( $diffOnly );
# 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->isAllowed( 'patrol' ) ) {
+ if( !is_null( $rcid ) && $rcid != 0 && $wgUser->isAllowed( 'patrol' ) && $this->mTitle->exists() ) {
$wgOut->addHTML(
"<div class='patrollink'>" .
wfMsgHtml( 'markaspatrolledlink',
return;
}
- if ((!$wgUser->isAllowed('delete'))) {
- $wgOut->permissionRequired( 'delete' );
- return;
- }
+ $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
- if (wfReadOnly()) {
- $wgOut->readOnlyPage();
+ if (count($permission_errors)>0)
+ {
+ $wgOut->showPermissionsErrorPage( $permission_errors );
return;
}
}
} else {
$msg = $wgOut->parse( wfMsg( 'confirm_purge' ) );
- $action = $this->mTitle->escapeLocalURL( 'action=purge' );
+ $action = htmlspecialchars( $_SERVER['REQUEST_URI'] );
$button = htmlspecialchars( wfMsg( 'confirm_purge_button' ) );
$msg = str_replace( '$1',
"<form method=\"post\" action=\"$action\">\n" .
/**
* @return string Complete article text, or null if error
*/
- function replaceSection($section, $text, &$summary = '', $edittime = NULL) {
+ function replaceSection($section, $text, $summary = '', $edittime = NULL) {
wfProfileIn( __METHOD__ );
if( $section == '' ) {
if( $section == 'new' ) {
# Inserting a new section
$subject = $summary ? wfMsgForContent('newsectionheaderdefaultlevel',$summary) . "\n\n" : '';
- # Heading has been added to text, now wrap comment for RC linking to heading
- $summary = "/* {$summary} */";
$text = strlen( trim( $oldtext ) ) > 0
? "{$oldtext}\n\n{$subject}{$text}"
: "{$subject}{$text}";
/**
* @deprecated use Article::doEdit()
*/
- function insertNewArticle( $text, &$summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) {
+ function insertNewArticle( $text, $summary, $isminor, $watchthis, $suppressRC=false, $comment=false ) {
$flags = EDIT_NEW | EDIT_DEFER_UPDATES | EDIT_AUTOSUMMARY |
( $isminor ? EDIT_MINOR : 0 ) |
( $suppressRC ? EDIT_SUPPRESS_RC : 0 );
# If this is a comment, add the summary as headline
if ( $comment && $summary != "" ) {
$text = wfMsgForContent('newsectionheaderdefaultlevel',$summary) . "\n\n".$text;
- # Heading has been added to text, now wrap comment for RC linking to heading
- $summary = "/* {$summary} */";
}
$this->doEdit( $text, $summary, $flags );
if ($flags & EDIT_AUTOSUMMARY && $summary == '')
$summary = $this->getAutosummary( $oldtext, $text, $flags );
- $text = $this->preSaveTransform( $text );
+ $editInfo = $this->prepareTextForEdit( $text );
+ $text = $editInfo->pst;
$newsize = strlen( $text );
$dbw = wfGetDB( DB_MASTER );
$rcid = RecentChange::notifyNew( $now, $this->mTitle, $isminor, $wgUser, $summary, $bot,
'', strlen( $text ), $revisionId );
# Mark as patrolled if the user can
- if( $GLOBALS['wgUseRCPatrol'] && $wgUser->isAllowed( 'autopatrol' ) ) {
+ if( ($GLOBALS['wgUseRCPatrol'] || $GLOBALS['wgUseNPPatrol']) && $wgUser->isAllowed( 'autopatrol' ) ) {
RecentChange::markPatrolled( $rcid );
PatrolLog::record( $rcid, true );
}
wfDoUpdates();
}
- wfRunHooks( 'ArticleSaveComplete', array( &$this, &$wgUser, $text, $summary,
- $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+ if ( $good ) {
+ wfRunHooks( 'ArticleSaveComplete', array( &$this, &$wgUser, $text, $summary,
+ $flags & EDIT_MINOR, null, null, &$flags, $revision ) );
+ }
wfProfileOut( __METHOD__ );
return $good;
}
/**
- * Mark this particular edit as patrolled
+ * Mark this particular edit/page as patrolled
*/
function markpatrolled() {
- global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUser;
+ global $wgOut, $wgRequest, $wgUseRCPatrol, $wgUseNPPatrol, $wgUser;
$wgOut->setRobotPolicy( 'noindex,nofollow' );
- # Check RC patrol config. option
- if( !$wgUseRCPatrol ) {
+ # Check patrol config options
+
+ if ( !($wgUseNPPatrol || $wgUseRCPatrol)) {
$wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
+ return;
+ }
+
+ # If we haven't been given an rc_id value, we can't do anything
+ $rcid = (int) $wgRequest->getVal('rcid');
+ $rc = $rcid ? RecentChange::newFromId($rcid) : null;
+ if ( is_null ( $rc ) )
+ {
+ $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
return;
}
- # Check permissions
- if( !$wgUser->isAllowed( 'patrol' ) ) {
- $wgOut->permissionRequired( 'patrol' );
+ if ( !$wgUseRCPatrol && $rc->mAttribs['rc_type'] != RC_NEW) {
+ // Only new pages can be patrolled if the general patrolling is off....???
+ // @fixme -- is this necessary? Shouldn't we only bother controlling the
+ // front end here?
+ $wgOut->errorPage( 'rcpatroldisabled', 'rcpatroldisabledtext' );
return;
}
+
+ # Check permissions
+ $permission_errors = $this->mTitle->getUserPermissionsErrors( 'patrol', $wgUser );
- # If we haven't been given an rc_id value, we can't do anything
- $rcid = $wgRequest->getVal( 'rcid' );
- if( !$rcid ) {
- $wgOut->errorPage( 'markedaspatrollederror', 'markedaspatrollederrortext' );
+ if (count($permission_errors)>0)
+ {
+ $wgOut->showPermissionsErrorPage( $permission_errors );
return;
}
return;
}
- $return = SpecialPage::getTitleFor( 'Recentchanges' );
+ #It would be nice to see where the user had actually come from, but for now just guess
+ $returnto = $rc->mAttribs['rc_type'] == RC_NEW ? 'Newpages' : 'Recentchanges';
+ $return = Title::makeTitle( NS_SPECIAL, $returnto );
+
# If it's left up to us, check that the user is allowed to patrol this edit
# If the user has the "autopatrol" right, then we'll assume there are no
# other conditions stopping them doing so
global $wgUser, $wgRestrictionTypes, $wgContLang;
$id = $this->mTitle->getArticleID();
- if( !$wgUser->isAllowed( 'protect' ) || wfReadOnly() || $id == 0 ) {
+ if( array() != $this->mTitle->getUserPermissionsErrors( 'protect', $wgUser ) || wfReadOnly() || $id == 0 ) {
return false;
}
}
return implode( ':', $bits );
}
+
+ /**
+ * Auto-generates a deletion reason
+ * @param bool &$hasHistory Whether the page has a history
+ */
+ public function generateReason(&$hasHistory)
+ {
+ global $wgContLang;
+ $dbw = wfGetDB(DB_MASTER);
+ // Get the last revision
+ $rev = Revision::newFromTitle($this->mTitle);
+ if(is_null($rev))
+ return false;
+ // Get the article's contents
+ $contents = $rev->getText();
+ $blank = false;
+ // If the page is blank, use the text from the previous revision,
+ // which can only be blank if there's a move/import/protect dummy revision involved
+ if($contents == '')
+ {
+ $prev = $rev->getPrevious();
+ if($prev)
+ {
+ $contents = $prev->getText();
+ $blank = true;
+ }
+ }
+
+ // Find out if there was only one contributor
+ // Only scan the last 20 revisions
+ $limit = 20;
+ $res = $dbw->select('revision', 'rev_user_text', array('rev_page' => $this->getID()), __METHOD__,
+ array('LIMIT' => $limit));
+ if($res === false)
+ // This page has no revisions, which is very weird
+ return false;
+ if($res->numRows() > 1)
+ $hasHistory = true;
+ else
+ $hasHistory = false;
+ $row = $dbw->fetchObject($res);
+ $onlyAuthor = $row->rev_user_text;
+ // Try to find a second contributor
+ while(($row = $dbw->fetchObject($res)))
+ if($row->rev_user_text != $onlyAuthor)
+ {
+ $onlyAuthor = false;
+ break;
+ }
+ $dbw->freeResult($res);
+
+ // Generate the summary with a '$1' placeholder
+ if($blank)
+ // The current revision is blank and the one before is also
+ // blank. It's just not our lucky day
+ $reason = wfMsgForContent('exbeforeblank', '$1');
+ else
+ {
+ if($onlyAuthor)
+ $reason = wfMsgForContent('excontentauthor', '$1', $onlyAuthor);
+ else
+ $reason = wfMsgForContent('excontent', '$1');
+ }
+
+ // Replace newlines with spaces to prevent uglyness
+ $contents = preg_replace("/[\n\r]/", ' ', $contents);
+ // Calculate the maximum amount of chars to get
+ // Max content length = max comment length - length of the comment (excl. $1) - '...'
+ $maxLength = 255 - (strlen($reason) - 2) - 3;
+ $contents = $wgContLang->truncate($contents, $maxLength, '...');
+ // Remove possible unfinished links
+ $contents = preg_replace( '/\[\[([^\]]*)\]?$/', '$1', $contents );
+ // Now replace the '$1' placeholder
+ $reason = str_replace( '$1', $contents, $reason );
+ return $reason;
+ }
+
/*
* UI entry point for page deletion
*/
function delete() {
global $wgUser, $wgOut, $wgRequest;
+
$confirm = $wgRequest->wasPosted() &&
- $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
- $reason = $wgRequest->getText( 'wpReason' );
+ $wgUser->matchEditToken( $wgRequest->getVal( 'wpEditToken' ) );
+
+ $this->DeleteReasonList = $wgRequest->getText( 'wpDeleteReasonList', 'other' );
+ $this->DeleteReason = $wgRequest->getText( 'wpReason' );
+
+ $reason = $this->DeleteReasonList;
+
+ if ( $reason != 'other' && $this->DeleteReason != '') {
+ // Entry from drop down menu + additional comment
+ $reason .= ': ' . $this->DeleteReason;
+ } elseif ( $reason == 'other' ) {
+ $reason = $this->DeleteReason;
+ }
# This code desperately needs to be totally rewritten
# Check permissions
- if( $wgUser->isAllowed( 'delete' ) ) {
- if( $wgUser->isBlocked( !$confirm ) ) {
- $wgOut->blockedPage();
- return;
- }
- } else {
- $wgOut->permissionRequired( 'delete' );
- return;
- }
+ $permission_errors = $this->mTitle->getUserPermissionsErrors( 'delete', $wgUser );
- if( wfReadOnly() ) {
- $wgOut->readOnlyPage();
+ if (count($permission_errors)>0)
+ {
+ $wgOut->showPermissionsErrorPage( $permission_errors );
return;
}
return;
}
- # determine whether this page has earlier revisions
- # and insert a warning if it does
- $maxRevisions = 20;
- $authors = $this->getLastNAuthors( $maxRevisions, $latest );
+ // Generate deletion reason
+ $hasHistory = false;
+ if ( !$reason ) $reason = $this->generateReason($hasHistory);
- if( count( $authors ) > 1 && !$confirm ) {
+ // If the page has a history, insert a warning
+ if( $hasHistory && !$confirm ) {
$skin=$wgUser->getSkin();
$wgOut->addHTML( '<strong>' . wfMsg( 'historywarning' ) . ' ' . $skin->historyLink() . '</strong>' );
}
-
- # If a single user is responsible for all revisions, find out who they are
- if ( count( $authors ) == $maxRevisions ) {
- // Query bailed out, too many revisions to find out if they're all the same
- $authorOfAll = false;
- } else {
- $authorOfAll = reset( $authors );
- foreach ( $authors as $author ) {
- if ( $authorOfAll != $author ) {
- $authorOfAll = false;
- break;
- }
- }
- }
- # Fetch article text
- $rev = Revision::newFromTitle( $this->mTitle );
-
- if( !is_null( $rev ) ) {
- # if this is a mini-text, we can paste part of it into the deletion reason
- $text = $rev->getText();
-
- #if this is empty, an earlier revision may contain "useful" text
- $blanked = false;
- if( $text == '' ) {
- $prev = $rev->getPrevious();
- if( $prev ) {
- $text = $prev->getText();
- $blanked = true;
- }
- }
-
- $length = strlen( $text );
-
- # this should not happen, since it is not possible to store an empty, new
- # page. Let's insert a standard text in case it does, though
- if( $length == 0 && $reason === '' ) {
- $reason = wfMsgForContent( 'exblank' );
- }
-
- if( $reason === '' ) {
- # comment field=255, let's grep the first 150 to have some user
- # space left
- global $wgContLang;
- $text = $wgContLang->truncate( $text, 150, '...' );
-
- # let's strip out newlines
- $text = preg_replace( "/[\n\r]/", '', $text );
-
- if( !$blanked ) {
- if( $authorOfAll === false ) {
- $reason = wfMsgForContent( 'excontent', $text );
- } else {
- $reason = wfMsgForContent( 'excontentauthor', $text, $authorOfAll );
- }
- } else {
- $reason = wfMsgForContent( 'exbeforeblank', $text );
- }
- }
- }
-
+
return $this->confirmDelete( '', $reason );
}
$formaction = $this->mTitle->escapeLocalURL( 'action=delete' . $par );
$confirm = htmlspecialchars( wfMsg( 'deletepage' ) );
- $delcom = htmlspecialchars( wfMsg( 'deletecomment' ) );
+ $delcom = Xml::label( wfMsg( 'deletecomment' ), 'wpDeleteReasonList' );
$token = htmlspecialchars( $wgUser->editToken() );
$watch = Xml::checkLabel( wfMsg( 'watchthis' ), 'wpWatch', 'wpWatch', $wgUser->getBoolOption( 'watchdeletion' ) || $this->mTitle->userIsWatching(), array( 'tabindex' => '2' ) );
-
+
+ $mDeletereasonother = Xml::label( wfMsg( 'deleteotherreason' ), 'wpReason' );
+ $mDeletereasonotherlist = wfMsgHtml( 'deletereasonotherlist' );
+ $scDeleteReasonList = wfMsgForContent( 'deletereason-dropdown' );
+
+ $deleteReasonList = '';
+ if ( $scDeleteReasonList != '' && $scDeleteReasonList != '-' ) {
+ $deleteReasonList = "<option value=\"other\">$mDeletereasonotherlist</option>";
+ $optgroup = "";
+ foreach ( explode( "\n", $scDeleteReasonList ) as $option) {
+ $value = trim( htmlspecialchars($option) );
+ if ( $value == '' ) {
+ continue;
+ } elseif ( substr( $value, 0, 1) == '*' && substr( $value, 1, 1) != '*' ) {
+ // A new group is starting ...
+ $value = trim( substr( $value, 1 ) );
+ $deleteReasonList .= "$optgroup<optgroup label=\"$value\">";
+ $optgroup = "</optgroup>";
+ } elseif ( substr( $value, 0, 2) == '**' ) {
+ // groupmember
+ $selected = "";
+ $value = trim( substr( $value, 2 ) );
+ if ( $this->DeleteReasonList === $value)
+ $selected = ' selected="selected"';
+ $deleteReasonList .= "<option value=\"$value\"$selected>$value</option>";
+ } else {
+ // groupless delete reason
+ $selected = "";
+ if ( $this->DeleteReasonList === $value)
+ $selected = ' selected="selected"';
+ $deleteReasonList .= "$optgroup<option value=\"$value\"$selected>$value</option>";
+ $optgroup = "";
+ }
+ }
+ $deleteReasonList .= $optgroup;
+ }
$wgOut->addHTML( "
<form id='deleteconfirm' method='post' action=\"{$formaction}\">
<table border='0'>
- <tr>
+ <tr id=\"wpDeleteReasonListRow\" name=\"wpDeleteReasonListRow\">
<td align='right'>
- <label for='wpReason'>{$delcom}:</label>
+ $delcom:
</td>
<td align='left'>
- <input type='text' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" tabindex=\"1\" />
+ <select tabindex='1' id='wpDeleteReasonList' name=\"wpDeleteReasonList\">
+ $deleteReasonList
+ </select>
+ </td>
+ </tr>
+ <tr id=\"wpDeleteReasonRow\" name=\"wpDeleteReasonRow\">
+ <td>
+ $mDeletereasonother
+ </td>
+ <td align='left'>
+ <input type='text' maxlength='255' size='60' name='wpReason' id='wpReason' value=\"" . htmlspecialchars( $reason ) . "\" tabindex=\"2\" />
</td>
</tr>
<tr>
<input type='hidden' name='wpEditToken' value=\"{$token}\" />
</form>\n" );
- $wgOut->returnToMain( false );
+ $wgOut->returnToMain( false, $this->mTitle );
$this->showLogExtract( $wgOut );
}
public function doRollback( $fromP, $summary, $token, $bot, &$resultDetails ) {
global $wgUser, $wgUseRCPatrol;
$resultDetails = null;
-
- if( $wgUser->isAllowed( 'rollback' ) ) {
+
+ # Just in case it's being called from elsewhere
+
+ if( $wgUser->isAllowed( 'rollback' ) && $this->mTitle->userCan( 'edit' ) ) {
if( $wgUser->isBlocked() ) {
return self::BLOCKED;
}
if ( wfReadOnly() ) {
return self::READONLY;
}
+
if( !$wgUser->matchEditToken( $token, array( $this->mTitle->getPrefixedText(), $fromP ) ) )
return self::BAD_TOKEN;
+ if ( $wgUser->pingLimiter('rollback') || $wgUser->pingLimiter() ) {
+ return self::RATE_LIMITED;
+ }
+
$dbw = wfGetDB( DB_MASTER );
# Get the last editor
}
$set = array();
- if ( $bot ) {
+ if ( $bot && $wgUser->isAllowed('markbotedits') ) {
# Mark all reverted edits as bot
$set['rc_bot'] = 1;
}
$summary = wfMsgForContent( 'revertpage', $target->getUserText(), $from );
# Save
- $flags = EDIT_UPDATE | EDIT_MINOR;
+ $flags = EDIT_UPDATE;
+
+ if ($wgUser->isAllowed('minoredit'))
+ $flags |= EDIT_MINOR;
+
if( $bot )
$flags |= EDIT_FORCE_BOT;
$this->doEdit( $target->getText(), $summary, $flags );
+ wfRunHooks( 'ArticleRollbackComplete', array( $this, $wgUser, $target ) );
+
$resultDetails = array(
'summary' => $summary,
'current' => $current,
global $wgUser, $wgOut, $wgRequest, $wgUseRCPatrol;
$details = null;
+
+ # Skip the permissions-checking in doRollback() itself, by checking permissions here.
+
+ $perm_errors = array_merge( $this->mTitle->getUserPermissionsErrors( 'edit', $wgUser ),
+ $this->mTitle->getUserPermissionsErrors( 'rollback', $wgUser ) );
+
+ if (count($perm_errors)) {
+ $wgOut->showPermissionsErrorPage( $perm_errors );
+ return;
+ }
+
$result = $this->doRollback(
$wgRequest->getVal( 'from' ),
$wgRequest->getText( 'summary' ),
$wgOut->setPageTitle( wfMsg( 'rollbackfailed' ) );
$wgOut->addHtml( wfMsg( 'cantrollback' ) );
break;
+ case self::RATE_LIMITED:
+ $wgOut->rateLimited();
+ break;
case self::SUCCESS:
$current = $details['current'];
$target = $details['target'];
$wgUser->clearNotification( $this->mTitle );
}
+ /**
+ * Prepare text which is about to be saved.
+ * Returns a stdclass with source, pst and output members
+ */
+ function prepareTextForEdit( $text, $revid=null ) {
+ if ( $this->mPreparedEdit && $this->mPreparedEdit->newText == $text && $this->mPreparedEdit->revid == $revid) {
+ // Already prepared
+ return $this->mPreparedEdit;
+ }
+ global $wgParser;
+ $edit = (object)array();
+ $edit->revid = $revid;
+ $edit->newText = $text;
+ $edit->pst = $this->preSaveTransform( $text );
+ $options = new ParserOptions;
+ $options->setTidy( true );
+ $options->enableLimitReport();
+ $edit->output = $wgParser->parse( $edit->pst, $this->mTitle, $options, true, true, $revid );
+ $edit->oldText = $this->getContent();
+ $this->mPreparedEdit = $edit;
+ return $edit;
+ }
+
/**
* 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.
+ * Every 100th edit, prune the recent changes table.
*
* @private
* @param $text New text of the article
wfProfileIn( __METHOD__ );
# Parse the text
- $options = new ParserOptions;
- $options->setTidy(true);
- $poutput = $wgParser->parse( $text, $this->mTitle, $options, true, true, $newid );
+ # Be careful not to double-PST: $text is usually already PST-ed once
+ if ( !$this->mPreparedEdit || $this->mPreparedEdit->output->getFlag( 'vary-revision' ) ) {
+ wfDebug( __METHOD__ . ": No prepared edit or vary-revision is set...\n" );
+ $editInfo = $this->prepareTextForEdit( $text, $newid );
+ } else {
+ wfDebug( __METHOD__ . ": No vary-revision, using prepared edit...\n" );
+ $editInfo = $this->mPreparedEdit;
+ }
# Save it to the parser cache
$parserCache =& ParserCache::singleton();
- $parserCache->save( $poutput, $this, $wgUser );
+ $parserCache->save( $editInfo->output, $this, $wgUser );
# Update the links tables
- $u = new LinksUpdate( $this->mTitle, $poutput );
+ $u = new LinksUpdate( $this->mTitle, $editInfo->output );
$u->doUpdate();
if( wfRunHooks( 'ArticleEditUpdatesDeleteFromRecentchanges', array( &$this ) ) ) {
$title->touchLinks();
$title->purgeSquid();
+ $title->deleteTitleProtection();
}
static function onArticleDelete( $title ) {
$popts = $wgOut->parserOptions();
$popts->setTidy(true);
+ $popts->enableLimitReport();
$parserOutput = $wgParser->parse( $text, $this->mTitle,
$popts, true, true, $this->getRevIdFetched() );
$popts->setTidy(false);
+ $popts->enableLimitReport( false );
if ( $cache && $this && $parserOutput->getCacheTime() != -1 ) {
$parserCache =& ParserCache::singleton();
$parserCache->save( $parserOutput, $this, $wgUser );