Added ApiStashEdit module for pre-emptive edit parsing
* This lets edits be prepared while users enter edit summaries. * The edit form will now make use of this API, controlled by $wgAjaxEditStash. Change-Id: I4f4057bc0d1d4a66a8f7cfb7cdc26d443a8eb0c4
This commit is contained in:
parent
30e009794b
commit
3a6c9d36c9
9 changed files with 405 additions and 25 deletions
|
|
@ -27,6 +27,8 @@ production.
|
|||
longer be used. If extracts and page images are desired, the TextExtracts and
|
||||
PageImages extensions are required.
|
||||
* $wgOpenSearchTemplate is deprecated in favor of $wgOpenSearchTemplates.
|
||||
* Edits are now prepared via AJAX as users type edit summaries. This behavior
|
||||
can be disabled via $wgAjaxEditStash.
|
||||
|
||||
=== New features in 1.25 ===
|
||||
* (T64861) Updated plural rules to CLDR 26. Includes incompatible changes
|
||||
|
|
|
|||
|
|
@ -120,6 +120,7 @@ $wgAutoloadLocalClasses = array(
|
|||
'ApiRollback' => __DIR__ . '/includes/api/ApiRollback.php',
|
||||
'ApiRsd' => __DIR__ . '/includes/api/ApiRsd.php',
|
||||
'ApiSetNotificationTimestamp' => __DIR__ . '/includes/api/ApiSetNotificationTimestamp.php',
|
||||
'ApiStashEdit' => __DIR__ . '/includes/api/ApiStashEdit.php',
|
||||
'ApiTokens' => __DIR__ . '/includes/api/ApiTokens.php',
|
||||
'ApiUnblock' => __DIR__ . '/includes/api/ApiUnblock.php',
|
||||
'ApiUndelete' => __DIR__ . '/includes/api/ApiUndelete.php',
|
||||
|
|
|
|||
|
|
@ -5024,6 +5024,13 @@ $wgRateLimits = array(
|
|||
'ip' => null,
|
||||
'subnet' => null,
|
||||
),
|
||||
'stashedit' => array( // stashing edits into cache before save
|
||||
'anon' => null,
|
||||
'user' => null,
|
||||
'newbie' => null,
|
||||
'ip' => null,
|
||||
'subnet' => null,
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
|
|
@ -7037,6 +7044,12 @@ $wgAjaxUploadDestCheck = true;
|
|||
*/
|
||||
$wgAjaxLicensePreview = true;
|
||||
|
||||
/**
|
||||
* Have clients send edits to be prepared when filling in edit summaries.
|
||||
* This gives the server a head start on the expensive parsing operation.
|
||||
*/
|
||||
$wgAjaxEditStash = true;
|
||||
|
||||
/**
|
||||
* Settings for incoming cross-site AJAX requests:
|
||||
* Newer browsers support cross-site AJAX when the target resource allows requests
|
||||
|
|
|
|||
|
|
@ -321,6 +321,9 @@ class EditPage {
|
|||
/** @var int */
|
||||
public $oldid = 0;
|
||||
|
||||
/** @var int */
|
||||
public $parentRevId = 0;
|
||||
|
||||
/** @var string */
|
||||
public $editintro = '';
|
||||
|
||||
|
|
@ -881,6 +884,7 @@ class EditPage {
|
|||
}
|
||||
|
||||
$this->oldid = $request->getInt( 'oldid' );
|
||||
$this->parentRevId = $request->getInt( 'parentRevId' );
|
||||
|
||||
$this->bot = $request->getBool( 'bot', true );
|
||||
$this->nosummary = $request->getBool( 'nosummary' );
|
||||
|
|
@ -2071,7 +2075,7 @@ class EditPage {
|
|||
}
|
||||
|
||||
function setHeaders() {
|
||||
global $wgOut, $wgUser;
|
||||
global $wgOut, $wgUser, $wgAjaxEditStash;
|
||||
|
||||
$wgOut->addModules( 'mediawiki.action.edit' );
|
||||
$wgOut->addModuleStyles( 'mediawiki.action.edit.styles' );
|
||||
|
|
@ -2084,6 +2088,10 @@ class EditPage {
|
|||
$wgOut->addModules( 'mediawiki.action.edit.editWarning' );
|
||||
}
|
||||
|
||||
if ( $wgAjaxEditStash ) {
|
||||
$wgOut->addModules( 'mediawiki.action.edit.stash' );
|
||||
}
|
||||
|
||||
$wgOut->setRobotPolicy( 'noindex,nofollow' );
|
||||
|
||||
# Enabled article-related sidebar, toplinks, etc.
|
||||
|
|
@ -2448,6 +2456,8 @@ class EditPage {
|
|||
$wgOut->addHTML( Html::hidden( 'wpAutoSummary', $autosumm ) );
|
||||
|
||||
$wgOut->addHTML( Html::hidden( 'oldid', $this->oldid ) );
|
||||
$wgOut->addHTML( Html::hidden( 'parentRevId',
|
||||
$this->parentRevId ?: $this->mArticle->getRevIdFetched() ) );
|
||||
|
||||
$wgOut->addHTML( Html::hidden( 'format', $this->contentFormat ) );
|
||||
$wgOut->addHTML( Html::hidden( 'model', $this->contentModel ) );
|
||||
|
|
@ -2856,7 +2866,7 @@ class EditPage {
|
|||
global $wgOut;
|
||||
$section = htmlspecialchars( $this->section );
|
||||
$wgOut->addHTML( <<<HTML
|
||||
<input type='hidden' value="{$section}" name="wpSection" />
|
||||
<input type='hidden' value="{$section}" name="wpSection"/>
|
||||
<input type='hidden' value="{$this->starttime}" name="wpStarttime" />
|
||||
<input type='hidden' value="{$this->edittime}" name="wpEdittime" />
|
||||
<input type='hidden' value="{$this->scrolltop}" name="wpScrolltop" id="wpScrolltop" />
|
||||
|
|
|
|||
|
|
@ -54,6 +54,7 @@ class ApiMain extends ApiBase {
|
|||
'query' => 'ApiQuery',
|
||||
'expandtemplates' => 'ApiExpandTemplates',
|
||||
'parse' => 'ApiParse',
|
||||
'stashedit' => 'ApiStashEdit',
|
||||
'opensearch' => 'ApiOpenSearch',
|
||||
'feedcontributions' => 'ApiFeedContributions',
|
||||
'feedrecentchanges' => 'ApiFeedRecentChanges',
|
||||
|
|
|
|||
293
includes/api/ApiStashEdit.php
Normal file
293
includes/api/ApiStashEdit.php
Normal file
|
|
@ -0,0 +1,293 @@
|
|||
<?php
|
||||
/**
|
||||
* This program is free software; you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation; either version 2 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License along
|
||||
* with this program; if not, write to the Free Software Foundation, Inc.,
|
||||
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
|
||||
* http://www.gnu.org/copyleft/gpl.html
|
||||
*
|
||||
* @file
|
||||
* @author Aaron Schulz
|
||||
*/
|
||||
|
||||
/**
|
||||
* Prepare and edit in shared cache so that it can be reused on edit
|
||||
*
|
||||
* This endpoint can be called via AJAX as the user focuses on the edit
|
||||
* summary box. By the time of submission, the parse may have already
|
||||
* finished, and can be immediately used on page save. Certain parser
|
||||
* functions like {{REVISIONID}} or {{CURRENTTIME}} may cause the cache
|
||||
* to not be used on edit. Template and files used are check for changes
|
||||
* since the output was generated. The cache TTL is also kept low for sanity.
|
||||
*
|
||||
* @ingroup API
|
||||
* @since 1.25
|
||||
*/
|
||||
class ApiStashEdit extends ApiBase {
|
||||
public function execute() {
|
||||
global $wgMemc;
|
||||
|
||||
$user = $this->getUser();
|
||||
$params = $this->extractRequestParams();
|
||||
|
||||
$page = $this->getTitleOrPageId( $params );
|
||||
$title = $page->getTitle();
|
||||
|
||||
if ( !ContentHandler::getForModelID( $params['contentmodel'] )
|
||||
->isSupportedFormat( $params['contentformat'] )
|
||||
) {
|
||||
$this->dieUsage( "Unsupported content model/format", 'badmodelformat' );
|
||||
}
|
||||
|
||||
$text = trim( $params['text'] ); // needed so the key SHA1's match
|
||||
$textContent = ContentHandler::makeContent(
|
||||
$text, $title, $params['contentmodel'], $params['contentformat'] );
|
||||
|
||||
$page = WikiPage::factory( $title );
|
||||
if ( $page->exists() ) {
|
||||
// Page exists: get the merged content with the proposed change
|
||||
$baseRev = Revision::newFromPageId( $page->getId(), $params['baserevid'] );
|
||||
if ( !$baseRev ) {
|
||||
$this->dieUsage( "No revision ID {$params['baserevid']}", 'missingrev' );
|
||||
}
|
||||
$currentRev = $page->getRevision();
|
||||
if ( !$currentRev ) {
|
||||
$this->dieUsage( "No current revision of page ID {$page->getId()}", 'missingrev' );
|
||||
}
|
||||
// Merge in the new version of the section to get the proposed version
|
||||
$editContent = $page->replaceSectionAtRev(
|
||||
$params['section'],
|
||||
$textContent,
|
||||
$params['sectiontitle'],
|
||||
$baseRev->getId()
|
||||
);
|
||||
if ( !$editContent ) {
|
||||
$this->dieUsage( "Could not merge updated section.", 'replacefailed' );
|
||||
}
|
||||
if ( $currentRev->getId() == $baseRev->getId() ) {
|
||||
// Base revision was still the latest; nothing to merge
|
||||
$content = $editContent;
|
||||
} else {
|
||||
// Merge the edit into the current version
|
||||
$baseContent = $baseRev->getContent();
|
||||
$currentContent = $currentRev->getContent();
|
||||
if ( !$baseContent || !$currentContent ) {
|
||||
$this->dieUsage( "Missing content for page ID {$page->getId()}", 'missingrev' );
|
||||
}
|
||||
$handler = ContentHandler::getForModelID( $baseContent->getModel() );
|
||||
$content = $handler->merge3( $baseContent, $editContent, $currentContent );
|
||||
}
|
||||
} else {
|
||||
// New pages: use the user-provided content model
|
||||
$content = $textContent;
|
||||
}
|
||||
|
||||
if ( !$content ) { // merge3() failed
|
||||
$this->getResult()->addValue( null,
|
||||
$this->getModuleName(), array( 'status' => 'editconflict' ) );
|
||||
return;
|
||||
}
|
||||
|
||||
// The user will abort the AJAX request by pressing "save", so ignore that
|
||||
ignore_user_abort( true );
|
||||
|
||||
// Get a key based on the source text, format, and user preferences
|
||||
$key = self::getStashKey( $title, $content, $user );
|
||||
// De-duplicate requests on the same key
|
||||
if ( $user->pingLimiter( 'stashedit' ) ) {
|
||||
$editInfo = false;
|
||||
$status = 'ratelimited';
|
||||
} elseif ( $wgMemc->lock( $key, 0, 30 ) ) {
|
||||
$contentFormat = $content->getDefaultFormat();
|
||||
$editInfo = $page->prepareContentForEdit( $content, null, $user, $contentFormat );
|
||||
$wgMemc->unlock( $key );
|
||||
$status = 'error'; // default
|
||||
} else {
|
||||
$editInfo = false;
|
||||
$status = 'busy';
|
||||
}
|
||||
|
||||
if ( $editInfo && $editInfo->output ) {
|
||||
$parserOutput = $editInfo->output;
|
||||
// If an item is renewed, mind the cache TTL determined by config and parser functions
|
||||
$since = time() - wfTimestamp( TS_UNIX, $parserOutput->getTimestamp() );
|
||||
$ttl = min( $parserOutput->getCacheExpiry() - $since, 5 * 60 );
|
||||
if ( $ttl > 0 && !$parserOutput->getFlag( 'vary-revision' ) ) {
|
||||
// Only store what is actually needed
|
||||
$stashInfo = (object)array(
|
||||
'pstContent' => $editInfo->pstContent,
|
||||
'output' => $editInfo->output,
|
||||
'timestamp' => $editInfo->timestamp
|
||||
);
|
||||
$ok = $wgMemc->set( $key, $stashInfo, $ttl );
|
||||
if ( $ok ) {
|
||||
$status = 'stashed';
|
||||
wfDebugLog( 'PreparedEdit', "Cached parser output for key '$key'." );
|
||||
} else {
|
||||
$status = 'error';
|
||||
wfDebugLog( 'PreparedEdit', "Failed to cache parser output for key '$key'." );
|
||||
}
|
||||
} else {
|
||||
$status = 'uncacheable';
|
||||
wfDebugLog( 'PreparedEdit', "Uncacheable parser output for key '$key'." );
|
||||
}
|
||||
}
|
||||
|
||||
$this->getResult()->addValue( null, $this->getModuleName(), array( 'status' => $status ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the temporary prepared edit stash key for a user
|
||||
*
|
||||
* @param Title $title
|
||||
* @param Content $content
|
||||
* @param User $user User to get parser options from
|
||||
* @return string
|
||||
*/
|
||||
protected static function getStashKey(
|
||||
Title $title, Content $content, User $user
|
||||
) {
|
||||
return wfMemcKey( 'prepared-edit',
|
||||
md5( $title->getPrefixedDBkey() ), // handle rename races
|
||||
$content->getModel(),
|
||||
$content->getDefaultFormat(),
|
||||
sha1( $content->serialize( $content->getDefaultFormat() ) ),
|
||||
$user->getId() ?: md5( $user->getName() ), // account for user parser options
|
||||
$user->getId() ? $user->getTouched() : '-' // handle preference change races
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check that a prepared edit is in cache and still up-to-date
|
||||
*
|
||||
* This method blocks if the prepared edit is already being rendered,
|
||||
* waiting until rendering finishes before doing final validity checks.
|
||||
*
|
||||
* The cache is rejected if template or file changes are detected.
|
||||
* Note that foreign template or file transclusions are not checked.
|
||||
*
|
||||
* The result is a map (pstContent,output,timestamp) with fields
|
||||
* extracted directly from WikiPage::prepareContentForEdit().
|
||||
*
|
||||
* @param Title $title
|
||||
* @param Content $content
|
||||
* @param User $user User to get parser options from
|
||||
* @return stdClass|bool Returns false on cache miss
|
||||
*/
|
||||
public static function checkCache( Title $title, Content $content, User $user ) {
|
||||
global $wgMemc;
|
||||
|
||||
$key = self::getStashKey( $title, $content, $user );
|
||||
$editInfo = $wgMemc->get( $key );
|
||||
if ( !is_object( $editInfo ) ) {
|
||||
$start = microtime( true );
|
||||
// We ignore user aborts and keep parsing. Block on any prior parsing
|
||||
// so as to use it's results and make use of the time spent parsing.
|
||||
if ( $wgMemc->lock( $key, 30, 30 ) ) {
|
||||
$editInfo = $wgMemc->get( $key );
|
||||
$wgMemc->unlock( $key );
|
||||
$sec = microtime( true ) - $start;
|
||||
wfDebugLog( 'PreparedEdit', "Waited $sec seconds on '$key'." );
|
||||
}
|
||||
}
|
||||
|
||||
if ( !is_object( $editInfo ) || !$editInfo->output ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
$time = wfTimestamp( TS_UNIX, $editInfo->output->getTimestamp() );
|
||||
if ( ( time() - $time ) <= 3 ) {
|
||||
wfDebugLog( 'PreparedEdit', "Timestamp-based cache hit for key '$key'." );
|
||||
return $editInfo; // assume nothing changed
|
||||
}
|
||||
|
||||
$dbr = wfGetDB( DB_SLAVE );
|
||||
// Check that no templates used in the output changed...
|
||||
$cWhr = array(); // conditions to find changes/creations
|
||||
$dWhr = array(); // conditions to find deletions
|
||||
foreach ( $editInfo->output->getTemplateIds() as $ns => $stuff ) {
|
||||
foreach ( $stuff as $dbkey => $revId ) {
|
||||
$cWhr[] = array( 'page_namespace' => $ns, 'page_title' => $dbkey,
|
||||
'page_latest != ' . intval( $revId ) );
|
||||
$dWhr[] = array( 'page_namespace' => $ns, 'page_title' => $dbkey );
|
||||
}
|
||||
}
|
||||
$change = $dbr->selectField( 'page', '1', $dbr->makeList( $cWhr, LIST_OR ), __METHOD__ );
|
||||
$n = $dbr->selectField( 'page', 'COUNT(*)', $dbr->makeList( $dWhr, LIST_OR ), __METHOD__ );
|
||||
if ( $change || $n != count( $dWhr ) ) {
|
||||
wfDebugLog( 'PreparedEdit', "Stale cache for key '$key'; template changed." );
|
||||
return false;
|
||||
}
|
||||
|
||||
// Check that no files used in the output changed...
|
||||
$cWhr = array(); // conditions to find changes/creations
|
||||
$dWhr = array(); // conditions to find deletions
|
||||
foreach ( $editInfo->output->getFileSearchOptions() as $name => $options ) {
|
||||
$cWhr[] = array( 'img_name' => $dbkey,
|
||||
'img_sha1 != ' . $dbr->addQuotes( strval( $options['sha1'] ) ) );
|
||||
$dWhr[] = array( 'img_name' => $dbkey );
|
||||
}
|
||||
$change = $dbr->selectField( 'image', '1', $dbr->makeList( $cWhr, LIST_OR ), __METHOD__ );
|
||||
$n = $dbr->selectField( 'image', 'COUNT(*)', $dbr->makeList( $dWhr, LIST_OR ), __METHOD__ );
|
||||
if ( $change || $n != count( $dWhr ) ) {
|
||||
wfDebugLog( 'PreparedEdit', "Stale cache for key '$key'; file changed." );
|
||||
return false;
|
||||
}
|
||||
|
||||
wfDebugLog( 'PreparedEdit', "Cache hit for key '$key'." );
|
||||
|
||||
return $editInfo;
|
||||
}
|
||||
|
||||
public function getAllowedParams() {
|
||||
return array(
|
||||
'title' => array(
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true
|
||||
),
|
||||
'section' => array(
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
),
|
||||
'sectiontitle' => array(
|
||||
ApiBase::PARAM_TYPE => 'string'
|
||||
),
|
||||
'text' => array(
|
||||
ApiBase::PARAM_TYPE => 'string',
|
||||
ApiBase::PARAM_REQUIRED => true
|
||||
),
|
||||
'contentmodel' => array(
|
||||
ApiBase::PARAM_TYPE => ContentHandler::getContentModels(),
|
||||
ApiBase::PARAM_REQUIRED => true
|
||||
),
|
||||
'contentformat' => array(
|
||||
ApiBase::PARAM_TYPE => ContentHandler::getAllContentFormats(),
|
||||
ApiBase::PARAM_REQUIRED => true
|
||||
),
|
||||
'baserevid' => array(
|
||||
ApiBase::PARAM_TYPE => 'integer',
|
||||
ApiBase::PARAM_REQUIRED => true
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
function needsToken() {
|
||||
return 'csrf';
|
||||
}
|
||||
|
||||
function mustBePosted() {
|
||||
return true;
|
||||
}
|
||||
|
||||
function isInternal() {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
@ -1696,7 +1696,7 @@ class WikiPage implements Page, IDBAccessObject {
|
|||
*
|
||||
* @param bool|int $baseRevId The revision ID this edit was based off, if any
|
||||
* @param User $user The user doing the edit
|
||||
* @param string $serialisation_format Format for storing the content in the
|
||||
* @param string $serialFormat Format for storing the content in the
|
||||
* database.
|
||||
*
|
||||
* @throws MWException
|
||||
|
|
@ -1717,7 +1717,7 @@ class WikiPage implements Page, IDBAccessObject {
|
|||
* @since 1.21
|
||||
*/
|
||||
public function doEditContent( Content $content, $summary, $flags = 0, $baseRevId = false,
|
||||
User $user = null, $serialisation_format = null
|
||||
User $user = null, $serialFormat = null
|
||||
) {
|
||||
global $wgUser, $wgUseAutomaticEditSummaries, $wgUseRCPatrol, $wgUseNPPatrol;
|
||||
|
||||
|
|
@ -1783,7 +1783,7 @@ class WikiPage implements Page, IDBAccessObject {
|
|||
$summary = $handler->getAutosummary( $old_content, $content, $flags );
|
||||
}
|
||||
|
||||
$editInfo = $this->prepareContentForEdit( $content, null, $user, $serialisation_format );
|
||||
$editInfo = $this->prepareContentForEdit( $content, null, $user, $serialFormat, true );
|
||||
$serialized = $editInfo->pst;
|
||||
|
||||
/**
|
||||
|
|
@ -1825,7 +1825,7 @@ class WikiPage implements Page, IDBAccessObject {
|
|||
'user_text' => $user->getName(),
|
||||
'timestamp' => $now,
|
||||
'content_model' => $content->getModel(),
|
||||
'content_format' => $serialisation_format,
|
||||
'content_format' => $serialFormat,
|
||||
) ); // XXX: pass content object?!
|
||||
|
||||
$changed = !$content->equals( $old_content );
|
||||
|
|
@ -1951,7 +1951,7 @@ class WikiPage implements Page, IDBAccessObject {
|
|||
'user_text' => $user->getName(),
|
||||
'timestamp' => $now,
|
||||
'content_model' => $content->getModel(),
|
||||
'content_format' => $serialisation_format,
|
||||
'content_format' => $serialFormat,
|
||||
) );
|
||||
$revisionId = $revision->insertOn( $dbw );
|
||||
|
||||
|
|
@ -2068,49 +2068,71 @@ class WikiPage implements Page, IDBAccessObject {
|
|||
* @param Content $content
|
||||
* @param int|null $revid
|
||||
* @param User|null $user
|
||||
* @param string|null $serialization_format
|
||||
* @param string|null $serialFormat
|
||||
* @param bool $useCache Check shared prepared edit cache
|
||||
*
|
||||
* @return bool|object
|
||||
* @return object
|
||||
*
|
||||
* @since 1.21
|
||||
*/
|
||||
public function prepareContentForEdit( Content $content, $revid = null, User $user = null,
|
||||
$serialization_format = null
|
||||
public function prepareContentForEdit(
|
||||
Content $content, $revid = null, User $user = null, $serialFormat = null, $useCache = false
|
||||
) {
|
||||
global $wgContLang, $wgUser;
|
||||
|
||||
$user = is_null( $user ) ? $wgUser : $user;
|
||||
//XXX: check $user->getId() here???
|
||||
|
||||
// Use a sane default for $serialization_format, see bug 57026
|
||||
if ( $serialization_format === null ) {
|
||||
$serialization_format = $content->getContentHandler()->getDefaultFormat();
|
||||
// Use a sane default for $serialFormat, see bug 57026
|
||||
if ( $serialFormat === null ) {
|
||||
$serialFormat = $content->getContentHandler()->getDefaultFormat();
|
||||
}
|
||||
|
||||
if ( $this->mPreparedEdit
|
||||
&& $this->mPreparedEdit->newContent
|
||||
&& $this->mPreparedEdit->newContent->equals( $content )
|
||||
&& $this->mPreparedEdit->revid == $revid
|
||||
&& $this->mPreparedEdit->format == $serialization_format
|
||||
&& $this->mPreparedEdit->format == $serialFormat
|
||||
// XXX: also check $user here?
|
||||
) {
|
||||
// Already prepared
|
||||
return $this->mPreparedEdit;
|
||||
}
|
||||
|
||||
// The edit may have already been prepared via api.php?action=stashedit
|
||||
$cachedEdit = $useCache
|
||||
? ApiStashEdit::checkCache( $this->getTitle(), $content, $user )
|
||||
: false;
|
||||
|
||||
$popts = ParserOptions::newFromUserAndLang( $user, $wgContLang );
|
||||
wfRunHooks( 'ArticlePrepareTextForEdit', array( $this, $popts ) );
|
||||
|
||||
$edit = (object)array();
|
||||
if ( $cachedEdit ) {
|
||||
$edit->timestamp = $cachedEdit->timestamp;
|
||||
} else {
|
||||
$edit->timestamp = wfTimestampNow();
|
||||
}
|
||||
// @note: $cachedEdit is not used if the rev ID was referenced in the text
|
||||
$edit->revid = $revid;
|
||||
$edit->timestamp = wfTimestampNow();
|
||||
|
||||
$edit->pstContent = $content ? $content->preSaveTransform( $this->mTitle, $user, $popts ) : null;
|
||||
if ( $cachedEdit ) {
|
||||
$edit->pstContent = $cachedEdit->pstContent;
|
||||
} else {
|
||||
$edit->pstContent = $content
|
||||
? $content->preSaveTransform( $this->mTitle, $user, $popts )
|
||||
: null;
|
||||
}
|
||||
|
||||
$edit->format = $serialization_format;
|
||||
$edit->format = $serialFormat;
|
||||
$edit->popts = $this->makeParserOptions( 'canonical' );
|
||||
$edit->output = $edit->pstContent
|
||||
? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
|
||||
: null;
|
||||
if ( $cachedEdit ) {
|
||||
$edit->output = $cachedEdit->output;
|
||||
} else {
|
||||
$edit->output = $edit->pstContent
|
||||
? $edit->pstContent->getParserOutput( $this->mTitle, $revid, $edit->popts )
|
||||
: null;
|
||||
}
|
||||
|
||||
$edit->newContent = $content;
|
||||
$edit->oldContent = $this->getContent( Revision::RAW );
|
||||
|
|
@ -2118,7 +2140,7 @@ class WikiPage implements Page, IDBAccessObject {
|
|||
// NOTE: B/C for hooks! don't use these fields!
|
||||
$edit->newText = $edit->newContent ? ContentHandler::getContentText( $edit->newContent ) : '';
|
||||
$edit->oldText = $edit->oldContent ? ContentHandler::getContentText( $edit->oldContent ) : '';
|
||||
$edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialization_format ) : '';
|
||||
$edit->pst = $edit->pstContent ? $edit->pstContent->serialize( $serialFormat ) : '';
|
||||
|
||||
$this->mPreparedEdit = $edit;
|
||||
return $edit;
|
||||
|
|
@ -2288,14 +2310,14 @@ class WikiPage implements Page, IDBAccessObject {
|
|||
* @param User $user The relevant user
|
||||
* @param string $comment Comment submitted
|
||||
* @param bool $minor Whereas it's a minor modification
|
||||
* @param string $serialisation_format Format for storing the content in the database
|
||||
* @param string $serialFormat Format for storing the content in the database
|
||||
*/
|
||||
public function doQuickEditContent( Content $content, User $user, $comment = '', $minor = false,
|
||||
$serialisation_format = null
|
||||
$serialFormat = null
|
||||
) {
|
||||
wfProfileIn( __METHOD__ );
|
||||
|
||||
$serialized = $content->serialize( $serialisation_format );
|
||||
$serialized = $content->serialize( $serialFormat );
|
||||
|
||||
$dbw = wfGetDB( DB_MASTER );
|
||||
$revision = new Revision( array(
|
||||
|
|
|
|||
|
|
@ -1050,6 +1050,13 @@ return array(
|
|||
'mediawiki.action.history.diff',
|
||||
),
|
||||
),
|
||||
'mediawiki.action.edit.stash' => array(
|
||||
'scripts' => 'resources/src/mediawiki.action/mediawiki.action.edit.stash.js',
|
||||
'dependencies' => array(
|
||||
'jquery.getAttrs',
|
||||
'mediawiki.api',
|
||||
),
|
||||
),
|
||||
'mediawiki.action.history' => array(
|
||||
'scripts' => 'resources/src/mediawiki.action/mediawiki.action.history.js',
|
||||
'styles' => 'resources/src/mediawiki.action/mediawiki.action.history.css',
|
||||
|
|
|
|||
|
|
@ -0,0 +1,31 @@
|
|||
/*!
|
||||
* Scripts for pre-emptive edit preparing on action=edit
|
||||
*/
|
||||
( function ( mw, $ ) {
|
||||
$( function () {
|
||||
var api = new mw.Api(), pending = null, $form = $( '#editform' );
|
||||
|
||||
function stashEdit( token ) {
|
||||
var data = $form.serializeObject();
|
||||
|
||||
pending = api.post( {
|
||||
action: 'stashedit',
|
||||
token: token,
|
||||
title: mw.config.get( 'wgPageName' ),
|
||||
section: data.wpSection,
|
||||
sectiontitle: data.wpSection === 'new' ? data.wpSummary : '',
|
||||
text: data.wpTextbox1,
|
||||
contentmodel: data.model,
|
||||
contentformat: data.format,
|
||||
baserevid: data.parentRevId
|
||||
} );
|
||||
}
|
||||
|
||||
$form.on( 'change', function () {
|
||||
if ( pending ) {
|
||||
pending.abort();
|
||||
}
|
||||
api.getToken( 'edit' ).then( stashEdit );
|
||||
} );
|
||||
} );
|
||||
}( mediaWiki, jQuery ) );
|
||||
Loading…
Reference in a new issue