From: Jeroen De Dauw Date: Sat, 17 Mar 2012 22:26:02 +0000 (+0000) Subject: adding special page with scaffolding for caching chunks of HTML X-Git-Tag: 1.31.0-rc.0~24220 X-Git-Url: http://git.cyclocoop.org/%40spipnet%40?a=commitdiff_plain;h=b2134bbd0e3d59db1af9165b57bd110f276c956f;p=lhc%2Fweb%2Fwiklou.git adding special page with scaffolding for caching chunks of HTML --- diff --git a/includes/AutoLoader.php b/includes/AutoLoader.php index b59efe669e..905d35b6c6 100644 --- a/includes/AutoLoader.php +++ b/includes/AutoLoader.php @@ -823,6 +823,7 @@ $wgAutoloadLocalClasses = array( 'SpecialBlockList' => 'includes/specials/SpecialBlockList.php', 'SpecialBlockme' => 'includes/specials/SpecialBlockme.php', 'SpecialBookSources' => 'includes/specials/SpecialBooksources.php', + 'SpecialCachedPage' => 'includes/specials/SpecialCachedPage.php', 'SpecialCategories' => 'includes/specials/SpecialCategories.php', 'SpecialChangeEmail' => 'includes/specials/SpecialChangeEmail.php', 'SpecialChangePassword' => 'includes/specials/SpecialChangePassword.php', diff --git a/includes/specials/SpecialCachedPage.php b/includes/specials/SpecialCachedPage.php new file mode 100644 index 0000000000..6a2a21aff0 --- /dev/null +++ b/includes/specials/SpecialCachedPage.php @@ -0,0 +1,215 @@ +addCachedHTML( array( $this, 'displayCachedContent' ) ); + * + * After adding the last HTML that should be cached, call $this->saveCache(); + * + * @since 1.20 + * + * @file SpecialCachedPage.php + * @ingroup SpecialPage + * + * @licence GNU GPL v2 or later + * @author Jeroen De Dauw < jeroendedauw@gmail.com > + */ +abstract class SpecialCachedPage extends SpecialPage { + + /** + * The time to live for the cache, in seconds or a unix timestamp indicating the point of expiry. + * + * @since 1.20 + * @var integer|null + */ + protected $cacheExpiry = null; + + /** + * List of HTML chunks to be cached (if !hasCached) or that where cashed (of hasCached). + * If no cached already, then the newly computed chunks are added here, + * if it as cached already, chunks are removed from this list as they are needed. + * + * @since 1.20 + * @var array + */ + protected $cachedChunks; + + /** + * Indicates if the to be cached content was already cached. + * Null if this information is not available yet. + * + * @since 1.20 + * @var boolean|null + */ + protected $hasCached = null; + + /** + * Main method. + * + * @since 1.20 + * + * @param string|null $subPage + */ + public function execute( $subPage ) { + if ( $this->getRequest()->getText( 'action' ) === 'purge' ) { + $this->hasCached = false; + } + + if ( !is_null( $this->cacheExpiry ) ) { + $this->initCaching(); + + if ( $this->hasCached === true ) { + $this->getOutput()->setSubtitle( $this->getCachedNotice( $subPage ) ); + } + } + } + + /** + * Returns a message that notifies the user he/she is looking at + * a cached version of the page, including a refresh link. + * + * @since 1.20 + * + * @param string|null $subPage + * + * @return string + */ + protected function getCachedNotice( $subPage ) { + $refreshArgs = $_GET; + unset( $refreshArgs['title'] ); + $refreshArgs['action'] = 'purge'; + + $refreshLink = Linker::link( + $this->getTitle( $subPage ), + $this->msg( 'cachedspecial-refresh-now' )->escaped(), + array(), + $refreshArgs + ); + + if ( $this->cacheExpiry < 1000000000 ) { + $message = $this->msg( + 'cachedspecial-viewing-cached-ttl', + $this->getLanguage()->duration( $this->cacheExpiry ) + )->escaped(); + } + else { + $message = $this->msg( + 'cachedspecial-viewing-cached-ts' + )->escaped(); + } + + return $message . ' ' . $refreshLink; + } + + /** + * Initializes the caching if not already done so. + * Should be called before any of the caching functionality is used. + * + * @since 1.20 + */ + protected function initCaching() { + if ( is_null( $this->hasCached ) ) { + $cachedChunks = wfGetCache( CACHE_ANYTHING )->get( $this->getCacheKey() ); + + $this->hasCached = is_array( $cachedChunks ); + $this->cachedChunks = $this->hasCached ? $cachedChunks : array(); + } + } + + /** + * Add some HTML to be cached. + * This is done by providing a callback function that should + * return the HTML to be added. It will only be called if the + * item is not in the cache yet or when the cache has been invalidated. + * + * @since 1.20 + * + * @param {function} $callback + * @param array $args + * @param string|null $key + */ + public function addCachedHTML( $callback, $args = array(), $key = null ) { + $this->initCaching(); + + if ( $this->hasCached ) { + $html = ''; + + if ( is_null( $key ) ) { + $itemKey = array_keys( array_slice( $this->cachedChunks, 0, 1 ) ); + $itemKey = array_shift( $itemKey ); + + if ( !is_integer( $itemKey ) ) { + wfWarn( "Attempted to get item with non-numeric key while the next item in the queue has a key ($itemKey) in " . __METHOD__ ); + } + elseif ( is_null( $itemKey ) ) { + wfWarn( "Attempted to get an item while the queue is empty in " . __METHOD__ ); + } + else { + $html = array_shift( $this->cachedChunks ); + } + } + else { + if ( array_key_exists( $key, $this->cachedChunks ) ) { + $html = $this->cachedChunks[$key]; + unset( $this->cachedChunks[$key] ); + } + else { + wfWarn( "There is no item with key '$key' in this->cachedChunks in " . __METHOD__ ); + } + } + } + else { + $html = call_user_func_array( $callback, $args ); + + if ( is_null( $key ) ) { + $this->cachedChunks[] = $html; + } + else { + $this->cachedChunks[$key] = $html; + } + } + + $this->getOutput()->addHTML( $html ); + } + + /** + * Saves the HTML to the cache in case it got recomputed. + * Should be called after the last time anything is added via addCachedHTML. + * + * @since 1.20 + */ + public function saveCache() { + if ( $this->hasCached === false && !empty( $this->cachedChunks ) ) { + wfGetCache( CACHE_ANYTHING )->set( $this->getCacheKey(), $this->cachedChunks, $this->cacheExpiry ); + } + } + + /** + * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.. + * + * @since 1.20 + * + * @param integer $cacheExpiry + */ + protected function setExpirey( $cacheExpiry ) { + $this->cacheExpiry = $cacheExpiry; + } + + /** + * Returns the cache key to use to cache this page's HTML output. + * Is constructed from the special page name and language code. + * + * @since 1.20 + * + * @return string + */ + protected function getCacheKey() { + return wfMemcKey( $this->mName, $this->getLanguage()->getCode() ); + } + +} diff --git a/languages/messages/MessagesEn.php b/languages/messages/MessagesEn.php index 22dd3269d4..56ac6e3ec4 100644 --- a/languages/messages/MessagesEn.php +++ b/languages/messages/MessagesEn.php @@ -2661,6 +2661,11 @@ You can narrow down the view by selecting a log type, the username (case-sensiti It may contain one or more characters which cannot be used in titles.', 'allpages-bad-ns' => '{{SITENAME}} does not have namespace "$1".', +# SpecialCachedPage +'cachedspecial-viewing-cached-ttl' => 'You are viewing a cached version of this page, which can be up to $1 old.', +'cachedspecial-viewing-cached-ts' => 'You are viewing a cached version of this page, which might not be completely actual.', +'cachedspecial-refresh-now' => 'View latest.', + # Special:Categories 'categories' => 'Categories', 'categories-summary' => '', # do not translate or duplicate this message to other languages diff --git a/maintenance/language/messages.inc b/maintenance/language/messages.inc index 5aed95fd25..e5089139a9 100644 --- a/maintenance/language/messages.inc +++ b/maintenance/language/messages.inc @@ -1749,6 +1749,11 @@ $wgMessageStructure = array( 'allpagesbadtitle', 'allpages-bad-ns', ), + 'cachedspecial' => array( + 'cachedspecial-viewing-cached-ttl', + 'cachedspecial-viewing-cached-ts', + 'cachedspecial-refresh-now', + ), 'categories' => array( 'categories', 'categories-summary', @@ -3935,4 +3940,5 @@ Variants for Chinese language", 'feedback' => 'Feedback', 'apierrors' => 'API errors', 'duration' => 'Durations', + 'cachedspecial' => 'SpecialCachedPage', );