X-Git-Url: https://git.cyclocoop.org/?a=blobdiff_plain;f=includes%2Fapi%2FApiStashEdit.php;h=345767089a8f01891281cc957f777dcff418d3b7;hb=2086cd118020f6388d7b6952ac2d9e2b55e6ef1f;hp=fd5bd7e9f2d491162a2da4a62b84baeed2da3088;hpb=8181f97897e2ce419d4e8599240af46d33b5ea63;p=lhc%2Fweb%2Fwiklou.git diff --git a/includes/api/ApiStashEdit.php b/includes/api/ApiStashEdit.php index fd5bd7e9f2..345767089a 100644 --- a/includes/api/ApiStashEdit.php +++ b/includes/api/ApiStashEdit.php @@ -108,8 +108,8 @@ class ApiStashEdit extends ApiBase { $editInfo = false; $status = 'ratelimited'; } elseif ( $wgMemc->lock( $key, 0, 30 ) ) { - $contentFormat = $content->getDefaultFormat(); - $editInfo = $page->prepareContentForEdit( $content, null, $user, $contentFormat ); + $format = $content->getDefaultFormat(); + $editInfo = $page->prepareContentForEdit( $content, null, $user, $format, false ); $status = 'error'; // default $unlocker = new ScopedCallback( function() use ( $key ) { global $wgMemc; @@ -121,17 +121,10 @@ class ApiStashEdit extends ApiBase { } 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 - ); + list( $stashInfo, $ttl ) = self::buildStashValue( + $editInfo->pstContent, $editInfo->output, $editInfo->timestamp + ); + if ( $stashInfo ) { $ok = $wgMemc->set( $key, $stashInfo, $ttl ); if ( $ok ) { $status = 'stashed'; @@ -150,24 +143,69 @@ class ApiStashEdit extends ApiBase { } /** - * Get the temporary prepared edit stash key for a user + * Attempt to cache PST content and corresponding parser output in passing * - * @param Title $title - * @param Content $content - * @param User $user User to get parser options from - * @return string + * This method can be called when the output was already generated for other + * reasons. Parsing should not be done just to call this method, however. + * $pstOpts must be that of the user doing the edit preview. If $pOpts does + * not match the options of WikiPage::makeParserOptions( 'canonical' ), this + * will do nothing. Provided the values are cacheable, they will be stored + * in memcached so that final edit submission might make use of them. + * + * @param Article|WikiPage $page Page title + * @param Content $content Proposed page content + * @param Content $pstContent The result of preSaveTransform() on $content + * @param ParserOutput $pOut The result of getParserOutput() on $pstContent + * @param ParserOptions $pstOpts Options for $pstContent (MUST be for prospective author) + * @param ParserOptions $pOpts Options for $pOut + * @param string $timestamp TS_MW timestamp of parser output generation + * @return bool Success */ - protected static function getStashKey( - Title $title, Content $content, User $user + public static function stashEditFromPreview( + Page $page, Content $content, Content $pstContent, ParserOutput $pOut, + ParserOptions $pstOpts, ParserOptions $pOpts, $timestamp ) { - 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 - ); + global $wgMemc; + + // getIsPreview() controls parser function behavior that references things + // like user/revision that don't exists yet. The user/text should already + // be set correctly by callers, just double check the preview flag. + if ( !$pOpts->getIsPreview() ) { + return false; // sanity + } elseif ( $pOpts->getIsSectionPreview() ) { + return false; // short-circuit (need the full content) + } + + // PST parser options are for the user (handles signatures, etc...) + $user = $pstOpts->getUser(); + // Get a key based on the source text, format, and user preferences + $key = self::getStashKey( $page->getTitle(), $content, $user ); + + // Parser output options must match cannonical options. + // Treat some options as matching that are different but don't matter. + $canonicalPOpts = $page->makeParserOptions( 'canonical' ); + $canonicalPOpts->setIsPreview( true ); // force match + $canonicalPOpts->setTimestamp( $pOpts->getTimestamp() ); // force match + if ( !$pOpts->matches( $canonicalPOpts ) ) { + wfDebugLog( 'StashEdit', "Uncacheable preview output for key '$key' (options)." ); + return false; + } + + // Build a value to cache with a proper TTL + list( $stashInfo, $ttl ) = self::buildStashValue( $pstContent, $pOut, $timestamp ); + if ( !$stashInfo ) { + wfDebugLog( 'StashEdit', "Uncacheable parser output for key '$key' (rev/TTL)." ); + return false; + } + + $ok = $wgMemc->set( $key, $stashInfo, $ttl ); + if ( !$ok ) { + wfDebugLog( 'StashEdit', "Failed to cache preview parser output for key '$key'." ); + } else { + wfDebugLog( 'StashEdit', "Cached preview output for key '$key'." ); + } + + return $ok; } /** @@ -201,7 +239,9 @@ class ApiStashEdit extends ApiBase { $wgMemc->unlock( $key ); } $sec = microtime( true ) - $start; - wfDebugLog( 'StashEdit', "Waited $sec seconds on '$key'." ); + if ( $sec > .01 ) { + wfDebugLog( 'StashEdit', "Waited $sec seconds on '$key'." ); + } } if ( !is_object( $editInfo ) || !$editInfo->output ) { @@ -253,6 +293,60 @@ class ApiStashEdit extends ApiBase { return $editInfo; } + /** + * Get the temporary prepared edit stash key for a user + * + * This key can be used for caching prepared edits provided: + * - a) The $user was used for PST options + * - b) The parser output was made from the PST using cannonical matching options + * + * @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 ) { + $hash = sha1( implode( ':', array( + $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 + ) ) ); + + return wfMemcKey( 'prepared-edit', md5( $title->getPrefixedDBkey() ), $hash ); + } + + /** + * Build a value to store in memcached based on the PST content and parser output + * + * This makes a simple version of WikiPage::prepareContentForEdit() as stash info + * + * @param Content $pstContent + * @param ParserOutput $parserOutput + * @param string $timestamp TS_MW + * @return array (stash info array, TTL in seconds) or (null, 0) + */ + protected static function buildStashValue( + Content $pstContent, ParserOutput $parserOutput, $timestamp + ) { + // 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' => $pstContent, + 'output' => $parserOutput, + 'timestamp' => $timestamp + ); + return array( $stashInfo, $ttl ); + } + + return array( null, 0 ); + } + public function getAllowedParams() { return array( 'title' => array(