AjaxResponse::setVary()   A
last analyzed

Complexity

Conditions 1
Paths 1

Size

Total Lines 3
Code Lines 2

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 2
nc 1
nop 1
dl 0
loc 3
rs 10
c 0
b 0
f 0
1
<?php
2
/**
3
 * Response handler for Ajax requests.
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 Ajax
22
 */
23
24
/**
25
 * Handle responses for Ajax requests (send headers, print
26
 * content, that sort of thing)
27
 *
28
 * @ingroup Ajax
29
 */
30
class AjaxResponse {
31
	/**
32
	 * Number of seconds to get the response cached by a proxy
33
	 * @var int $mCacheDuration
34
	 */
35
	private $mCacheDuration;
36
37
	/**
38
	 * HTTP header Content-Type
39
	 * @var string $mContentType
40
	 */
41
	private $mContentType;
42
43
	/**
44
	 * Disables output. Can be set by calling $AjaxResponse->disable()
45
	 * @var bool $mDisabled
46
	 */
47
	private $mDisabled;
48
49
	/**
50
	 * Date for the HTTP header Last-modified
51
	 * @var string|bool $mLastModified
52
	 */
53
	private $mLastModified;
54
55
	/**
56
	 * HTTP response code
57
	 * @var string $mResponseCode
58
	 */
59
	private $mResponseCode;
60
61
	/**
62
	 * HTTP Vary header
63
	 * @var string $mVary
64
	 */
65
	private $mVary;
66
67
	/**
68
	 * Content of our HTTP response
69
	 * @var string $mText
70
	 */
71
	private $mText;
72
73
	/**
74
	 * @var Config
75
	 */
76
	private $mConfig;
77
78
	/**
79
	 * @param string|null $text
80
	 * @param Config|null $config
81
	 */
82
	function __construct( $text = null, Config $config = null ) {
83
		$this->mCacheDuration = null;
84
		$this->mVary = null;
85
		$this->mConfig = $config ?: ConfigFactory::getDefaultInstance()->makeConfig( 'main' );
86
87
		$this->mDisabled = false;
88
		$this->mText = '';
89
		$this->mResponseCode = 200;
0 ignored issues
show
Documentation Bug introduced by
The property $mResponseCode was declared of type string, but 200 is of type integer. Maybe add a type cast?

This check looks for assignments to scalar types that may be of the wrong type.

To ensure the code behaves as expected, it may be a good idea to add an explicit type cast.

$answer = 42;

$correct = false;

$correct = (bool) $answer;
Loading history...
90
		$this->mLastModified = false;
91
		$this->mContentType = 'application/x-wiki';
92
93
		if ( $text ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $text of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
94
			$this->addText( $text );
95
		}
96
	}
97
98
	/**
99
	 * Set the number of seconds to get the response cached by a proxy
100
	 * @param int $duration
101
	 */
102
	function setCacheDuration( $duration ) {
103
		$this->mCacheDuration = $duration;
104
	}
105
106
	/**
107
	 * Set the HTTP Vary header
108
	 * @param string $vary
109
	 */
110
	function setVary( $vary ) {
111
		$this->mVary = $vary;
112
	}
113
114
	/**
115
	 * Set the HTTP response code
116
	 * @param string $code
117
	 */
118
	function setResponseCode( $code ) {
119
		$this->mResponseCode = $code;
120
	}
121
122
	/**
123
	 * Set the HTTP header Content-Type
124
	 * @param string $type
125
	 */
126
	function setContentType( $type ) {
127
		$this->mContentType = $type;
128
	}
129
130
	/**
131
	 * Disable output.
132
	 */
133
	function disable() {
134
		$this->mDisabled = true;
135
	}
136
137
	/**
138
	 * Add content to the response
139
	 * @param string $text
140
	 */
141
	function addText( $text ) {
142
		if ( !$this->mDisabled && $text ) {
143
			$this->mText .= $text;
144
		}
145
	}
146
147
	/**
148
	 * Output text
149
	 */
150
	function printText() {
151
		if ( !$this->mDisabled ) {
152
			print $this->mText;
153
		}
154
	}
155
156
	/**
157
	 * Construct the header and output it
158
	 */
159
	function sendHeaders() {
160
		if ( $this->mResponseCode ) {
161
			// For back-compat, it is supported that mResponseCode be a string like " 200 OK"
162
			// (with leading space and the status message after). Cast response code to an integer
163
			// to take advantage of PHP's conversion rules which will turn "  200 OK" into 200.
164
			// https://secure.php.net/manual/en/language.types.string.php#language.types.string.conversion
165
			$n = intval( trim( $this->mResponseCode ) );
166
			HttpStatus::header( $n );
167
		}
168
169
		header( "Content-Type: " . $this->mContentType );
170
171
		if ( $this->mLastModified ) {
172
			header( "Last-Modified: " . $this->mLastModified );
173
		} else {
174
			header( "Last-Modified: " . gmdate( "D, d M Y H:i:s" ) . " GMT" );
175
		}
176
177
		if ( $this->mCacheDuration ) {
178
			# If CDN caches are configured, tell them to cache the response,
179
			# and tell the client to always check with the CDN. Otherwise,
180
			# tell the client to use a cached copy, without a way to purge it.
181
182
			if ( $this->mConfig->get( 'UseSquid' ) ) {
183
				# Expect explicit purge of the proxy cache, but require end user agents
184
				# to revalidate against the proxy on each visit.
185
				# Surrogate-Control controls our CDN, Cache-Control downstream caches
186
187
				if ( $this->mConfig->get( 'UseESI' ) ) {
188
					header( 'Surrogate-Control: max-age=' . $this->mCacheDuration . ', content="ESI/1.0"' );
189
					header( 'Cache-Control: s-maxage=0, must-revalidate, max-age=0' );
190
				} else {
191
					header( 'Cache-Control: s-maxage=' . $this->mCacheDuration . ', must-revalidate, max-age=0' );
192
				}
193
194
			} else {
195
				# Let the client do the caching. Cache is not purged.
196
				header( "Expires: " . gmdate( "D, d M Y H:i:s", time() + $this->mCacheDuration ) . " GMT" );
197
				header( "Cache-Control: s-maxage={$this->mCacheDuration}," .
198
					"public,max-age={$this->mCacheDuration}" );
199
			}
200
201
		} else {
202
			# always expired, always modified
203
			header( "Expires: Mon, 26 Jul 1997 05:00:00 GMT" );    // Date in the past
204
			header( "Cache-Control: no-cache, must-revalidate" );  // HTTP/1.1
205
			header( "Pragma: no-cache" );                          // HTTP/1.0
206
		}
207
208
		if ( $this->mVary ) {
209
			header( "Vary: " . $this->mVary );
210
		}
211
	}
212
213
	/**
214
	 * checkLastModified tells the client to use the client-cached response if
215
	 * possible. If successful, the AjaxResponse is disabled so that
216
	 * any future call to AjaxResponse::printText() have no effect.
217
	 *
218
	 * @param string $timestamp
219
	 * @return bool Returns true if the response code was set to 304 Not Modified.
220
	 */
221
	function checkLastModified( $timestamp ) {
0 ignored issues
show
Coding Style introduced by
checkLastModified uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
222
		global $wgCachePages, $wgCacheEpoch, $wgUser;
223
		$fname = 'AjaxResponse::checkLastModified';
224
225
		if ( !$timestamp || $timestamp == '19700101000000' ) {
226
			wfDebug( "$fname: CACHE DISABLED, NO TIMESTAMP", 'private' );
227
			return false;
228
		}
229
230
		if ( !$wgCachePages ) {
231
			wfDebug( "$fname: CACHE DISABLED", 'private' );
232
			return false;
233
		}
234
235
		$timestamp = wfTimestamp( TS_MW, $timestamp );
236
		$lastmod = wfTimestamp( TS_RFC2822, max( $timestamp, $wgUser->getTouched(), $wgCacheEpoch ) );
237
238
		if ( !empty( $_SERVER['HTTP_IF_MODIFIED_SINCE'] ) ) {
239
			# IE sends sizes after the date like this:
240
			# Wed, 20 Aug 2003 06:51:19 GMT; length=5202
241
			# this breaks strtotime().
242
			$modsince = preg_replace( '/;.*$/', '', $_SERVER["HTTP_IF_MODIFIED_SINCE"] );
243
			$modsinceTime = strtotime( $modsince );
244
			$ismodsince = wfTimestamp( TS_MW, $modsinceTime ? $modsinceTime : 1 );
245
			wfDebug( "$fname: -- client send If-Modified-Since: $modsince", 'private' );
246
			wfDebug( "$fname: --  we might send Last-Modified : $lastmod", 'private' );
247
248
			if ( ( $ismodsince >= $timestamp )
249
				&& $wgUser->validateCache( $ismodsince ) &&
250
				$ismodsince >= $wgCacheEpoch
251
			) {
252
				ini_set( 'zlib.output_compression', 0 );
253
				$this->setResponseCode( 304 );
254
				$this->disable();
255
				$this->mLastModified = $lastmod;
256
257
				wfDebug( "$fname: CACHED client: $ismodsince ; user: {$wgUser->getTouched()} ; " .
258
					"page: $timestamp ; site $wgCacheEpoch", 'private' );
259
260
				return true;
261
			} else {
262
				wfDebug( "$fname: READY  client: $ismodsince ; user: {$wgUser->getTouched()} ; " .
263
					"page: $timestamp ; site $wgCacheEpoch", 'private' );
264
				$this->mLastModified = $lastmod;
265
			}
266
		} else {
267
			wfDebug( "$fname: client did not send If-Modified-Since header", 'private' );
268
			$this->mLastModified = $lastmod;
269
		}
270
		return false;
271
	}
272
273
	/**
274
	 * @param string $mckey
275
	 * @param int $touched
276
	 * @return bool
277
	 */
278
	function loadFromMemcached( $mckey, $touched ) {
279
		if ( !$touched ) {
280
			return false;
281
		}
282
283
		$mcvalue = ObjectCache::getMainWANInstance()->get( $mckey );
284
		if ( $mcvalue ) {
285
			# Check to see if the value has been invalidated
286
			if ( $touched <= $mcvalue['timestamp'] ) {
287
				wfDebug( "Got $mckey from cache" );
288
				$this->mText = $mcvalue['value'];
289
290
				return true;
291
			} else {
292
				wfDebug( "$mckey has expired" );
293
			}
294
		}
295
296
		return false;
297
	}
298
299
	/**
300
	 * @param string $mckey
301
	 * @param int $expiry
302
	 * @return bool
303
	 */
304
	function storeInMemcached( $mckey, $expiry = 86400 ) {
305
		ObjectCache::getMainWANInstance()->set( $mckey,
306
			[
307
				'timestamp' => wfTimestampNow(),
308
				'value' => $this->mText
309
			], $expiry
310
		);
311
312
		return true;
313
	}
314
}
315