some refactoring to allow for nicer usage in deriving classes
[lhc/web/wiklou.git] / includes / specials / SpecialCachedPage.php
1 <?php
2
3 /**
4 * Abstract special page class with scaffolding for caching the HTML output.
5 *
6 * To enable the caching functionality, the cacheExpiry field should be set
7 * before parent::execute is called (and it should be called from execute).
8 *
9 * To add HTML that should be cached, use addCachedHTML like this:
10 * $this->addCachedHTML( array( $this, 'displayCachedContent' ) );
11 *
12 * Before the first addCachedHTML call, you should call $this->startCache();
13 * After adding the last HTML that should be cached, call $this->saveCache();
14 *
15 * @since 1.20
16 *
17 * @file SpecialCachedPage.php
18 * @ingroup SpecialPage
19 *
20 * @licence GNU GPL v2 or later
21 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
22 */
23 abstract class SpecialCachedPage extends SpecialPage {
24
25 /**
26 * The time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
27 *
28 * @since 1.20
29 * @var integer
30 */
31 protected $cacheExpiry = 3600;
32
33 /**
34 * List of HTML chunks to be cached (if !hasCached) or that where cashed (of hasCached).
35 * If no cached already, then the newly computed chunks are added here,
36 * if it as cached already, chunks are removed from this list as they are needed.
37 *
38 * @since 1.20
39 * @var array
40 */
41 protected $cachedChunks;
42
43 /**
44 * Indicates if the to be cached content was already cached.
45 * Null if this information is not available yet.
46 *
47 * @since 1.20
48 * @var boolean|null
49 */
50 protected $hasCached = null;
51
52 /**
53 * If the cache is enabled or not.
54 *
55 * @since 1.20
56 * @var boolean
57 */
58 protected $cacheEnabled = true;
59
60 /**
61 * Sets if the cache should be enabled or not.
62 *
63 * @since 1.20
64 * @param boolean $cacheEnabled
65 */
66 public function setCacheEnabled( $cacheEnabled ) {
67 $this->cacheEnabled = $cacheEnabled;
68 }
69
70 /**
71 * Initializes the caching.
72 * Should be called before the first time anything is added via addCachedHTML.
73 *
74 * @since 1.20
75 *
76 * @param integer|null $cacheExpiry Sets the cache expirty, either ttl in seconds or unix timestamp.
77 * @param boolean|null $cacheEnabled Sets if the cache should be enabled or not.
78 */
79 public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
80 if ( !is_null( $cacheExpiry ) ) {
81 $this->cacheExpiry = $cacheExpiry;
82 }
83
84 if ( !is_null( $cacheEnabled ) ) {
85 $this->setCacheEnabled( $cacheEnabled );
86 }
87
88 if ( $this->getRequest()->getText( 'action' ) === 'purge' ) {
89 $this->hasCached = false;
90 }
91
92 $this->initCaching();
93 }
94
95 /**
96 * Returns a message that notifies the user he/she is looking at
97 * a cached version of the page, including a refresh link.
98 *
99 * @since 1.20
100 *
101 * @return string
102 */
103 protected function getCachedNotice() {
104 $refreshArgs = $this->getRequest()->getQueryValues();
105 unset( $refreshArgs['title'] );
106 $refreshArgs['action'] = 'purge';
107
108 $refreshLink = Linker::link(
109 $this->getTitle( $this->getTitle()->getSubpageText() ),
110 $this->msg( 'cachedspecial-refresh-now' )->escaped(),
111 array(),
112 $refreshArgs
113 );
114
115 if ( $this->cacheExpiry < 86400 * 3650 ) {
116 $message = $this->msg(
117 'cachedspecial-viewing-cached-ttl',
118 $this->getLanguage()->formatDuration( $this->cacheExpiry )
119 )->escaped();
120 }
121 else {
122 $message = $this->msg(
123 'cachedspecial-viewing-cached-ts'
124 )->escaped();
125 }
126
127 return $message . ' ' . $refreshLink;
128 }
129
130 /**
131 * Initializes the caching if not already done so.
132 * Should be called before any of the caching functionality is used.
133 *
134 * @since 1.20
135 */
136 protected function initCaching() {
137 if ( is_null( $this->hasCached ) ) {
138 $cachedChunks = wfGetCache( CACHE_ANYTHING )->get( $this->getCacheKeyString() );
139
140 $this->hasCached = is_array( $cachedChunks );
141 $this->cachedChunks = $this->hasCached ? $cachedChunks : array();
142
143 $this->onCacheInitialized();
144 }
145 }
146
147 protected function onCacheInitialized() {
148 if ( $this->hasCached ) {
149 $this->getOutput()->setSubtitle( $this->getCachedNotice() );
150 }
151 }
152
153 /**
154 * Add some HTML to be cached.
155 * This is done by providing a callback function that should
156 * return the HTML to be added. It will only be called if the
157 * item is not in the cache yet or when the cache has been invalidated.
158 *
159 * @since 1.20
160 *
161 * @param {function} $callback
162 * @param array $args
163 * @param string|null $key
164 */
165 public function addCachedHTML( $callback, $args = array(), $key = null ) {
166 $this->initCaching();
167
168 if ( $this->cacheEnabled && $this->hasCached ) {
169 $html = '';
170
171 if ( is_null( $key ) ) {
172 $itemKey = array_keys( array_slice( $this->cachedChunks, 0, 1 ) );
173 $itemKey = array_shift( $itemKey );
174
175 if ( !is_integer( $itemKey ) ) {
176 wfWarn( "Attempted to get item with non-numeric key while the next item in the queue has a key ($itemKey) in " . __METHOD__ );
177 }
178 elseif ( is_null( $itemKey ) ) {
179 wfWarn( "Attempted to get an item while the queue is empty in " . __METHOD__ );
180 }
181 else {
182 $html = array_shift( $this->cachedChunks );
183 }
184 }
185 else {
186 if ( array_key_exists( $key, $this->cachedChunks ) ) {
187 $html = $this->cachedChunks[$key];
188 unset( $this->cachedChunks[$key] );
189 }
190 else {
191 wfWarn( "There is no item with key '$key' in this->cachedChunks in " . __METHOD__ );
192 }
193 }
194 }
195 else {
196 $html = call_user_func_array( $callback, $args );
197
198 if ( $this->cacheEnabled ) {
199 if ( is_null( $key ) ) {
200 $this->cachedChunks[] = $html;
201 }
202 else {
203 $this->cachedChunks[$key] = $html;
204 }
205 }
206 }
207
208 $this->getOutput()->addHTML( $html );
209 }
210
211 /**
212 * Saves the HTML to the cache in case it got recomputed.
213 * Should be called after the last time anything is added via addCachedHTML.
214 *
215 * @since 1.20
216 */
217 public function saveCache() {
218 if ( $this->cacheEnabled && $this->hasCached === false && !empty( $this->cachedChunks ) ) {
219 wfGetCache( CACHE_ANYTHING )->set( $this->getCacheKeyString(), $this->cachedChunks, $this->cacheExpiry );
220 }
221 }
222
223 /**
224 * Sets the time to live for the cache, in seconds or a unix timestamp indicating the point of expiry..
225 *
226 * @since 1.20
227 *
228 * @param integer $cacheExpiry
229 */
230 protected function setExpirey( $cacheExpiry ) {
231 $this->cacheExpiry = $cacheExpiry;
232 }
233
234 /**
235 * Returns the cache key to use to cache this page's HTML output.
236 * Is constructed from the special page name and language code.
237 *
238 * @since 1.20
239 *
240 * @return string
241 */
242 protected function getCacheKeyString() {
243 return call_user_func_array( 'wfMemcKey', $this->getCacheKey() );
244 }
245
246 /**
247 * Returns the variables used to constructed the cache key in an array.
248 *
249 * @since 1.20
250 *
251 * @return array
252 */
253 protected function getCacheKey() {
254 return array(
255 $this->mName,
256 $this->getLanguage()->getCode()
257 );
258 }
259
260 }