Issues (4967)

Security Analysis    not enabled

This project does not seem to handle request data directly as such no vulnerable execution paths were found.

  Cross-Site Scripting
Cross-Site Scripting enables an attacker to inject code into the response of a web-request that is viewed by other users. It can for example be used to bypass access controls, or even to take over other users' accounts.
  File Exposure
File Exposure allows an attacker to gain access to local files that he should not be able to access. These files can for example include database credentials, or other configuration files.
  File Manipulation
File Manipulation enables an attacker to write custom data to files. This potentially leads to injection of arbitrary code on the server.
  Object Injection
Object Injection enables an attacker to inject an object into PHP code, and can lead to arbitrary code execution, file exposure, or file manipulation attacks.
  Code Injection
Code Injection enables an attacker to execute arbitrary code on the server.
  Response Splitting
Response Splitting can be used to send arbitrary responses.
  File Inclusion
File Inclusion enables an attacker to inject custom files into PHP's file loading mechanism, either explicitly passed to include, or for example via PHP's auto-loading mechanism.
  Command Injection
Command Injection enables an attacker to inject a shell command that is execute with the privileges of the web-server. This can be used to expose sensitive data, or gain access of your server.
  SQL Injection
SQL Injection enables an attacker to execute arbitrary SQL code on your database server gaining access to user data, or manipulating user data.
  XPath Injection
XPath Injection enables an attacker to modify the parts of XML document that are read. If that XML document is for example used for authentication, this can lead to further vulnerabilities similar to SQL Injection.
  LDAP Injection
LDAP Injection enables an attacker to inject LDAP statements potentially granting permission to run unauthorized queries, or modify content inside the LDAP tree.
  Header Injection
  Other Vulnerability
This category comprises other attack vectors such as manipulating the PHP runtime, loading custom extensions, freezing the runtime, or similar.
  Regex Injection
Regex Injection enables an attacker to execute arbitrary code in your PHP process.
  XML Injection
XML Injection enables an attacker to read files on your local filesystem including configuration files, or can be abused to freeze your web-server process.
  Variable Injection
Variable Injection enables an attacker to overwrite program variables with custom data, and can lead to further vulnerabilities.
Unfortunately, the security analysis is currently not available for your project. If you are a non-commercial open-source project, please contact support to gain access.

src/wp-includes/class-wp.php (11 issues)

Upgrade to new PHP Analysis Engine

These results are based on our legacy PHP analysis, consider migrating to our new PHP analysis engine instead. Learn more

1
<?php
2
/**
3
 * WordPress environment setup class.
4
 *
5
 * @package WordPress
6
 * @since 2.0.0
7
 */
