Remove parameter 'options' from hook 'SkinEditSectionLinks'
[lhc/web/wiklou.git] / includes / cache / CacheHelper.php
1 <?php
2 /**
3 * Cache of various elements in a single cache entry.
4 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License along
16 * with this program; if not, write to the Free Software Foundation, Inc.,
17 * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
18 * http://www.gnu.org/copyleft/gpl.html
19 *
20 * @file
21 * @license GPL-2.0-or-later
22 * @author Jeroen De Dauw < jeroendedauw@gmail.com >
23 */
24
25 use MediaWiki\MediaWikiServices;
26
27 /**
28 * Helper class for caching various elements in a single cache entry.
29 *
30 * To get a cached value or compute it, use getCachedValue like this:
31 * $this->getCachedValue( $callback );
32 *
33 * To add HTML that should be cached, use addCachedHTML like this:
34 * $this->addCachedHTML( $callback );
35 *
36 * The callback function is only called when needed, so do all your expensive
37 * computations here. This function should returns the HTML to be cached.
38 * It should not add anything to the PageOutput object!
39 *
40 * Before the first addCachedHTML call, you should call $this->startCache();
41 * After adding the last HTML that should be cached, call $this->saveCache();
42 *
43 * @since 1.20
44 */
45 class CacheHelper implements ICacheHelper {
46 /**
47 * The time to live for the cache, in seconds or a unix timestamp indicating the point of expiry.
48 *
49 * @since 1.20
50 * @var int
51 */
52 protected $cacheExpiry = 3600;
53
54 /**
55 * List of HTML chunks to be cached (if !hasCached) or that where cached (of hasCached).
56 * If not cached already, then the newly computed chunks are added here,
57 * if it as cached already, chunks are removed from this list as they are needed.
58 *
59 * @since 1.20
60 * @var array
61 */
62 protected $cachedChunks;
63
64 /**
65 * Indicates if the to be cached content was already cached.
66 * Null if this information is not available yet.
67 *
68 * @since 1.20
69 * @var bool|null
70 */
71 protected $hasCached = null;
72
73 /**
74 * If the cache is enabled or not.
75 *
76 * @since 1.20
77 * @var bool
78 */
79 protected $cacheEnabled = true;
80
81 /**
82 * Function that gets called when initialization is done.
83 *
84 * @since 1.20
85 * @var callable
86 */
87 protected $onInitHandler = false;
88
89 /**
90 * Elements to build a cache key with.
91 *
92 * @since 1.20
93 * @var array
94 */
95 protected $cacheKey = [];
96
97 /**
98 * Sets if the cache should be enabled or not.
99 *
100 * @since 1.20
101 * @param bool $cacheEnabled
102 */
103 public function setCacheEnabled( $cacheEnabled ) {
104 $this->cacheEnabled = $cacheEnabled;
105 }
106
107 /**
108 * Initializes the caching.
109 * Should be called before the first time anything is added via addCachedHTML.
110 *
111 * @since 1.20
112 *
113 * @param int|null $cacheExpiry Sets the cache expiry, either ttl in seconds or unix timestamp.
114 * @param bool|null $cacheEnabled Sets if the cache should be enabled or not.
115 */
116 public function startCache( $cacheExpiry = null, $cacheEnabled = null ) {
117 if ( is_null( $this->hasCached ) ) {
118 if ( !is_null( $cacheExpiry ) ) {
119 $this->cacheExpiry = $cacheExpiry;
120 }
121
122 if ( !is_null( $cacheEnabled ) ) {
123 $this->setCacheEnabled( $cacheEnabled );
124 }
125
126 $this->initCaching();
127 }
128 }
129
130 /**
131 * Returns a message that notifies the user he/she is looking at
132 * a cached version of the page, including a refresh link.
133 *
134 * @since 1.20
135 *
136 * @param IContextSource $context
137 * @param bool $includePurgeLink
138 *
139 * @return string
140 */
141 public function getCachedNotice( IContextSource $context, $includePurgeLink = true ) {
142 if ( $this->cacheExpiry < 86400 * 3650 ) {
143 $message = $context->msg(
144 'cachedspecial-viewing-cached-ttl',
145 $context->getLanguage()->formatDuration( $this->cacheExpiry )
146 )->escaped();
147 } else {
148 $message = $context->msg(
149 'cachedspecial-viewing-cached-ts'
150 )->escaped();
151 }
152
153 if ( $includePurgeLink ) {
154 $refreshArgs = $context->getRequest()->getQueryValues();
155 unset( $refreshArgs['title'] );
156 $refreshArgs['action'] = 'purge';
157
158 $subPage = $context->getTitle()->getFullText();
159 $subPage = explode( '/', $subPage, 2 );
160 $subPage = count( $subPage ) > 1 ? $subPage[1] : false;
161
162 $message .= ' ' . MediaWikiServices::getInstance()->getLinkRenderer()->makeLink(
163 $context->getTitle( $subPage ),
164 $context->msg( 'cachedspecial-refresh-now' )->text(),
165 [],
166 $refreshArgs
167 );
168 }
169
170 return $message;
171 }
172
173 /**
174 * Initializes the caching if not already done so.
175 * Should be called before any of the caching functionality is used.
176 *
177 * @since 1.20
178 */
179 protected function initCaching() {
180 if ( $this->cacheEnabled && is_null( $this->hasCached ) ) {
181 $cachedChunks = wfGetCache( CACHE_ANYTHING )->get( $this->getCacheKeyString() );
182
183 $this->hasCached = is_array( $cachedChunks );
184 $this->cachedChunks = $this->hasCached ? $cachedChunks : [];
185
186 if ( $this->onInitHandler !== false ) {
187 call_user_func( $this->onInitHandler, $this->hasCached );
188 }
189 }
190 }
191
192 /**
193 * Get a cached value if available or compute it if not and then cache it if possible.
194 * The provided $computeFunction is only called when the computation needs to happen
195 * and should return a result value. $args are arguments that will be passed to the
196 * compute function when called.
197 *
198 * @since 1.20
199 *
200 * @param callable $computeFunction
201 * @param array|mixed $args
202 * @param string|null $key
203 *
204 * @return mixed
205 */
206 public function getCachedValue( $computeFunction, $args = [], $key = null ) {
207 $this->initCaching();
208
209 if ( $this->cacheEnabled && $this->hasCached ) {
210 $value = null;
211
212 if ( is_null( $key ) ) {
213 reset( $this->cachedChunks );
214 $itemKey = key( $this->cachedChunks );
215
216 if ( !is_int( $itemKey ) ) {
217 wfWarn( "Attempted to get item with non-numeric key while " .
218 "the next item in the queue has a key ($itemKey) in " . __METHOD__ );
219 } elseif ( is_null( $itemKey ) ) {
220 wfWarn( "Attempted to get an item while the queue is empty in " . __METHOD__ );
221 } else {
222 $value = array_shift( $this->cachedChunks );
223 }
224 } elseif ( array_key_exists( $key, $this->cachedChunks ) ) {
225 $value = $this->cachedChunks[$key];
226 unset( $this->cachedChunks[$key] );
227 } else {
228 wfWarn( "There is no item with key '$key' in this->cachedChunks in " . __METHOD__ );
229 }
230 } else {
231 if ( !is_array( $args ) ) {
232 $args = [ $args ];
233 }
234
235 $value = $computeFunction( ...$args );
236
237 if ( $this->cacheEnabled ) {
238 if ( is_null( $key ) ) {
239 $this->cachedChunks[] = $value;
240 } else {
241 $this->cachedChunks[$key] = $value;
242 }
243 }
244 }
245
246 return $value;
247 }
248
249 /**
250 * Saves the HTML to the cache in case it got recomputed.
251 * Should be called after the last time anything is added via addCachedHTML.
252 *
253 * @since 1.20
254 */
255 public function saveCache() {
256 if ( $this->cacheEnabled && $this->hasCached === false && !empty( $this->cachedChunks ) ) {
257 wfGetCache( CACHE_ANYTHING )->set(
258 $this->getCacheKeyString(),
259 $this->cachedChunks,
260 $this->cacheExpiry
261 );
262 }
263 }
264
265 /**
266 * Sets the time to live for the cache, in seconds or a unix timestamp
267 * indicating the point of expiry...
268 *
269 * @since 1.20
270 *
271 * @param int $cacheExpiry
272 */
273 public function setExpiry( $cacheExpiry ) {
274 $this->cacheExpiry = $cacheExpiry;
275 }
276
277 /**
278 * Returns the cache key to use to cache this page's HTML output.
279 * Is constructed from the special page name and language code.
280 *
281 * @since 1.20
282 *
283 * @return string
284 * @throws MWException
285 */
286 protected function getCacheKeyString() {
287 if ( $this->cacheKey === [] ) {
288 throw new MWException( 'No cache key set, so cannot obtain or save the CacheHelper values.' );
289 }
290
291 return wfMemcKey( ...array_values( $this->cacheKey ) );
292 }
293
294 /**
295 * Sets the cache key that should be used.
296 *
297 * @since 1.20
298 *
299 * @param array $cacheKey
300 */
301 public function setCacheKey( array $cacheKey ) {
302 $this->cacheKey = $cacheKey;
303 }
304
305 /**
306 * Rebuild the content, even if it's already cached.
307 * This effectively has the same effect as purging the cache,
308 * since it will be overridden with the new value on the next request.
309 *
310 * @since 1.20
311 */
312 public function rebuildOnDemand() {
313 $this->hasCached = false;
314 }
315
316 /**
317 * Sets a function that gets called when initialization of the cache is done.
318 *
319 * @since 1.20
320 *
321 * @param callable $handlerFunction
322 */
323 public function setOnInitializedHandler( $handlerFunction ) {
324 $this->onInitHandler = $handlerFunction;
325 }
326 }