Completed
Branch master (174b3a)
by
unknown
26:51
created

HTMLFileCache   B

Complexity

Total Complexity 36

Size/Duplication

Total Lines 226
Duplicated Lines 0 %

Coupling/Cohesion

Components 3
Dependencies 11

Importance

Changes 0
Metric Value
dl 0
loc 226
rs 8.8
c 0
b 0
f 0
wmc 36
lcom 3
cbo 11

9 Methods

Rating   Name   Duplication   Size   Complexity  
A newFromTitle() 0 3 1
A __construct() 0 13 3
A cacheablePageActions() 0 3 1
A cacheDirectory() 0 3 1
A typeSubdirectory() 0 7 2
C useFileCache() 0 46 15
B loadFromFileCache() 0 29 4
B saveToFileCache() 0 37 6
A clearFileCache() 0 14 3
1
<?php
2
/**
3
 * Page view caching in the file system.
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
 * @ingroup Cache
22
 */
23
24
use MediaWiki\MediaWikiServices;
25
26
/**
27
 * Page view caching in the file system.
28
 * The only cacheable actions are "view" and "history". Also special pages
29
 * will not be cached.
30
 *
31
 * @ingroup Cache
32
 */
33
class HTMLFileCache extends FileCacheBase {
34
	const MODE_NORMAL = 0; // normal cache mode
35
	const MODE_OUTAGE = 1; // fallback cache for DB outages
36
	const MODE_REBUILD = 2; // background cache rebuild mode
37
38
	/**
39
	 * Construct an HTMLFileCache object from a Title and an action
40
	 *
41
	 * @deprecated since 1.24, instantiate this class directly
42
	 * @param Title|string $title Title object or prefixed DB key string
43
	 * @param string $action
44
	 * @throws MWException
45
	 * @return HTMLFileCache
46
	 */
47
	public static function newFromTitle( $title, $action ) {
48
		return new self( $title, $action );
49
	}
50
51
	/**
52
	 * @param Title|string $title Title object or prefixed DB key string
53
	 * @param string $action
54
	 * @throws MWException
55
	 */
56
	public function __construct( $title, $action ) {
57
		parent::__construct();
58
59
		$allowedTypes = self::cacheablePageActions();
60
		if ( !in_array( $action, $allowedTypes ) ) {
61
			throw new MWException( 'Invalid file cache type given.' );
62
		}
63
		$this->mKey = ( $title instanceof Title )
64
			? $title->getPrefixedDBkey()
65
			: (string)$title;
66
		$this->mType = (string)$action;
67
		$this->mExt = 'html';
68
	}
69
70
	/**
71
	 * Cacheable actions
72
	 * @return array
73
	 */
74
	protected static function cacheablePageActions() {
75
		return [ 'view', 'history' ];
76
	}
77
78
	/**
79
	 * Get the base file cache directory
80
	 * @return string
81
	 */
82
	protected function cacheDirectory() {
83
		return $this->baseCacheDirectory(); // no subdir for b/c with old cache files
84
	}
85
86
	/**
87
	 * Get the cache type subdirectory (with the trailing slash) or the empty string
88
	 * Alter the type -> directory mapping to put action=view cache at the root.
89
	 *
90
	 * @return string
91
	 */
92
	protected function typeSubdirectory() {
93
		if ( $this->mType === 'view' ) {
94
			return ''; //  b/c to not skip existing cache
95
		} else {
96
			return $this->mType . '/';
97
		}
98
	}
99
100
	/**
101
	 * Check if pages can be cached for this request/user
102
	 * @param IContextSource $context
103
	 * @param integer $mode One of the HTMLFileCache::MODE_* constants (since 1.28)
104
	 * @return bool
105
	 */
106
	public static function useFileCache( IContextSource $context, $mode = self::MODE_NORMAL ) {
107
		$config = MediaWikiServices::getInstance()->getMainConfig();
108
109
		if ( !$config->get( 'UseFileCache' ) && $mode !== self::MODE_REBUILD ) {
110
			return false;
111
		} elseif ( $config->get( 'DebugToolbar' ) ) {
112
			wfDebug( "HTML file cache skipped. \$wgDebugToolbar on\n" );
113
114
			return false;
115
		}
116
117
		// Get all query values
118
		$queryVals = $context->getRequest()->getValues();
119
		foreach ( $queryVals as $query => $val ) {
120
			if ( $query === 'title' || $query === 'curid' ) {
121
				continue; // note: curid sets title
122
			// Normal page view in query form can have action=view.
123
			} elseif ( $query === 'action' && in_array( $val, self::cacheablePageActions() ) ) {
124
				continue;
125
			// Below are header setting params
126
			} elseif ( $query === 'maxage' || $query === 'smaxage' ) {
127
				continue;
128
			}
129
130
			return false;
131
		}
132
133
		$user = $context->getUser();
134
		// Check for non-standard user language; this covers uselang,
135
		// and extensions for auto-detecting user language.
136
		$ulang = $context->getLanguage();
137
138
		// Check that there are no other sources of variation
139
		if ( $user->getId() || $ulang->getCode() !== $config->get( 'LanguageCode' ) ) {
140
			return false;
141
		}
142
143
		if ( $mode === self::MODE_NORMAL ) {
144
			if ( $user->getNewtalk() ) {
145
				return false;
146
			}
147
		}
148
149
		// Allow extensions to disable caching
150
		return Hooks::run( 'HTMLFileCache::useFileCache', [ $context ] );
151
	}
152
153
	/**
154
	 * Read from cache to context output
155
	 * @param IContextSource $context
156
	 * @param integer $mode One of the HTMLFileCache::MODE_* constants
157
	 * @return void
158
	 */
159
	public function loadFromFileCache( IContextSource $context, $mode = self::MODE_NORMAL ) {
160
		$config = MediaWikiServices::getInstance()->getMainConfig();
161
162
		wfDebug( __METHOD__ . "()\n" );
163
		$filename = $this->cachePath();
164
165
		if ( $mode === self::MODE_OUTAGE ) {
166
			// Avoid DB errors for queries in sendCacheControl()
167
			$context->getTitle()->resetArticleID( 0 );
168
		}
169
170
		$context->getOutput()->sendCacheControl();
171
		header( "Content-Type: {$config->get( 'MimeType' )}; charset=UTF-8" );
172
		header( "Content-Language: {$config->get( 'LanguageCode' )}" );
173
		if ( $this->useGzip() ) {
174
			if ( wfClientAcceptsGzip() ) {
175
				header( 'Content-Encoding: gzip' );
176
				readfile( $filename );
177
			} else {
178
				/* Send uncompressed */
179
				wfDebug( __METHOD__ . " uncompressing cache file and sending it\n" );
180
				readgzfile( $filename );
181
			}
182
		} else {
183
			readfile( $filename );
184
		}
185
186
		$context->getOutput()->disable(); // tell $wgOut that output is taken care of
187
	}
188
189
	/**
190
	 * Save this cache object with the given text.
191
	 * Use this as an ob_start() handler.
192
	 *
193
	 * Normally this is only registed as a handler if $wgUseFileCache is on.
194
	 * If can be explicitly called by rebuildFileCache.php when it takes over
195
	 * handling file caching itself, disabling any automatic handling the the
196
	 * process.
197
	 *
198
	 * @param string $text
199
	 * @return string|bool The annotated $text or false on error
200
	 */
201
	public function saveToFileCache( $text ) {
202
		if ( strlen( $text ) < 512 ) {
203
			// Disabled or empty/broken output (OOM and PHP errors)
204
			return $text;
205
		}
206
207
		wfDebug( __METHOD__ . "()\n", 'private' );
208
209
		$now = wfTimestampNow();
210
		if ( $this->useGzip() ) {
211
			$text = str_replace(
212
				'</html>', '<!-- Cached/compressed ' . $now . " -->\n</html>", $text );
213
		} else {
214
			$text = str_replace(
215
				'</html>', '<!-- Cached ' . $now . " -->\n</html>", $text );
216
		}
217
218
		// Store text to FS...
219
		$compressed = $this->saveText( $text );
220
		if ( $compressed === false ) {
221
			return $text; // error
222
		}
223
224
		// gzip output to buffer as needed and set headers...
225
		if ( $this->useGzip() ) {
226
			// @todo Ugly wfClientAcceptsGzip() function - use context!
227
			if ( wfClientAcceptsGzip() ) {
228
				header( 'Content-Encoding: gzip' );
229
230
				return $compressed;
231
			} else {
232
				return $text;
233
			}
234
		} else {
235
			return $text;
236
		}
237
	}
238
239
	/**
240
	 * Clear the file caches for a page for all actions
241
	 * @param Title $title
242
	 * @return bool Whether $wgUseFileCache is enabled
243
	 */
244
	public static function clearFileCache( Title $title ) {
245
		$config = MediaWikiServices::getInstance()->getMainConfig();
246
247
		if ( !$config->get( 'UseFileCache' ) ) {
248
			return false;
249
		}
250
251
		foreach ( self::cacheablePageActions() as $type ) {
252
			$fc = new self( $title, $type );
253
			$fc->clearCache();
254
		}
255
256
		return true;
257
	}
258
}
259