8
class WP {
9
	/**
10
	 * Public query variables.
11
	 *
12
	 * Long list of public query variables.
13
	 *
14
	 * @since 2.0.0
15
	 * @access public
16
	 * @var array
17
	 */
18
	public $public_query_vars = array('m', 'p', 'posts', 'w', 'cat', 'withcomments', 'withoutcomments', 's', 'search', 'exact', 'sentence', 'calendar', 'page', 'paged', 'more', 'tb', 'pb', 'author', 'order', 'orderby', 'year', 'monthnum', 'day', 'hour', 'minute', 'second', 'name', 'category_name', 'tag', 'feed', 'author_name', 'static', 'pagename', 'page_id', 'error', 'attachment', 'attachment_id', 'subpost', 'subpost_id', 'preview', 'robots', 'taxonomy', 'term', 'cpage', 'post_type', 'embed' );
19
20
	/**
21
	 * Private query variables.
22
	 *
23
	 * Long list of private query variables.
24
	 *
25
	 * @since 2.0.0
26
	 * @access public
27
	 * @var array
28
	 */
29
	public $private_query_vars = array( 'offset', 'posts_per_page', 'posts_per_archive_page', 'showposts', 'nopaging', 'post_type', 'post_status', 'category__in', 'category__not_in', 'category__and', 'tag__in', 'tag__not_in', 'tag__and', 'tag_slug__in', 'tag_slug__and', 'tag_id', 'post_mime_type', 'perm', 'comments_per_page', 'post__in', 'post__not_in', 'post_parent', 'post_parent__in', 'post_parent__not_in', 'title', 'fields' );
30
31
	/**
32
	 * Extra query variables set by the user.
33
	 *
34
	 * @since 2.1.0
35
	 * @access public
36
	 * @var array
37
	 */
38
	public $extra_query_vars = array();
39
40
	/**
41
	 * Query variables for setting up the WordPress Query Loop.
42
	 *
43
	 * @since 2.0.0
44
	 * @access public
45
	 * @var array
46
	 */
47
	public $query_vars;
48
49
	/**
50
	 * String parsed to set the query variables.
51
	 *
52
	 * @since 2.0.0
53
	 * @access public
54
	 * @var string
55
	 */
56
	public $query_string;
57
58
	/**
59
	 * The request path, e.g. 2015/05/06.
60
	 *
61
	 * @since 2.0.0
62
	 * @access public
63
	 * @var string
64
	 */
65
	public $request;
66
67
	/**
68
	 * Rewrite rule the request matched.
69
	 *
70
	 * @since 2.0.0
71
	 * @access public
72
	 * @var string
73
	 */
74
	public $matched_rule;
75
76
	/**
77
	 * Rewrite query the request matched.
78
	 *
79
	 * @since 2.0.0
80
	 * @access public
81
	 * @var string
82
	 */
83
	public $matched_query;
84
85
	/**
86
	 * Whether already did the permalink.
87
	 *
88
	 * @since 2.0.0
89
	 * @access public
90
	 * @var bool
91
	 */
92
	public $did_permalink = false;
93
94
	/**
95
	 * Add name to list of public query variables.
96
	 *
97
	 * @since 2.1.0
98
	 * @access public
99
	 *
100
	 * @param string $qv Query variable name.
101
	 */
102
	public function add_query_var($qv) {
103
		if ( !in_array($qv, $this->public_query_vars) )
104
			$this->public_query_vars[] = $qv;
105
	}
106
107
	/**
108
	 * Removes a query variable from a list of public query variables.
109
	 *
110
	 * @since 4.5.0
111
	 * @access public
112
	 *
113
	 * @param string $name Query variable name.
114
	 */
115
	public function remove_query_var( $name ) {
116
		$this->public_query_vars = array_diff( $this->public_query_vars, array( $name ) );
117
	}
118
119
	/**
120
	 * Set the value of a query variable.
121
	 *
122
	 * @since 2.3.0
123
	 * @access public
124
	 *
125
	 * @param string $key Query variable name.
126
	 * @param mixed $value Query variable value.
127
	 */
128
	public function set_query_var($key, $value) {
129
		$this->query_vars[$key] = $value;
130
	}
131
132
	/**
133
	 * Parse request to find correct WordPress query.
134
	 *
135
	 * Sets up the query variables based on the request. There are also many
136
	 * filters and actions that can be used to further manipulate the result.
137
	 *
138
	 * @since 2.0.0
139
	 * @access public
140
	 *
141
	 * @global WP_Rewrite $wp_rewrite
142
	 *
143
	 * @param array|string $extra_query_vars Set the extra query variables.
144
	 */
145
	public function parse_request($extra_query_vars = '') {
146
		global $wp_rewrite;
147
148
		/**
149
		 * Filters whether to parse the request.
150
		 *
151
		 * @since 3.5.0
152
		 *
153
		 * @param bool         $bool             Whether or not to parse the request. Default true.
154
		 * @param WP           $this             Current WordPress environment instance.
155
		 * @param array|string $extra_query_vars Extra passed query variables.
156
		 */
157
		if ( ! apply_filters( 'do_parse_request', true, $this, $extra_query_vars ) )
158
			return;
159
160
		$this->query_vars = array();
161
		$post_type_query_vars = array();
162
163
		if ( is_array( $extra_query_vars ) ) {
164
			$this->extra_query_vars = & $extra_query_vars;
165
		} elseif ( ! empty( $extra_query_vars ) ) {
166
			parse_str( $extra_query_vars, $this->extra_query_vars );
167
		}
168
		// Process PATH_INFO, REQUEST_URI, and 404 for permalinks.
169
170
		// Fetch the rewrite rules.
171
		$rewrite = $wp_rewrite->wp_rewrite_rules();
172
173
		if ( ! empty($rewrite) ) {
174
			// If we match a rewrite rule, this will be cleared.
175
			$error = '404';
176
			$this->did_permalink = true;
177
178
			$pathinfo = isset( $_SERVER['PATH_INFO'] ) ? $_SERVER['PATH_INFO'] : '';
179
			list( $pathinfo ) = explode( '?', $pathinfo );
180
			$pathinfo = str_replace( "%", "%25", $pathinfo );
181
182
			list( $req_uri ) = explode( '?', $_SERVER['REQUEST_URI'] );
183
			$self = $_SERVER['PHP_SELF'];
184
			$home_path = trim( parse_url( home_url(), PHP_URL_PATH ), '/' );
185
			$home_path_regex = sprintf( '|^%s|i', preg_quote( $home_path, '|' ) );
186
187
			// Trim path info from the end and the leading home path from the
188
			// front. For path info requests, this leaves us with the requesting
189
			// filename, if any. For 404 requests, this leaves us with the
190
			// requested permalink.
191
			$req_uri = str_replace($pathinfo, '', $req_uri);
192
			$req_uri = trim($req_uri, '/');
193
			$req_uri = preg_replace( $home_path_regex, '', $req_uri );
194
			$req_uri = trim($req_uri, '/');
195
			$pathinfo = trim($pathinfo, '/');
196
			$pathinfo = preg_replace( $home_path_regex, '', $pathinfo );
197
			$pathinfo = trim($pathinfo, '/');
198
			$self = trim($self, '/');
199
			$self = preg_replace( $home_path_regex, '', $self );
200
			$self = trim($self, '/');
201
202
			// The requested permalink is in $pathinfo for path info requests and
203
			//  $req_uri for other requests.
204
			if ( ! empty($pathinfo) && !preg_match('|^.*' . $wp_rewrite->index . '$|', $pathinfo) ) {
205
				$requested_path = $pathinfo;
206
			} else {
207
				// If the request uri is the index, blank it out so that we don't try to match it against a rule.
208
				if ( $req_uri == $wp_rewrite->index )
209
					$req_uri = '';
210
				$requested_path = $req_uri;
211
			}
212
			$requested_file = $req_uri;
213
214
			$this->request = $requested_path;
215
216
			// Look for matches.
217
			$request_match = $requested_path;
218
			if ( empty( $request_match ) ) {
219
				// An empty request could only match against ^$ regex
220
				if ( isset( $rewrite['$'] ) ) {
221
					$this->matched_rule = '$';
222
					$query = $rewrite['$'];
223
					$matches = array('');
224
				}
225
			} else {
226
				foreach ( (array) $rewrite as $match => $query ) {
227
					// If the requested file is the anchor of the match, prepend it to the path info.
228 View Code Duplication
					if ( ! empty($requested_file) && strpos($match, $requested_file) === 0 && $requested_file != $requested_path )
229
						$request_match = $requested_file . '/' . $requested_path;
230
231
					if ( preg_match("#^$match#", $request_match, $matches) ||
232
						preg_match("#^$match#", urldecode($request_match), $matches) ) {
233
234 View Code Duplication
						if ( $wp_rewrite->use_verbose_page_rules && preg_match( '/pagename=\$matches\[([0-9]+)\]/', $query, $varmatch ) ) {
235
							// This is a verbose page match, let's check to be sure about it.
236
							$page = get_page_by_path( $matches[ $varmatch[1] ] );
237
							if ( ! $page ) {
238
						 		continue;
239
							}
240
241
							$post_status_obj = get_post_status_object( $page->post_status );
242
							if ( ! $post_status_obj->public && ! $post_status_obj->protected
243
								&& ! $post_status_obj->private && $post_status_obj->exclude_from_search ) {
244
								continue;
245
							}
246
						}
247
248
						// Got a match.
249
						$this->matched_rule = $match;
0 ignored issues
show
Documentation Bug introduced by
It seems like $match can also be of type integer. However, the property $matched_rule is declared as type string. Maybe add an additional type check?

Our type inference engine has found a suspicous assignment of a value to a property. This check raises an issue when a value that can be of a mixed type is assigned to a property that is type hinted more strictly.

For example, imagine you have a variable $accountId that can either hold an Id object or false (if there is no account id yet). Your code now assigns that value to the id property of an instance of the Account class. This class holds a proper account, so the id value must no longer be false.

Either this assignment is in error or a type check should be added for that assignment.

class Id
{
    public $id;

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

}

class Account
{
    /** @var  Id $id */
    public $id;
}

$account_id = false;

if (starsAreRight()) {
    $account_id = new Id(42);
}

$account = new Account();
if ($account instanceof Id)
{
    $account->id = $account_id;
}
Loading history...
250
						break;
251
					}
252
				}
253
			}
254
255
			if ( isset( $this->matched_rule ) ) {
256
				// Trim the query of everything up to the '?'.
257
				$query = preg_replace("!^.+\?!", '', $query);
0 ignored issues
show
The variable $query does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
258
259
				// Substitute the substring matches into the query.
260
				$query = addslashes(WP_MatchesMapRegex::apply($query, $matches));
0 ignored issues
show
The variable $matches does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
261
262
				$this->matched_query = $query;
263
264
				// Parse the query.
265
				parse_str($query, $perma_query_vars);
266
267
				// If we're processing a 404 request, clear the error var since we found something.
268
				if ( '404' == $error )
269
					unset( $error, $_GET['error'] );
270
			}
271
272
			// If req_uri is empty or if it is a request for ourself, unset error.
273
			if ( empty($requested_path) || $requested_file == $self || strpos($_SERVER['PHP_SELF'], 'wp-admin/') !== false ) {
274
				unset( $error, $_GET['error'] );
275
276
				if ( isset($perma_query_vars) && strpos($_SERVER['PHP_SELF'], 'wp-admin/') !== false )
277
					unset( $perma_query_vars );
278
279
				$this->did_permalink = false;
280
			}
281
		}
282
283
		/**
284
		 * Filters the query variables whitelist before processing.
285
		 *
286
		 * Allows (publicly allowed) query vars to be added, removed, or changed prior
287
		 * to executing the query. Needed to allow custom rewrite rules using your own arguments
288
		 * to work, or any other custom query variables you want to be publicly available.
289
		 *
290
		 * @since 1.5.0
291
		 *
292
		 * @param array $public_query_vars The array of whitelisted query variables.
293
		 */
294
		$this->public_query_vars = apply_filters( 'query_vars', $this->public_query_vars );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters('query_var...his->public_query_vars) of type * is incompatible with the declared type array of property $public_query_vars.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
295
296 View Code Duplication
		foreach ( get_post_types( array(), 'objects' ) as $post_type => $t ) {
297
			if ( is_post_type_viewable( $t ) && $t->query_var ) {
298
				$post_type_query_vars[$t->query_var] = $post_type;
299
			}
300
		}
301
302
		foreach ( $this->public_query_vars as $wpvar ) {
303
			if ( isset( $this->extra_query_vars[$wpvar] ) )
304
				$this->query_vars[$wpvar] = $this->extra_query_vars[$wpvar];
305
			elseif ( isset( $_POST[$wpvar] ) )
306
				$this->query_vars[$wpvar] = $_POST[$wpvar];
307
			elseif ( isset( $_GET[$wpvar] ) )
308
				$this->query_vars[$wpvar] = $_GET[$wpvar];
309
			elseif ( isset( $perma_query_vars[$wpvar] ) )
310
				$this->query_vars[$wpvar] = $perma_query_vars[$wpvar];
311
312
			if ( !empty( $this->query_vars[$wpvar] ) ) {
313
				if ( ! is_array( $this->query_vars[$wpvar] ) ) {
314
					$this->query_vars[$wpvar] = (string) $this->query_vars[$wpvar];
315
				} else {
316
					foreach ( $this->query_vars[$wpvar] as $vkey => $v ) {
317
						if ( !is_object( $v ) ) {
318
							$this->query_vars[$wpvar][$vkey] = (string) $v;
319
						}
320
					}
321
				}
322
323
				if ( isset($post_type_query_vars[$wpvar] ) ) {
324
					$this->query_vars['post_type'] = $post_type_query_vars[$wpvar];
325
					$this->query_vars['name'] = $this->query_vars[$wpvar];
326
				}
327
			}
328
		}
329
330
		// Convert urldecoded spaces back into +
331
		foreach ( get_taxonomies( array() , 'objects' ) as $taxonomy => $t )
332
			if ( $t->query_var && isset( $this->query_vars[$t->query_var] ) )
333
				$this->query_vars[$t->query_var] = str_replace( ' ', '+', $this->query_vars[$t->query_var] );
334
335
		// Don't allow non-publicly queryable taxonomies to be queried from the front end.
336
		if ( ! is_admin() ) {
337
			foreach ( get_taxonomies( array( 'publicly_queryable' => false ), 'objects' ) as $taxonomy => $t ) {
338
				/*
339
				 * Disallow when set to the 'taxonomy' query var.
340
				 * Non-publicly queryable taxonomies cannot register custom query vars. See register_taxonomy().
341
				 */
342
				if ( isset( $this->query_vars['taxonomy'] ) && $taxonomy === $this->query_vars['taxonomy'] ) {
343
					unset( $this->query_vars['taxonomy'], $this->query_vars['term'] );
344
				}
345
			}
346
		}
347
348
		// Limit publicly queried post_types to those that are publicly_queryable
349
		if ( isset( $this->query_vars['post_type']) ) {
350
			$queryable_post_types = get_post_types( array('publicly_queryable' => true) );
351
			if ( ! is_array( $this->query_vars['post_type'] ) ) {
352
				if ( ! in_array( $this->query_vars['post_type'], $queryable_post_types ) )
353
					unset( $this->query_vars['post_type'] );
354
			} else {
355
				$this->query_vars['post_type'] = array_intersect( $this->query_vars['post_type'], $queryable_post_types );
356
			}
357
		}
358
359
		// Resolve conflicts between posts with numeric slugs and date archive queries.
360
		$this->query_vars = wp_resolve_numeric_slug_conflicts( $this->query_vars );
361
362
		foreach ( (array) $this->private_query_vars as $var) {
363
			if ( isset($this->extra_query_vars[$var]) )
364
				$this->query_vars[$var] = $this->extra_query_vars[$var];
365
		}
366
367
		if ( isset($error) )
368
			$this->query_vars['error'] = $error;
369
370
		/**
371
		 * Filters the array of parsed query variables.
372
		 *
373
		 * @since 2.1.0
374
		 *
375
		 * @param array $query_vars The array of requested query variables.
376
		 */
377
		$this->query_vars = apply_filters( 'request', $this->query_vars );
0 ignored issues
show
Documentation Bug introduced by
It seems like apply_filters('request', $this->query_vars) of type * is incompatible with the declared type array of property $query_vars.

Our type inference engine has found an assignment to a property that is incompatible with the declared type of that property.

Either this assignment is in error or the assigned type should be added to the documentation/type hint for that property..

Loading history...
378
379
		/**
380
		 * Fires once all query variables for the current request have been parsed.
381
		 *
382
		 * @since 2.1.0
383
		 *
384
		 * @param WP &$this Current WordPress environment instance (passed by reference).
385
		 */
386
		do_action_ref_array( 'parse_request', array( &$this ) );
387
	}
388
389
	/**
390
	 * Sends additional HTTP headers for caching, content type, etc.
391
	 *
392
	 * Sets the Content-Type header. Sets the 'error' status (if passed) and optionally exits.
393
	 * If showing a feed, it will also send Last-Modified, ETag, and 304 status if needed.
394
	 *
395
	 * @since 2.0.0
396
	 * @since 4.4.0 `X-Pingback` header is added conditionally after posts have been queried in handle_404().
397
	 * @access public
398
	 */
399
	public function send_headers() {
400
		$headers = array();
401
		$status = null;
402
		$exit_required = false;
403
404
		if ( is_user_logged_in() )
405
			$headers = array_merge($headers, wp_get_nocache_headers());
406
		if ( ! empty( $this->query_vars['error'] ) ) {
407
			$status = (int) $this->query_vars['error'];
408
			if ( 404 === $status ) {
409
				if ( ! is_user_logged_in() )
410
					$headers = array_merge($headers, wp_get_nocache_headers());
411
				$headers['Content-Type'] = get_option('html_type') . '; charset=' . get_option('blog_charset');
412
			} elseif ( in_array( $status, array( 403, 500, 502, 503 ) ) ) {
413
				$exit_required = true;
414
			}
415
		} elseif ( empty( $this->query_vars['feed'] ) ) {
416
			$headers['Content-Type'] = get_option('html_type') . '; charset=' . get_option('blog_charset');
417
		} else {
418
			// Set the correct content type for feeds
419
			$type = $this->query_vars['feed'];
420
			if ( 'feed' == $this->query_vars['feed'] ) {
421
				$type = get_default_feed();
422
			}
423
			$headers['Content-Type'] = feed_content_type( $type ) . '; charset=' . get_option( 'blog_charset' );
424
425
			// We're showing a feed, so WP is indeed the only thing that last changed.
426
			if ( ! empty( $this->query_vars['withcomments'] )
427
			     || false !== strpos( $this->query_vars['feed'], 'comments-' )
428
			     || ( empty( $this->query_vars['withoutcomments'] )
429
			          && ( ! empty( $this->query_vars['p'] )
430
			               || ! empty( $this->query_vars['name'] )
431
			               || ! empty( $this->query_vars['page_id'] )
432
			               || ! empty( $this->query_vars['pagename'] )
433
			               || ! empty( $this->query_vars['attachment'] )
434
			               || ! empty( $this->query_vars['attachment_id'] )
435
			          )
436
			     )
437
			) {
438
				$wp_last_modified = mysql2date( 'D, d M Y H:i:s', get_lastcommentmodified( 'GMT' ), false );
0 ignored issues
show
It seems like get_lastcommentmodified('GMT') targeting get_lastcommentmodified() can also be of type false; however, mysql2date() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
439
			} else {
440
				$wp_last_modified = mysql2date( 'D, d M Y H:i:s', get_lastpostmodified( 'GMT' ), false );
441
			}
442
443
			if ( ! $wp_last_modified ) {
444
				$wp_last_modified = date( 'D, d M Y H:i:s' );
445
			}
446
447
			$wp_last_modified .= ' GMT';
448
449
			$wp_etag = '"' . md5($wp_last_modified) . '"';
450
			$headers['Last-Modified'] = $wp_last_modified;
451
			$headers['ETag'] = $wp_etag;
452
453
			// Support for Conditional GET
454
			if (isset($_SERVER['HTTP_IF_NONE_MATCH']))
455
				$client_etag = wp_unslash( $_SERVER['HTTP_IF_NONE_MATCH'] );
456
			else $client_etag = false;
457
458
			$client_last_modified = empty($_SERVER['HTTP_IF_MODIFIED_SINCE']) ? '' : trim($_SERVER['HTTP_IF_MODIFIED_SINCE']);
459
			// If string is empty, return 0. If not, attempt to parse into a timestamp
460
			$client_modified_timestamp = $client_last_modified ? strtotime($client_last_modified) : 0;
461
462
			// Make a timestamp for our most recent modification...
463
			$wp_modified_timestamp = strtotime($wp_last_modified);
464
465
			if ( ($client_last_modified && $client_etag) ?
466
					 (($client_modified_timestamp >= $wp_modified_timestamp) && ($client_etag == $wp_etag)) :
467
					 (($client_modified_timestamp >= $wp_modified_timestamp) || ($client_etag == $wp_etag)) ) {
468
				$status = 304;
469
				$exit_required = true;
470
			}
471
		}
472
473
		/**
474
		 * Filters the HTTP headers before they're sent to the browser.
475
		 *
476
		 * @since 2.8.0
477
		 *
478
		 * @param array $headers The list of headers to be sent.
479
		 * @param WP    $this    Current WordPress environment instance.
480
		 */
481
		$headers = apply_filters( 'wp_headers', $headers, $this );
482
483
		if ( ! empty( $status ) )
484
			status_header( $status );
485
486
		// If Last-Modified is set to false, it should not be sent (no-cache situation).
487
		if ( isset( $headers['Last-Modified'] ) && false === $headers['Last-Modified'] ) {
488
			unset( $headers['Last-Modified'] );
489
490
			// In PHP 5.3+, make sure we are not sending a Last-Modified header.
491 View Code Duplication
			if ( function_exists( 'header_remove' ) ) {
492
				@header_remove( 'Last-Modified' );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
493
			} else {
494
				// In PHP 5.2, send an empty Last-Modified header, but only as a
495
				// last resort to override a header already sent. #WP23021
496
				foreach ( headers_list() as $header ) {
497
					if ( 0 === stripos( $header, 'Last-Modified' ) ) {
498
						$headers['Last-Modified'] = '';
499
						break;
500
					}
501
				}
502
			}
503
		}
504
505
		foreach ( (array) $headers as $name => $field_value )
506
			@header("{$name}: {$field_value}");
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
507
508
		if ( $exit_required )
509
			exit();
0 ignored issues
show
Coding Style Compatibility introduced by
The method send_headers() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
510
511
		/**
512
		 * Fires once the requested HTTP headers for caching, content type, etc. have been sent.
513
		 *
514
		 * @since 2.1.0
515
		 *
516
		 * @param WP &$this Current WordPress environment instance (passed by reference).
517
		 */
518
		do_action_ref_array( 'send_headers', array( &$this ) );
519
	}
520
521
	/**
522
	 * Sets the query string property based off of the query variable property.
523
	 *
524
	 * The {@see 'query_string'} filter is deprecated, but still works. Plugins should
525
	 * use the {@see 'request'} filter instead.
526
	 *
527
	 * @since 2.0.0
528
	 * @access public
529
	 */
530
	public function build_query_string() {
531
		$this->query_string = '';
532
		foreach ( (array) array_keys($this->query_vars) as $wpvar) {
533
			if ( '' != $this->query_vars[$wpvar] ) {
534
				$this->query_string .= (strlen($this->query_string) < 1) ? '' : '&';
535
				if ( !is_scalar($this->query_vars[$wpvar]) ) // Discard non-scalars.
536
					continue;
537
				$this->query_string .= $wpvar . '=' . rawurlencode($this->query_vars[$wpvar]);
538
			}
539
		}
540
541
		if ( has_filter( 'query_string' ) ) {  // Don't bother filtering and parsing if no plugins are hooked in.
542
			/**
543
			 * Filters the query string before parsing.
544
			 *
545
			 * @since 1.5.0
546
			 * @deprecated 2.1.0 Use 'query_vars' or 'request' filters instead.
547
			 *
548
			 * @param string $query_string The query string to modify.
549
			 */
550
			$this->query_string = apply_filters( 'query_string', $this->query_string );
551
			parse_str($this->query_string, $this->query_vars);
552
		}
553
	}
554
555
	/**
556
	 * Set up the WordPress Globals.
557
	 *
558
	 * The query_vars property will be extracted to the GLOBALS. So care should
559
	 * be taken when naming global variables that might interfere with the
560
	 * WordPress environment.
561
	 *
562
	 * @since 2.0.0
563
	 * @access public
564
	 *
565
	 * @global WP_Query     $wp_query
566
	 * @global string       $query_string Query string for the loop.
567
	 * @global array        $posts The found posts.
568
	 * @global WP_Post|null $post The current post, if available.
569
	 * @global string       $request The SQL statement for the request.
570
	 * @global int          $more Only set, if single page or post.
571
	 * @global int          $single If single page or post. Only set, if single page or post.
572
	 * @global WP_User      $authordata Only set, if author archive.
573
	 */
574
	public function register_globals() {
575
		global $wp_query;
576
577
		// Extract updated query vars back into global namespace.
578
		foreach ( (array) $wp_query->query_vars as $key => $value ) {
579
			$GLOBALS[ $key ] = $value;
580
		}
581
582
		$GLOBALS['query_string'] = $this->query_string;
583
		$GLOBALS['posts'] = & $wp_query->posts;
584
		$GLOBALS['post'] = isset( $wp_query->post ) ? $wp_query->post : null;
585
		$GLOBALS['request'] = $wp_query->request;
586
587
		if ( $wp_query->is_single() || $wp_query->is_page() ) {
588
			$GLOBALS['more']   = 1;
589
			$GLOBALS['single'] = 1;
590
		}
591
592
		if ( $wp_query->is_author() && isset( $wp_query->post ) )
593
			$GLOBALS['authordata'] = get_userdata( $wp_query->post->post_author );
594
	}
595
596
	/**
597
	 * Set up the current user.
598
	 *
599
	 * @since 2.0.0
600
	 * @access public
601
	 */
602
	public function init() {
603
		wp_get_current_user();
604
	}
605
606
	/**
607
	 * Set up the Loop based on the query variables.
608
	 *
609
	 * @since 2.0.0
610
	 * @access public
611
	 *
612
	 * @global WP_Query $wp_the_query
613
	 */
614
	public function query_posts() {
615
		global $wp_the_query;
616
		$this->build_query_string();
617
		$wp_the_query->query($this->query_vars);
618
 	}
619
620
 	/**
0 ignored issues
show
There is some trailing whitespace on this line which should be avoided as per coding-style.
Loading history...
621
	 * Set the Headers for 404, if nothing is found for requested URL.
622
	 *
623
	 * Issue a 404 if a request doesn't match any posts and doesn't match
624
	 * any object (e.g. an existing-but-empty category, tag, author) and a 404 was not already
625
	 * issued, and if the request was not a search or the homepage.
626
	 *
627
	 * Otherwise, issue a 200.
628
	 *
629
	 * This sets headers after posts have been queried. handle_404() really means "handle status."
630
	 * By inspecting the result of querying posts, seemingly successful requests can be switched to
631
	 * a 404 so that canonical redirection logic can kick in.
632
	 *
633
	 * @since 2.0.0
634
     * @access public
635
	 *
636
	 * @global WP_Query $wp_query
637
 	 */
638
	public function handle_404() {
639
		global $wp_query;
640
641
		/**
642
		 * Filters whether to short-circuit default header status handling.
643
		 *
644
		 * Returning a non-false value from the filter will short-circuit the handling
645
		 * and return early.
646
		 *
647
		 * @since 4.5.0
648
		 *
649
		 * @param bool     $preempt  Whether to short-circuit default header status handling. Default false.
650
		 * @param WP_Query $wp_query WordPress Query object.
651
		 */
652
		if ( false !== apply_filters( 'pre_handle_404', false, $wp_query ) ) {
653
			return;
654
		}
655
656
		// If we've already issued a 404, bail.
657
		if ( is_404() )
658
			return;
659
660
		// Never 404 for the admin, robots, or if we found posts.
661
		if ( is_admin() || is_robots() || $wp_query->posts ) {
662
663
			$success = true;
664
			if ( is_singular() ) {
665
				$p = false;
666
667
				if ( $wp_query->post instanceof WP_Post ) {
668
					$p = clone $wp_query->post;
669
				}
670
671
				// Only set X-Pingback for single posts that allow pings.
672
				if ( $p && pings_open( $p ) ) {
673
					@header( 'X-Pingback: ' . get_bloginfo( 'pingback_url', 'display' ) );
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
674
				}
675
676
				// check for paged content that exceeds the max number of pages
677
				$next = '<!--nextpage-->';
678
				if ( $p && false !== strpos( $p->post_content, $next ) && ! empty( $this->query_vars['page'] ) ) {
679
					$page = trim( $this->query_vars['page'], '/' );
680
					$success = (int) $page <= ( substr_count( $p->post_content, $next ) + 1 );
681
				}
682
			}
683
684
			if ( $success ) {
685
				status_header( 200 );
686
				return;
687
			}
688
		}
689
690
		// We will 404 for paged queries, as no posts were found.
691
		if ( ! is_paged() ) {
692
693
			// Don't 404 for authors without posts as long as they matched an author on this site.
694
			$author = get_query_var( 'author' );
695
			if ( is_author() && is_numeric( $author ) && $author > 0 && is_user_member_of_blog( $author ) ) {
696
				status_header( 200 );
697
				return;
698
			}
699
700
			// Don't 404 for these queries if they matched an object.
701
			if ( ( is_tag() || is_category() || is_tax() || is_post_type_archive() ) && get_queried_object() ) {
702
				status_header( 200 );
703
				return;
704
			}
705
706
			// Don't 404 for these queries either.
707
			if ( is_home() || is_search() || is_feed() ) {
708
				status_header( 200 );
709
				return;
710
			}
711
		}
712
713
		// Guess it's time to 404.
714
		$wp_query->set_404();
715
		status_header( 404 );
716
		nocache_headers();
717
	}
718
719
	/**
720
	 * Sets up all of the variables required by the WordPress environment.
721
	 *
722
	 * The action {@see 'wp'} has one parameter that references the WP object. It
723
	 * allows for accessing the properties and methods to further manipulate the
724
	 * object.
725
	 *
726
	 * @since 2.0.0
727
	 * @access public
728
	 *
729
	 * @param string|array $query_args Passed to parse_request().
730
	 */
731
	public function main($query_args = '') {
732
		$this->init();
733
		$this->parse_request($query_args);
734
		$this->send_headers();
735
		$this->query_posts();
736
		$this->handle_404();
737
		$this->register_globals();
738
739
		/**
740
		 * Fires once the WordPress environment has been set up.
741
		 *
742
		 * @since 2.1.0
743
		 *
744
		 * @param WP &$this Current WordPress environment instance (passed by reference).
745
		 */
746
		do_action_ref_array( 'wp', array( &$this ) );
747
	}
748
}
749