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/post.php (54 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
 * Core Post API
4
 *
5
 * @package WordPress
6
 * @subpackage Post
7
 */
8
9
//
10
// Post Type Registration
11
//
12
13
/**
14
 * Creates the initial post types when 'init' action is fired.
15
 *
16
 * See {@see 'init'}.
17
 *
18
 * @since 2.9.0
19
 */
20
function create_initial_post_types() {
21
	register_post_type( 'post', array(
22
		'labels' => array(
23
			'name_admin_bar' => _x( 'Post', 'add new from admin bar' ),
24
		),
25
		'public'  => true,
26
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
27
		'_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
28
		'capability_type' => 'post',
29
		'map_meta_cap' => true,
30
		'menu_position' => 5,
31
		'hierarchical' => false,
32
		'rewrite' => false,
33
		'query_var' => false,
34
		'delete_with_user' => true,
35
		'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ),
36
		'show_in_rest' => true,
37
		'rest_base' => 'posts',
38
		'rest_controller_class' => 'WP_REST_Posts_Controller',
39
	) );
40
41
	register_post_type( 'page', array(
42
		'labels' => array(
43
			'name_admin_bar' => _x( 'Page', 'add new from admin bar' ),
44
		),
45
		'public' => true,
46
		'publicly_queryable' => false,
47
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
48
		'_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
49
		'capability_type' => 'page',
50
		'map_meta_cap' => true,
51
		'menu_position' => 20,
52
		'hierarchical' => true,
53
		'rewrite' => false,
54
		'query_var' => false,
55
		'delete_with_user' => true,
56
		'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ),
57
		'show_in_rest' => true,
58
		'rest_base' => 'pages',
59
		'rest_controller_class' => 'WP_REST_Posts_Controller',
60
	) );
61
62
	register_post_type( 'attachment', array(
63
		'labels' => array(
64
			'name' => _x('Media', 'post type general name'),
65
			'name_admin_bar' => _x( 'Media', 'add new from admin bar' ),
66
			'add_new' => _x( 'Add New', 'add new media' ),
67
 			'edit_item' => __( 'Edit Media' ),
68
 			'view_item' => __( 'View Attachment Page' ),
69
			'attributes' => __( 'Attachment Attributes' ),
70
		),
71
		'public' => true,
72
		'show_ui' => true,
73
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
74
		'_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
75
		'capability_type' => 'post',
76
		'capabilities' => array(
77
			'create_posts' => 'upload_files',
78
		),
79
		'map_meta_cap' => true,
80
		'hierarchical' => false,
81
		'rewrite' => false,
82
		'query_var' => false,
83
		'show_in_nav_menus' => false,
84
		'delete_with_user' => true,
85
		'supports' => array( 'title', 'author', 'comments' ),
86
		'show_in_rest' => true,
87
		'rest_base' => 'media',
88
		'rest_controller_class' => 'WP_REST_Attachments_Controller',
89
	) );
90
	add_post_type_support( 'attachment:audio', 'thumbnail' );
91
	add_post_type_support( 'attachment:video', 'thumbnail' );
92
93
	register_post_type( 'revision', array(
94
		'labels' => array(
95
			'name' => __( 'Revisions' ),
96
			'singular_name' => __( 'Revision' ),
97
		),
98
		'public' => false,
99
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
100
		'_edit_link' => 'revision.php?revision=%d', /* internal use only. don't use this when registering your own post type. */
101
		'capability_type' => 'post',
102
		'map_meta_cap' => true,
103
		'hierarchical' => false,
104
		'rewrite' => false,
105
		'query_var' => false,
106
		'can_export' => false,
107
		'delete_with_user' => true,
108
		'supports' => array( 'author' ),
109
	) );
110
111
	register_post_type( 'nav_menu_item', array(
112
		'labels' => array(
113
			'name' => __( 'Navigation Menu Items' ),
114
			'singular_name' => __( 'Navigation Menu Item' ),
115
		),
116
		'public' => false,
117
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
118
		'hierarchical' => false,
119
		'rewrite' => false,
120
		'delete_with_user' => false,
121
		'query_var' => false,
122
	) );
123
124
	register_post_type( 'custom_css', array(
125
		'labels' => array(
126
			'name'          => __( 'Custom CSS' ),
127
			'singular_name' => __( 'Custom CSS' ),
128
		),
129
		'public'           => false,
130
		'hierarchical'     => false,
131
		'rewrite'          => false,
132
		'query_var'        => false,
133
		'delete_with_user' => false,
134
		'can_export'       => true,
135
		'_builtin'         => true, /* internal use only. don't use this when registering your own post type. */
136
		'supports'         => array( 'title', 'revisions' ),
137
		'capabilities'     => array(
138
			'delete_posts'           => 'edit_theme_options',
139
			'delete_post'            => 'edit_theme_options',
140
			'delete_published_posts' => 'edit_theme_options',
141
			'delete_private_posts'   => 'edit_theme_options',
142
			'delete_others_posts'    => 'edit_theme_options',
143
			'edit_post'              => 'edit_css',
144
			'edit_posts'             => 'edit_css',
145
			'edit_others_posts'      => 'edit_css',
146
			'edit_published_posts'   => 'edit_css',
147
			'read_post'              => 'read',
148
			'read_private_posts'     => 'read',
149
			'publish_posts'          => 'edit_theme_options',
150
		),
151
	) );
152
153
	register_post_type( 'customize_changeset', array(
154
		'labels' => array(
155
			'name'               => _x( 'Changesets', 'post type general name' ),
156
			'singular_name'      => _x( 'Changeset', 'post type singular name' ),
157
			'menu_name'          => _x( 'Changesets', 'admin menu' ),
158
			'name_admin_bar'     => _x( 'Changeset', 'add new on admin bar' ),
159
			'add_new'            => _x( 'Add New', 'Customize Changeset' ),
160
			'add_new_item'       => __( 'Add New Changeset' ),
161
			'new_item'           => __( 'New Changeset' ),
162
			'edit_item'          => __( 'Edit Changeset' ),
163
			'view_item'          => __( 'View Changeset' ),
164
			'all_items'          => __( 'All Changesets' ),
165
			'search_items'       => __( 'Search Changesets' ),
166
			'not_found'          => __( 'No changesets found.' ),
167
			'not_found_in_trash' => __( 'No changesets found in Trash.' ),
168
		),
169
		'public' => false,
170
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
171
		'map_meta_cap' => true,
172
		'hierarchical' => false,
173
		'rewrite' => false,
174
		'query_var' => false,
175
		'can_export' => false,
176
		'delete_with_user' => false,
177
		'supports' => array( 'title', 'author' ),
178
		'capability_type' => 'customize_changeset',
179
		'capabilities' => array(
180
			'create_posts' => 'customize',
181
			'delete_others_posts' => 'customize',
182
			'delete_post' => 'customize',
183
			'delete_posts' => 'customize',
184
			'delete_private_posts' => 'customize',
185
			'delete_published_posts' => 'customize',
186
			'edit_others_posts' => 'customize',
187
			'edit_post' => 'customize',
188
			'edit_posts' => 'customize',
189
			'edit_private_posts' => 'customize',
190
			'edit_published_posts' => 'do_not_allow',
191
			'publish_posts' => 'customize',
192
			'read' => 'read',
193
			'read_post' => 'customize',
194
			'read_private_posts' => 'customize',
195
		),
196
	) );
197
198
	register_post_status( 'publish', array(
199
		'label'       => _x( 'Published', 'post status' ),
200
		'public'      => true,
201
		'_builtin'    => true, /* internal use only. */
202
		'label_count' => _n_noop( 'Published <span class="count">(%s)</span>', 'Published <span class="count">(%s)</span>' ),
203
	) );
204
205
	register_post_status( 'future', array(
206
		'label'       => _x( 'Scheduled', 'post status' ),
207
		'protected'   => true,
208
		'_builtin'    => true, /* internal use only. */
209
		'label_count' => _n_noop('Scheduled <span class="count">(%s)</span>', 'Scheduled <span class="count">(%s)</span>' ),
210
	) );
211
212
	register_post_status( 'draft', array(
213
		'label'       => _x( 'Draft', 'post status' ),
214
		'protected'   => true,
215
		'_builtin'    => true, /* internal use only. */
216
		'label_count' => _n_noop( 'Draft <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>' ),
217
	) );
218
219
	register_post_status( 'pending', array(
220
		'label'       => _x( 'Pending', 'post status' ),
221
		'protected'   => true,
222
		'_builtin'    => true, /* internal use only. */
223
		'label_count' => _n_noop( 'Pending <span class="count">(%s)</span>', 'Pending <span class="count">(%s)</span>' ),
224
	) );
225
226
	register_post_status( 'private', array(
227
		'label'       => _x( 'Private', 'post status' ),
228
		'private'     => true,
229
		'_builtin'    => true, /* internal use only. */
230
		'label_count' => _n_noop( 'Private <span class="count">(%s)</span>', 'Private <span class="count">(%s)</span>' ),
231
	) );
232
233
	register_post_status( 'trash', array(
234
		'label'       => _x( 'Trash', 'post status' ),
235
		'internal'    => true,
236
		'_builtin'    => true, /* internal use only. */
237
		'label_count' => _n_noop( 'Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>' ),
238
		'show_in_admin_status_list' => true,
239
	) );
240
241
	register_post_status( 'auto-draft', array(
242
		'label'    => 'auto-draft',
243
		'internal' => true,
244
		'_builtin' => true, /* internal use only. */
245
	) );
246
247
	register_post_status( 'inherit', array(
248
		'label'    => 'inherit',
249
		'internal' => true,
250
		'_builtin' => true, /* internal use only. */
251
		'exclude_from_search' => false,
252
	) );
253
}
254
255
/**
256
 * Retrieve attached file path based on attachment ID.
257
 *
258
 * By default the path will go through the 'get_attached_file' filter, but
259
 * passing a true to the $unfiltered argument of get_attached_file() will
260
 * return the file path unfiltered.
261
 *
262
 * The function works by getting the single post meta name, named
263
 * '_wp_attached_file' and returning it. This is a convenience function to
264
 * prevent looking up the meta name and provide a mechanism for sending the
265
 * attached filename through a filter.
266
 *
267
 * @since 2.0.0
268
 *
269
 * @param int  $attachment_id Attachment ID.
270
 * @param bool $unfiltered    Optional. Whether to apply filters. Default false.
271
 * @return string|false The file path to where the attached file should be, false otherwise.
272
 */
273
function get_attached_file( $attachment_id, $unfiltered = false ) {
274
	$file = get_post_meta( $attachment_id, '_wp_attached_file', true );
275
276
	// If the file is relative, prepend upload dir.
277
	if ( $file && 0 !== strpos( $file, '/' ) && ! preg_match( '|^.:\\\|', $file ) && ( ( $uploads = wp_get_upload_dir() ) && false === $uploads['error'] ) ) {
278
		$file = $uploads['basedir'] . "/$file";
279
	}
280
281
	if ( $unfiltered ) {
282
		return $file;
283
	}
284
285
	/**
286
	 * Filters the attached file based on the given ID.
287
	 *
288
	 * @since 2.1.0
289
	 *
290
	 * @param string $file          Path to attached file.
291
	 * @param int    $attachment_id Attachment ID.
292
	 */
293
	return apply_filters( 'get_attached_file', $file, $attachment_id );
294
}
295
296
/**
297
 * Update attachment file path based on attachment ID.
298
 *
299
 * Used to update the file path of the attachment, which uses post meta name
300
 * '_wp_attached_file' to store the path of the attachment.
301
 *
302
 * @since 2.1.0
303
 *
304
 * @param int    $attachment_id Attachment ID.
305
 * @param string $file          File path for the attachment.
306
 * @return bool True on success, false on failure.
0 ignored issues
show
Should the return type not be boolean|integer?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
307
 */
308
function update_attached_file( $attachment_id, $file ) {
309
	if ( !get_post( $attachment_id ) )
310
		return false;
311
312
	/**
313
	 * Filters the path to the attached file to update.
314
	 *
315
	 * @since 2.1.0
316
	 *
317
	 * @param string $file          Path to the attached file to update.
318
	 * @param int    $attachment_id Attachment ID.
319
	 */
320
	$file = apply_filters( 'update_attached_file', $file, $attachment_id );
321
322
	if ( $file = _wp_relative_upload_path( $file ) )
323
		return update_post_meta( $attachment_id, '_wp_attached_file', $file );
324
	else
325
		return delete_post_meta( $attachment_id, '_wp_attached_file' );
326
}
327
328
/**
329
 * Return relative path to an uploaded file.
330
 *
331
 * The path is relative to the current upload dir.
332
 *
333
 * @since 2.9.0
334
 *
335
 * @param string $path Full path to the file.
336
 * @return string Relative path on success, unchanged path on failure.
337
 */
338
function _wp_relative_upload_path( $path ) {
339
	$new_path = $path;
340
341
	$uploads = wp_get_upload_dir();
342
	if ( 0 === strpos( $new_path, $uploads['basedir'] ) ) {
343
			$new_path = str_replace( $uploads['basedir'], '', $new_path );
344
			$new_path = ltrim( $new_path, '/' );
345
	}
346
347
	/**
348
	 * Filters the relative path to an uploaded file.
349
	 *
350
	 * @since 2.9.0
351
	 *
352
	 * @param string $new_path Relative path to the file.
353
	 * @param string $path     Full path to the file.
354
	 */
355
	return apply_filters( '_wp_relative_upload_path', $new_path, $path );
356
}
357
358
/**
359
 * Retrieve all children of the post parent ID.
360
 *
361
 * Normally, without any enhancements, the children would apply to pages. In the
362
 * context of the inner workings of WordPress, pages, posts, and attachments
363
 * share the same table, so therefore the functionality could apply to any one
364
 * of them. It is then noted that while this function does not work on posts, it
365
 * does not mean that it won't work on posts. It is recommended that you know
366
 * what context you wish to retrieve the children of.
367
 *
368
 * Attachments may also be made the child of a post, so if that is an accurate
369
 * statement (which needs to be verified), it would then be possible to get
370
 * all of the attachments for a post. Attachments have since changed since
371
 * version 2.5, so this is most likely inaccurate, but serves generally as an
372
 * example of what is possible.
373
 *
374
 * The arguments listed as defaults are for this function and also of the
375
 * get_posts() function. The arguments are combined with the get_children defaults
376
 * and are then passed to the get_posts() function, which accepts additional arguments.
377
 * You can replace the defaults in this function, listed below and the additional
378
 * arguments listed in the get_posts() function.
379
 *
380
 * The 'post_parent' is the most important argument and important attention
381
 * needs to be paid to the $args parameter. If you pass either an object or an
382
 * integer (number), then just the 'post_parent' is grabbed and everything else
383
 * is lost. If you don't specify any arguments, then it is assumed that you are
384
 * in The Loop and the post parent will be grabbed for from the current post.
385
 *
386
 * The 'post_parent' argument is the ID to get the children. The 'numberposts'
387
 * is the amount of posts to retrieve that has a default of '-1', which is
388
 * used to get all of the posts. Giving a number higher than 0 will only
389
 * retrieve that amount of posts.
390
 *
391
 * The 'post_type' and 'post_status' arguments can be used to choose what
392
 * criteria of posts to retrieve. The 'post_type' can be anything, but WordPress
393
 * post types are 'post', 'pages', and 'attachments'. The 'post_status'
394
 * argument will accept any post status within the write administration panels.
395
 *
396
 * @since 2.0.0
397
 *
398
 * @see get_posts()
399
 * @todo Check validity of description.
400
 *
401
 * @global WP_Post $post
402
 *
403
 * @param mixed  $args   Optional. User defined arguments for replacing the defaults. Default empty.
404
 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
405
 *                       a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
406
 * @return array Array of children, where the type of each element is determined by $output parameter.
407
 *               Empty array on failure.
408
 */
409
function get_children( $args = '', $output = OBJECT ) {
410
	$kids = array();
411
	if ( empty( $args ) ) {
412
		if ( isset( $GLOBALS['post'] ) ) {
413
			$args = array('post_parent' => (int) $GLOBALS['post']->post_parent );
414
		} else {
415
			return $kids;
416
		}
417
	} elseif ( is_object( $args ) ) {
418
		$args = array('post_parent' => (int) $args->post_parent );
419
	} elseif ( is_numeric( $args ) ) {
420
		$args = array('post_parent' => (int) $args);
421
	}
422
423
	$defaults = array(
424
		'numberposts' => -1, 'post_type' => 'any',
425
		'post_status' => 'any', 'post_parent' => 0,
426
	);
427
428
	$r = wp_parse_args( $args, $defaults );
429
430
	$children = get_posts( $r );
431
432
	if ( ! $children )
0 ignored issues
show
Bug Best Practice introduced by
The expression $children of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
433
		return $kids;
434
435
	if ( ! empty( $r['fields'] ) )
436
		return $children;
437
438
	update_post_cache($children);
439
440
	foreach ( $children as $key => $child )
441
		$kids[$child->ID] = $children[$key];
442
443
	if ( $output == OBJECT ) {
444
		return $kids;
445
	} elseif ( $output == ARRAY_A ) {
446
		$weeuns = array();
447
		foreach ( (array) $kids as $kid ) {
448
			$weeuns[$kid->ID] = get_object_vars($kids[$kid->ID]);
449
		}
450
		return $weeuns;
451
	} elseif ( $output == ARRAY_N ) {
452
		$babes = array();
453
		foreach ( (array) $kids as $kid ) {
454
			$babes[$kid->ID] = array_values(get_object_vars($kids[$kid->ID]));
455
		}
456
		return $babes;
457
	} else {
458
		return $kids;
459
	}
460
}
461
462
/**
463
 * Get extended entry info (<!--more-->).
464
 *
465
 * There should not be any space after the second dash and before the word
466
 * 'more'. There can be text or space(s) after the word 'more', but won't be
467
 * referenced.
468
 *
469
 * The returned array has 'main', 'extended', and 'more_text' keys. Main has the text before
470
 * the `<!--more-->`. The 'extended' key has the content after the
471
 * `<!--more-->` comment. The 'more_text' key has the custom "Read More" text.
472
 *
473
 * @since 1.0.0
474
 *
475
 * @param string $post Post content.
476
 * @return array Post before ('main'), after ('extended'), and custom read more ('more_text').
477
 */
478
function get_extended( $post ) {
479
	//Match the new style more links.
480
	if ( preg_match('/<!--more(.*?)?-->/', $post, $matches) ) {
481
		list($main, $extended) = explode($matches[0], $post, 2);
482
		$more_text = $matches[1];
483
	} else {
484
		$main = $post;
485
		$extended = '';
486
		$more_text = '';
487
	}
488
489
	//  leading and trailing whitespace.
490
	$main = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $main);
491
	$extended = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $extended);
492
	$more_text = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $more_text);
493
494
	return array( 'main' => $main, 'extended' => $extended, 'more_text' => $more_text );
495
}
496
497
/**
498
 * Retrieves post data given a post ID or post object.
499
 *
500
 * See sanitize_post() for optional $filter values. Also, the parameter
501
 * `$post`, must be given as a variable, since it is passed by reference.
502
 *
503
 * @since 1.5.1
504
 *
505
 * @global WP_Post $post
506
 *
507
 * @param int|WP_Post|null $post   Optional. Post ID or post object. Defaults to global $post.
508
 * @param string           $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
509
 *                                 a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
510
 * @param string           $filter Optional. Type of filter to apply. Accepts 'raw', 'edit', 'db',
511
 *                                 or 'display'. Default 'raw'.
512
 * @return WP_Post|array|null Type corresponding to $output on success or null on failure.
513
 *                            When $output is OBJECT, a `WP_Post` instance is returned.
514
 */
515
function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) {
516
	if ( empty( $post ) && isset( $GLOBALS['post'] ) )
517
		$post = $GLOBALS['post'];
518
519 View Code Duplication
	if ( $post instanceof WP_Post ) {
520
		$_post = $post;
521
	} elseif ( is_object( $post ) ) {
522
		if ( empty( $post->filter ) ) {
523
			$_post = sanitize_post( $post, 'raw' );
524
			$_post = new WP_Post( $_post );
0 ignored issues
show
It seems like $_post can also be of type array<string,?,{"filter":"?"}>; however, WP_Post::__construct() does only seem to accept object, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
525
		} elseif ( 'raw' == $post->filter ) {
526
			$_post = new WP_Post( $post );
527
		} else {
528
			$_post = WP_Post::get_instance( $post->ID );
529
		}
530
	} else {
531
		$_post = WP_Post::get_instance( $post );
532
	}
533
534
	if ( ! $_post )
535
		return null;
536
537
	$_post = $_post->filter( $filter );
538
539 View Code Duplication
	if ( $output == ARRAY_A )
540
		return $_post->to_array();
541
	elseif ( $output == ARRAY_N )
542
		return array_values( $_post->to_array() );
543
544
	return $_post;
545
}
546
547
/**
548
 * Retrieve ancestors of a post.
549
 *
550
 * @since 2.5.0
551
 *
552
 * @param int|WP_Post $post Post ID or post object.
553
 * @return array Ancestor IDs or empty array if none are found.
554
 */
555
function get_post_ancestors( $post ) {
556
	$post = get_post( $post );
557
558
	if ( ! $post || empty( $post->post_parent ) || $post->post_parent == $post->ID )
559
		return array();
560
561
	$ancestors = array();
562
563
	$id = $ancestors[] = $post->post_parent;
564
565
	while ( $ancestor = get_post( $id ) ) {
566
		// Loop detection: If the ancestor has been seen before, break.
567
		if ( empty( $ancestor->post_parent ) || ( $ancestor->post_parent == $post->ID ) || in_array( $ancestor->post_parent, $ancestors ) )
568
			break;
569
570
		$id = $ancestors[] = $ancestor->post_parent;
571
	}
572
573
	return $ancestors;
574
}
575
576
/**
577
 * Retrieve data from a post field based on Post ID.
578
 *
579
 * Examples of the post field will be, 'post_type', 'post_status', 'post_content',
580
 * etc and based off of the post object property or key names.
581
 *
582
 * The context values are based off of the taxonomy filter functions and
583
 * supported values are found within those functions.
584
 *
585
 * @since 2.3.0
586
 * @since 4.5.0 The `$post` parameter was made optional.
587
 *
588
 * @see sanitize_post_field()
589
 *
590
 * @param string      $field   Post field name.
591
 * @param int|WP_Post $post    Optional. Post ID or post object. Defaults to current post.
592
 * @param string      $context Optional. How to filter the field. Accepts 'raw', 'edit', 'db',
593
 *                             or 'display'. Default 'display'.
594
 * @return string The value of the post field on success, empty string on failure.
595
 */
596
function get_post_field( $field, $post = null, $context = 'display' ) {
597
	$post = get_post( $post );
598
599
	if ( !$post )
600
		return '';
601
602
	if ( !isset($post->$field) )
603
		return '';
604
605
	return sanitize_post_field($field, $post->$field, $post->ID, $context);
606
}
607
608
/**
609
 * Retrieve the mime type of an attachment based on the ID.
610
 *
611
 * This function can be used with any post type, but it makes more sense with
612
 * attachments.
613
 *
614
 * @since 2.0.0
615
 *
616
 * @param int|WP_Post $ID Optional. Post ID or post object. Default empty.
617
 * @return string|false The mime type on success, false on failure.
618
 */
619
function get_post_mime_type( $ID = '' ) {
620
	$post = get_post($ID);
621
622
	if ( is_object($post) )
623
		return $post->post_mime_type;
624
625
	return false;
626
}
627
628
/**
629
 * Retrieve the post status based on the Post ID.
630
 *
631
 * If the post ID is of an attachment, then the parent post status will be given
632
 * instead.
633
 *
634
 * @since 2.0.0
635
 *
636
 * @param int|WP_Post $ID Optional. Post ID or post object. Default empty.
637
 * @return string|false Post status on success, false on failure.
638
 */
639
function get_post_status( $ID = '' ) {
640
	$post = get_post($ID);
641
642
	if ( !is_object($post) )
643
		return false;
644
645
	if ( 'attachment' == $post->post_type ) {
646
		if ( 'private' == $post->post_status )
647
			return 'private';
648
649
		// Unattached attachments are assumed to be published.
650
		if ( ( 'inherit' == $post->post_status ) && ( 0 == $post->post_parent) )
651
			return 'publish';
652
653
		// Inherit status from the parent.
654
		if ( $post->post_parent && ( $post->ID != $post->post_parent ) ) {
655
			$parent_post_status = get_post_status( $post->post_parent );
656
			if ( 'trash' == $parent_post_status ) {
657
				return get_post_meta( $post->post_parent, '_wp_trash_meta_status', true );
658
			} else {
659
				return $parent_post_status;
660
			}
661
		}
662
663
	}
664
665
	/**
666
	 * Filters the post status.
667
	 *
668
	 * @since 4.4.0
669
	 *
670
	 * @param string  $post_status The post status.
671
	 * @param WP_Post $post        The post object.
672
	 */
673
	return apply_filters( 'get_post_status', $post->post_status, $post );
674
}
675
676
/**
677
 * Retrieve all of the WordPress supported post statuses.
678
 *
679
 * Posts have a limited set of valid status values, this provides the
680
 * post_status values and descriptions.
681
 *
682
 * @since 2.5.0
683
 *
684
 * @return array List of post statuses.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
685
 */
686 View Code Duplication
function get_post_statuses() {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
687
	$status = array(
688
		'draft'   => __( 'Draft' ),
689
		'pending' => __( 'Pending Review' ),
690
		'private' => __( 'Private' ),
691
		'publish' => __( 'Published' )
692
	);
693
694
	return $status;
695
}
696
697
/**
698
 * Retrieve all of the WordPress support page statuses.
699
 *
700
 * Pages have a limited set of valid status values, this provides the
701
 * post_status values and descriptions.
702
 *
703
 * @since 2.5.0
704
 *
705
 * @return array List of page statuses.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<string,string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
706
 */
707 View Code Duplication
function get_page_statuses() {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
708
	$status = array(
709
		'draft'   => __( 'Draft' ),
710
		'private' => __( 'Private' ),
711
		'publish' => __( 'Published' )
712
	);
713
714
	return $status;
715
}
716
717
/**
718
 * Register a post status. Do not use before init.
719
 *
720
 * A simple function for creating or modifying a post status based on the
721
 * parameters given. The function will accept an array (second optional
722
 * parameter), along with a string for the post status name.
723
 *
724
 * Arguments prefixed with an _underscore shouldn't be used by plugins and themes.
725
 *
726
 * @since 3.0.0
727
 * @global array $wp_post_statuses Inserts new post status object into the list
728
 *
729
 * @param string $post_status Name of the post status.
730
 * @param array|string $args {
731
 *     Optional. Array or string of post status arguments.
732
 *
733
 *     @type bool|string $label                     A descriptive name for the post status marked
734
 *                                                  for translation. Defaults to value of $post_status.
735
 *     @type bool|array  $label_count               Descriptive text to use for nooped plurals.
736
 *                                                  Default array of $label, twice
737
 *     @type bool        $exclude_from_search       Whether to exclude posts with this post status
738
 *                                                  from search results. Default is value of $internal.
739
 *     @type bool        $_builtin                  Whether the status is built-in. Core-use only.
740
 *                                                  Default false.
741
 *     @type bool        $public                    Whether posts of this status should be shown
742
 *                                                  in the front end of the site. Default false.
743
 *     @type bool        $internal                  Whether the status is for internal use only.
744
 *                                                  Default false.
745
 *     @type bool        $protected                 Whether posts with this status should be protected.
746
 *                                                  Default false.
747
 *     @type bool        $private                   Whether posts with this status should be private.
748
 *                                                  Default false.
749
 *     @type bool        $publicly_queryable        Whether posts with this status should be publicly-
750
 *                                                  queryable. Default is value of $public.
751
 *     @type bool        $show_in_admin_all_list    Whether to include posts in the edit listing for
752
 *                                                  their post type. Default is value of $internal.
753
 *     @type bool        $show_in_admin_status_list Show in the list of statuses with post counts at
754
 *                                                  the top of the edit listings,
755
 *                                                  e.g. All (12) | Published (9) | My Custom Status (2)
756
 *                                                  Default is value of $internal.
757
 * }
758
 * @return object
759
 */
760
function register_post_status( $post_status, $args = array() ) {
761
	global $wp_post_statuses;
762
763
	if (!is_array($wp_post_statuses))
764
		$wp_post_statuses = array();
765
766
	// Args prefixed with an underscore are reserved for internal use.
767
	$defaults = array(
768
		'label' => false,
769
		'label_count' => false,
770
		'exclude_from_search' => null,
771
		'_builtin' => false,
772
		'public' => null,
773
		'internal' => null,
774
		'protected' => null,
775
		'private' => null,
776
		'publicly_queryable' => null,
777
		'show_in_admin_status_list' => null,
778
		'show_in_admin_all_list' => null,
779
	);
780
	$args = wp_parse_args($args, $defaults);
781
	$args = (object) $args;
782
783
	$post_status = sanitize_key($post_status);
784
	$args->name = $post_status;
785
786
	// Set various defaults.
787
	if ( null === $args->public && null === $args->internal && null === $args->protected && null === $args->private )
788
		$args->internal = true;
789
790
	if ( null === $args->public  )
791
		$args->public = false;
792
793
	if ( null === $args->private  )
794
		$args->private = false;
795
796
	if ( null === $args->protected  )
797
		$args->protected = false;
798
799
	if ( null === $args->internal  )
800
		$args->internal = false;
801
802
	if ( null === $args->publicly_queryable )
803
		$args->publicly_queryable = $args->public;
804
805
	if ( null === $args->exclude_from_search )
806
		$args->exclude_from_search = $args->internal;
807
808
	if ( null === $args->show_in_admin_all_list )
809
		$args->show_in_admin_all_list = !$args->internal;
810
811
	if ( null === $args->show_in_admin_status_list )
812
		$args->show_in_admin_status_list = !$args->internal;
813
814
	if ( false === $args->label )
815
		$args->label = $post_status;
816
817
	if ( false === $args->label_count )
818
		$args->label_count = _n_noop( $args->label, $args->label );
819
820
	$wp_post_statuses[$post_status] = $args;
821
822
	return $args;
823
}
824
825
/**
826
 * Retrieve a post status object by name.
827
 *
828
 * @since 3.0.0
829
 *
830
 * @global array $wp_post_statuses List of post statuses.
831
 *
832
 * @see register_post_status()
833
 *
834
 * @param string $post_status The name of a registered post status.
835
 * @return object|null A post status object.
836
 */
837
function get_post_status_object( $post_status ) {
838
	global $wp_post_statuses;
839
840
	if ( empty($wp_post_statuses[$post_status]) )
841
		return null;
842
843
	return $wp_post_statuses[$post_status];
844
}
845
846
/**
847
 * Get a list of post statuses.
848
 *
849
 * @since 3.0.0
850
 *
851
 * @global array $wp_post_statuses List of post statuses.
852
 *
853
 * @see register_post_status()
854
 *
855
 * @param array|string $args     Optional. Array or string of post status arguments to compare against
856
 *                               properties of the global `$wp_post_statuses objects`. Default empty array.
857
 * @param string       $output   Optional. The type of output to return, either 'names' or 'objects'. Default 'names'.
858
 * @param string       $operator Optional. The logical operation to perform. 'or' means only one element
859
 *                               from the array needs to match; 'and' means all elements must match.
860
 *                               Default 'and'.
861
 * @return array A list of post status names or objects.
862
 */
863 View Code Duplication
function get_post_stati( $args = array(), $output = 'names', $operator = 'and' ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
864
	global $wp_post_statuses;
865
866
	$field = ('names' == $output) ? 'name' : false;
867
868
	return wp_filter_object_list($wp_post_statuses, $args, $operator, $field);
0 ignored issues
show
It seems like $args defined by parameter $args on line 863 can also be of type string; however, wp_filter_object_list() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
869
}
870
871
/**
872
 * Whether the post type is hierarchical.
873
 *
874
 * A false return value might also mean that the post type does not exist.
875
 *
876
 * @since 3.0.0
877
 *
878
 * @see get_post_type_object()
879
 *
880
 * @param string $post_type Post type name
881
 * @return bool Whether post type is hierarchical.
882
 */
883
function is_post_type_hierarchical( $post_type ) {
884
	if ( ! post_type_exists( $post_type ) )
885
		return false;
886
887
	$post_type = get_post_type_object( $post_type );
888
	return $post_type->hierarchical;
889
}
890
891
/**
892
 * Check if a post type is registered.
893
 *
894
 * @since 3.0.0
895
 *
896
 * @see get_post_type_object()
897
 *
898
 * @param string $post_type Post type name.
899
 * @return bool Whether post type is registered.
900
 */
901
function post_type_exists( $post_type ) {
902
	return (bool) get_post_type_object( $post_type );
903
}
904
905
/**
906
 * Retrieves the post type of the current post or of a given post.
907
 *
908
 * @since 2.1.0
909
 *
910
 * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
911
 * @return string|false          Post type on success, false on failure.
912
 */
913
function get_post_type( $post = null ) {
914
	if ( $post = get_post( $post ) )
915
		return $post->post_type;
916
917
	return false;
918
}
919
920
/**
921
 * Retrieves a post type object by name.
922
 *
923
 * @since 3.0.0
924
 * @since 4.6.0 Object returned is now an instance of WP_Post_Type.
925
 *
926
 * @global array $wp_post_types List of post types.
927
 *
928
 * @see register_post_type()
929
 *
930
 * @param string $post_type The name of a registered post type.
931
 * @return WP_Post_Type|null WP_Post_Type object if it exists, null otherwise.
932
 */
933
function get_post_type_object( $post_type ) {
934
	global $wp_post_types;
935
936
	if ( ! is_scalar( $post_type ) || empty( $wp_post_types[ $post_type ] ) ) {
937
		return null;
938
	}
939
940
	return $wp_post_types[ $post_type ];
941
}
942
943
/**
944
 * Get a list of all registered post type objects.
945
 *
946
 * @since 2.9.0
947
 *
948
 * @global array $wp_post_types List of post types.
949
 *
950
 * @see register_post_type() for accepted arguments.
951
 *
952
 * @param array|string $args     Optional. An array of key => value arguments to match against
953
 *                               the post type objects. Default empty array.
954
 * @param string       $output   Optional. The type of output to return. Accepts post type 'names'
955
 *                               or 'objects'. Default 'names'.
956
 * @param string       $operator Optional. The logical operation to perform. 'or' means only one
957
 *                               element from the array needs to match; 'and' means all elements
958
 *                               must match; 'not' means no elements may match. Default 'and'.
959
 * @return array A list of post type names or objects.
960
 */
961 View Code Duplication
function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
962
	global $wp_post_types;
963
964
	$field = ('names' == $output) ? 'name' : false;
965
966
	return wp_filter_object_list($wp_post_types, $args, $operator, $field);
0 ignored issues
show
It seems like $args defined by parameter $args on line 961 can also be of type string; however, wp_filter_object_list() does only seem to accept array, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
967
}
968
969
/**
970
 * Registers a post type.
971
 *
972
 * Note: Post type registrations should not be hooked before the
973
 * {@see 'init'} action. Also, any taxonomy connections should be
974
 * registered via the `$taxonomies` argument to ensure consistency
975
 * when hooks such as {@see 'parse_query'} or {@see 'pre_get_posts'}
976
 * are used.
977
 *
978
 * Post types can support any number of built-in core features such
979
 * as meta boxes, custom fields, post thumbnails, post statuses,
980
 * comments, and more. See the `$supports` argument for a complete
981
 * list of supported features.
982
 *
983
 * @since 2.9.0
984
 * @since 3.0.0 The `show_ui` argument is now enforced on the new post screen.
985
 * @since 4.4.0 The `show_ui` argument is now enforced on the post type listing
986
 *              screen and post editing screen.
987
 * @since 4.6.0 Post type object returned is now an instance of WP_Post_Type.
988
 * @since 4.7.0 Introduced `show_in_rest`, 'rest_base' and 'rest_controller_class'
989
 *              arguments to register the post type in REST API.
990
 *
991
 * @global array $wp_post_types List of post types.
992
 *
993
 * @param string $post_type Post type key. Must not exceed 20 characters and may
994
 *                          only contain lowercase alphanumeric characters, dashes,
995
 *                          and underscores. See sanitize_key().
996
 * @param array|string $args {
997
 *     Array or string of arguments for registering a post type.
998
 *
999
 *     @type string      $label                 Name of the post type shown in the menu. Usually plural.
1000
 *                                              Default is value of $labels['name'].
1001
 *     @type array       $labels                An array of labels for this post type. If not set, post
1002
 *                                              labels are inherited for non-hierarchical types and page
1003
 *                                              labels for hierarchical ones. See get_post_type_labels() for a full
1004
 *                                              list of supported labels.
1005
 *     @type string      $description           A short descriptive summary of what the post type is.
1006
 *                                              Default empty.
1007
 *     @type bool        $public                Whether a post type is intended for use publicly either via
1008
 *                                              the admin interface or by front-end users. While the default
1009
 *                                              settings of $exclude_from_search, $publicly_queryable, $show_ui,
1010
 *                                              and $show_in_nav_menus are inherited from public, each does not
1011
 *                                              rely on this relationship and controls a very specific intention.
1012
 *                                              Default false.
1013
 *     @type bool        $hierarchical          Whether the post type is hierarchical (e.g. page). Default false.
1014
 *     @type bool        $exclude_from_search   Whether to exclude posts with this post type from front end search
1015
 *                                              results. Default is the opposite value of $public.
1016
 *     @type bool        $publicly_queryable    Whether queries can be performed on the front end for the post type
1017
 *                                              as part of parse_request(). Endpoints would include:
1018
 *                                              * ?post_type={post_type_key}
1019
 *                                              * ?{post_type_key}={single_post_slug}
1020
 *                                              * ?{post_type_query_var}={single_post_slug}
1021
 *                                              If not set, the default is inherited from $public.
1022
 *     @type bool        $show_ui               Whether to generate and allow a UI for managing this post type in the
1023
 *                                              admin. Default is value of $public.
1024
 *     @type bool        $show_in_menu          Where to show the post type in the admin menu. To work, $show_ui
1025
 *                                              must be true. If true, the post type is shown in its own top level
1026
 *                                              menu. If false, no menu is shown. If a string of an existing top
1027
 *                                              level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post
1028
 *                                              type will be placed as a sub-menu of that.
1029
 *                                              Default is value of $show_ui.
1030
 *     @type bool        $show_in_nav_menus     Makes this post type available for selection in navigation menus.
1031
 *                                              Default is value $public.
1032
 *     @type bool        $show_in_admin_bar     Makes this post type available via the admin bar. Default is value
1033
 *                                              of $show_in_menu.
1034
 *     @type bool        $show_in_rest          Whether to add the post type route in the REST API 'wp/v2' namespace.
1035
 *     @type string      $rest_base             To change the base url of REST API route. Default is $post_type.
1036
 *     @type string      $rest_controller_class REST API Controller class name. Default is 'WP_REST_Posts_Controller'.
1037
 *     @type int         $menu_position         The position in the menu order the post type should appear. To work,
1038
 *                                              $show_in_menu must be true. Default null (at the bottom).
1039
 *     @type string      $menu_icon             The url to the icon to be used for this menu. Pass a base64-encoded
1040
 *                                              SVG using a data URI, which will be colored to match the color scheme
1041
 *                                              -- this should begin with 'data:image/svg+xml;base64,'. Pass the name
1042
 *                                              of a Dashicons helper class to use a font icon, e.g.
1043
 *                                              'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty
1044
 *                                              so an icon can be added via CSS. Defaults to use the posts icon.
1045
 *     @type string      $capability_type       The string to use to build the read, edit, and delete capabilities.
1046
 *                                              May be passed as an array to allow for alternative plurals when using
1047
 *                                              this argument as a base to construct the capabilities, e.g.
1048
 *                                              array('story', 'stories'). Default 'post'.
1049
 *     @type array       $capabilities          Array of capabilities for this post type. $capability_type is used
1050
 *                                              as a base to construct capabilities by default.
1051
 *                                              See get_post_type_capabilities().
1052
 *     @type bool        $map_meta_cap          Whether to use the internal default meta capability handling.
1053
 *                                              Default false.
1054
 *     @type array       $supports              Core feature(s) the post type supports. Serves as an alias for calling
1055
 *                                              add_post_type_support() directly. Core features include 'title',
1056
 *                                              'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt',
1057
 *                                              'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'.
1058
 *                                              Additionally, the 'revisions' feature dictates whether the post type
1059
 *                                              will store revisions, and the 'comments' feature dictates whether the
1060
 *                                              comments count will show on the edit screen. Defaults is an array
1061
 *                                              containing 'title' and 'editor'.
1062
 *     @type callable    $register_meta_box_cb  Provide a callback function that sets up the meta boxes for the
1063
 *                                              edit form. Do remove_meta_box() and add_meta_box() calls in the
1064
 *                                              callback. Default null.
1065
 *     @type array       $taxonomies            An array of taxonomy identifiers that will be registered for the
1066
 *                                              post type. Taxonomies can be registered later with register_taxonomy()
1067
 *                                              or register_taxonomy_for_object_type().
1068
 *                                              Default empty array.
1069
 *     @type bool|string $has_archive           Whether there should be post type archives, or if a string, the
1070
 *                                              archive slug to use. Will generate the proper rewrite rules if
1071
 *                                              $rewrite is enabled. Default false.
1072
 *     @type bool|array  $rewrite              {
1073
 *         Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
1074
 *         Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be
1075
 *         passed with any of these keys:
1076
 *
1077
 *         @type string $slug       Customize the permastruct slug. Defaults to $post_type key.
1078
 *         @type bool   $with_front Whether the permastruct should be prepended with WP_Rewrite::$front.
1079
 *                                  Default true.
1080
 *         @type bool   $feeds      Whether the feed permastruct should be built for this post type.
1081
 *                                  Default is value of $has_archive.
1082
 *         @type bool   $pages      Whether the permastruct should provide for pagination. Default true.
1083
 *         @type const  $ep_mask    Endpoint mask to assign. If not specified and permalink_epmask is set,
1084
 *                                  inherits from $permalink_epmask. If not specified and permalink_epmask
1085
 *                                  is not set, defaults to EP_PERMALINK.
1086
 *     }
1087
 *     @type string|bool $query_var             Sets the query_var key for this post type. Defaults to $post_type
1088
 *                                              key. If false, a post type cannot be loaded at
1089
 *                                              ?{query_var}={post_slug}. If specified as a string, the query
1090
 *                                              ?{query_var_string}={post_slug} will be valid.
1091
 *     @type bool        $can_export            Whether to allow this post type to be exported. Default true.
1092
 *     @type bool        $delete_with_user      Whether to delete posts of this type when deleting a user. If true,
1093
 *                                              posts of this type belonging to the user will be moved to trash
1094
 *                                              when then user is deleted. If false, posts of this type belonging
1095
 *                                              to the user will *not* be trashed or deleted. If not set (the default),
1096
 *                                              posts are trashed if post_type_supports('author'). Otherwise posts
1097
 *                                              are not trashed or deleted. Default null.
1098
 *     @type bool        $_builtin              FOR INTERNAL USE ONLY! True if this post type is a native or
1099
 *                                              "built-in" post_type. Default false.
1100
 *     @type string      $_edit_link            FOR INTERNAL USE ONLY! URL segment to use for edit link of
1101
 *                                              this post type. Default 'post.php?post=%d'.
1102
 * }
1103
 * @return WP_Post_Type|WP_Error The registered post type object, or an error object.
1104
 */
1105
function register_post_type( $post_type, $args = array() ) {
1106
	global $wp_post_types;
1107
1108
	if ( ! is_array( $wp_post_types ) ) {
1109
		$wp_post_types = array();
1110
	}
1111
1112
	// Sanitize post type name
1113
	$post_type = sanitize_key( $post_type );
1114
1115
	if ( empty( $post_type ) || strlen( $post_type ) > 20 ) {
1116
		_doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' );
1117
		return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) );
1118
	}
1119
1120
	$post_type_object = new WP_Post_Type( $post_type, $args );
1121
	$post_type_object->add_supports();
1122
	$post_type_object->add_rewrite_rules();
1123
	$post_type_object->register_meta_boxes();
1124
1125
	$wp_post_types[ $post_type ] = $post_type_object;
1126
1127
	$post_type_object->add_hooks();
1128
	$post_type_object->register_taxonomies();
1129
1130
	/**
1131
	 * Fires after a post type is registered.
1132
	 *
1133
	 * @since 3.3.0
1134
	 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1135
	 *
1136
	 * @param string       $post_type        Post type.
1137
	 * @param WP_Post_Type $post_type_object Arguments used to register the post type.
1138
	 */
1139
	do_action( 'registered_post_type', $post_type, $post_type_object );
1140
1141
	return $post_type_object;
1142
}
1143
1144
/**
1145
 * Unregisters a post type.
1146
 *
1147
 * Can not be used to unregister built-in post types.
1148
 *
1149
 * @since 4.5.0
1150
 *
1151
 * @global array $wp_post_types List of post types.
1152
 *
1153
 * @param string $post_type Post type to unregister.
1154
 * @return bool|WP_Error True on success, WP_Error on failure or if the post type doesn't exist.
1155
 */
1156
function unregister_post_type( $post_type ) {
1157
	global $wp_post_types;
1158
1159
	if ( ! post_type_exists( $post_type ) ) {
1160
		return new WP_Error( 'invalid_post_type', __( 'Invalid post type.' ) );
1161
	}
1162
1163
	$post_type_object = get_post_type_object( $post_type );
1164
1165
	// Do not allow unregistering internal post types.
1166
	if ( $post_type_object->_builtin ) {
1167
		return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) );
1168
	}
1169
1170
	$post_type_object->remove_supports();
1171
	$post_type_object->remove_rewrite_rules();
1172
	$post_type_object->unregister_meta_boxes();
1173
	$post_type_object->remove_hooks();
1174
	$post_type_object->unregister_taxonomies();
1175
1176
	unset( $wp_post_types[ $post_type ] );
1177
1178
	/**
1179
	 * Fires after a post type was unregistered.
1180
	 *
1181
	 * @since 4.5.0
1182
	 *
1183
	 * @param string $post_type Post type key.
1184
	 */
1185
	do_action( 'unregistered_post_type', $post_type );
1186
1187
	return true;
1188
}
1189
1190
/**
1191
 * Build an object with all post type capabilities out of a post type object
1192
 *
1193
 * Post type capabilities use the 'capability_type' argument as a base, if the
1194
 * capability is not set in the 'capabilities' argument array or if the
1195
 * 'capabilities' argument is not supplied.
1196
 *
1197
 * The capability_type argument can optionally be registered as an array, with
1198
 * the first value being singular and the second plural, e.g. array('story, 'stories')
1199
 * Otherwise, an 's' will be added to the value for the plural form. After
1200
 * registration, capability_type will always be a string of the singular value.
1201
 *
1202
 * By default, seven keys are accepted as part of the capabilities array:
1203
 *
1204
 * - edit_post, read_post, and delete_post are meta capabilities, which are then
1205
 *   generally mapped to corresponding primitive capabilities depending on the
1206
 *   context, which would be the post being edited/read/deleted and the user or
1207
 *   role being checked. Thus these capabilities would generally not be granted
1208
 *   directly to users or roles.
1209
 *
1210
 * - edit_posts - Controls whether objects of this post type can be edited.
1211
 * - edit_others_posts - Controls whether objects of this type owned by other users
1212
 *   can be edited. If the post type does not support an author, then this will
1213
 *   behave like edit_posts.
1214
 * - publish_posts - Controls publishing objects of this post type.
1215
 * - read_private_posts - Controls whether private objects can be read.
1216
 *
1217
 * These four primitive capabilities are checked in core in various locations.
1218
 * There are also seven other primitive capabilities which are not referenced
1219
 * directly in core, except in map_meta_cap(), which takes the three aforementioned
1220
 * meta capabilities and translates them into one or more primitive capabilities
1221
 * that must then be checked against the user or role, depending on the context.
1222
 *
1223
 * - read - Controls whether objects of this post type can be read.
1224
 * - delete_posts - Controls whether objects of this post type can be deleted.
1225
 * - delete_private_posts - Controls whether private objects can be deleted.
1226
 * - delete_published_posts - Controls whether published objects can be deleted.
1227
 * - delete_others_posts - Controls whether objects owned by other users can be
1228
 *   can be deleted. If the post type does not support an author, then this will
1229
 *   behave like delete_posts.
1230
 * - edit_private_posts - Controls whether private objects can be edited.
1231
 * - edit_published_posts - Controls whether published objects can be edited.
1232
 *
1233
 * These additional capabilities are only used in map_meta_cap(). Thus, they are
1234
 * only assigned by default if the post type is registered with the 'map_meta_cap'
1235
 * argument set to true (default is false).
1236
 *
1237
 * @since 3.0.0
1238
 *
1239
 * @see register_post_type()
1240
 * @see map_meta_cap()
1241
 *
1242
 * @param object $args Post type registration arguments.
1243
 * @return object object with all the capabilities as member variables.
1244
 */
1245
function get_post_type_capabilities( $args ) {
1246
	if ( ! is_array( $args->capability_type ) )
1247
		$args->capability_type = array( $args->capability_type, $args->capability_type . 's' );
1248
1249
	// Singular base for meta capabilities, plural base for primitive capabilities.
1250
	list( $singular_base, $plural_base ) = $args->capability_type;
1251
1252
	$default_capabilities = array(
1253
		// Meta capabilities
1254
		'edit_post'          => 'edit_'         . $singular_base,
1255
		'read_post'          => 'read_'         . $singular_base,
1256
		'delete_post'        => 'delete_'       . $singular_base,
1257
		// Primitive capabilities used outside of map_meta_cap():
1258
		'edit_posts'         => 'edit_'         . $plural_base,
1259
		'edit_others_posts'  => 'edit_others_'  . $plural_base,
1260
		'publish_posts'      => 'publish_'      . $plural_base,
1261
		'read_private_posts' => 'read_private_' . $plural_base,
1262
	);
1263
1264
	// Primitive capabilities used within map_meta_cap():
1265
	if ( $args->map_meta_cap ) {
1266
		$default_capabilities_for_mapping = array(
1267
			'read'                   => 'read',
1268
			'delete_posts'           => 'delete_'           . $plural_base,
1269
			'delete_private_posts'   => 'delete_private_'   . $plural_base,
1270
			'delete_published_posts' => 'delete_published_' . $plural_base,
1271
			'delete_others_posts'    => 'delete_others_'    . $plural_base,
1272
			'edit_private_posts'     => 'edit_private_'     . $plural_base,
1273
			'edit_published_posts'   => 'edit_published_'   . $plural_base,
1274
		);
1275
		$default_capabilities = array_merge( $default_capabilities, $default_capabilities_for_mapping );
1276
	}
1277
1278
	$capabilities = array_merge( $default_capabilities, $args->capabilities );
1279
1280
	// Post creation capability simply maps to edit_posts by default:
1281
	if ( ! isset( $capabilities['create_posts'] ) )
1282
		$capabilities['create_posts'] = $capabilities['edit_posts'];
1283
1284
	// Remember meta capabilities for future reference.
1285
	if ( $args->map_meta_cap )
1286
		_post_type_meta_capabilities( $capabilities );
1287
1288
	return (object) $capabilities;
1289
}
1290
1291
/**
1292
 * Store or return a list of post type meta caps for map_meta_cap().
1293
 *
1294
 * @since 3.1.0
1295
 * @access private
1296
 *
1297
 * @global array $post_type_meta_caps Used to store meta capabilities.
1298
 *
1299
 * @param array $capabilities Post type meta capabilities.
0 ignored issues
show
Should the type for parameter $capabilities not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1300
 */
1301
function _post_type_meta_capabilities( $capabilities = null ) {
1302
	global $post_type_meta_caps;
1303
1304
	foreach ( $capabilities as $core => $custom ) {
0 ignored issues
show
The expression $capabilities of type array|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
1305
		if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ) ) ) {
1306
			$post_type_meta_caps[ $custom ] = $core;
1307
		}
1308
	}
1309
}
1310
1311
/**
1312
 * Builds an object with all post type labels out of a post type object.
1313
 *
1314
 * Accepted keys of the label array in the post type object:
1315
 *
1316
 * - `name` - General name for the post type, usually plural. The same and overridden
1317
 *          by `$post_type_object->label`. Default is 'Posts' / 'Pages'.
1318
 * - `singular_name` - Name for one object of this post type. Default is 'Post' / 'Page'.
1319
 * - `add_new` - Default is 'Add New' for both hierarchical and non-hierarchical types.
1320
 *             When internationalizing this string, please use a {@link https://codex.wordpress.org/I18n_for_WordPress_Developers#Disambiguation_by_context gettext context}
1321
 *             matching your post type. Example: `_x( 'Add New', 'product', 'textdomain' );`.
1322
 * - `add_new_item` - Label for adding a new singular item. Default is 'Add New Post' / 'Add New Page'.
1323
 * - `edit_item` - Label for editing a singular item. Default is 'Edit Post' / 'Edit Page'.
1324
 * - `new_item` - Label for the new item page title. Default is 'New Post' / 'New Page'.
1325
 * - `view_item` - Label for viewing a singular item. Default is 'View Post' / 'View Page'.
1326
 * - `view_items` - Label for viewing post type archives. Default is 'View Posts' / 'View Pages'.
1327
 * - `search_items` - Label for searching plural items. Default is 'Search Posts' / 'Search Pages'.
1328
 * - `not_found` - Label used when no items are found. Default is 'No posts found' / 'No pages found'.
1329
 * - `not_found_in_trash` - Label used when no items are in the trash. Default is 'No posts found in Trash' /
1330
 *                        'No pages found in Trash'.
1331
 * - `parent_item_colon` - Label used to prefix parents of hierarchical items. Not used on non-hierarchical
1332
 *                       post types. Default is 'Parent Page:'.
1333
 * - `all_items` - Label to signify all items in a submenu link. Default is 'All Posts' / 'All Pages'.
1334
 * - `archives` - Label for archives in nav menus. Default is 'Post Archives' / 'Page Archives'.
1335
 * - `attributes` - Label for the attributes meta box. Default is 'Post Attributes' / 'Page Attributes'.
1336
 * - `insert_into_item` - Label for the media frame button. Default is 'Insert into post' / 'Insert into page'.
1337
 * - `uploaded_to_this_item` - Label for the media frame filter. Default is 'Uploaded to this post' /
1338
 *                           'Uploaded to this page'.
1339
 * - `featured_image` - Label for the Featured Image meta box title. Default is 'Featured Image'.
1340
 * - `set_featured_image` - Label for setting the featured image. Default is 'Set featured image'.
1341
 * - `remove_featured_image` - Label for removing the featured image. Default is 'Remove featured image'.
1342
 * - `use_featured_image` - Label in the media frame for using a featured image. Default is 'Use as featured image'.
1343
 * - `menu_name` - Label for the menu name. Default is the same as `name`.
1344
 * - `filter_items_list` - Label for the table views hidden heading. Default is 'Filter posts list' /
1345
 *                       'Filter pages list'.
1346
 * - `items_list_navigation` - Label for the table pagination hidden heading. Default is 'Posts list navigation' /
1347
 *                           'Pages list navigation'.
1348
 * - `items_list` - Label for the table hidden heading. Default is 'Posts list' / 'Pages list'.
1349
 *
1350
 * Above, the first default value is for non-hierarchical post types (like posts)
1351
 * and the second one is for hierarchical post types (like pages).
1352
 *
1353
 * Note: To set labels used in post type admin notices, see the {@see 'post_updated_messages'} filter.
1354
 *
1355
 * @since 3.0.0
1356
 * @since 4.3.0 Added the `featured_image`, `set_featured_image`, `remove_featured_image`,
1357
 *              and `use_featured_image` labels.
1358
 * @since 4.4.0 Added the `archives`, `insert_into_item`, `uploaded_to_this_item`, `filter_items_list`,
1359
 *              `items_list_navigation`, and `items_list` labels.
1360
 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1361
 * @since 4.7.0 Added the `view_items` and `attributes` labels.
1362
 *
1363
 * @access private
1364
 *
1365
 * @param object|WP_Post_Type $post_type_object Post type object.
1366
 * @return object Object with all the labels as member variables.
1367
 */
1368
function get_post_type_labels( $post_type_object ) {
1369
	$nohier_vs_hier_defaults = array(
1370
		'name' => array( _x('Posts', 'post type general name'), _x('Pages', 'post type general name') ),
1371
		'singular_name' => array( _x('Post', 'post type singular name'), _x('Page', 'post type singular name') ),
1372
		'add_new' => array( _x('Add New', 'post'), _x('Add New', 'page') ),
1373
		'add_new_item' => array( __('Add New Post'), __('Add New Page') ),
1374
		'edit_item' => array( __('Edit Post'), __('Edit Page') ),
1375
		'new_item' => array( __('New Post'), __('New Page') ),
1376
		'view_item' => array( __('View Post'), __('View Page') ),
1377
		'view_items' => array( __('View Posts'), __('View Pages') ),
1378
		'search_items' => array( __('Search Posts'), __('Search Pages') ),
1379
		'not_found' => array( __('No posts found.'), __('No pages found.') ),
1380
		'not_found_in_trash' => array( __('No posts found in Trash.'), __('No pages found in Trash.') ),
1381
		'parent_item_colon' => array( null, __('Parent Page:') ),
1382
		'all_items' => array( __( 'All Posts' ), __( 'All Pages' ) ),
1383
		'archives' => array( __( 'Post Archives' ), __( 'Page Archives' ) ),
1384
		'attributes' => array( __( 'Post Attributes' ), __( 'Page Attributes' ) ),
1385
		'insert_into_item' => array( __( 'Insert into post' ), __( 'Insert into page' ) ),
1386
		'uploaded_to_this_item' => array( __( 'Uploaded to this post' ), __( 'Uploaded to this page' ) ),
1387
		'featured_image' => array( _x( 'Featured Image', 'post' ), _x( 'Featured Image', 'page' ) ),
1388
		'set_featured_image' => array( _x( 'Set featured image', 'post' ), _x( 'Set featured image', 'page' ) ),
1389
		'remove_featured_image' => array( _x( 'Remove featured image', 'post' ), _x( 'Remove featured image', 'page' ) ),
1390
		'use_featured_image' => array( _x( 'Use as featured image', 'post' ), _x( 'Use as featured image', 'page' ) ),
1391
		'filter_items_list' => array( __( 'Filter posts list' ), __( 'Filter pages list' ) ),
1392
		'items_list_navigation' => array( __( 'Posts list navigation' ), __( 'Pages list navigation' ) ),
1393
		'items_list' => array( __( 'Posts list' ), __( 'Pages list' ) ),
1394
	);
1395
	$nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
1396
1397
	$labels = _get_custom_object_labels( $post_type_object, $nohier_vs_hier_defaults );
1398
1399
	$post_type = $post_type_object->name;
1400
1401
	$default_labels = clone $labels;
1402
1403
	/**
1404
	 * Filters the labels of a specific post type.
1405
	 *
1406
	 * The dynamic portion of the hook name, `$post_type`, refers to
1407
	 * the post type slug.
1408
	 *
1409
	 * @since 3.5.0
1410
	 *
1411
	 * @see get_post_type_labels() for the full list of labels.
1412
	 *
1413
	 * @param object $labels Object with labels for the post type as member variables.
1414
	 */
1415
	$labels = apply_filters( "post_type_labels_{$post_type}", $labels );
1416
1417
	// Ensure that the filtered labels contain all required default values.
1418
	$labels = (object) array_merge( (array) $default_labels, (array) $labels );
1419
1420
	return $labels;
1421
}
1422
1423
/**
1424
 * Build an object with custom-something object (post type, taxonomy) labels
1425
 * out of a custom-something object
1426
 *
1427
 * @since 3.0.0
1428
 * @access private
1429
 *
1430
 * @param object $object                  A custom-something object.
1431
 * @param array  $nohier_vs_hier_defaults Hierarchical vs non-hierarchical default labels.
1432
 * @return object Object containing labels for the given custom-something object.
1433
 */
1434
function _get_custom_object_labels( $object, $nohier_vs_hier_defaults ) {
1435
	$object->labels = (array) $object->labels;
1436
1437
	if ( isset( $object->label ) && empty( $object->labels['name'] ) )
1438
		$object->labels['name'] = $object->label;
1439
1440 View Code Duplication
	if ( !isset( $object->labels['singular_name'] ) && isset( $object->labels['name'] ) )
1441
		$object->labels['singular_name'] = $object->labels['name'];
1442
1443 View Code Duplication
	if ( ! isset( $object->labels['name_admin_bar'] ) )
1444
		$object->labels['name_admin_bar'] = isset( $object->labels['singular_name'] ) ? $object->labels['singular_name'] : $object->name;
1445
1446 View Code Duplication
	if ( !isset( $object->labels['menu_name'] ) && isset( $object->labels['name'] ) )
1447
		$object->labels['menu_name'] = $object->labels['name'];
1448
1449 View Code Duplication
	if ( !isset( $object->labels['all_items'] ) && isset( $object->labels['menu_name'] ) )
1450
		$object->labels['all_items'] = $object->labels['menu_name'];
1451
1452 View Code Duplication
	if ( !isset( $object->labels['archives'] ) && isset( $object->labels['all_items'] ) ) {
1453
		$object->labels['archives'] = $object->labels['all_items'];
1454
	}
1455
1456
	$defaults = array();
1457
	foreach ( $nohier_vs_hier_defaults as $key => $value ) {
1458
		$defaults[$key] = $object->hierarchical ? $value[1] : $value[0];
1459
	}
1460
	$labels = array_merge( $defaults, $object->labels );
1461
	$object->labels = (object) $object->labels;
1462
1463
	return (object) $labels;
1464
}
1465
1466
/**
1467
 * Add submenus for post types.
1468
 *
1469
 * @access private
1470
 * @since 3.1.0
1471
 */
1472
function _add_post_type_submenus() {
1473
	foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
1474
		$ptype_obj = get_post_type_object( $ptype );
1475
		// Sub-menus only.
1476
		if ( ! $ptype_obj->show_in_menu || $ptype_obj->show_in_menu === true )
1477
			continue;
1478
		add_submenu_page( $ptype_obj->show_in_menu, $ptype_obj->labels->name, $ptype_obj->labels->all_items, $ptype_obj->cap->edit_posts, "edit.php?post_type=$ptype" );
1479
	}
1480
}
1481
1482
/**
1483
 * Register support of certain features for a post type.
1484
 *
1485
 * All core features are directly associated with a functional area of the edit
1486
 * screen, such as the editor or a meta box. Features include: 'title', 'editor',
1487
 * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
1488
 * 'thumbnail', 'custom-fields', and 'post-formats'.
1489
 *
1490
 * Additionally, the 'revisions' feature dictates whether the post type will
1491
 * store revisions, and the 'comments' feature dictates whether the comments
1492
 * count will show on the edit screen.
1493
 *
1494
 * @since 3.0.0
1495
 *
1496
 * @global array $_wp_post_type_features
1497
 *
1498
 * @param string       $post_type The post type for which to add the feature.
1499
 * @param string|array $feature   The feature being added, accepts an array of
1500
 *                                feature strings or a single string.
1501
 */
1502
function add_post_type_support( $post_type, $feature ) {
1503
	global $_wp_post_type_features;
1504
1505
	$features = (array) $feature;
1506
	foreach ($features as $feature) {
1507
		if ( func_num_args() == 2 )
1508
			$_wp_post_type_features[$post_type][$feature] = true;
1509
		else
1510
			$_wp_post_type_features[$post_type][$feature] = array_slice( func_get_args(), 2 );
1511
	}
1512
}
1513
1514
/**
1515
 * Remove support for a feature from a post type.
1516
 *
1517
 * @since 3.0.0
1518
 *
1519
 * @global array $_wp_post_type_features
1520
 *
1521
 * @param string $post_type The post type for which to remove the feature.
1522
 * @param string $feature   The feature being removed.
1523
 */
1524
function remove_post_type_support( $post_type, $feature ) {
1525
	global $_wp_post_type_features;
1526
1527
	unset( $_wp_post_type_features[ $post_type ][ $feature ] );
1528
}
1529
1530
/**
1531
 * Get all the post type features
1532
 *
1533
 * @since 3.4.0
1534
 *
1535
 * @global array $_wp_post_type_features
1536
 *
1537
 * @param string $post_type The post type.
1538
 * @return array Post type supports list.
1539
 */
1540
function get_all_post_type_supports( $post_type ) {
1541
	global $_wp_post_type_features;
1542
1543
	if ( isset( $_wp_post_type_features[$post_type] ) )
1544
		return $_wp_post_type_features[$post_type];
1545
1546
	return array();
1547
}
1548
1549
/**
1550
 * Check a post type's support for a given feature.
1551
 *
1552
 * @since 3.0.0
1553
 *
1554
 * @global array $_wp_post_type_features
1555
 *
1556
 * @param string $post_type The post type being checked.
1557
 * @param string $feature   The feature being checked.
1558
 * @return bool Whether the post type supports the given feature.
1559
 */
1560
function post_type_supports( $post_type, $feature ) {
1561
	global $_wp_post_type_features;
1562
1563
	return ( isset( $_wp_post_type_features[$post_type][$feature] ) );
1564
}
1565
1566
/**
1567
 * Retrieves a list of post type names that support a specific feature.
1568
 *
1569
 * @since 4.5.0
1570
 *
1571
 * @global array $_wp_post_type_features Post type features
1572
 *
1573
 * @param array|string $feature  Single feature or an array of features the post types should support.
1574
 * @param string       $operator Optional. The logical operation to perform. 'or' means
1575
 *                               only one element from the array needs to match; 'and'
1576
 *                               means all elements must match; 'not' means no elements may
1577
 *                               match. Default 'and'.
1578
 * @return array A list of post type names.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use array<integer|string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1579
 */
1580
function get_post_types_by_support( $feature, $operator = 'and' ) {
1581
	global $_wp_post_type_features;
1582
1583
	$features = array_fill_keys( (array) $feature, true );
1584
1585
	return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
1586
}
1587
1588
/**
1589
 * Update the post type for the post ID.
1590
 *
1591
 * The page or post cache will be cleaned for the post ID.
1592
 *
1593
 * @since 2.5.0
1594
 *
1595
 * @global wpdb $wpdb WordPress database abstraction object.
1596
 *
1597
 * @param int    $post_id   Optional. Post ID to change post type. Default 0.
1598
 * @param string $post_type Optional. Post type. Accepts 'post' or 'page' to
1599
 *                          name a few. Default 'post'.
1600
 * @return int|false Amount of rows changed. Should be 1 for success and 0 for failure.
1601
 */
1602
function set_post_type( $post_id = 0, $post_type = 'post' ) {
1603
	global $wpdb;
1604
1605
	$post_type = sanitize_post_field('post_type', $post_type, $post_id, 'db');
1606
	$return = $wpdb->update( $wpdb->posts, array('post_type' => $post_type), array('ID' => $post_id) );
1607
1608
	clean_post_cache( $post_id );
1609
1610
	return $return;
1611
}
1612
1613
/**
1614
 * Determines whether a post type is considered "viewable".
1615
 *
1616
 * For built-in post types such as posts and pages, the 'public' value will be evaluated.
1617
 * For all others, the 'publicly_queryable' value will be used.
1618
 *
1619
 * @since 4.4.0
1620
 * @since 4.5.0 Added the ability to pass a post type name in addition to object.
1621
 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1622
 *
1623
 * @param string|WP_Post_Type $post_type Post type name or object.
1624
 * @return bool Whether the post type should be considered viewable.
1625
 */
1626
function is_post_type_viewable( $post_type ) {
1627
	if ( is_scalar( $post_type ) ) {
1628
		$post_type = get_post_type_object( $post_type );
1629
		if ( ! $post_type ) {
1630
			return false;
1631
		}
1632
	}
1633
1634
	return $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
1635
}
1636
1637
/**
1638
 * Retrieve list of latest posts or posts matching criteria.
1639
 *
1640
 * The defaults are as follows:
1641
 *
1642
 * @since 1.2.0
1643
 *
1644
 * @see WP_Query::parse_query()
1645
 *
1646
 * @param array $args {
0 ignored issues
show
Should the type for parameter $args not be array|null? Also, consider making the array more specific, something like array<String>, or String[].

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive. In addition it looks for parameters that have the generic type array and suggests a stricter type like array<String>.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
1647
 *     Optional. Arguments to retrieve posts. See WP_Query::parse_query() for all
1648
 *     available arguments.
1649
 *
1650
 *     @type int        $numberposts      Total number of posts to retrieve. Is an alias of $posts_per_page
1651
 *                                        in WP_Query. Accepts -1 for all. Default 5.
1652
 *     @type int|string $category         Category ID or comma-separated list of IDs (this or any children).
1653
 *                                        Is an alias of $cat in WP_Query. Default 0.
1654
 *     @type array      $include          An array of post IDs to retrieve, sticky posts will be included.
1655
 *                                        Is an alias of $post__in in WP_Query. Default empty array.
1656
 *     @type array      $exclude          An array of post IDs not to retrieve. Default empty array.
1657
 *     @type bool       $suppress_filters Whether to suppress filters. Default true.
1658
 * }
1659
 * @return array List of posts.
1660
 */
1661
function get_posts( $args = null ) {
1662
	$defaults = array(
1663
		'numberposts' => 5,
1664
		'category' => 0, 'orderby' => 'date',
1665
		'order' => 'DESC', 'include' => array(),
1666
		'exclude' => array(), 'meta_key' => '',
1667
		'meta_value' =>'', 'post_type' => 'post',
1668
		'suppress_filters' => true
1669
	);
1670
1671
	$r = wp_parse_args( $args, $defaults );
0 ignored issues
show
It seems like $args defined by parameter $args on line 1661 can also be of type null; however, wp_parse_args() does only seem to accept string|array|object, maybe add an additional type check?

This check looks at variables that have been passed in as parameters and are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
1672
	if ( empty( $r['post_status'] ) )
1673
		$r['post_status'] = ( 'attachment' == $r['post_type'] ) ? 'inherit' : 'publish';
1674
	if ( ! empty($r['numberposts']) && empty($r['posts_per_page']) )
1675
		$r['posts_per_page'] = $r['numberposts'];
1676
	if ( ! empty($r['category']) )
1677
		$r['cat'] = $r['category'];
1678
	if ( ! empty($r['include']) ) {
1679
		$incposts = wp_parse_id_list( $r['include'] );
1680
		$r['posts_per_page'] = count($incposts);  // only the number of posts included
1681
		$r['post__in'] = $incposts;
1682
	} elseif ( ! empty($r['exclude']) )
1683
		$r['post__not_in'] = wp_parse_id_list( $r['exclude'] );
1684
1685
	$r['ignore_sticky_posts'] = true;
1686
	$r['no_found_rows'] = true;
1687
1688
	$get_posts = new WP_Query;
1689
	return $get_posts->query($r);
1690
1691
}
1692
1693
//
1694
// Post meta functions
1695
//
1696
1697
/**
1698
 * Add meta data field to a post.
1699
 *
1700
 * Post meta data is called "Custom Fields" on the Administration Screen.
1701
 *
1702
 * @since 1.5.0
1703
 *
1704
 * @param int    $post_id    Post ID.
1705
 * @param string $meta_key   Metadata name.
1706
 * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
1707
 * @param bool   $unique     Optional. Whether the same key should not be added.
1708
 *                           Default false.
1709
 * @return int|false Meta ID on success, false on failure.
1710
 */
1711
function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
1712
	// Make sure meta is added to the post, not a revision.
1713
	if ( $the_post = wp_is_post_revision($post_id) )
1714
		$post_id = $the_post;
1715
1716
	return add_metadata('post', $post_id, $meta_key, $meta_value, $unique);
1717
}
1718
1719
/**
1720
 * Remove metadata matching criteria from a post.
1721
 *
1722
 * You can match based on the key, or key and value. Removing based on key and
1723
 * value, will keep from removing duplicate metadata with the same key. It also
1724
 * allows removing all metadata matching key, if needed.
1725
 *
1726
 * @since 1.5.0
1727
 *
1728
 * @param int    $post_id    Post ID.
1729
 * @param string $meta_key   Metadata name.
1730
 * @param mixed  $meta_value Optional. Metadata value. Must be serializable if
1731
 *                           non-scalar. Default empty.
1732
 * @return bool True on success, false on failure.
1733
 */
1734
function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
1735
	// Make sure meta is added to the post, not a revision.
1736
	if ( $the_post = wp_is_post_revision($post_id) )
1737
		$post_id = $the_post;
1738
1739
	return delete_metadata('post', $post_id, $meta_key, $meta_value);
1740
}
1741
1742
/**
1743
 * Retrieve post meta field for a post.
1744
 *
1745
 * @since 1.5.0
1746
 *
1747
 * @param int    $post_id Post ID.
1748
 * @param string $key     Optional. The meta key to retrieve. By default, returns
1749
 *                        data for all keys. Default empty.
1750
 * @param bool   $single  Optional. Whether to return a single value. Default false.
1751
 * @return mixed Will be an array if $single is false. Will be value of meta data
1752
 *               field if $single is true.
1753
 */
1754
function get_post_meta( $post_id, $key = '', $single = false ) {
1755
	return get_metadata('post', $post_id, $key, $single);
1756
}
1757
1758
/**
1759
 * Update post meta field based on post ID.
1760
 *
1761
 * Use the $prev_value parameter to differentiate between meta fields with the
1762
 * same key and post ID.
1763
 *
1764
 * If the meta field for the post does not exist, it will be added.
1765
 *
1766
 * @since 1.5.0
1767
 *
1768
 * @param int    $post_id    Post ID.
1769
 * @param string $meta_key   Metadata key.
1770
 * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
1771
 * @param mixed  $prev_value Optional. Previous value to check before removing.
1772
 *                           Default empty.
1773
 * @return int|bool Meta ID if the key didn't exist, true on successful update,
1774
 *                  false on failure.
1775
 */
1776
function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
1777
	// Make sure meta is added to the post, not a revision.
1778
	if ( $the_post = wp_is_post_revision($post_id) )
1779
		$post_id = $the_post;
1780
1781
	return update_metadata('post', $post_id, $meta_key, $meta_value, $prev_value);
1782
}
1783
1784
/**
1785
 * Delete everything from post meta matching meta key.
1786
 *
1787
 * @since 2.3.0
1788
 *
1789
 * @param string $post_meta_key Key to search for when deleting.
1790
 * @return bool Whether the post meta key was deleted from the database.
1791
 */
1792
function delete_post_meta_by_key( $post_meta_key ) {
1793
	return delete_metadata( 'post', null, $post_meta_key, '', true );
1794
}
1795
1796
/**
1797
 * Retrieve post meta fields, based on post ID.
1798
 *
1799
 * The post meta fields are retrieved from the cache where possible,
1800
 * so the function is optimized to be called more than once.
1801
 *
1802
 * @since 1.2.0
1803
 *
1804
 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
1805
 * @return array Post meta for the given post.
1806
 */
1807
function get_post_custom( $post_id = 0 ) {
1808
	$post_id = absint( $post_id );
1809
	if ( ! $post_id )
1810
		$post_id = get_the_ID();
1811
1812
	return get_post_meta( $post_id );
0 ignored issues
show
It seems like $post_id can also be of type double or false; however, get_post_meta() does only seem to accept integer, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
1813
}
1814
1815
/**
1816
 * Retrieve meta field names for a post.
1817
 *
1818
 * If there are no meta fields, then nothing (null) will be returned.
1819
 *
1820
 * @since 1.2.0
1821
 *
1822
 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
1823
 * @return array|void Array of the keys, if retrieved.
0 ignored issues
show
Consider making the return type a bit more specific; maybe use null|array<integer|string>.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1824
 */
1825
function get_post_custom_keys( $post_id = 0 ) {
1826
	$custom = get_post_custom( $post_id );
1827
1828
	if ( !is_array($custom) )
1829
		return;
1830
1831
	if ( $keys = array_keys($custom) )
1832
		return $keys;
1833
}
1834
1835
/**
1836
 * Retrieve values for a custom post field.
1837
 *
1838
 * The parameters must not be considered optional. All of the post meta fields
1839
 * will be retrieved and only the meta field key values returned.
1840
 *
1841
 * @since 1.2.0
1842
 *
1843
 * @param string $key     Optional. Meta field key. Default empty.
1844
 * @param int    $post_id Optional. Post ID. Default is ID of the global $post.
1845
 * @return array|null Meta field values.
1846
 */
1847
function get_post_custom_values( $key = '', $post_id = 0 ) {
1848
	if ( !$key )
1849
		return null;
1850
1851
	$custom = get_post_custom($post_id);
1852
1853
	return isset($custom[$key]) ? $custom[$key] : null;
1854
}
1855
1856
/**
1857
 * Check if post is sticky.
1858
 *
1859
 * Sticky posts should remain at the top of The Loop. If the post ID is not
1860
 * given, then The Loop ID for the current post will be used.
1861
 *
1862
 * @since 2.7.0
1863
 *
1864
 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
1865
 * @return bool Whether post is sticky.
1866
 */
1867
function is_sticky( $post_id = 0 ) {
1868
	$post_id = absint( $post_id );
1869
1870
	if ( ! $post_id )
1871
		$post_id = get_the_ID();
1872
1873
	$stickies = get_option( 'sticky_posts' );
1874
1875
	if ( ! is_array( $stickies ) )
1876
		return false;
1877
1878
	if ( in_array( $post_id, $stickies ) )
0 ignored issues
show
This if statement, and the following return statement can be replaced with return in_array($post_id, $stickies);.
Loading history...
1879
		return true;
1880
1881
	return false;
1882
}
1883
1884
/**
1885
 * Sanitize every post field.
1886
 *
1887
 * If the context is 'raw', then the post object or array will get minimal
1888
 * sanitization of the integer fields.
1889
 *
1890
 * @since 2.3.0
1891
 *
1892
 * @see sanitize_post_field()
1893
 *
1894
 * @param object|WP_Post|array $post    The Post Object or Array
1895
 * @param string               $context Optional. How to sanitize post fields.
1896
 *                                      Accepts 'raw', 'edit', 'db', or 'display'.
1897
 *                                      Default 'display'.
1898
 * @return object|WP_Post|array The now sanitized Post Object or Array (will be the
0 ignored issues
show
Consider making the return type a bit more specific; maybe use object|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1899
 *                              same type as $post).
1900
 */
1901
function sanitize_post( $post, $context = 'display' ) {
1902
	if ( is_object($post) ) {
1903
		// Check if post already filtered for this context.
1904
		if ( isset($post->filter) && $context == $post->filter )
1905
			return $post;
1906
		if ( !isset($post->ID) )
1907
			$post->ID = 0;
1908
		foreach ( array_keys(get_object_vars($post)) as $field )
1909
			$post->$field = sanitize_post_field($field, $post->$field, $post->ID, $context);
1910
		$post->filter = $context;
1911
	} elseif ( is_array( $post ) ) {
1912
		// Check if post already filtered for this context.
1913
		if ( isset($post['filter']) && $context == $post['filter'] )
1914
			return $post;
1915
		if ( !isset($post['ID']) )
1916
			$post['ID'] = 0;
1917
		foreach ( array_keys($post) as $field )
1918
			$post[$field] = sanitize_post_field($field, $post[$field], $post['ID'], $context);
1919
		$post['filter'] = $context;
1920
	}
1921
	return $post;
1922
}
1923
1924
/**
1925
 * Sanitize post field based on context.
1926
 *
1927
 * Possible context values are:  'raw', 'edit', 'db', 'display', 'attribute' and
1928
 * 'js'. The 'display' context is used by default. 'attribute' and 'js' contexts
1929
 * are treated like 'display' when calling filters.
1930
 *
1931
 * @since 2.3.0
1932
 * @since 4.4.0 Like `sanitize_post()`, `$context` defaults to 'display'.
1933
 *
1934
 * @param string $field   The Post Object field name.
1935
 * @param mixed  $value   The Post Object value.
1936
 * @param int    $post_id Post ID.
1937
 * @param string $context Optional. How to sanitize post fields. Looks for 'raw', 'edit',
1938
 *                        'db', 'display', 'attribute' and 'js'. Default 'display'.
1939
 * @return mixed Sanitized value.
1940
 */
1941
function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
1942
	$int_fields = array('ID', 'post_parent', 'menu_order');
1943
	if ( in_array($field, $int_fields) )
1944
		$value = (int) $value;
1945
1946
	// Fields which contain arrays of integers.
1947
	$array_int_fields = array( 'ancestors' );
1948
	if ( in_array($field, $array_int_fields) ) {
1949
		$value = array_map( 'absint', $value);
1950
		return $value;
1951
	}
1952
1953
	if ( 'raw' == $context )
1954
		return $value;
1955
1956
	$prefixed = false;
1957
	if ( false !== strpos($field, 'post_') ) {
1958
		$prefixed = true;
1959
		$field_no_prefix = str_replace('post_', '', $field);
1960
	}
1961
1962
	if ( 'edit' == $context ) {
1963
		$format_to_edit = array('post_content', 'post_excerpt', 'post_title', 'post_password');
1964
1965
		if ( $prefixed ) {
1966
1967
			/**
1968
			 * Filters the value of a specific post field to edit.
1969
			 *
1970
			 * The dynamic portion of the hook name, `$field`, refers to the post
1971
			 * field name.
1972
			 *
1973
			 * @since 2.3.0
1974
			 *
1975
			 * @param mixed $value   Value of the post field.
1976
			 * @param int   $post_id Post ID.
1977
			 */
1978
			$value = apply_filters( "edit_{$field}", $value, $post_id );
1979
1980
			/**
1981
			 * Filters the value of a specific post field to edit.
1982
			 *
1983
			 * The dynamic portion of the hook name, `$field_no_prefix`, refers to
1984
			 * the post field name.
1985
			 *
1986
			 * @since 2.3.0
1987
			 *
1988
			 * @param mixed $value   Value of the post field.
1989
			 * @param int   $post_id Post ID.
1990
			 */
1991
			$value = apply_filters( "{$field_no_prefix}_edit_pre", $value, $post_id );
0 ignored issues
show
The variable $field_no_prefix 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...
1992
		} else {
1993
			$value = apply_filters( "edit_post_{$field}", $value, $post_id );
1994
		}
1995
1996
		if ( in_array($field, $format_to_edit) ) {
1997
			if ( 'post_content' == $field )
1998
				$value = format_to_edit($value, user_can_richedit());
1999
			else
2000
				$value = format_to_edit($value);
2001
		} else {
2002
			$value = esc_attr($value);
2003
		}
2004 View Code Duplication
	} elseif ( 'db' == $context ) {
2005
		if ( $prefixed ) {
2006
2007
			/**
2008
			 * Filters the value of a specific post field before saving.
2009
			 *
2010
			 * The dynamic portion of the hook name, `$field`, refers to the post
2011
			 * field name.
2012
			 *
2013
			 * @since 2.3.0
2014
			 *
2015
			 * @param mixed $value Value of the post field.
2016
			 */
2017
			$value = apply_filters( "pre_{$field}", $value );
2018
2019
			/**
2020
			 * Filters the value of a specific field before saving.
2021
			 *
2022
			 * The dynamic portion of the hook name, `$field_no_prefix`, refers
2023
			 * to the post field name.
2024
			 *
2025
			 * @since 2.3.0
2026
			 *
2027
			 * @param mixed $value Value of the post field.
2028
			 */
2029
			$value = apply_filters( "{$field_no_prefix}_save_pre", $value );
2030
		} else {
2031
			$value = apply_filters( "pre_post_{$field}", $value );
2032
2033
			/**
2034
			 * Filters the value of a specific post field before saving.
2035
			 *
2036
			 * The dynamic portion of the hook name, `$field`, refers to the post
2037
			 * field name.
2038
			 *
2039
			 * @since 2.3.0
2040
			 *
2041
			 * @param mixed $value Value of the post field.
2042
			 */
2043
			$value = apply_filters( "{$field}_pre", $value );
2044
		}
2045
	} else {
2046
2047
		// Use display filters by default.
2048
		if ( $prefixed ) {
2049
2050
			/**
2051
			 * Filters the value of a specific post field for display.
2052
			 *
2053
			 * The dynamic portion of the hook name, `$field`, refers to the post
2054
			 * field name.
2055
			 *
2056
			 * @since 2.3.0
2057
			 *
2058
			 * @param mixed  $value   Value of the prefixed post field.
2059
			 * @param int    $post_id Post ID.
2060
			 * @param string $context Context for how to sanitize the field. Possible
2061
			 *                        values include 'raw', 'edit', 'db', 'display',
2062
			 *                        'attribute' and 'js'.
2063
			 */
2064
			$value = apply_filters( "{$field}", $value, $post_id, $context );
2065
		} else {
2066
			$value = apply_filters( "post_{$field}", $value, $post_id, $context );
2067
		}
2068
2069 View Code Duplication
		if ( 'attribute' == $context ) {
2070
			$value = esc_attr( $value );
2071
		} elseif ( 'js' == $context ) {
2072
			$value = esc_js( $value );
2073
		}
2074
	}
2075
2076
	return $value;
2077
}
2078
2079
/**
2080
 * Make a post sticky.
2081
 *
2082
 * Sticky posts should be displayed at the top of the front page.
2083
 *
2084
 * @since 2.7.0
2085
 *
2086
 * @param int $post_id Post ID.
2087
 */
2088
function stick_post( $post_id ) {
2089
	$stickies = get_option('sticky_posts');
2090
2091
	if ( !is_array($stickies) )
2092
		$stickies = array($post_id);
2093
2094
	if ( ! in_array($post_id, $stickies) )
2095
		$stickies[] = $post_id;
2096
2097
	$updated = update_option( 'sticky_posts', $stickies );
2098
2099
	if ( $updated ) {
2100
		/**
2101
		 * Fires once a post has been added to the sticky list.
2102
		 *
2103
		 * @since 4.6.0
2104
		 *
2105
		 * @param int $post_id ID of the post that was stuck.
2106
		 */
2107
		do_action( 'post_stuck', $post_id );
2108
	}
2109
}
2110
2111
/**
2112
 * Un-stick a post.
2113
 *
2114
 * Sticky posts should be displayed at the top of the front page.
2115
 *
2116
 * @since 2.7.0
2117
 *
2118
 * @param int $post_id Post ID.
2119
 */
2120
function unstick_post( $post_id ) {
2121
	$stickies = get_option('sticky_posts');
2122
2123
	if ( !is_array($stickies) )
2124
		return;
2125
2126
	if ( ! in_array($post_id, $stickies) )
2127
		return;
2128
2129
	$offset = array_search($post_id, $stickies);
2130
	if ( false === $offset )
2131
		return;
2132
2133
	array_splice($stickies, $offset, 1);
2134
2135
	$updated = update_option( 'sticky_posts', $stickies );
2136
2137
	if ( $updated ) {
2138
		/**
2139
		 * Fires once a post has been removed from the sticky list.
2140
		 *
2141
		 * @since 4.6.0
2142
		 *
2143
		 * @param int $post_id ID of the post that was unstuck.
2144
		 */
2145
		do_action( 'post_unstuck', $post_id );
2146
	}
2147
}
2148
2149
/**
2150
 * Return the cache key for wp_count_posts() based on the passed arguments.
2151
 *
2152
 * @since 3.9.0
2153
 *
2154
 * @param string $type Optional. Post type to retrieve count Default 'post'.
2155
 * @param string $perm Optional. 'readable' or empty. Default empty.
2156
 * @return string The cache key.
2157
 */
2158
function _count_posts_cache_key( $type = 'post', $perm = '' ) {
2159
	$cache_key = 'posts-' . $type;
2160
	if ( 'readable' == $perm && is_user_logged_in() ) {
2161
		$post_type_object = get_post_type_object( $type );
2162
		if ( $post_type_object && ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2163
			$cache_key .= '_' . $perm . '_' . get_current_user_id();
2164
		}
2165
	}
2166
	return $cache_key;
2167
}
2168
2169
/**
2170
 * Count number of posts of a post type and if user has permissions to view.
2171
 *
2172
 * This function provides an efficient method of finding the amount of post's
2173
 * type a blog has. Another method is to count the amount of items in
2174
 * get_posts(), but that method has a lot of overhead with doing so. Therefore,
2175
 * when developing for 2.5+, use this function instead.
2176
 *
2177
 * The $perm parameter checks for 'readable' value and if the user can read
2178
 * private posts, it will display that for the user that is signed in.
2179
 *
2180
 * @since 2.5.0
2181
 *
2182
 * @global wpdb $wpdb WordPress database abstraction object.
2183
 *
2184
 * @param string $type Optional. Post type to retrieve count. Default 'post'.
2185
 * @param string $perm Optional. 'readable' or empty. Default empty.
2186
 * @return object Number of posts for each status.
2187
 */
2188
function wp_count_posts( $type = 'post', $perm = '' ) {
2189
	global $wpdb;
2190
2191
	if ( ! post_type_exists( $type ) )
2192
		return new stdClass;
2193
2194
	$cache_key = _count_posts_cache_key( $type, $perm );
2195
2196
	$counts = wp_cache_get( $cache_key, 'counts' );
2197
	if ( false !== $counts ) {
2198
		/** This filter is documented in wp-includes/post.php */
2199
		return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2200
	}
2201
2202
	$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
2203
	if ( 'readable' == $perm && is_user_logged_in() ) {
2204
		$post_type_object = get_post_type_object($type);
2205
		if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2206
			$query .= $wpdb->prepare( " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
2207
				get_current_user_id()
2208
			);
2209
		}
2210
	}
2211
	$query .= ' GROUP BY post_status';
2212
2213
	$results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
2214
	$counts = array_fill_keys( get_post_stati(), 0 );
2215
2216
	foreach ( $results as $row ) {
2217
		$counts[ $row['post_status'] ] = $row['num_posts'];
2218
	}
2219
2220
	$counts = (object) $counts;
2221
	wp_cache_set( $cache_key, $counts, 'counts' );
2222
2223
	/**
2224
	 * Modify returned post counts by status for the current post type.
2225
	 *
2226
	 * @since 3.7.0
2227
	 *
2228
	 * @param object $counts An object containing the current post_type's post
2229
	 *                       counts by status.
2230
	 * @param string $type   Post type.
2231
	 * @param string $perm   The permission to determine if the posts are 'readable'
2232
	 *                       by the current user.
2233
	 */
2234
	return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2235
}
2236
2237
/**
2238
 * Count number of attachments for the mime type(s).
2239
 *
2240
 * If you set the optional mime_type parameter, then an array will still be
2241
 * returned, but will only have the item you are looking for. It does not give
2242
 * you the number of attachments that are children of a post. You can get that
2243
 * by counting the number of children that post has.
2244
 *
2245
 * @since 2.5.0
2246
 *
2247
 * @global wpdb $wpdb WordPress database abstraction object.
2248
 *
2249
 * @param string|array $mime_type Optional. Array or comma-separated list of
2250
 *                                MIME patterns. Default empty.
2251
 * @return object An object containing the attachment counts by mime type.
2252
 */
2253
function wp_count_attachments( $mime_type = '' ) {
2254
	global $wpdb;
2255
2256
	$and = wp_post_mime_type_where( $mime_type );
2257
	$count = $wpdb->get_results( "SELECT post_mime_type, COUNT( * ) AS num_posts FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status != 'trash' $and GROUP BY post_mime_type", ARRAY_A );
2258
2259
	$counts = array();
2260
	foreach ( (array) $count as $row ) {
2261
		$counts[ $row['post_mime_type'] ] = $row['num_posts'];
2262
	}
2263
	$counts['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and");
2264
2265
	/**
2266
	 * Modify returned attachment counts by mime type.
2267
	 *
2268
	 * @since 3.7.0
2269
	 *
2270
	 * @param object $counts    An object containing the attachment counts by
2271
	 *                          mime type.
2272
	 * @param string $mime_type The mime type pattern used to filter the attachments
2273
	 *                          counted.
2274
	 */
2275
	return apply_filters( 'wp_count_attachments', (object) $counts, $mime_type );
2276
}
2277
2278
/**
2279
 * Get default post mime types.
2280
 *
2281
 * @since 2.9.0
2282
 *
2283
 * @return array List of post mime types.
2284
 */
2285
function get_post_mime_types() {
2286
	$post_mime_types = array(	//	array( adj, noun )
2287
		'image' => array(__('Images'), __('Manage Images'), _n_noop('Image <span class="count">(%s)</span>', 'Images <span class="count">(%s)</span>')),
2288
		'audio' => array(__('Audio'), __('Manage Audio'), _n_noop('Audio <span class="count">(%s)</span>', 'Audio <span class="count">(%s)</span>')),
2289
		'video' => array(__('Video'), __('Manage Video'), _n_noop('Video <span class="count">(%s)</span>', 'Video <span class="count">(%s)</span>')),
2290
	);
2291
2292
	/**
2293
	 * Filters the default list of post mime types.
2294
	 *
2295
	 * @since 2.5.0
2296
	 *
2297
	 * @param array $post_mime_types Default list of post mime types.
2298
	 */
2299
	return apply_filters( 'post_mime_types', $post_mime_types );
2300
}
2301
2302
/**
2303
 * Check a MIME-Type against a list.
2304
 *
2305
 * If the wildcard_mime_types parameter is a string, it must be comma separated
2306
 * list. If the real_mime_types is a string, it is also comma separated to
2307
 * create the list.
2308
 *
2309
 * @since 2.5.0
2310
 *
2311
 * @param string|array $wildcard_mime_types Mime types, e.g. audio/mpeg or image (same as image/*)
2312
 *                                          or flash (same as *flash*).
2313
 * @param string|array $real_mime_types     Real post mime type values.
2314
 * @return array array(wildcard=>array(real types)).
2315
 */
2316
function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) {
2317
	$matches = array();
2318
	if ( is_string( $wildcard_mime_types ) ) {
2319
		$wildcard_mime_types = array_map( 'trim', explode( ',', $wildcard_mime_types ) );
2320
	}
2321
	if ( is_string( $real_mime_types ) ) {
2322
		$real_mime_types = array_map( 'trim', explode( ',', $real_mime_types ) );
2323
	}
2324
2325
	$patternses = array();
2326
	$wild = '[-._a-z0-9]*';
2327
2328
	foreach ( (array) $wildcard_mime_types as $type ) {
2329
		$mimes = array_map( 'trim', explode( ',', $type ) );
2330
		foreach ( $mimes as $mime ) {
2331
			$regex = str_replace( '__wildcard__', $wild, preg_quote( str_replace( '*', '__wildcard__', $mime ) ) );
2332
			$patternses[][$type] = "^$regex$";
2333
			if ( false === strpos( $mime, '/' ) ) {
2334
				$patternses[][$type] = "^$regex/";
2335
				$patternses[][$type] = $regex;
2336
			}
2337
		}
2338
	}
2339
	asort( $patternses );
2340
2341
	foreach ( $patternses as $patterns ) {
2342
		foreach ( $patterns as $type => $pattern ) {
2343
			foreach ( (array) $real_mime_types as $real ) {
2344
				if ( preg_match( "#$pattern#", $real ) && ( empty( $matches[$type] ) || false === array_search( $real, $matches[$type] ) ) ) {
2345
					$matches[$type][] = $real;
2346
				}
2347
			}
2348
		}
2349
	}
2350
	return $matches;
2351
}
2352
2353
/**
2354
 * Convert MIME types into SQL.
2355
 *
2356
 * @since 2.5.0
2357
 *
2358
 * @param string|array $post_mime_types List of mime types or comma separated string
2359
 *                                      of mime types.
2360
 * @param string       $table_alias     Optional. Specify a table alias, if needed.
2361
 *                                      Default empty.
2362
 * @return string The SQL AND clause for mime searching.
2363
 */
2364
function wp_post_mime_type_where( $post_mime_types, $table_alias = '' ) {
2365
	$where = '';
2366
	$wildcards = array('', '%', '%/%');
2367
	if ( is_string($post_mime_types) )
2368
		$post_mime_types = array_map('trim', explode(',', $post_mime_types));
2369
2370
	$wheres = array();
2371
2372
	foreach ( (array) $post_mime_types as $mime_type ) {
2373
		$mime_type = preg_replace('/\s/', '', $mime_type);
2374
		$slashpos = strpos($mime_type, '/');
2375
		if ( false !== $slashpos ) {
2376
			$mime_group = preg_replace('/[^-*.a-zA-Z0-9]/', '', substr($mime_type, 0, $slashpos));
2377
			$mime_subgroup = preg_replace('/[^-*.+a-zA-Z0-9]/', '', substr($mime_type, $slashpos + 1));
2378
			if ( empty($mime_subgroup) )
2379
				$mime_subgroup = '*';
2380
			else
2381
				$mime_subgroup = str_replace('/', '', $mime_subgroup);
2382
			$mime_pattern = "$mime_group/$mime_subgroup";
2383
		} else {
2384
			$mime_pattern = preg_replace('/[^-*.a-zA-Z0-9]/', '', $mime_type);
2385
			if ( false === strpos($mime_pattern, '*') )
2386
				$mime_pattern .= '/*';
2387
		}
2388
2389
		$mime_pattern = preg_replace('/\*+/', '%', $mime_pattern);
2390
2391
		if ( in_array( $mime_type, $wildcards ) )
2392
			return '';
2393
2394
		if ( false !== strpos($mime_pattern, '%') )
2395
			$wheres[] = empty($table_alias) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
2396
		else
2397
			$wheres[] = empty($table_alias) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
2398
	}
2399
	if ( !empty($wheres) )
2400
		$where = ' AND (' . join(' OR ', $wheres) . ') ';
2401
	return $where;
2402
}
2403
2404
/**
2405
 * Trash or delete a post or page.
2406
 *
2407
 * When the post and page is permanently deleted, everything that is tied to
2408
 * it is deleted also. This includes comments, post meta fields, and terms
2409
 * associated with the post.
2410
 *
2411
 * The post or page is moved to trash instead of permanently deleted unless
2412
 * trash is disabled, item is already in the trash, or $force_delete is true.
2413
 *
2414
 * @since 1.0.0
2415
 *
2416
 * @global wpdb $wpdb WordPress database abstraction object.
2417
 * @see wp_delete_attachment()
2418
 * @see wp_trash_post()
2419
 *
2420
 * @param int  $postid       Optional. Post ID. Default 0.
2421
 * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
2422
 *                           Default false.
2423
 * @return array|false|WP_Post False on failure.
2424
 */
2425
function wp_delete_post( $postid = 0, $force_delete = false ) {
2426
	global $wpdb;
2427
2428
	if ( !$post = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $postid)) )
2429
		return $post;
2430
2431
	if ( !$force_delete && ( $post->post_type == 'post' || $post->post_type == 'page') && get_post_status( $postid ) != 'trash' && EMPTY_TRASH_DAYS )
2432
		return wp_trash_post( $postid );
2433
2434
	if ( $post->post_type == 'attachment' )
2435
		return wp_delete_attachment( $postid, $force_delete );
2436
2437
	/**
2438
	 * Filters whether a post deletion should take place.
2439
	 *
2440
	 * @since 4.4.0
2441
	 *
2442
	 * @param bool    $delete       Whether to go forward with deletion.
2443
	 * @param WP_Post $post         Post object.
2444
	 * @param bool    $force_delete Whether to bypass the trash.
2445
	 */
2446
	$check = apply_filters( 'pre_delete_post', null, $post, $force_delete );
2447
	if ( null !== $check ) {
2448
		return $check;
2449
	}
2450
2451
	/**
2452
	 * Fires before a post is deleted, at the start of wp_delete_post().
2453
	 *
2454
	 * @since 3.2.0
2455
	 *
2456
	 * @see wp_delete_post()
2457
	 *
2458
	 * @param int $postid Post ID.
2459
	 */
2460
	do_action( 'before_delete_post', $postid );
2461
2462
	delete_post_meta($postid,'_wp_trash_meta_status');
2463
	delete_post_meta($postid,'_wp_trash_meta_time');
2464
2465
	wp_delete_object_term_relationships($postid, get_object_taxonomies($post->post_type));
2466
2467
	$parent_data = array( 'post_parent' => $post->post_parent );
2468
	$parent_where = array( 'post_parent' => $postid );
2469
2470
	if ( is_post_type_hierarchical( $post->post_type ) ) {
2471
		// Point children of this page to its parent, also clean the cache of affected children.
2472
		$children_query = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type = %s", $postid, $post->post_type );
2473
		$children = $wpdb->get_results( $children_query );
2474
		if ( $children ) {
2475
			$wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => $post->post_type ) );
2476
		}
2477
	}
2478
2479
	// Do raw query. wp_get_post_revisions() is filtered.
2480
	$revision_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $postid ) );
2481
	// Use wp_delete_post (via wp_delete_post_revision) again. Ensures any meta/misplaced data gets cleaned up.
2482
	foreach ( $revision_ids as $revision_id )
2483
		wp_delete_post_revision( $revision_id );
2484
2485
	// Point all attachments to this post up one level.
2486
	$wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
2487
2488
	wp_defer_comment_counting( true );
2489
2490
	$comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ));
2491
	foreach ( $comment_ids as $comment_id ) {
2492
		wp_delete_comment( $comment_id, true );
2493
	}
2494
2495
	wp_defer_comment_counting( false );
2496
2497
	$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ));
2498
	foreach ( $post_meta_ids as $mid )
2499
		delete_metadata_by_mid( 'post', $mid );
2500
2501
	/**
2502
	 * Fires immediately before a post is deleted from the database.
2503
	 *
2504
	 * @since 1.2.0
2505
	 *
2506
	 * @param int $postid Post ID.
2507
	 */
2508
	do_action( 'delete_post', $postid );
2509
	$result = $wpdb->delete( $wpdb->posts, array( 'ID' => $postid ) );
2510
	if ( ! $result ) {
2511
		return false;
2512
	}
2513
2514
	/**
2515
	 * Fires immediately after a post is deleted from the database.
2516
	 *
2517
	 * @since 2.2.0
2518
	 *
2519
	 * @param int $postid Post ID.
2520
	 */
2521
	do_action( 'deleted_post', $postid );
2522
2523
	clean_post_cache( $post );
2524
2525
	if ( is_post_type_hierarchical( $post->post_type ) && $children ) {
2526
		foreach ( $children as $child )
0 ignored issues
show
The variable $children 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...
2527
			clean_post_cache( $child );
2528
	}
2529
2530
	wp_clear_scheduled_hook('publish_future_post', array( $postid ) );
2531
2532
	/**
2533
	 * Fires after a post is deleted, at the conclusion of wp_delete_post().
2534
	 *
2535
	 * @since 3.2.0
2536
	 *
2537
	 * @see wp_delete_post()
2538
	 *
2539
	 * @param int $postid Post ID.
2540
	 */
2541
	do_action( 'after_delete_post', $postid );
2542
2543
	return $post;
2544
}
2545
2546
/**
2547
 * Reset the page_on_front, show_on_front, and page_for_post settings when
2548
 * a linked page is deleted or trashed.
2549
 *
2550
 * Also ensures the post is no longer sticky.
2551
 *
2552
 * @since 3.7.0
2553
 * @access private
2554
 *
2555
 * @param int $post_id Post ID.
2556
 */
2557
function _reset_front_page_settings_for_post( $post_id ) {
2558
	$post = get_post( $post_id );
2559
	if ( 'page' == $post->post_type ) {
2560
	 	/*
2561
	 	 * If the page is defined in option page_on_front or post_for_posts,
2562
	 	 * adjust the corresponding options.
2563
	 	 */
2564
		if ( get_option( 'page_on_front' ) == $post->ID ) {
2565
			update_option( 'show_on_front', 'posts' );
2566
			update_option( 'page_on_front', 0 );
2567
		}
2568
		if ( get_option( 'page_for_posts' ) == $post->ID ) {
2569
			delete_option( 'page_for_posts', 0 );
2570
		}
2571
	}
2572
	unstick_post( $post->ID );
2573
}
2574
2575
/**
2576
 * Move a post or page to the Trash
2577
 *
2578
 * If trash is disabled, the post or page is permanently deleted.
2579
 *
2580
 * @since 2.9.0
2581
 *
2582
 * @see wp_delete_post()
2583
 *
2584
 * @param int $post_id Optional. Post ID. Default is ID of the global $post
2585
 *                     if EMPTY_TRASH_DAYS equals true.
2586
 * @return false|array|WP_Post|null Post data array, otherwise false.
2587
 */
2588
function wp_trash_post( $post_id = 0 ) {
2589
	if ( !EMPTY_TRASH_DAYS )
2590
		return wp_delete_post($post_id, true);
2591
2592
	if ( !$post = get_post($post_id, ARRAY_A) )
2593
		return $post;
2594
2595
	if ( $post['post_status'] == 'trash' )
2596
		return false;
2597
2598
	/**
2599
	 * Fires before a post is sent to the trash.
2600
	 *
2601
	 * @since 3.3.0
2602
	 *
2603
	 * @param int $post_id Post ID.
2604
	 */
2605
	do_action( 'wp_trash_post', $post_id );
2606
2607
	add_post_meta($post_id,'_wp_trash_meta_status', $post['post_status']);
2608
	add_post_meta($post_id,'_wp_trash_meta_time', time());
2609
2610
	$post['post_status'] = 'trash';
2611
	wp_insert_post( wp_slash( $post ) );
0 ignored issues
show
It seems like wp_slash($post) targeting wp_slash() can also be of type string; however, wp_insert_post() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2612
2613
	wp_trash_post_comments($post_id);
2614
2615
	/**
2616
	 * Fires after a post is sent to the trash.
2617
	 *
2618
	 * @since 2.9.0
2619
	 *
2620
	 * @param int $post_id Post ID.
2621
	 */
2622
	do_action( 'trashed_post', $post_id );
2623
2624
	return $post;
2625
}
2626
2627
/**
2628
 * Restore a post or page from the Trash.
2629
 *
2630
 * @since 2.9.0
2631
 *
2632
 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2633
 * @return WP_Post|false WP_Post object. False on failure.
0 ignored issues
show
Should the return type not be array|null|false|WP_Post? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
2634
 */
2635
function wp_untrash_post( $post_id = 0 ) {
2636
	if ( !$post = get_post($post_id, ARRAY_A) )
2637
		return $post;
2638
2639
	if ( $post['post_status'] != 'trash' )
2640
		return false;
2641
2642
	/**
2643
	 * Fires before a post is restored from the trash.
2644
	 *
2645
	 * @since 2.9.0
2646
	 *
2647
	 * @param int $post_id Post ID.
2648
	 */
2649
	do_action( 'untrash_post', $post_id );
2650
2651
	$post_status = get_post_meta($post_id, '_wp_trash_meta_status', true);
2652
2653
	$post['post_status'] = $post_status;
2654
2655
	delete_post_meta($post_id, '_wp_trash_meta_status');
2656
	delete_post_meta($post_id, '_wp_trash_meta_time');
2657
2658
	wp_insert_post( wp_slash( $post ) );
0 ignored issues
show
It seems like wp_slash($post) targeting wp_slash() can also be of type string; however, wp_insert_post() does only seem to accept array, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
2659
2660
	wp_untrash_post_comments($post_id);
2661
2662
	/**
2663
	 * Fires after a post is restored from the trash.
2664
	 *
2665
	 * @since 2.9.0
2666
	 *
2667
	 * @param int $post_id Post ID.
2668
	 */
2669
	do_action( 'untrashed_post', $post_id );
2670
2671
	return $post;
2672
}
2673
2674
/**
2675
 * Moves comments for a post to the trash.
2676
 *
2677
 * @since 2.9.0
2678
 *
2679
 * @global wpdb $wpdb WordPress database abstraction object.
2680
 *
2681
 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
2682
 * @return mixed|void False on failure.
2683
 */
2684
function wp_trash_post_comments( $post = null ) {
2685
	global $wpdb;
2686
2687
	$post = get_post($post);
2688
	if ( empty($post) )
2689
		return;
2690
2691
	$post_id = $post->ID;
2692
2693
	/**
2694
	 * Fires before comments are sent to the trash.
2695
	 *
2696
	 * @since 2.9.0
2697
	 *
2698
	 * @param int $post_id Post ID.
2699
	 */
2700
	do_action( 'trash_post_comments', $post_id );
2701
2702
	$comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id) );
2703
	if ( empty($comments) )
2704
		return;
2705
2706
	// Cache current status for each comment.
2707
	$statuses = array();
2708
	foreach ( $comments as $comment )
2709
		$statuses[$comment->comment_ID] = $comment->comment_approved;
2710
	add_post_meta($post_id, '_wp_trash_meta_comments_status', $statuses);
2711
2712
	// Set status for all comments to post-trashed.
2713
	$result = $wpdb->update($wpdb->comments, array('comment_approved' => 'post-trashed'), array('comment_post_ID' => $post_id));
2714
2715
	clean_comment_cache( array_keys($statuses) );
2716
2717
	/**
2718
	 * Fires after comments are sent to the trash.
2719
	 *
2720
	 * @since 2.9.0
2721
	 *
2722
	 * @param int   $post_id  Post ID.
2723
	 * @param array $statuses Array of comment statuses.
2724
	 */
2725
	do_action( 'trashed_post_comments', $post_id, $statuses );
2726
2727
	return $result;
2728
}
2729
2730
/**
2731
 * Restore comments for a post from the trash.
2732
 *
2733
 * @since 2.9.0
2734
 *
2735
 * @global wpdb $wpdb WordPress database abstraction object.
2736
 *
2737
 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
2738
 * @return true|void
0 ignored issues
show
Should the return type not be null|boolean?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2739
 */
2740
function wp_untrash_post_comments( $post = null ) {
2741
	global $wpdb;
2742
2743
	$post = get_post($post);
2744
	if ( empty($post) )
2745
		return;
2746
2747
	$post_id = $post->ID;
2748
2749
	$statuses = get_post_meta($post_id, '_wp_trash_meta_comments_status', true);
2750
2751
	if ( empty($statuses) )
2752
		return true;
2753
2754
	/**
2755
	 * Fires before comments are restored for a post from the trash.
2756
	 *
2757
	 * @since 2.9.0
2758
	 *
2759
	 * @param int $post_id Post ID.
2760
	 */
2761
	do_action( 'untrash_post_comments', $post_id );
2762
2763
	// Restore each comment to its original status.
2764
	$group_by_status = array();
2765
	foreach ( $statuses as $comment_id => $comment_status )
2766
		$group_by_status[$comment_status][] = $comment_id;
2767
2768
	foreach ( $group_by_status as $status => $comments ) {
2769
		// Sanity check. This shouldn't happen.
2770
		if ( 'post-trashed' == $status ) {
2771
			$status = '0';
2772
		}
2773
		$comments_in = implode( ', ', array_map( 'intval', $comments ) );
2774
		$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->comments SET comment_approved = %s WHERE comment_ID IN ($comments_in)", $status ) );
2775
	}
2776
2777
	clean_comment_cache( array_keys($statuses) );
2778
2779
	delete_post_meta($post_id, '_wp_trash_meta_comments_status');
2780
2781
	/**
2782
	 * Fires after comments are restored for a post from the trash.
2783
	 *
2784
	 * @since 2.9.0
2785
	 *
2786
	 * @param int $post_id Post ID.
2787
	 */
2788
	do_action( 'untrashed_post_comments', $post_id );
2789
}
2790
2791
/**
2792
 * Retrieve the list of categories for a post.
2793
 *
2794
 * Compatibility layer for themes and plugins. Also an easy layer of abstraction
2795
 * away from the complexity of the taxonomy layer.
2796
 *
2797
 * @since 2.1.0
2798
 *
2799
 * @see wp_get_object_terms()
2800
 *
2801
 * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
2802
 *                       global $post. Default 0.
2803
 * @param array $args    Optional. Category query parameters. Default empty array.
2804
 *                       See WP_Term_Query::__construct() for supported arguments.
2805
 * @return array|WP_Error List of categories. If the `$fields` argument passed via `$args` is 'all' or
2806
 *                        'all_with_object_id', an array of WP_Term objects will be returned. If `$fields`
2807
 *                        is 'ids', an array of category ids. If `$fields` is 'names', an array of category names.
2808
 *                        WP_Error object if 'category' taxonomy doesn't exist.
2809
 */
2810 View Code Duplication
function wp_get_post_categories( $post_id = 0, $args = array() ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2811
	$post_id = (int) $post_id;
2812
2813
	$defaults = array('fields' => 'ids');
2814
	$args = wp_parse_args( $args, $defaults );
2815
2816
	$cats = wp_get_object_terms($post_id, 'category', $args);
2817
	return $cats;
2818
}
2819
2820
/**
2821
 * Retrieve the tags for a post.
2822
 *
2823
 * There is only one default for this function, called 'fields' and by default
2824
 * is set to 'all'. There are other defaults that can be overridden in
2825
 * wp_get_object_terms().
2826
 *
2827
 * @since 2.3.0
2828
 *
2829
 * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
2830
 *                       global $post. Default 0.
2831
 * @param array $args    Optional. Tag query parameters. Default empty array.
2832
 *                       See WP_Term_Query::__construct() for supported arguments.
2833
 * @return array|WP_Error Array of WP_Term objects on success or empty array if no tags were found.
2834
 *                        WP_Error object if 'post_tag' taxonomy doesn't exist.
2835
 */
2836
function wp_get_post_tags( $post_id = 0, $args = array() ) {
2837
	return wp_get_post_terms( $post_id, 'post_tag', $args);
2838
}
2839
2840
/**
2841
 * Retrieve the terms for a post.
2842
 *
2843
 * There is only one default for this function, called 'fields' and by default
2844
 * is set to 'all'. There are other defaults that can be overridden in
2845
 * wp_get_object_terms().
2846
 *
2847
 * @since 2.8.0
2848
 *
2849
 * @param int    $post_id  Optional. The Post ID. Does not default to the ID of the
2850
 *                         global $post. Default 0.
2851
 * @param string $taxonomy Optional. The taxonomy for which to retrieve terms. Default 'post_tag'.
2852
 * @param array  $args     Optional. Term query parameters. Default empty array.
2853
 *                         See WP_Term_Query::__construct() for supported arguments.
2854
 * @return array|WP_Error  Array of WP_Term objects on success or empty array if no terms were found.
2855
 *                         WP_Error object if `$taxonomy` doesn't exist.
2856
 */
2857 View Code Duplication
function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
2858
	$post_id = (int) $post_id;
2859
2860
	$defaults = array('fields' => 'all');
2861
	$args = wp_parse_args( $args, $defaults );
2862
2863
	$tags = wp_get_object_terms($post_id, $taxonomy, $args);
2864
2865
	return $tags;
2866
}
2867
2868
/**
2869
 * Retrieve a number of recent posts.
2870
 *
2871
 * @since 1.0.0
2872
 *
2873
 * @see get_posts()
2874
 *
2875
 * @param array  $args   Optional. Arguments to retrieve posts. Default empty array.
2876
 * @param string $output Optional. The required return type. One of OBJECT or ARRAY_A, which correspond to
2877
 *                       a WP_Post object or an associative array, respectively. Default ARRAY_A.
2878
 * @return array|false Array of recent posts, where the type of each element is determined by $output parameter.
2879
 *                     Empty array on failure.
2880
 */
2881
function wp_get_recent_posts( $args = array(), $output = ARRAY_A ) {
2882
2883
	if ( is_numeric( $args ) ) {
2884
		_deprecated_argument( __FUNCTION__, '3.1.0', __( 'Passing an integer number of posts is deprecated. Pass an array of arguments instead.' ) );
2885
		$args = array( 'numberposts' => absint( $args ) );
2886
	}
2887
2888
	// Set default arguments.
2889
	$defaults = array(
2890
		'numberposts' => 10, 'offset' => 0,
2891
		'category' => 0, 'orderby' => 'post_date',
2892
		'order' => 'DESC', 'include' => '',
2893
		'exclude' => '', 'meta_key' => '',
2894
		'meta_value' =>'', 'post_type' => 'post', 'post_status' => 'draft, publish, future, pending, private',
2895
		'suppress_filters' => true
2896
	);
2897
2898
	$r = wp_parse_args( $args, $defaults );
2899
2900
	$results = get_posts( $r );
2901
2902
	// Backward compatibility. Prior to 3.1 expected posts to be returned in array.
2903
	if ( ARRAY_A == $output ){
2904
		foreach ( $results as $key => $result ) {
2905
			$results[$key] = get_object_vars( $result );
2906
		}
2907
		return $results ? $results : array();
2908
	}
2909
2910
	return $results ? $results : false;
2911
2912
}
2913
2914
/**
2915
 * Insert or update a post.
2916
 *
2917
 * If the $postarr parameter has 'ID' set to a value, then post will be updated.
2918
 *
2919
 * You can set the post date manually, by setting the values for 'post_date'
2920
 * and 'post_date_gmt' keys. You can close the comments or open the comments by
2921
 * setting the value for 'comment_status' key.
2922
 *
2923
 * @since 1.0.0
2924
 * @since 4.2.0 Support was added for encoding emoji in the post title, content, and excerpt.
2925
 * @since 4.4.0 A 'meta_input' array can now be passed to `$postarr` to add post meta data.
2926
 *
2927
 * @see sanitize_post()
2928
 * @global wpdb $wpdb WordPress database abstraction object.
2929
 *
2930
 * @param array $postarr {
2931
 *     An array of elements that make up a post to update or insert.
2932
 *
2933
 *     @type int    $ID                    The post ID. If equal to something other than 0,
2934
 *                                         the post with that ID will be updated. Default 0.
2935
 *     @type int    $post_author           The ID of the user who added the post. Default is
2936
 *                                         the current user ID.
2937
 *     @type string $post_date             The date of the post. Default is the current time.
2938
 *     @type string $post_date_gmt         The date of the post in the GMT timezone. Default is
2939
 *                                         the value of `$post_date`.
2940
 *     @type mixed  $post_content          The post content. Default empty.
2941
 *     @type string $post_content_filtered The filtered post content. Default empty.
2942
 *     @type string $post_title            The post title. Default empty.
2943
 *     @type string $post_excerpt          The post excerpt. Default empty.
2944
 *     @type string $post_status           The post status. Default 'draft'.
2945
 *     @type string $post_type             The post type. Default 'post'.
2946
 *     @type string $comment_status        Whether the post can accept comments. Accepts 'open' or 'closed'.
2947
 *                                         Default is the value of 'default_comment_status' option.
2948
 *     @type string $ping_status           Whether the post can accept pings. Accepts 'open' or 'closed'.
2949
 *                                         Default is the value of 'default_ping_status' option.
2950
 *     @type string $post_password         The password to access the post. Default empty.
2951
 *     @type string $post_name             The post name. Default is the sanitized post title
2952
 *                                         when creating a new post.
2953
 *     @type string $to_ping               Space or carriage return-separated list of URLs to ping.
2954
 *                                         Default empty.
2955
 *     @type string $pinged                Space or carriage return-separated list of URLs that have
2956
 *                                         been pinged. Default empty.
2957
 *     @type string $post_modified         The date when the post was last modified. Default is
2958
 *                                         the current time.
2959
 *     @type string $post_modified_gmt     The date when the post was last modified in the GMT
2960
 *                                         timezone. Default is the current time.
2961
 *     @type int    $post_parent           Set this for the post it belongs to, if any. Default 0.
2962
 *     @type int    $menu_order            The order the post should be displayed in. Default 0.
2963
 *     @type string $post_mime_type        The mime type of the post. Default empty.
2964
 *     @type string $guid                  Global Unique ID for referencing the post. Default empty.
2965
 *     @type array  $post_category         Array of category names, slugs, or IDs.
2966
 *                                         Defaults to value of the 'default_category' option.
2967
 *     @type array  $tax_input             Array of taxonomy terms keyed by their taxonomy name. Default empty.
2968
 *     @type array  $meta_input            Array of post meta values keyed by their post meta key. Default empty.
2969
 * }
2970
 * @param bool  $wp_error Optional. Whether to return a WP_Error on failure. Default false.
2971
 * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
2972
 */
2973
function wp_insert_post( $postarr, $wp_error = false ) {
2974
	global $wpdb;
2975
2976
	$user_id = get_current_user_id();
2977
2978
	$defaults = array(
2979
		'post_author' => $user_id,
2980
		'post_content' => '',
2981
		'post_content_filtered' => '',
2982
		'post_title' => '',
2983
		'post_excerpt' => '',
2984
		'post_status' => 'draft',
2985
		'post_type' => 'post',
2986
		'comment_status' => '',
2987
		'ping_status' => '',
2988
		'post_password' => '',
2989
		'to_ping' =>  '',
2990
		'pinged' => '',
2991
		'post_parent' => 0,
2992
		'menu_order' => 0,
2993
		'guid' => '',
2994
		'import_id' => 0,
2995
		'context' => '',
2996
	);
2997
2998
	$postarr = wp_parse_args($postarr, $defaults);
2999
3000
	unset( $postarr[ 'filter' ] );
3001
3002
	$postarr = sanitize_post($postarr, 'db');
3003
3004
	// Are we updating or creating?
3005
	$post_ID = 0;
3006
	$update = false;
3007
	$guid = $postarr['guid'];
3008
3009
	if ( ! empty( $postarr['ID'] ) ) {
3010
		$update = true;
3011
3012
		// Get the post ID and GUID.
3013
		$post_ID = $postarr['ID'];
3014
		$post_before = get_post( $post_ID );
3015
		if ( is_null( $post_before ) ) {
3016
			if ( $wp_error ) {
3017
				return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3018
			}
3019
			return 0;
3020
		}
3021
3022
		$guid = get_post_field( 'guid', $post_ID );
3023
		$previous_status = get_post_field('post_status', $post_ID );
3024
	} else {
3025
		$previous_status = 'new';
3026
	}
3027
3028
	$post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
3029
3030
	$post_title = $postarr['post_title'];
3031
	$post_content = $postarr['post_content'];
3032
	$post_excerpt = $postarr['post_excerpt'];
3033
	if ( isset( $postarr['post_name'] ) ) {
3034
		$post_name = $postarr['post_name'];
3035
	} elseif ( $update ) {
3036
		// For an update, don't modify the post_name if it wasn't supplied as an argument.
3037
		$post_name = $post_before->post_name;
0 ignored issues
show
The variable $post_before 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...
3038
	}
3039
3040
	$maybe_empty = 'attachment' !== $post_type
3041
		&& ! $post_content && ! $post_title && ! $post_excerpt
3042
		&& post_type_supports( $post_type, 'editor' )
3043
		&& post_type_supports( $post_type, 'title' )
3044
		&& post_type_supports( $post_type, 'excerpt' );
3045
3046
	/**
3047
	 * Filters whether the post should be considered "empty".
3048
	 *
3049
	 * The post is considered "empty" if both:
3050
	 * 1. The post type supports the title, editor, and excerpt fields
3051
	 * 2. The title, editor, and excerpt fields are all empty
3052
	 *
3053
	 * Returning a truthy value to the filter will effectively short-circuit
3054
	 * the new post being inserted, returning 0. If $wp_error is true, a WP_Error
3055
	 * will be returned instead.
3056
	 *
3057
	 * @since 3.3.0
3058
	 *
3059
	 * @param bool  $maybe_empty Whether the post should be considered "empty".
3060
	 * @param array $postarr     Array of post data.
3061
	 */
3062
	if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
3063
		if ( $wp_error ) {
3064
			return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
3065
		} else {
3066
			return 0;
3067
		}
3068
	}
3069
3070
	$post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
3071
	if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
3072
		$post_status = 'inherit';
3073
	}
3074
3075
	if ( ! empty( $postarr['post_category'] ) ) {
3076
		// Filter out empty terms.
3077
		$post_category = array_filter( $postarr['post_category'] );
3078
	}
3079
3080
	// Make sure we set a valid category.
3081
	if ( empty( $post_category ) || 0 == count( $post_category ) || ! is_array( $post_category ) ) {
3082
		// 'post' requires at least one category.
3083
		if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
3084
			$post_category = array( get_option('default_category') );
3085
		} else {
3086
			$post_category = array();
3087
		}
3088
	}
3089
3090
	// Don't allow contributors to set the post slug for pending review posts.
3091
	if ( 'pending' == $post_status && !current_user_can( 'publish_posts' ) ) {
3092
		$post_name = '';
3093
	}
3094
3095
	/*
3096
	 * Create a valid post name. Drafts and pending posts are allowed to have
3097
	 * an empty post name.
3098
	 */
3099
	if ( empty($post_name) ) {
3100 View Code Duplication
		if ( !in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3101
			$post_name = sanitize_title($post_title);
3102
		} else {
3103
			$post_name = '';
3104
		}
3105
	} else {
3106
		// On updates, we need to check to see if it's using the old, fixed sanitization context.
3107
		$check_name = sanitize_title( $post_name, '', 'old-save' );
3108
		if ( $update && strtolower( urlencode( $post_name ) ) == $check_name && get_post_field( 'post_name', $post_ID ) == $check_name ) {
3109
			$post_name = $check_name;
3110
		} else { // new post, or slug has changed.
3111
			$post_name = sanitize_title($post_name);
3112
		}
3113
	}
3114
3115
	/*
3116
	 * If the post date is empty (due to having been new or a draft) and status
3117
	 * is not 'draft' or 'pending', set date to now.
3118
	 */
3119
	if ( empty( $postarr['post_date'] ) || '0000-00-00 00:00:00' == $postarr['post_date'] ) {
3120
		if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3121
			$post_date = current_time( 'mysql' );
3122
		} else {
3123
			$post_date = get_date_from_gmt( $postarr['post_date_gmt'] );
3124
		}
3125
	} else {
3126
		$post_date = $postarr['post_date'];
3127
	}
3128
3129
	// Validate the date.
3130
	$mm = substr( $post_date, 5, 2 );
3131
	$jj = substr( $post_date, 8, 2 );
3132
	$aa = substr( $post_date, 0, 4 );
3133
	$valid_date = wp_checkdate( $mm, $jj, $aa, $post_date );
3134
	if ( ! $valid_date ) {
3135
		if ( $wp_error ) {
3136
			return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
3137
		} else {
3138
			return 0;
3139
		}
3140
	}
3141
3142
	if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3143 View Code Duplication
		if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3144
			$post_date_gmt = get_gmt_from_date( $post_date );
3145
		} else {
3146
			$post_date_gmt = '0000-00-00 00:00:00';
3147
		}
3148
	} else {
3149
		$post_date_gmt = $postarr['post_date_gmt'];
3150
	}
3151
3152
	if ( $update || '0000-00-00 00:00:00' == $post_date ) {
3153
		$post_modified     = current_time( 'mysql' );
3154
		$post_modified_gmt = current_time( 'mysql', 1 );
3155
	} else {
3156
		$post_modified     = $post_date;
3157
		$post_modified_gmt = $post_date_gmt;
3158
	}
3159
3160
	if ( 'attachment' !== $post_type ) {
3161
		if ( 'publish' == $post_status ) {
3162
			$now = gmdate('Y-m-d H:i:59');
3163 View Code Duplication
			if ( mysql2date('U', $post_date_gmt, false) > mysql2date('U', $now, false) ) {
3164
				$post_status = 'future';
3165
			}
3166
		} elseif ( 'future' == $post_status ) {
3167
			$now = gmdate('Y-m-d H:i:59');
3168 View Code Duplication
			if ( mysql2date('U', $post_date_gmt, false) <= mysql2date('U', $now, false) ) {
3169
				$post_status = 'publish';
3170
			}
3171
		}
3172
	}
3173
3174
	// Comment status.
3175
	if ( empty( $postarr['comment_status'] ) ) {
3176
		if ( $update ) {
3177
			$comment_status = 'closed';
3178
		} else {
3179
			$comment_status = get_default_comment_status( $post_type );
3180
		}
3181
	} else {
3182
		$comment_status = $postarr['comment_status'];
3183
	}
3184
3185
	// These variables are needed by compact() later.
3186
	$post_content_filtered = $postarr['post_content_filtered'];
3187
	$post_author = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
3188
	$ping_status = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
3189
	$to_ping = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
3190
	$pinged = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
3191
	$import_id = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
3192
3193
	/*
3194
	 * The 'wp_insert_post_parent' filter expects all variables to be present.
3195
	 * Previously, these variables would have already been extracted
3196
	 */
3197
	if ( isset( $postarr['menu_order'] ) ) {
3198
		$menu_order = (int) $postarr['menu_order'];
3199
	} else {
3200
		$menu_order = 0;
3201
	}
3202
3203
	$post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
3204
	if ( 'private' == $post_status ) {
3205
		$post_password = '';
3206
	}
3207
3208
	if ( isset( $postarr['post_parent'] ) ) {
3209
		$post_parent = (int) $postarr['post_parent'];
3210
	} else {
3211
		$post_parent = 0;
3212
	}
3213
3214
	/**
3215
	 * Filters the post parent -- used to check for and prevent hierarchy loops.
3216
	 *
3217
	 * @since 3.1.0
3218
	 *
3219
	 * @param int   $post_parent Post parent ID.
3220
	 * @param int   $post_ID     Post ID.
3221
	 * @param array $new_postarr Array of parsed post data.
3222
	 * @param array $postarr     Array of sanitized, but otherwise unmodified post data.
3223
	 */
3224
	$post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, compact( array_keys( $postarr ) ), $postarr );
3225
3226
	/*
3227
	 * If the post is being untrashed and it has a desired slug stored in post meta,
3228
	 * reassign it.
3229
	 */
3230
	if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
3231
		$desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
3232
		if ( $desired_post_slug ) {
3233
			delete_post_meta( $post_ID, '_wp_desired_post_slug' );
3234
			$post_name = $desired_post_slug;
3235
		}
3236
	}
3237
3238
	// If a trashed post has the desired slug, change it and let this post have it.
3239
	if ( 'trash' !== $post_status && $post_name ) {
3240
		wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID );
3241
	}
3242
3243
	// When trashing an existing post, change its slug to allow non-trashed posts to use it.
3244
	if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
3245
		$post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
3246
	}
3247
3248
	$post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
3249
3250
	// Don't unslash.
3251
	$post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
3252
3253
	// Expected_slashed (everything!).
3254
	$data = compact( 'post_author', 'post_date', 'post_date_gmt', 'post_content', 'post_content_filtered', 'post_title', 'post_excerpt', 'post_status', 'post_type', 'comment_status', 'ping_status', 'post_password', 'post_name', 'to_ping', 'pinged', 'post_modified', 'post_modified_gmt', 'post_parent', 'menu_order', 'post_mime_type', 'guid' );
3255
3256
	$emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
3257
3258
	foreach ( $emoji_fields as $emoji_field ) {
3259
		if ( isset( $data[ $emoji_field ] ) ) {
3260
			$charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
3261
			if ( 'utf8' === $charset ) {
3262
				$data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
3263
			}
3264
		}
3265
	}
3266
3267
	if ( 'attachment' === $post_type ) {
3268
		/**
3269
		 * Filters attachment post data before it is updated in or added to the database.
3270
		 *
3271
		 * @since 3.9.0
3272
		 *
3273
		 * @param array $data    An array of sanitized attachment post data.
3274
		 * @param array $postarr An array of unsanitized attachment post data.
3275
		 */
3276
		$data = apply_filters( 'wp_insert_attachment_data', $data, $postarr );
3277
	} else {
3278
		/**
3279
		 * Filters slashed post data just before it is inserted into the database.
3280
		 *
3281
		 * @since 2.7.0
3282
		 *
3283
		 * @param array $data    An array of slashed post data.
3284
		 * @param array $postarr An array of sanitized, but otherwise unmodified post data.
3285
		 */
3286
		$data = apply_filters( 'wp_insert_post_data', $data, $postarr );
3287
	}
3288
	$data = wp_unslash( $data );
3289
	$where = array( 'ID' => $post_ID );
3290
3291
	if ( $update ) {
3292
		/**
3293
		 * Fires immediately before an existing post is updated in the database.
3294
		 *
3295
		 * @since 2.5.0
3296
		 *
3297
		 * @param int   $post_ID Post ID.
3298
		 * @param array $data    Array of unslashed post data.
3299
		 */
3300
		do_action( 'pre_post_update', $post_ID, $data );
3301
		if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
3302
			if ( $wp_error ) {
3303
				return new WP_Error('db_update_error', __('Could not update post in the database'), $wpdb->last_error);
3304
			} else {
3305
				return 0;
3306
			}
3307
		}
3308
	} else {
3309
		// If there is a suggested ID, use it if not already present.
3310
		if ( ! empty( $import_id ) ) {
3311
			$import_id = (int) $import_id;
3312
			if ( ! $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id) ) ) {
3313
				$data['ID'] = $import_id;
3314
			}
3315
		}
3316 View Code Duplication
		if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
3317
			if ( $wp_error ) {
3318
				return new WP_Error('db_insert_error', __('Could not insert post into the database'), $wpdb->last_error);
3319
			} else {
3320
				return 0;
3321
			}
3322
		}
3323
		$post_ID = (int) $wpdb->insert_id;
3324
3325
		// Use the newly generated $post_ID.
3326
		$where = array( 'ID' => $post_ID );
3327
	}
3328
3329
	if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {
3330
		$data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data['post_status'], $post_type, $post_parent );
3331
		$wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
3332
		clean_post_cache( $post_ID );
3333
	}
3334
3335
	if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
3336
		wp_set_post_categories( $post_ID, $post_category );
3337
	}
3338
3339
	if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
3340
		wp_set_post_tags( $post_ID, $postarr['tags_input'] );
3341
	}
3342
3343
	// New-style support for all custom taxonomies.
3344
	if ( ! empty( $postarr['tax_input'] ) ) {
3345
		foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
3346
			$taxonomy_obj = get_taxonomy($taxonomy);
3347
			if ( ! $taxonomy_obj ) {
3348
				/* translators: %s: taxonomy name */
3349
				_doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
3350
				continue;
3351
			}
3352
3353
			// array = hierarchical, string = non-hierarchical.
3354
			if ( is_array( $tags ) ) {
3355
				$tags = array_filter($tags);
3356
			}
3357
			if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
3358
				wp_set_post_terms( $post_ID, $tags, $taxonomy );
3359
			}
3360
		}
3361
	}
3362
3363
	if ( ! empty( $postarr['meta_input'] ) ) {
3364
		foreach ( $postarr['meta_input'] as $field => $value ) {
3365
			update_post_meta( $post_ID, $field, $value );
3366
		}
3367
	}
3368
3369
	$current_guid = get_post_field( 'guid', $post_ID );
3370
3371
	// Set GUID.
3372
	if ( ! $update && '' == $current_guid ) {
3373
		$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
3374
	}
3375
3376
	if ( 'attachment' === $postarr['post_type'] ) {
3377
		if ( ! empty( $postarr['file'] ) ) {
3378
			update_attached_file( $post_ID, $postarr['file'] );
3379
		}
3380
3381
		if ( ! empty( $postarr['context'] ) ) {
3382
			add_post_meta( $post_ID, '_wp_attachment_context', $postarr['context'], true );
3383
		}
3384
	}
3385
3386
	// Set or remove featured image.
3387
	if ( isset( $postarr['_thumbnail_id'] ) ) {
3388
		$thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
3389 View Code Duplication
		if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
3390
			if ( wp_attachment_is( 'audio', $post_ID ) ) {
3391
				$thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
3392
			} elseif ( wp_attachment_is( 'video', $post_ID ) ) {
3393
				$thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
3394
			}
3395
		}
3396
3397
		if ( $thumbnail_support ) {
3398
			$thumbnail_id = intval( $postarr['_thumbnail_id'] );
3399
			if ( -1 === $thumbnail_id ) {
3400
				delete_post_thumbnail( $post_ID );
3401
			} else {
3402
				set_post_thumbnail( $post_ID, $thumbnail_id );
3403
			}
3404
		}
3405
	}
3406
3407
	clean_post_cache( $post_ID );
3408
3409
	$post = get_post( $post_ID );
3410
3411
	if ( ! empty( $postarr['page_template'] ) ) {
3412
		$post->page_template = $postarr['page_template'];
3413
		$page_templates = wp_get_theme()->get_page_templates( $post );
3414
		if ( 'default' != $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
3415
			if ( $wp_error ) {
3416
				return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
3417
			}
3418
			update_post_meta( $post_ID, '_wp_page_template', 'default' );
3419
		} else {
3420
			update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
3421
		}
3422
	}
3423
3424
	if ( 'attachment' !== $postarr['post_type'] ) {
3425
		wp_transition_post_status( $data['post_status'], $previous_status, $post );
3426
	} else {
3427
		if ( $update ) {
3428
			/**
3429
			 * Fires once an existing attachment has been updated.
3430
			 *
3431
			 * @since 2.0.0
3432
			 *
3433
			 * @param int $post_ID Attachment ID.
3434
			 */
3435
			do_action( 'edit_attachment', $post_ID );
3436
			$post_after = get_post( $post_ID );
3437
3438
			/**
3439
			 * Fires once an existing attachment has been updated.
3440
			 *
3441
			 * @since 4.4.0
3442
			 *
3443
			 * @param int     $post_ID      Post ID.
3444
			 * @param WP_Post $post_after   Post object following the update.
3445
			 * @param WP_Post $post_before  Post object before the update.
3446
			 */
3447
			do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
3448
		} else {
3449
3450
			/**
3451
			 * Fires once an attachment has been added.
3452
			 *
3453
			 * @since 2.0.0
3454
			 *
3455
			 * @param int $post_ID Attachment ID.
3456
			 */
3457
			do_action( 'add_attachment', $post_ID );
3458
		}
3459
3460
		return $post_ID;
3461
	}
3462
3463
	if ( $update ) {
3464
		/**
3465
		 * Fires once an existing post has been updated.
3466
		 *
3467
		 * @since 1.2.0
3468
		 *
3469
		 * @param int     $post_ID Post ID.
3470
		 * @param WP_Post $post    Post object.
3471
		 */
3472
		do_action( 'edit_post', $post_ID, $post );
3473
		$post_after = get_post($post_ID);
3474
3475
		/**
3476
		 * Fires once an existing post has been updated.
3477
		 *
3478
		 * @since 3.0.0
3479
		 *
3480
		 * @param int     $post_ID      Post ID.
3481
		 * @param WP_Post $post_after   Post object following the update.
3482
		 * @param WP_Post $post_before  Post object before the update.
3483
		 */
3484
		do_action( 'post_updated', $post_ID, $post_after, $post_before);
3485
	}
3486
3487
	/**
3488
	 * Fires once a post has been saved.
3489
	 *
3490
	 * The dynamic portion of the hook name, `$post->post_type`, refers to
3491
	 * the post type slug.
3492
	 *
3493
	 * @since 3.7.0
3494
	 *
3495
	 * @param int     $post_ID Post ID.
3496
	 * @param WP_Post $post    Post object.
3497
	 * @param bool    $update  Whether this is an existing post being updated or not.
3498
	 */
3499
	do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );
3500
3501
	/**
3502
	 * Fires once a post has been saved.
3503
	 *
3504
	 * @since 1.5.0
3505
	 *
3506
	 * @param int     $post_ID Post ID.
3507
	 * @param WP_Post $post    Post object.
3508
	 * @param bool    $update  Whether this is an existing post being updated or not.
3509
	 */
3510
	do_action( 'save_post', $post_ID, $post, $update );
3511
3512
	/**
3513
	 * Fires once a post has been saved.
3514
	 *
3515
	 * @since 2.0.0
3516
	 *
3517
	 * @param int     $post_ID Post ID.
3518
	 * @param WP_Post $post    Post object.
3519
	 * @param bool    $update  Whether this is an existing post being updated or not.
3520
	 */
3521
	do_action( 'wp_insert_post', $post_ID, $post, $update );
3522
3523
	return $post_ID;
3524
}
3525
3526
/**
3527
 * Update a post with new post data.
3528
 *
3529
 * The date does not have to be set for drafts. You can set the date and it will
3530
 * not be overridden.
3531
 *
3532
 * @since 1.0.0
3533
 *
3534
 * @param array|object $postarr  Optional. Post data. Arrays are expected to be escaped,
3535
 *                               objects are not. Default array.
3536
 * @param bool         $wp_error Optional. Allow return of WP_Error on failure. Default false.
3537
 * @return int|WP_Error The value 0 or WP_Error on failure. The post ID on success.
3538
 */
3539
function wp_update_post( $postarr = array(), $wp_error = false ) {
3540
	if ( is_object($postarr) ) {
3541
		// Non-escaped post was passed.
3542
		$postarr = get_object_vars($postarr);
3543
		$postarr = wp_slash($postarr);
3544
	}
3545
3546
	// First, get all of the original fields.
3547
	$post = get_post($postarr['ID'], ARRAY_A);
3548
3549
	if ( is_null( $post ) ) {
3550
		if ( $wp_error )
3551
			return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3552
		return 0;
3553
	}
3554
3555
	// Escape data pulled from DB.
3556
	$post = wp_slash($post);
0 ignored issues
show
It seems like $post can also be of type object<WP_Post>; however, wp_slash() does only seem to accept string|array, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
3557
3558
	// Passed post category list overwrites existing category list if not empty.
3559
	if ( isset($postarr['post_category']) && is_array($postarr['post_category'])
3560
			 && 0 != count($postarr['post_category']) )
3561
		$post_cats = $postarr['post_category'];
3562
	else
3563
		$post_cats = $post['post_category'];
3564
3565
	// Drafts shouldn't be assigned a date unless explicitly done so by the user.
3566
	if ( isset( $post['post_status'] ) && in_array($post['post_status'], array('draft', 'pending', 'auto-draft')) && empty($postarr['edit_date']) &&
3567
			 ('0000-00-00 00:00:00' == $post['post_date_gmt']) )
3568
		$clear_date = true;
3569
	else
3570
		$clear_date = false;
3571
3572
	// Merge old and new fields with new fields overwriting old ones.
3573
	$postarr = array_merge($post, $postarr);
3574
	$postarr['post_category'] = $post_cats;
3575
	if ( $clear_date ) {
3576
		$postarr['post_date'] = current_time('mysql');
3577
		$postarr['post_date_gmt'] = '';
3578
	}
3579
3580
	if ($postarr['post_type'] == 'attachment')
3581
		return wp_insert_attachment($postarr);
3582
3583
	return wp_insert_post( $postarr, $wp_error );
3584
}
3585
3586
/**
3587
 * Publish a post by transitioning the post status.
3588
 *
3589
 * @since 2.1.0
3590
 *
3591
 * @global wpdb $wpdb WordPress database abstraction object.
3592
 *
3593
 * @param int|WP_Post $post Post ID or post object.
3594
 */
3595
function wp_publish_post( $post ) {
3596
	global $wpdb;
3597
3598
	if ( ! $post = get_post( $post ) )
3599
		return;
3600
3601
	if ( 'publish' == $post->post_status )
3602
		return;
3603
3604
	$wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
3605
3606
	clean_post_cache( $post->ID );
3607
3608
	$old_status = $post->post_status;
3609
	$post->post_status = 'publish';
3610
	wp_transition_post_status( 'publish', $old_status, $post );
3611
3612
	/** This action is documented in wp-includes/post.php */
3613
	do_action( 'edit_post', $post->ID, $post );
3614
3615
	/** This action is documented in wp-includes/post.php */
3616
	do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
3617
3618
	/** This action is documented in wp-includes/post.php */
3619
	do_action( 'save_post', $post->ID, $post, true );
3620
3621
	/** This action is documented in wp-includes/post.php */
3622
	do_action( 'wp_insert_post', $post->ID, $post, true );
3623
}
3624
3625
/**
3626
 * Publish future post and make sure post ID has future post status.
3627
 *
3628
 * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
3629
 * from publishing drafts, etc.
3630
 *
3631
 * @since 2.5.0
3632
 *
3633
 * @param int|WP_Post $post_id Post ID or post object.
3634
 */
3635
function check_and_publish_future_post( $post_id ) {
3636
	$post = get_post($post_id);
3637
3638
	if ( empty($post) )
3639
		return;
3640
3641
	if ( 'future' != $post->post_status )
3642
		return;
3643
3644
	$time = strtotime( $post->post_date_gmt . ' GMT' );
3645
3646
	// Uh oh, someone jumped the gun!
3647
	if ( $time > time() ) {
3648
		wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // clear anything else in the system
3649
		wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
3650
		return;
3651
	}
3652
3653
	// wp_publish_post() returns no meaningful value.
3654
	wp_publish_post( $post_id );
3655
}
3656
3657
/**
3658
 * Computes a unique slug for the post, when given the desired slug and some post details.
3659
 *
3660
 * @since 2.8.0
3661
 *
3662
 * @global wpdb       $wpdb WordPress database abstraction object.
3663
 * @global WP_Rewrite $wp_rewrite
3664
 *
3665
 * @param string $slug        The desired slug (post_name).
3666
 * @param int    $post_ID     Post ID.
3667
 * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
3668
 * @param string $post_type   Post type.
3669
 * @param int    $post_parent Post parent ID.
3670
 * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
3671
 */
3672
function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
3673
	if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) || ( 'inherit' == $post_status && 'revision' == $post_type ) )
3674
		return $slug;
3675
3676
	global $wpdb, $wp_rewrite;
3677
3678
	$original_slug = $slug;
3679
3680
	$feeds = $wp_rewrite->feeds;
3681
	if ( ! is_array( $feeds ) )
3682
		$feeds = array();
3683
3684
	if ( 'attachment' == $post_type ) {
3685
		// Attachment slugs must be unique across all types.
3686
		$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
3687
		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
3688
3689
		/**
3690
		 * Filters whether the post slug would make a bad attachment slug.
3691
		 *
3692
		 * @since 3.1.0
3693
		 *
3694
		 * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
3695
		 * @param string $slug     The post slug.
3696
		 */
3697
		if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
3698
			$suffix = 2;
3699 View Code Duplication
			do {
3700
				$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
3701
				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID ) );
3702
				$suffix++;
3703
			} while ( $post_name_check );
3704
			$slug = $alt_post_name;
3705
		}
3706
	} elseif ( is_post_type_hierarchical( $post_type ) ) {
3707
		if ( 'nav_menu_item' == $post_type )
3708
			return $slug;
3709
3710
		/*
3711
		 * Page slugs must be unique within their own trees. Pages are in a separate
3712
		 * namespace than posts so page slugs are allowed to overlap post slugs.
3713
		 */
3714
		$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type IN ( %s, 'attachment' ) AND ID != %d AND post_parent = %d LIMIT 1";
3715
		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
3716
3717
		/**
3718
		 * Filters whether the post slug would make a bad hierarchical post slug.
3719
		 *
3720
		 * @since 3.1.0
3721
		 *
3722
		 * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
3723
		 * @param string $slug        The post slug.
3724
		 * @param string $post_type   Post type.
3725
		 * @param int    $post_parent Post parent ID.
3726
		 */
3727
		if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )  || apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent ) ) {
3728
			$suffix = 2;
3729 View Code Duplication
			do {
3730
				$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
3731
				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
3732
				$suffix++;
3733
			} while ( $post_name_check );
3734
			$slug = $alt_post_name;
3735
		}
3736
	} else {
3737
		// Post slugs must be unique across all posts.
3738
		$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
3739
		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
3740
3741
		// Prevent new post slugs that could result in URLs that conflict with date archives.
3742
		$post = get_post( $post_ID );
3743
		$conflicts_with_date_archive = false;
3744
		if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) && $slug_num = intval( $slug ) ) {
3745
			$permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
3746
			$postname_index = array_search( '%postname%', $permastructs );
3747
3748
			/*
3749
			 * Potential date clashes are as follows:
3750
			 *
3751
			 * - Any integer in the first permastruct position could be a year.
3752
			 * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
3753
			 * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
3754
			 */
3755
			if ( 0 === $postname_index ||
3756
				( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
0 ignored issues
show
Bug Best Practice introduced by
The expression $postname_index of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3757
				( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
0 ignored issues
show
Bug Best Practice introduced by
The expression $postname_index of type false|integer is loosely compared to true; this is ambiguous if the integer can be zero. 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 integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
3758
			) {
3759
				$conflicts_with_date_archive = true;
3760
			}
3761
		}
3762
3763
		/**
3764
		 * Filters whether the post slug would be bad as a flat slug.
3765
		 *
3766
		 * @since 3.1.0
3767
		 *
3768
		 * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
3769
		 * @param string $slug      The post slug.
3770
		 * @param string $post_type Post type.
3771
		 */
3772
		if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || $conflicts_with_date_archive || apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type ) ) {
3773
			$suffix = 2;
3774 View Code Duplication
			do {
3775
				$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
3776
				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
3777
				$suffix++;
3778
			} while ( $post_name_check );
3779
			$slug = $alt_post_name;
3780
		}
3781
	}
3782
3783
	/**
3784
	 * Filters the unique post slug.
3785
	 *
3786
	 * @since 3.3.0
3787
	 *
3788
	 * @param string $slug          The post slug.
3789
	 * @param int    $post_ID       Post ID.
3790
	 * @param string $post_status   The post status.
3791
	 * @param string $post_type     Post type.
3792
	 * @param int    $post_parent   Post parent ID
3793
	 * @param string $original_slug The original post slug.
3794
	 */
3795
	return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
3796
}
3797
3798
/**
3799
 * Truncate a post slug.
3800
 *
3801
 * @since 3.6.0
3802
 * @access private
3803
 *
3804
 * @see utf8_uri_encode()
3805
 *
3806
 * @param string $slug   The slug to truncate.
3807
 * @param int    $length Optional. Max length of the slug. Default 200 (characters).
3808
 * @return string The truncated slug.
3809
 */
3810
function _truncate_post_slug( $slug, $length = 200 ) {
3811
	if ( strlen( $slug ) > $length ) {
3812
		$decoded_slug = urldecode( $slug );
3813
		if ( $decoded_slug === $slug )
3814
			$slug = substr( $slug, 0, $length );
3815
		else
3816
			$slug = utf8_uri_encode( $decoded_slug, $length );
3817
	}
3818
3819
	return rtrim( $slug, '-' );
3820
}
3821
3822
/**
3823
 * Add tags to a post.
3824
 *
3825
 * @see wp_set_post_tags()
3826
 *
3827
 * @since 2.3.0
3828
 *
3829
 * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
3830
 * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
3831
 *                              separated by commas. Default empty.
3832
 * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
3833
 */
3834
function wp_add_post_tags( $post_id = 0, $tags = '' ) {
3835
	return wp_set_post_tags($post_id, $tags, true);
3836
}
3837
3838
/**
3839
 * Set the tags for a post.
3840
 *
3841
 * @since 2.3.0
3842
 *
3843
 * @see wp_set_object_terms()
3844
 *
3845
 * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
3846
 * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
3847
 *                              separated by commas. Default empty.
3848
 * @param bool         $append  Optional. If true, don't delete existing tags, just add on. If false,
3849
 *                              replace the tags with the new tags. Default false.
3850
 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
3851
 */
3852
function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
3853
	return wp_set_post_terms( $post_id, $tags, 'post_tag', $append);
3854
}
3855
3856
/**
3857
 * Set the terms for a post.
3858
 *
3859
 * @since 2.8.0
3860
 *
3861
 * @see wp_set_object_terms()
3862
 *
3863
 * @param int          $post_id  Optional. The Post ID. Does not default to the ID of the global $post.
3864
 * @param string|array $tags     Optional. An array of terms to set for the post, or a string of terms
3865
 *                               separated by commas. Default empty.
3866
 * @param string       $taxonomy Optional. Taxonomy name. Default 'post_tag'.
3867
 * @param bool         $append   Optional. If true, don't delete existing terms, just add on. If false,
3868
 *                               replace the terms with the new terms. Default false.
3869
 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
3870
 */
3871
function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
3872
	$post_id = (int) $post_id;
3873
3874
	if ( !$post_id )
3875
		return false;
3876
3877
	if ( empty($tags) )
3878
		$tags = array();
3879
3880 View Code Duplication
	if ( ! is_array( $tags ) ) {
3881
		$comma = _x( ',', 'tag delimiter' );
3882
		if ( ',' !== $comma )
3883
			$tags = str_replace( $comma, ',', $tags );
3884
		$tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
3885
	}
3886
3887
	/*
3888
	 * Hierarchical taxonomies must always pass IDs rather than names so that
3889
	 * children with the same names but different parents aren't confused.
3890
	 */
3891
	if ( is_taxonomy_hierarchical( $taxonomy ) ) {
3892
		$tags = array_unique( array_map( 'intval', $tags ) );
3893
	}
3894
3895
	return wp_set_object_terms( $post_id, $tags, $taxonomy, $append );
3896
}
3897
3898
/**
3899
 * Set categories for a post.
3900
 *
3901
 * If the post categories parameter is not set, then the default category is
3902
 * going used.
3903
 *
3904
 * @since 2.1.0
3905
 *
3906
 * @param int       $post_ID         Optional. The Post ID. Does not default to the ID
3907
 *                                   of the global $post. Default 0.
3908
 * @param array|int $post_categories Optional. List of categories or ID of category.
3909
 *                                   Default empty array.
3910
 * @param bool      $append         If true, don't delete existing categories, just add on.
3911
 *                                  If false, replace the categories with the new categories.
3912
 * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
0 ignored issues
show
Should the return type not be boolean|array|WP_Error? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
3913
 */
3914
function wp_set_post_categories( $post_ID = 0, $post_categories = array(), $append = false ) {
3915
	$post_ID = (int) $post_ID;
3916
	$post_type = get_post_type( $post_ID );
3917
	$post_status = get_post_status( $post_ID );
3918
	// If $post_categories isn't already an array, make it one:
3919
	$post_categories = (array) $post_categories;
3920
	if ( empty( $post_categories ) ) {
3921
		if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
3922
			$post_categories = array( get_option('default_category') );
3923
			$append = false;
3924
		} else {
3925
			$post_categories = array();
3926
		}
3927
	} elseif ( 1 == count( $post_categories ) && '' == reset( $post_categories ) ) {
3928
		return true;
3929
	}
3930
3931
	return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
3932
}
3933
3934
/**
3935
 * Fires actions related to the transitioning of a post's status.
3936
 *
3937
 * When a post is saved, the post status is "transitioned" from one status to another,
3938
 * though this does not always mean the status has actually changed before and after
3939
 * the save. This function fires a number of action hooks related to that transition:
3940
 * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
3941
 * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
3942
 * that the function does not transition the post object in the database.
3943
 *
3944
 * For instance: When publishing a post for the first time, the post status may transition
3945
 * from 'draft' – or some other status – to 'publish'. However, if a post is already
3946
 * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
3947
 * before and after the transition.
3948
 *
3949
 * @since 2.3.0
3950
 *
3951
 * @param string  $new_status Transition to this post status.
3952
 * @param string  $old_status Previous post status.
3953
 * @param WP_Post $post Post data.
3954
 */
3955
function wp_transition_post_status( $new_status, $old_status, $post ) {
3956
	/**
3957
	 * Fires when a post is transitioned from one status to another.
3958
	 *
3959
	 * @since 2.3.0
3960
	 *
3961
	 * @param string  $new_status New post status.
3962
	 * @param string  $old_status Old post status.
3963
	 * @param WP_Post $post       Post object.
3964
	 */
3965
	do_action( 'transition_post_status', $new_status, $old_status, $post );
3966
3967
	/**
3968
	 * Fires when a post is transitioned from one status to another.
3969
	 *
3970
	 * The dynamic portions of the hook name, `$new_status` and `$old status`,
3971
	 * refer to the old and new post statuses, respectively.
3972
	 *
3973
	 * @since 2.3.0
3974
	 *
3975
	 * @param WP_Post $post Post object.
3976
	 */
3977
	do_action( "{$old_status}_to_{$new_status}", $post );
3978
3979
	/**
3980
	 * Fires when a post is transitioned from one status to another.
3981
	 *
3982
	 * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
3983
	 * refer to the new post status and post type, respectively.
3984
	 *
3985
	 * Please note: When this action is hooked using a particular post status (like
3986
	 * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
3987
	 * first transitioned to that status from something else, as well as upon
3988
	 * subsequent post updates (old and new status are both the same).
3989
	 *
3990
	 * Therefore, if you are looking to only fire a callback when a post is first
3991
	 * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
3992
	 *
3993
	 * @since 2.3.0
3994
	 *
3995
	 * @param int     $post_id Post ID.
3996
	 * @param WP_Post $post    Post object.
3997
	 */
3998
	do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
3999
}
4000
4001
//
4002
// Comment, trackback, and pingback functions.
4003
//
4004
4005
/**
4006
 * Add a URL to those already pinged.
4007
 *
4008
 * @since 1.5.0
4009
 * @since 4.7.0 $post_id can be a WP_Post object.
4010
 * @since 4.7.0 $uri can be an array of URIs.
4011
 *
4012
 * @global wpdb $wpdb WordPress database abstraction object.
4013
 *
4014
 * @param int|WP_Post  $post_id Post object or ID.
4015
 * @param string|array $uri     Ping URI or array of URIs.
4016
 * @return int|false How many rows were updated.
4017
 */
4018
function add_ping( $post_id, $uri ) {
4019
	global $wpdb;
4020
4021
	$post = get_post( $post_id );
4022
	if ( ! $post ) {
4023
		return false;
4024
	}
4025
4026
	$pung = trim( $post->pinged );
4027
	$pung = preg_split( '/\s/', $pung );
4028
4029
	if ( is_array( $uri ) ) {
4030
		$pung = array_merge( $pung, $uri );
4031
	}
4032
	else {
4033
		$pung[] = $uri;
4034
	}
4035
	$new = implode("\n", $pung);
4036
4037
	/**
4038
	 * Filters the new ping URL to add for the given post.
4039
	 *
4040
	 * @since 2.0.0
4041
	 *
4042
	 * @param string $new New ping URL to add.
4043
	 */
4044
	$new = apply_filters( 'add_ping', $new );
4045
4046
	$return = $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post->ID ) );
4047
	clean_post_cache( $post->ID );
4048
	return $return;
4049
}
4050
4051
/**
4052
 * Retrieve enclosures already enclosed for a post.
4053
 *
4054
 * @since 1.5.0
4055
 *
4056
 * @param int $post_id Post ID.
4057
 * @return array List of enclosures.
4058
 */
4059
function get_enclosed( $post_id ) {
4060
	$custom_fields = get_post_custom( $post_id );
4061
	$pung = array();
4062
	if ( !is_array( $custom_fields ) )
4063
		return $pung;
4064
4065
	foreach ( $custom_fields as $key => $val ) {
4066
		if ( 'enclosure' != $key || !is_array( $val ) )
4067
			continue;
4068
		foreach ( $val as $enc ) {
4069
			$enclosure = explode( "\n", $enc );
4070
			$pung[] = trim( $enclosure[ 0 ] );
4071
		}
4072
	}
4073
4074
	/**
4075
	 * Filters the list of enclosures already enclosed for the given post.
4076
	 *
4077
	 * @since 2.0.0
4078
	 *
4079
	 * @param array $pung    Array of enclosures for the given post.
4080
	 * @param int   $post_id Post ID.
4081
	 */
4082
	return apply_filters( 'get_enclosed', $pung, $post_id );
4083
}
4084
4085
/**
4086
 * Retrieve URLs already pinged for a post.
4087
 *
4088
 * @since 1.5.0
4089
 *
4090
 * @since 4.7.0 $post_id can be a WP_Post object.
4091
 *
4092
 * @param int|WP_Post $post_id Post ID or object.
4093
 * @return array
4094
 */
4095
function get_pung( $post_id ) {
4096
	$post = get_post( $post_id );
4097
	if ( ! $post ) {
4098
		return false;
4099
	}
4100
4101
	$pung = trim( $post->pinged );
4102
	$pung = preg_split( '/\s/', $pung );
4103
4104
	/**
4105
	 * Filters the list of already-pinged URLs for the given post.
4106
	 *
4107
	 * @since 2.0.0
4108
	 *
4109
	 * @param array $pung Array of URLs already pinged for the given post.
4110
	 */
4111
	return apply_filters( 'get_pung', $pung );
4112
}
4113
4114
/**
4115
 * Retrieve URLs that need to be pinged.
4116
 *
4117
 * @since 1.5.0
4118
 * @since 4.7.0 $post_id can be a WP_Post object.
4119
 *
4120
 * @param int|WP_Post $post_id Post Object or ID
4121
 * @return array
4122
 */
4123
function get_to_ping( $post_id ) {
4124
	$post = get_post( $post_id );
4125
4126
	if ( ! $post ) {
4127
		return false;
4128
	}
4129
4130
	$to_ping = sanitize_trackback_urls( $post->to_ping );
4131
	$to_ping = preg_split('/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY);
4132
4133
	/**
4134
	 * Filters the list of URLs yet to ping for the given post.
4135
	 *
4136
	 * @since 2.0.0
4137
	 *
4138
	 * @param array $to_ping List of URLs yet to ping.
4139
	 */
4140
	return apply_filters( 'get_to_ping', $to_ping );
4141
}
4142
4143
/**
4144
 * Do trackbacks for a list of URLs.
4145
 *
4146
 * @since 1.0.0
4147
 *
4148
 * @param string $tb_list Comma separated list of URLs.
4149
 * @param int    $post_id Post ID.
4150
 */
4151
function trackback_url_list( $tb_list, $post_id ) {
4152
	if ( ! empty( $tb_list ) ) {
4153
		// Get post data.
4154
		$postdata = get_post( $post_id, ARRAY_A );
4155
4156
		// Form an excerpt.
4157
		$excerpt = strip_tags( $postdata['post_excerpt'] ? $postdata['post_excerpt'] : $postdata['post_content'] );
4158
4159
		if ( strlen( $excerpt ) > 255 ) {
4160
			$excerpt = substr( $excerpt, 0, 252 ) . '&hellip;';
4161
		}
4162
4163
		$trackback_urls = explode( ',', $tb_list );
4164
		foreach ( (array) $trackback_urls as $tb_url ) {
4165
			$tb_url = trim( $tb_url );
4166
			trackback( $tb_url, wp_unslash( $postdata['post_title'] ), $excerpt, $post_id );
0 ignored issues
show
It seems like wp_unslash($postdata['post_title']) targeting wp_unslash() can also be of type array; however, trackback() does only seem to accept string, maybe add an additional type check?

This check looks at variables that are passed out again to other methods.

If the outgoing method call has stricter type requirements than the method itself, an issue is raised.

An additional type check may prevent trouble.

Loading history...
4167
		}
4168
	}
4169
}
4170
4171
//
4172
// Page functions
4173
//
4174
4175
/**
4176
 * Get a list of page IDs.
4177
 *
4178
 * @since 2.0.0
4179
 *
4180
 * @global wpdb $wpdb WordPress database abstraction object.
4181
 *
4182
 * @return array List of page IDs.
4183
 */
4184
function get_all_page_ids() {
4185
	global $wpdb;
4186
4187
	$page_ids = wp_cache_get('all_page_ids', 'posts');
4188
	if ( ! is_array( $page_ids ) ) {
4189
		$page_ids = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_type = 'page'");
4190
		wp_cache_add('all_page_ids', $page_ids, 'posts');
4191
	}
4192
4193
	return $page_ids;
4194
}
4195
4196
/**
4197
 * Retrieves page data given a page ID or page object.
4198
 *
4199
 * Use get_post() instead of get_page().
4200
 *
4201
 * @since 1.5.1
4202
 * @deprecated 3.5.0 Use get_post()
4203
 *
4204
 * @param mixed  $page   Page object or page ID. Passed by reference.
4205
 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4206
 *                       a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4207
 * @param string $filter Optional. How the return value should be filtered. Accepts 'raw',
4208
 *                       'edit', 'db', 'display'. Default 'raw'.
4209
 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4210
 */
4211
function get_page( $page, $output = OBJECT, $filter = 'raw') {
4212
	return get_post( $page, $output, $filter );
4213
}
4214
4215
/**
4216
 * Retrieves a page given its path.
4217
 *
4218
 * @since 2.1.0
4219
 *
4220
 * @global wpdb $wpdb WordPress database abstraction object.
4221
 *
4222
 * @param string       $page_path Page path.
4223
 * @param string       $output    Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4224
 *                                a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4225
 * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4226
 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4227
 */
4228
function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4229
	global $wpdb;
4230
4231
	$last_changed = wp_cache_get_last_changed( 'posts' );
4232
4233
	$hash = md5( $page_path . serialize( $post_type ) );
4234
	$cache_key = "get_page_by_path:$hash:$last_changed";
4235
	$cached = wp_cache_get( $cache_key, 'posts' );
4236
	if ( false !== $cached ) {
4237
		// Special case: '0' is a bad `$page_path`.
4238
		if ( '0' === $cached || 0 === $cached ) {
4239
			return;
4240
		} else {
4241
			return get_post( $cached, $output );
4242
		}
4243
	}
4244
4245
	$page_path = rawurlencode(urldecode($page_path));
4246
	$page_path = str_replace('%2F', '/', $page_path);
4247
	$page_path = str_replace('%20', ' ', $page_path);
4248
	$parts = explode( '/', trim( $page_path, '/' ) );
4249
	$parts = esc_sql( $parts );
4250
	$parts = array_map( 'sanitize_title_for_query', $parts );
4251
4252
	$in_string = "'" . implode( "','", $parts ) . "'";
4253
4254
	if ( is_array( $post_type ) ) {
4255
		$post_types = $post_type;
4256
	} else {
4257
		$post_types = array( $post_type, 'attachment' );
4258
	}
4259
4260
	$post_types = esc_sql( $post_types );
4261
	$post_type_in_string = "'" . implode( "','", $post_types ) . "'";
4262
	$sql = "
4263
		SELECT ID, post_name, post_parent, post_type
4264
		FROM $wpdb->posts
4265
		WHERE post_name IN ($in_string)
4266
		AND post_type IN ($post_type_in_string)
4267
	";
4268
4269
	$pages = $wpdb->get_results( $sql, OBJECT_K );
4270
4271
	$revparts = array_reverse( $parts );
4272
4273
	$foundid = 0;
4274
	foreach ( (array) $pages as $page ) {
4275
		if ( $page->post_name == $revparts[0] ) {
4276
			$count = 0;
4277
			$p = $page;
4278
4279
			/*
4280
			 * Loop through the given path parts from right to left,
4281
			 * ensuring each matches the post ancestry.
4282
			 */
4283
			while ( $p->post_parent != 0 && isset( $pages[ $p->post_parent ] ) ) {
4284
				$count++;
4285
				$parent = $pages[ $p->post_parent ];
4286
				if ( ! isset( $revparts[ $count ] ) || $parent->post_name != $revparts[ $count ] )
4287
					break;
4288
				$p = $parent;
4289
			}
4290
4291
			if ( $p->post_parent == 0 && $count+1 == count( $revparts ) && $p->post_name == $revparts[ $count ] ) {
4292
				$foundid = $page->ID;
4293
				if ( $page->post_type == $post_type )
4294
					break;
4295
			}
4296
		}
4297
	}
4298
4299
	// We cache misses as well as hits.
4300
	wp_cache_set( $cache_key, $foundid, 'posts' );
4301
4302
	if ( $foundid ) {
4303
		return get_post( $foundid, $output );
4304
	}
4305
}
4306
4307
/**
4308
 * Retrieve a page given its title.
4309
 *
4310
 * @since 2.1.0
4311
 *
4312
 * @global wpdb $wpdb WordPress database abstraction object.
4313
 *
4314
 * @param string       $page_title Page title
4315
 * @param string       $output     Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4316
 *                                 a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4317
 * @param string|array $post_type  Optional. Post type or array of post types. Default 'page'.
4318
 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4319
 */
4320
function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
4321
	global $wpdb;
4322
4323
	if ( is_array( $post_type ) ) {
4324
		$post_type = esc_sql( $post_type );
4325
		$post_type_in_string = "'" . implode( "','", $post_type ) . "'";
4326
		$sql = $wpdb->prepare( "
4327
			SELECT ID
4328
			FROM $wpdb->posts
4329
			WHERE post_title = %s
4330
			AND post_type IN ($post_type_in_string)
4331
		", $page_title );
4332
	} else {
4333
		$sql = $wpdb->prepare( "
4334
			SELECT ID
4335
			FROM $wpdb->posts
4336
			WHERE post_title = %s
4337
			AND post_type = %s
4338
		", $page_title, $post_type );
4339
	}
4340
4341
	$page = $wpdb->get_var( $sql );
4342
4343
	if ( $page ) {
4344
		return get_post( $page, $output );
4345
	}
4346
}
4347
4348
/**
4349
 * Identify descendants of a given page ID in a list of page objects.
4350
 *
4351
 * Descendants are identified from the `$pages` array passed to the function. No database queries are performed.
4352
 *
4353
 * @since 1.5.1
4354
 *
4355
 * @param int   $page_id Page ID.
4356
 * @param array $pages   List of page objects from which descendants should be identified.
4357
 * @return array List of page children.
4358
 */
4359
function get_page_children( $page_id, $pages ) {
4360
	// Build a hash of ID -> children.
4361
	$children = array();
4362
	foreach ( (array) $pages as $page ) {
4363
		$children[ intval( $page->post_parent ) ][] = $page;
4364
	}
4365
4366
	$page_list = array();
4367
4368
	// Start the search by looking at immediate children.
4369
	if ( isset( $children[ $page_id ] ) ) {
4370
		// Always start at the end of the stack in order to preserve original `$pages` order.
4371
		$to_look = array_reverse( $children[ $page_id ] );
4372
4373
		while ( $to_look ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $to_look of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
4374
			$p = array_pop( $to_look );
4375
			$page_list[] = $p;
4376
			if ( isset( $children[ $p->ID ] ) ) {
4377
				foreach ( array_reverse( $children[ $p->ID ] ) as $child ) {
4378
					// Append to the `$to_look` stack to descend the tree.
4379
					$to_look[] = $child;
4380
				}
4381
			}
4382
		}
4383
	}
4384
4385
	return $page_list;
4386
}
4387
4388
/**
4389
 * Order the pages with children under parents in a flat list.
4390
 *
4391
 * It uses auxiliary structure to hold parent-children relationships and
4392
 * runs in O(N) complexity
4393
 *
4394
 * @since 2.0.0
4395
 *
4396
 * @param array $pages   Posts array, passed by reference.
4397
 * @param int   $page_id Optional. Parent page ID. Default 0.
4398
 * @return array A list arranged by hierarchy. Children immediately follow their parents.
4399
 */
4400
function get_page_hierarchy( &$pages, $page_id = 0 ) {
4401
	if ( empty( $pages ) ) {
4402
		return array();
4403
	}
4404
4405
	$children = array();
4406
	foreach ( (array) $pages as $p ) {
4407
		$parent_id = intval( $p->post_parent );
4408
		$children[ $parent_id ][] = $p;
4409
	}
4410
4411
	$result = array();
4412
	_page_traverse_name( $page_id, $children, $result );
4413
4414
	return $result;
4415
}
4416
4417
/**
4418
 * Traverse and return all the nested children post names of a root page.
4419
 *
4420
 * $children contains parent-children relations
4421
 *
4422
 * @since 2.9.0
4423
 *
4424
 * @see _page_traverse_name()
4425
 *
4426
 * @param int   $page_id   Page ID.
4427
 * @param array $children  Parent-children relations, passed by reference.
4428
 * @param array $result    Result, passed by reference.
4429
 */
4430
function _page_traverse_name( $page_id, &$children, &$result ){
4431
	if ( isset( $children[ $page_id ] ) ){
4432
		foreach ( (array)$children[ $page_id ] as $child ) {
4433
			$result[ $child->ID ] = $child->post_name;
4434
			_page_traverse_name( $child->ID, $children, $result );
4435
		}
4436
	}
4437
}
4438
4439
/**
4440
 * Build the URI path for a page.
4441
 *
4442
 * Sub pages will be in the "directory" under the parent page post name.
4443
 *
4444
 * @since 1.5.0
4445
 * @since 4.6.0 Converted the `$page` parameter to optional.
4446
 *
4447
 * @param WP_Post|object|int $page Optional. Page ID or WP_Post object. Default is global $post.
0 ignored issues
show
Consider making the type for parameter $page a bit more specific; maybe use integer.
Loading history...
4448
 * @return string|false Page URI, false on error.
4449
 */
4450
function get_page_uri( $page = 0 ) {
4451
	if ( ! $page instanceof WP_Post ) {
4452
		$page = get_post( $page );
4453
	}
4454
4455
	if ( ! $page )
4456
		return false;
4457
4458
	$uri = $page->post_name;
4459
4460
	foreach ( $page->ancestors as $parent ) {
4461
		$parent = get_post( $parent );
4462
		if ( $parent && $parent->post_name ) {
4463
			$uri = $parent->post_name . '/' . $uri;
4464
		}
4465
	}
4466
4467
	/**
4468
	 * Filters the URI for a page.
4469
	 *
4470
	 * @since 4.4.0
4471
	 *
4472
	 * @param string  $uri  Page URI.
4473
	 * @param WP_Post $page Page object.
4474
	 */
4475
	return apply_filters( 'get_page_uri', $uri, $page );
4476
}
4477
4478
/**
4479
 * Retrieve a list of pages (or hierarchical post type items).
4480
 *
4481
 * @global wpdb $wpdb WordPress database abstraction object.
4482
 *
4483
 * @since 1.5.0
4484
 *
4485
 * @param array|string $args {
4486
 *     Optional. Array or string of arguments to retrieve pages.
4487
 *
4488
 *     @type int          $child_of     Page ID to return child and grandchild pages of. Note: The value
4489
 *                                      of `$hierarchical` has no bearing on whether `$child_of` returns
4490
 *                                      hierarchical results. Default 0, or no restriction.
4491
 *     @type string       $sort_order   How to sort retrieved pages. Accepts 'ASC', 'DESC'. Default 'ASC'.
4492
 *     @type string       $sort_column  What columns to sort pages by, comma-separated. Accepts 'post_author',
4493
 *                                      'post_date', 'post_title', 'post_name', 'post_modified', 'menu_order',
4494
 *                                      'post_modified_gmt', 'post_parent', 'ID', 'rand', 'comment_count'.
4495
 *                                      'post_' can be omitted for any values that start with it.
4496
 *                                      Default 'post_title'.
4497
 *     @type bool         $hierarchical Whether to return pages hierarchically. If false in conjunction with
4498
 *                                      `$child_of` also being false, both arguments will be disregarded.
4499
 *                                      Default true.
4500
 *     @type array        $exclude      Array of page IDs to exclude. Default empty array.
4501
 *     @type array        $include      Array of page IDs to include. Cannot be used with `$child_of`,
4502
 *                                      `$parent`, `$exclude`, `$meta_key`, `$meta_value`, or `$hierarchical`.
4503
 *                                      Default empty array.
4504
 *     @type string       $meta_key     Only include pages with this meta key. Default empty.
4505
 *     @type string       $meta_value   Only include pages with this meta value. Requires `$meta_key`.
4506
 *                                      Default empty.
4507
 *     @type string       $authors      A comma-separated list of author IDs. Default empty.
4508
 *     @type int          $parent       Page ID to return direct children of. Default -1, or no restriction.
4509
 *     @type string|array $exclude_tree Comma-separated string or array of page IDs to exclude.
4510
 *                                      Default empty array.
4511
 *     @type int          $number       The number of pages to return. Default 0, or all pages.
4512
 *     @type int          $offset       The number of pages to skip before returning. Requires `$number`.
4513
 *                                      Default 0.
4514
 *     @type string       $post_type    The post type to query. Default 'page'.
4515
 *     @type string|array $post_status  A comma-separated list or array of post statuses to include.
4516
 *                                      Default 'publish'.
4517
 * }
4518
 * @return array|false List of pages matching defaults or `$args`.
4519
 */
4520
function get_pages( $args = array() ) {
4521
	global $wpdb;
4522
4523
	$defaults = array(
4524
		'child_of'     => 0,
4525
		'sort_order'   => 'ASC',
4526
		'sort_column'  => 'post_title',
4527
		'hierarchical' => 1,
4528
		'exclude'      => array(),
4529
		'include'      => array(),
4530
		'meta_key'     => '',
4531
		'meta_value'   => '',
4532
		'authors'      => '',
4533
		'parent'       => -1,
4534
		'exclude_tree' => array(),
4535
		'number'       => '',
4536
		'offset'       => 0,
4537
		'post_type'    => 'page',
4538
		'post_status'  => 'publish',
4539
	);
4540
4541
	$r = wp_parse_args( $args, $defaults );
4542
4543
	$number = (int) $r['number'];
4544
	$offset = (int) $r['offset'];
4545
	$child_of = (int) $r['child_of'];
4546
	$hierarchical = $r['hierarchical'];
4547
	$exclude = $r['exclude'];
4548
	$meta_key = $r['meta_key'];
4549
	$meta_value = $r['meta_value'];
4550
	$parent = $r['parent'];
4551
	$post_status = $r['post_status'];
4552
4553
	// Make sure the post type is hierarchical.
4554
	$hierarchical_post_types = get_post_types( array( 'hierarchical' => true ) );
4555
	if ( ! in_array( $r['post_type'], $hierarchical_post_types ) ) {
4556
		return false;
4557
	}
4558
4559
	if ( $parent > 0 && ! $child_of ) {
4560
		$hierarchical = false;
4561
	}
4562
4563
	// Make sure we have a valid post status.
4564
	if ( ! is_array( $post_status ) ) {
4565
		$post_status = explode( ',', $post_status );
4566
	}
4567
	if ( array_diff( $post_status, get_post_stati() ) ) {
4568
		return false;
4569
	}
4570
4571
	// $args can be whatever, only use the args defined in defaults to compute the key.
4572
	$key = md5( serialize( wp_array_slice_assoc( $r, array_keys( $defaults ) ) ) );
4573
	$last_changed = wp_cache_get_last_changed( 'posts' );
4574
4575
	$cache_key = "get_pages:$key:$last_changed";
4576
	if ( $cache = wp_cache_get( $cache_key, 'posts' ) ) {
4577
		// Convert to WP_Post instances.
4578
		$pages = array_map( 'get_post', $cache );
4579
		/** This filter is documented in wp-includes/post.php */
4580
		$pages = apply_filters( 'get_pages', $pages, $r );
4581
		return $pages;
4582
	}
4583
4584
	$inclusions = '';
4585
	if ( ! empty( $r['include'] ) ) {
4586
		$child_of = 0; //ignore child_of, parent, exclude, meta_key, and meta_value params if using include
4587
		$parent = -1;
4588
		$exclude = '';
4589
		$meta_key = '';
4590
		$meta_value = '';
4591
		$hierarchical = false;
4592
		$incpages = wp_parse_id_list( $r['include'] );
4593
		if ( ! empty( $incpages ) ) {
4594
			$inclusions = ' AND ID IN (' . implode( ',', $incpages ) .  ')';
4595
		}
4596
	}
4597
4598
	$exclusions = '';
4599
	if ( ! empty( $exclude ) ) {
4600
		$expages = wp_parse_id_list( $exclude );
4601
		if ( ! empty( $expages ) ) {
4602
			$exclusions = ' AND ID NOT IN (' . implode( ',', $expages ) .  ')';
4603
		}
4604
	}
4605
4606
	$author_query = '';
4607
	if ( ! empty( $r['authors'] ) ) {
4608
		$post_authors = preg_split( '/[\s,]+/', $r['authors'] );
4609
4610
		if ( ! empty( $post_authors ) ) {
4611
			foreach ( $post_authors as $post_author ) {
4612
				//Do we have an author id or an author login?
4613
				if ( 0 == intval($post_author) ) {
4614
					$post_author = get_user_by('login', $post_author);
4615
					if ( empty( $post_author ) ) {
4616
						continue;
4617
					}
4618
					if ( empty( $post_author->ID ) ) {
4619
						continue;
4620
					}
4621
					$post_author = $post_author->ID;
4622
				}
4623
4624
				if ( '' == $author_query ) {
4625
					$author_query = $wpdb->prepare(' post_author = %d ', $post_author);
4626
				} else {
4627
					$author_query .= $wpdb->prepare(' OR post_author = %d ', $post_author);
4628
				}
4629
			}
4630
			if ( '' != $author_query ) {
4631
				$author_query = " AND ($author_query)";
4632
			}
4633
		}
4634
	}
4635
4636
	$join = '';
4637
	$where = "$exclusions $inclusions ";
4638
	if ( '' !== $meta_key || '' !== $meta_value ) {
4639
		$join = " LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )";
4640
4641
		// meta_key and meta_value might be slashed
4642
		$meta_key = wp_unslash($meta_key);
4643
		$meta_value = wp_unslash($meta_value);
4644
		if ( '' !== $meta_key ) {
4645
			$where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_key = %s", $meta_key);
4646
		}
4647
		if ( '' !== $meta_value ) {
4648
			$where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_value = %s", $meta_value);
4649
		}
4650
4651
	}
4652
4653
	if ( is_array( $parent ) ) {
4654
		$post_parent__in = implode( ',', array_map( 'absint', (array) $parent ) );
4655
		if ( ! empty( $post_parent__in ) ) {
4656
			$where .= " AND post_parent IN ($post_parent__in)";
4657
		}
4658
	} elseif ( $parent >= 0 ) {
4659
		$where .= $wpdb->prepare(' AND post_parent = %d ', $parent);
4660
	}
4661
4662
	if ( 1 == count( $post_status ) ) {
4663
		$where_post_type = $wpdb->prepare( "post_type = %s AND post_status = %s", $r['post_type'], reset( $post_status ) );
4664
	} else {
4665
		$post_status = implode( "', '", $post_status );
4666
		$where_post_type = $wpdb->prepare( "post_type = %s AND post_status IN ('$post_status')", $r['post_type'] );
4667
	}
4668
4669
	$orderby_array = array();
4670
	$allowed_keys = array( 'author', 'post_author', 'date', 'post_date', 'title', 'post_title', 'name', 'post_name', 'modified',
4671
		'post_modified', 'modified_gmt', 'post_modified_gmt', 'menu_order', 'parent', 'post_parent',
4672
		'ID', 'rand', 'comment_count' );
4673
4674
	foreach ( explode( ',', $r['sort_column'] ) as $orderby ) {
4675
		$orderby = trim( $orderby );
4676
		if ( ! in_array( $orderby, $allowed_keys ) ) {
4677
			continue;
4678
		}
4679
4680
		switch ( $orderby ) {
4681
			case 'menu_order':
4682
				break;
4683
			case 'ID':
4684
				$orderby = "$wpdb->posts.ID";
4685
				break;
4686
			case 'rand':
4687
				$orderby = 'RAND()';
4688
				break;
4689
			case 'comment_count':
4690
				$orderby = "$wpdb->posts.comment_count";
4691
				break;
4692
			default:
4693
				if ( 0 === strpos( $orderby, 'post_' ) ) {
4694
					$orderby = "$wpdb->posts." . $orderby;
4695
				} else {
4696
					$orderby = "$wpdb->posts.post_" . $orderby;
4697
				}
4698
		}
4699
4700
		$orderby_array[] = $orderby;
4701
4702
	}
4703
	$sort_column = ! empty( $orderby_array ) ? implode( ',', $orderby_array ) : "$wpdb->posts.post_title";
4704
4705
	$sort_order = strtoupper( $r['sort_order'] );
4706
	if ( '' !== $sort_order && ! in_array( $sort_order, array( 'ASC', 'DESC' ) ) ) {
4707
		$sort_order = 'ASC';
4708
	}
4709
4710
	$query = "SELECT * FROM $wpdb->posts $join WHERE ($where_post_type) $where ";
4711
	$query .= $author_query;
4712
	$query .= " ORDER BY " . $sort_column . " " . $sort_order ;
4713
4714
	if ( ! empty( $number ) ) {
4715
		$query .= ' LIMIT ' . $offset . ',' . $number;
4716
	}
4717
4718
	$pages = $wpdb->get_results($query);
4719
4720
	if ( empty($pages) ) {
4721
		/** This filter is documented in wp-includes/post.php */
4722
		$pages = apply_filters( 'get_pages', array(), $r );
4723
		return $pages;
4724
	}
4725
4726
	// Sanitize before caching so it'll only get done once.
4727
	$num_pages = count($pages);
4728
	for ($i = 0; $i < $num_pages; $i++) {
4729
		$pages[$i] = sanitize_post($pages[$i], 'raw');
4730
	}
4731
4732
	// Update cache.
4733
	update_post_cache( $pages );
4734
4735
	if ( $child_of || $hierarchical ) {
4736
		$pages = get_page_children($child_of, $pages);
4737
	}
4738
4739
	if ( ! empty( $r['exclude_tree'] ) ) {
4740
		$exclude = wp_parse_id_list( $r['exclude_tree'] );
4741
		foreach ( $exclude as $id ) {
4742
			$children = get_page_children( $id, $pages );
4743
			foreach ( $children as $child ) {
4744
				$exclude[] = $child->ID;
4745
			}
4746
		}
4747
4748
		$num_pages = count( $pages );
4749
		for ( $i = 0; $i < $num_pages; $i++ ) {
4750
			if ( in_array( $pages[$i]->ID, $exclude ) ) {
4751
				unset( $pages[$i] );
4752
			}
4753
		}
4754
	}
4755
4756
	$page_structure = array();
4757
	foreach ( $pages as $page ) {
4758
		$page_structure[] = $page->ID;
4759
	}
4760
4761
	wp_cache_set( $cache_key, $page_structure, 'posts' );
4762
4763
	// Convert to WP_Post instances
4764
	$pages = array_map( 'get_post', $pages );
4765
4766
	/**
4767
	 * Filters the retrieved list of pages.
4768
	 *
4769
	 * @since 2.1.0
4770
	 *
4771
	 * @param array $pages List of pages to retrieve.
4772
	 * @param array $r     Array of get_pages() arguments.
4773
	 */
4774
	return apply_filters( 'get_pages', $pages, $r );
4775
}
4776
4777
//
4778
// Attachment functions
4779
//
4780
4781
/**
4782
 * Check if the attachment URI is local one and is really an attachment.
4783
 *
4784
 * @since 2.0.0
4785
 *
4786
 * @param string $url URL to check
4787
 * @return bool True on success, false on failure.
4788
 */
4789
function is_local_attachment($url) {
4790
	if (strpos($url, home_url()) === false)
4791
		return false;
4792
	if (strpos($url, home_url('/?attachment_id=')) !== false)
4793
		return true;
4794
	if ( $id = url_to_postid($url) ) {
4795
		$post = get_post($id);
4796
		if ( 'attachment' == $post->post_type )
4797
			return true;
4798
	}
4799
	return false;
4800
}
4801
4802
/**
4803
 * Insert an attachment.
4804
 *
4805
 * If you set the 'ID' in the $args parameter, it will mean that you are
4806
 * updating and attempt to update the attachment. You can also set the
4807
 * attachment name or title by setting the key 'post_name' or 'post_title'.
4808
 *
4809
 * You can set the dates for the attachment manually by setting the 'post_date'
4810
 * and 'post_date_gmt' keys' values.
4811
 *
4812
 * By default, the comments will use the default settings for whether the
4813
 * comments are allowed. You can close them manually or keep them open by
4814
 * setting the value for the 'comment_status' key.
4815
 *
4816
 * @since 2.0.0
4817
 * @since 4.7.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
4818
 *
4819
 * @see wp_insert_post()
4820
 *
4821
 * @param string|array $args     Arguments for inserting an attachment.
4822
 * @param string       $file     Optional. Filename.
4823
 * @param int          $parent   Optional. Parent post ID.
4824
 * @param bool         $wp_error Optional. Whether to return a WP_Error on failure. Default false.
4825
 * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
4826
 */
4827
function wp_insert_attachment( $args, $file = false, $parent = 0, $wp_error = false ) {
4828
	$defaults = array(
4829
		'file'        => $file,
4830
		'post_parent' => 0
4831
	);
4832
4833
	$data = wp_parse_args( $args, $defaults );
4834
4835
	if ( ! empty( $parent ) ) {
4836
		$data['post_parent'] = $parent;
4837
	}
4838
4839
	$data['post_type'] = 'attachment';
4840
4841
	return wp_insert_post( $data, $wp_error );
4842
}
4843
4844
/**
4845
 * Trash or delete an attachment.
4846
 *
4847
 * When an attachment is permanently deleted, the file will also be removed.
4848
 * Deletion removes all post meta fields, taxonomy, comments, etc. associated
4849
 * with the attachment (except the main post).
4850
 *
4851
 * The attachment is moved to the trash instead of permanently deleted unless trash
4852
 * for media is disabled, item is already in the trash, or $force_delete is true.
4853
 *
4854
 * @since 2.0.0
4855
 *
4856
 * @global wpdb $wpdb WordPress database abstraction object.
4857
 *
4858
 * @param int  $post_id      Attachment ID.
4859
 * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
4860
 *                           Default false.
4861
 * @return mixed False on failure. Post data on success.
4862
 */
4863
function wp_delete_attachment( $post_id, $force_delete = false ) {
4864
	global $wpdb;
4865
4866
	if ( !$post = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id) ) )
4867
		return $post;
4868
4869
	if ( 'attachment' != $post->post_type )
4870
		return false;
4871
4872
	if ( !$force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' != $post->post_status )
4873
		return wp_trash_post( $post_id );
4874
4875
	delete_post_meta($post_id, '_wp_trash_meta_status');
4876
	delete_post_meta($post_id, '_wp_trash_meta_time');
4877
4878
	$meta = wp_get_attachment_metadata( $post_id );
4879
	$backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
4880
	$file = get_attached_file( $post_id );
4881
4882
	if ( is_multisite() )
4883
		delete_transient( 'dirsize_cache' );
4884
4885
	/**
4886
	 * Fires before an attachment is deleted, at the start of wp_delete_attachment().
4887
	 *
4888
	 * @since 2.0.0
4889
	 *
4890
	 * @param int $post_id Attachment ID.
4891
	 */
4892
	do_action( 'delete_attachment', $post_id );
4893
4894
	wp_delete_object_term_relationships($post_id, array('category', 'post_tag'));
4895
	wp_delete_object_term_relationships($post_id, get_object_taxonomies($post->post_type));
4896
4897
	// Delete all for any posts.
4898
	delete_metadata( 'post', null, '_thumbnail_id', $post_id, true );
4899
4900
	wp_defer_comment_counting( true );
4901
4902
	$comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ));
4903
	foreach ( $comment_ids as $comment_id ) {
4904
		wp_delete_comment( $comment_id, true );
4905
	}
4906
4907
	wp_defer_comment_counting( false );
4908
4909
	$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ));
4910
	foreach ( $post_meta_ids as $mid )
4911
		delete_metadata_by_mid( 'post', $mid );
4912
4913
	/** This action is documented in wp-includes/post.php */
4914
	do_action( 'delete_post', $post_id );
4915
	$result = $wpdb->delete( $wpdb->posts, array( 'ID' => $post_id ) );
4916
	if ( ! $result ) {
4917
		return false;
4918
	}
4919
	/** This action is documented in wp-includes/post.php */
4920
	do_action( 'deleted_post', $post_id );
4921
4922
	$uploadpath = wp_get_upload_dir();
4923
4924
	if ( ! empty($meta['thumb']) ) {
4925
		// Don't delete the thumb if another attachment uses it.
4926
		if (! $wpdb->get_row( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE meta_key = '_wp_attachment_metadata' AND meta_value LIKE %s AND post_id <> %d", '%' . $wpdb->esc_like( $meta['thumb'] ) . '%', $post_id)) ) {
4927
			$thumbfile = str_replace(basename($file), $meta['thumb'], $file);
4928
			/** This filter is documented in wp-includes/functions.php */
4929
			$thumbfile = apply_filters( 'wp_delete_file', $thumbfile );
4930
			@ unlink( path_join($uploadpath['basedir'], $thumbfile) );
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...
4931
		}
4932
	}
4933
4934
	// Remove intermediate and backup images if there are any.
4935
	if ( isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) {
4936
		foreach ( $meta['sizes'] as $size => $sizeinfo ) {
4937
			$intermediate_file = str_replace( basename( $file ), $sizeinfo['file'], $file );
4938
			/** This filter is documented in wp-includes/functions.php */
4939
			$intermediate_file = apply_filters( 'wp_delete_file', $intermediate_file );
4940
			@ unlink( path_join( $uploadpath['basedir'], $intermediate_file ) );
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...
4941
		}
4942
	}
4943
4944
	if ( is_array($backup_sizes) ) {
4945
		foreach ( $backup_sizes as $size ) {
4946
			$del_file = path_join( dirname($meta['file']), $size['file'] );
4947
			/** This filter is documented in wp-includes/functions.php */
4948
			$del_file = apply_filters( 'wp_delete_file', $del_file );
4949
			@ unlink( path_join($uploadpath['basedir'], $del_file) );
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...
4950
		}
4951
	}
4952
4953
	wp_delete_file( $file );
0 ignored issues
show
It seems like $file defined by get_attached_file($post_id) on line 4880 can also be of type false; however, wp_delete_file() does only seem to accept string, did you maybe forget to handle an error condition?

This check looks for type mismatches where the missing type is false. This is usually indicative of an error condtion.

Consider the follow example

<?php

function getDate($date)
{
    if ($date !== null) {
        return new DateTime($date);
    }

    return false;
}

This function either returns a new DateTime object or false, if there was an error. This is a typical pattern in PHP programming to show that an error has occurred without raising an exception. The calling code should check for this returned false before passing on the value to another function or method that may not be able to handle a false.

Loading history...
4954
4955
	clean_post_cache( $post );
4956
4957
	return $post;
4958
}
4959
4960
/**
4961
 * Retrieve attachment meta field for attachment ID.
4962
 *
4963
 * @since 2.1.0
4964
 *
4965
 * @param int  $post_id    Attachment ID. Default 0.
4966
 * @param bool $unfiltered Optional. If true, filters are not run. Default false.
4967
 * @return mixed Attachment meta field. False on failure.
4968
 */
4969 View Code Duplication
function wp_get_attachment_metadata( $post_id = 0, $unfiltered = false ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
4970
	$post_id = (int) $post_id;
4971
	if ( !$post = get_post( $post_id ) )
4972
		return false;
4973
4974
	$data = get_post_meta( $post->ID, '_wp_attachment_metadata', true );
4975
4976
	if ( $unfiltered )
4977
		return $data;
4978
4979
	/**
4980
	 * Filters the attachment meta data.
4981
	 *
4982
	 * @since 2.1.0
4983
	 *
4984
	 * @param array|bool $data    Array of meta data for the given attachment, or false
4985
	 *                            if the object does not exist.
4986
	 * @param int        $post_id Attachment ID.
4987
	 */
4988
	return apply_filters( 'wp_get_attachment_metadata', $data, $post->ID );
4989
}
4990
4991
/**
4992
 * Update metadata for an attachment.
4993
 *
4994
 * @since 2.1.0
4995
 *
4996
 * @param int   $post_id Attachment ID.
4997
 * @param array $data    Attachment data.
4998
 * @return int|bool False if $post is invalid.
4999
 */
5000 View Code Duplication
function wp_update_attachment_metadata( $post_id, $data ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5001
	$post_id = (int) $post_id;
5002
	if ( !$post = get_post( $post_id ) )
5003
		return false;
5004
5005
	/**
5006
	 * Filters the updated attachment meta data.
5007
	 *
5008
	 * @since 2.1.0
5009
	 *
5010
	 * @param array $data    Array of updated attachment meta data.
5011
	 * @param int   $post_id Attachment ID.
5012
	 */
5013
	if ( $data = apply_filters( 'wp_update_attachment_metadata', $data, $post->ID ) )
5014
		return update_post_meta( $post->ID, '_wp_attachment_metadata', $data );
5015
	else
5016
		return delete_post_meta( $post->ID, '_wp_attachment_metadata' );
5017
}
5018
5019
/**
5020
 * Retrieve the URL for an attachment.
5021
 *
5022
 * @since 2.1.0
5023
 *
5024
 * @global string $pagenow
5025
 *
5026
 * @param int $post_id Optional. Attachment ID. Default 0.
5027
 * @return string|false Attachment URL, otherwise false.
5028
 */
5029
function wp_get_attachment_url( $post_id = 0 ) {
5030
	$post_id = (int) $post_id;
5031
	if ( !$post = get_post( $post_id ) )
5032
		return false;
5033
5034
	if ( 'attachment' != $post->post_type )
5035
		return false;
5036
5037
	$url = '';
5038
	// Get attached file.
5039
	if ( $file = get_post_meta( $post->ID, '_wp_attached_file', true ) ) {
5040
		// Get upload directory.
5041
		if ( ( $uploads = wp_get_upload_dir() ) && false === $uploads['error'] ) {
5042
			// Check that the upload base exists in the file location.
5043
			if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
5044
				// Replace file location with url location.
5045
				$url = str_replace($uploads['basedir'], $uploads['baseurl'], $file);
5046
			} elseif ( false !== strpos($file, 'wp-content/uploads') ) {
5047
				// Get the directory name relative to the basedir (back compat for pre-2.7 uploads)
5048
				$url = trailingslashit( $uploads['baseurl'] . '/' . _wp_get_attachment_relative_path( $file ) ) . basename( $file );
5049
			} else {
5050
				// It's a newly-uploaded file, therefore $file is relative to the basedir.
5051
				$url = $uploads['baseurl'] . "/$file";
5052
			}
5053
		}
5054
	}
5055
5056
	/*
5057
	 * If any of the above options failed, Fallback on the GUID as used pre-2.7,
5058
	 * not recommended to rely upon this.
5059
	 */
5060
	if ( empty($url) ) {
5061
		$url = get_the_guid( $post->ID );
5062
	}
5063
5064
	// On SSL front end, URLs should be HTTPS.
5065
	if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $GLOBALS['pagenow'] ) {
5066
		$url = set_url_scheme( $url );
5067
	}
5068
5069
	/**
5070
	 * Filters the attachment URL.
5071
	 *
5072
	 * @since 2.1.0
5073
	 *
5074
	 * @param string $url     URL for the given attachment.
5075
	 * @param int    $post_id Attachment ID.
5076
	 */
5077
	$url = apply_filters( 'wp_get_attachment_url', $url, $post->ID );
5078
5079
	if ( empty( $url ) )
5080
		return false;
5081
5082
	return $url;
5083
}
5084
5085
/**
5086
 * Retrieves the caption for an attachment.
5087
 *
5088
 * @since 4.6.0
5089
 *
5090
 * @param int $post_id Optional. Attachment ID. Default is the ID of the global `$post`.
5091
 * @return string|false False on failure. Attachment caption on success.
5092
 */
5093 View Code Duplication
function wp_get_attachment_caption( $post_id = 0 ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5094
	$post_id = (int) $post_id;
5095
	if ( ! $post = get_post( $post_id ) ) {
5096
		return false;
5097
	}
5098
5099
	if ( 'attachment' !== $post->post_type ) {
5100
		return false;
5101
	}
5102
5103
	$caption = $post->post_excerpt;
5104
5105
	/**
5106
	 * Filters the attachment caption.
5107
	 *
5108
	 * @since 4.6.0
5109
	 *
5110
	 * @param string $caption Caption for the given attachment.
5111
	 * @param int    $post_id Attachment ID.
5112
	 */
5113
	return apply_filters( 'wp_get_attachment_caption', $caption, $post->ID );
5114
}
5115
5116
/**
5117
 * Retrieve thumbnail for an attachment.
5118
 *
5119
 * @since 2.1.0
5120
 *
5121
 * @param int $post_id Optional. Attachment ID. Default 0.
5122
 * @return string|false False on failure. Thumbnail file path on success.
5123
 */
5124
function wp_get_attachment_thumb_file( $post_id = 0 ) {
5125
	$post_id = (int) $post_id;
5126
	if ( !$post = get_post( $post_id ) )
5127
		return false;
5128
	if ( !is_array( $imagedata = wp_get_attachment_metadata( $post->ID ) ) )
5129
		return false;
5130
5131
	$file = get_attached_file( $post->ID );
5132
5133
	if ( !empty($imagedata['thumb']) && ($thumbfile = str_replace(basename($file), $imagedata['thumb'], $file)) && file_exists($thumbfile) ) {
5134
		/**
5135
		 * Filters the attachment thumbnail file path.
5136
		 *
5137
		 * @since 2.1.0
5138
		 *
5139
		 * @param string $thumbfile File path to the attachment thumbnail.
5140
		 * @param int    $post_id   Attachment ID.
5141
		 */
5142
		return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
5143
	}
5144
	return false;
5145
}
5146
5147
/**
5148
 * Retrieve URL for an attachment thumbnail.
5149
 *
5150
 * @since 2.1.0
5151
 *
5152
 * @param int $post_id Optional. Attachment ID. Default 0.
5153
 * @return string|false False on failure. Thumbnail URL on success.
5154
 */
5155
function wp_get_attachment_thumb_url( $post_id = 0 ) {
5156
	$post_id = (int) $post_id;
5157
	if ( !$post = get_post( $post_id ) )
5158
		return false;
5159
	if ( !$url = wp_get_attachment_url( $post->ID ) )
5160
		return false;
5161
5162
	$sized = image_downsize( $post_id, 'thumbnail' );
5163
	if ( $sized )
5164
		return $sized[0];
5165
5166
	if ( !$thumb = wp_get_attachment_thumb_file( $post->ID ) )
5167
		return false;
5168
5169
	$url = str_replace(basename($url), basename($thumb), $url);
5170
5171
	/**
5172
	 * Filters the attachment thumbnail URL.
5173
	 *
5174
	 * @since 2.1.0
5175
	 *
5176
	 * @param string $url     URL for the attachment thumbnail.
5177
	 * @param int    $post_id Attachment ID.
5178
	 */
5179
	return apply_filters( 'wp_get_attachment_thumb_url', $url, $post->ID );
5180
}
5181
5182
/**
5183
 * Verifies an attachment is of a given type.
5184
 *
5185
 * @since 4.2.0
5186
 *
5187
 * @param string      $type Attachment type. Accepts 'image', 'audio', or 'video'.
5188
 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
5189
 * @return bool True if one of the accepted types, false otherwise.
5190
 */
5191
function wp_attachment_is( $type, $post = null ) {
5192
	if ( ! $post = get_post( $post ) ) {
5193
		return false;
5194
	}
5195
5196
	if ( ! $file = get_attached_file( $post->ID ) ) {
5197
		return false;
5198
	}
5199
5200
	if ( 0 === strpos( $post->post_mime_type, $type . '/' ) ) {
5201
		return true;
5202
	}
5203
5204
	$check = wp_check_filetype( $file );
5205
	if ( empty( $check['ext'] ) ) {
5206
		return false;
5207
	}
5208
5209
	$ext = $check['ext'];
5210
5211
	if ( 'import' !== $post->post_mime_type ) {
5212
		return $type === $ext;
5213
	}
5214
5215
	switch ( $type ) {
5216
	case 'image':
5217
		$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png' );
5218
		return in_array( $ext, $image_exts );
5219
5220
	case 'audio':
5221
		return in_array( $ext, wp_get_audio_extensions() );
5222
5223
	case 'video':
5224
		return in_array( $ext, wp_get_video_extensions() );
5225
5226
	default:
5227
		return $type === $ext;
5228
	}
5229
}
5230
5231
/**
5232
 * Checks if the attachment is an image.
5233
 *
5234
 * @since 2.1.0
5235
 * @since 4.2.0 Modified into wrapper for wp_attachment_is() and
5236
 *              allowed WP_Post object to be passed.
5237
 *
5238
 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
5239
 * @return bool Whether the attachment is an image.
5240
 */
5241
function wp_attachment_is_image( $post = null ) {
5242
	return wp_attachment_is( 'image', $post );
5243
}
5244
5245
/**
5246
 * Retrieve the icon for a MIME type.
5247
 *
5248
 * @since 2.1.0
5249
 *
5250
 * @param string|int $mime MIME type or attachment ID.
0 ignored issues
show
Consider making the type for parameter $mime a bit more specific; maybe use integer.
Loading history...
5251
 * @return string|false Icon, false otherwise.
5252
 */
5253
function wp_mime_type_icon( $mime = 0 ) {
5254
	if ( !is_numeric($mime) )
5255
		$icon = wp_cache_get("mime_type_icon_$mime");
5256
5257
	$post_id = 0;
5258
	if ( empty($icon) ) {
5259
		$post_mimes = array();
5260
		if ( is_numeric($mime) ) {
5261
			$mime = (int) $mime;
5262
			if ( $post = get_post( $mime ) ) {
5263
				$post_id = (int) $post->ID;
5264
				$file = get_attached_file( $post_id );
5265
				$ext = preg_replace('/^.+?\.([^.]+)$/', '$1', $file);
5266
				if ( !empty($ext) ) {
5267
					$post_mimes[] = $ext;
5268
					if ( $ext_type = wp_ext2type( $ext ) )
5269
						$post_mimes[] = $ext_type;
5270
				}
5271
				$mime = $post->post_mime_type;
5272
			} else {
5273
				$mime = 0;
5274
			}
5275
		} else {
5276
			$post_mimes[] = $mime;
5277
		}
5278
5279
		$icon_files = wp_cache_get('icon_files');
5280
5281
		if ( !is_array($icon_files) ) {
5282
			/**
5283
			 * Filters the icon directory path.
5284
			 *
5285
			 * @since 2.0.0
5286
			 *
5287
			 * @param string $path Icon directory absolute path.
5288
			 */
5289
			$icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
5290
5291
			/**
5292
			 * Filters the icon directory URI.
5293
			 *
5294
			 * @since 2.0.0
5295
			 *
5296
			 * @param string $uri Icon directory URI.
5297
			 */
5298
			$icon_dir_uri = apply_filters( 'icon_dir_uri', includes_url( 'images/media' ) );
5299
5300
			/**
5301
			 * Filters the list of icon directory URIs.
5302
			 *
5303
			 * @since 2.5.0
5304
			 *
5305
			 * @param array $uris List of icon directory URIs.
5306
			 */
5307
			$dirs = apply_filters( 'icon_dirs', array( $icon_dir => $icon_dir_uri ) );
5308
			$icon_files = array();
5309
			while ( $dirs ) {
5310
				$keys = array_keys( $dirs );
5311
				$dir = array_shift( $keys );
5312
				$uri = array_shift($dirs);
5313
				if ( $dh = opendir($dir) ) {
5314
					while ( false !== $file = readdir($dh) ) {
5315
						$file = basename($file);
5316
						if ( substr($file, 0, 1) == '.' )
5317
							continue;
5318
						if ( !in_array(strtolower(substr($file, -4)), array('.png', '.gif', '.jpg') ) ) {
5319
							if ( is_dir("$dir/$file") )
5320
								$dirs["$dir/$file"] = "$uri/$file";
5321
							continue;
5322
						}
5323
						$icon_files["$dir/$file"] = "$uri/$file";
5324
					}
5325
					closedir($dh);
5326
				}
5327
			}
5328
			wp_cache_add( 'icon_files', $icon_files, 'default', 600 );
5329
		}
5330
5331
		$types = array();
5332
		// Icon basename - extension = MIME wildcard.
5333
		foreach ( $icon_files as $file => $uri )
5334
			$types[ preg_replace('/^([^.]*).*$/', '$1', basename($file)) ] =& $icon_files[$file];
5335
5336
		if ( ! empty($mime) ) {
5337
			$post_mimes[] = substr($mime, 0, strpos($mime, '/'));
5338
			$post_mimes[] = substr($mime, strpos($mime, '/') + 1);
5339
			$post_mimes[] = str_replace('/', '_', $mime);
5340
		}
5341
5342
		$matches = wp_match_mime_types(array_keys($types), $post_mimes);
5343
		$matches['default'] = array('default');
5344
5345
		foreach ( $matches as $match => $wilds ) {
5346
			foreach ( $wilds as $wild ) {
5347
				if ( ! isset( $types[ $wild ] ) ) {
5348
					continue;
5349
				}
5350
5351
				$icon = $types[ $wild ];
5352
				if ( ! is_numeric( $mime ) ) {
5353
					wp_cache_add( "mime_type_icon_$mime", $icon );
5354
				}
5355
				break 2;
5356
			}
5357
		}
5358
	}
5359
5360
	/**
5361
	 * Filters the mime type icon.
5362
	 *
5363
	 * @since 2.1.0
5364
	 *
5365
	 * @param string $icon    Path to the mime type icon.
5366
	 * @param string $mime    Mime type.
5367
	 * @param int    $post_id Attachment ID. Will equal 0 if the function passed
5368
	 *                        the mime type.
5369
	 */
5370
	return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id );
0 ignored issues
show
The variable $icon 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...
5371
}
5372
5373
/**
5374
 * Check for changed slugs for published post objects and save the old slug.
5375
 *
5376
 * The function is used when a post object of any type is updated,
5377
 * by comparing the current and previous post objects.
5378
 *
5379
 * If the slug was changed and not already part of the old slugs then it will be
5380
 * added to the post meta field ('_wp_old_slug') for storing old slugs for that
5381
 * post.
5382
 *
5383
 * The most logically usage of this function is redirecting changed post objects, so
5384
 * that those that linked to an changed post will be redirected to the new post.
5385
 *
5386
 * @since 2.1.0
5387
 *
5388
 * @param int     $post_id     Post ID.
5389
 * @param WP_Post $post        The Post Object
5390
 * @param WP_Post $post_before The Previous Post Object
5391
 */
5392
function wp_check_for_changed_slugs( $post_id, $post, $post_before ) {
5393
	// Don't bother if it hasn't changed.
5394
	if ( $post->post_name == $post_before->post_name ) {
5395
		return;
5396
	}
5397
5398
	// We're only concerned with published, non-hierarchical objects.
5399
	if ( ! ( 'publish' === $post->post_status || ( 'attachment' === get_post_type( $post ) && 'inherit' === $post->post_status ) ) || is_post_type_hierarchical( $post->post_type ) ) {
5400
		return;
5401
	}
5402
5403
	$old_slugs = (array) get_post_meta( $post_id, '_wp_old_slug' );
5404
5405
	// If we haven't added this old slug before, add it now.
5406
	if ( ! empty( $post_before->post_name ) && ! in_array( $post_before->post_name, $old_slugs ) ) {
5407
		add_post_meta( $post_id, '_wp_old_slug', $post_before->post_name );
5408
	}
5409
5410
	// If the new slug was used previously, delete it from the list.
5411
	if ( in_array( $post->post_name, $old_slugs ) ) {
5412
		delete_post_meta( $post_id, '_wp_old_slug', $post->post_name );
5413
	}
5414
}
5415
5416
/**
5417
 * Retrieve the private post SQL based on capability.
5418
 *
5419
 * This function provides a standardized way to appropriately select on the
5420
 * post_status of a post type. The function will return a piece of SQL code
5421
 * that can be added to a WHERE clause; this SQL is constructed to allow all
5422
 * published posts, and all private posts to which the user has access.
5423
 *
5424
 * @since 2.2.0
5425
 * @since 4.3.0 Added the ability to pass an array to `$post_type`.
5426
 *
5427
 * @param string|array $post_type Single post type or an array of post types. Currently only supports 'post' or 'page'.
5428
 * @return string SQL code that can be added to a where clause.
5429
 */
5430
function get_private_posts_cap_sql( $post_type ) {
5431
	return get_posts_by_author_sql( $post_type, false );
5432
}
5433
5434
/**
5435
 * Retrieve the post SQL based on capability, author, and type.
5436
 *
5437
 * @since 3.0.0
5438
 * @since 4.3.0 Introduced the ability to pass an array of post types to `$post_type`.
5439
 *
5440
 * @see get_private_posts_cap_sql()
5441
 * @global wpdb $wpdb WordPress database abstraction object.
5442
 *
5443
 * @param array|string   $post_type   Single post type or an array of post types.
5444
 * @param bool           $full        Optional. Returns a full WHERE statement instead of just
5445
 *                                    an 'andalso' term. Default true.
5446
 * @param int            $post_author Optional. Query posts having a single author ID. Default null.
5447
 * @param bool           $public_only Optional. Only return public posts. Skips cap checks for
5448
 *                                    $current_user.  Default false.
5449
 * @return string SQL WHERE code that can be added to a query.
5450
 */
5451
function get_posts_by_author_sql( $post_type, $full = true, $post_author = null, $public_only = false ) {
5452
	global $wpdb;
5453
5454
	if ( is_array( $post_type ) ) {
5455
		$post_types = $post_type;
5456
	} else {
5457
		$post_types = array( $post_type );
5458
	}
5459
5460
	$post_type_clauses = array();
5461
	foreach ( $post_types as $post_type ) {
5462
		$post_type_obj = get_post_type_object( $post_type );
5463
		if ( ! $post_type_obj ) {
5464
			continue;
5465
		}
5466
5467
		/**
5468
		 * Filters the capability to read private posts for a custom post type
5469
		 * when generating SQL for getting posts by author.
5470
		 *
5471
		 * @since 2.2.0
5472
		 * @deprecated 3.2.0 The hook transitioned from "somewhat useless" to "totally useless".
5473
		 *
5474
		 * @param string $cap Capability.
5475
		 */
5476
		if ( ! $cap = apply_filters( 'pub_priv_sql_capability', '' ) ) {
5477
			$cap = current_user_can( $post_type_obj->cap->read_private_posts );
5478
		}
5479
5480
		// Only need to check the cap if $public_only is false.
5481
		$post_status_sql = "post_status = 'publish'";
5482
		if ( false === $public_only ) {
5483
			if ( $cap ) {
5484
				// Does the user have the capability to view private posts? Guess so.
5485
				$post_status_sql .= " OR post_status = 'private'";
5486
			} elseif ( is_user_logged_in() ) {
5487
				// Users can view their own private posts.
5488
				$id = get_current_user_id();
5489
				if ( null === $post_author || ! $full ) {
5490
					$post_status_sql .= " OR post_status = 'private' AND post_author = $id";
5491
				} elseif ( $id == (int) $post_author ) {
5492
					$post_status_sql .= " OR post_status = 'private'";
5493
				} // else none
5494
			} // else none
5495
		}
5496
5497
		$post_type_clauses[] = "( post_type = '" . $post_type . "' AND ( $post_status_sql ) )";
5498
	}
5499
5500
	if ( empty( $post_type_clauses ) ) {
5501
		return $full ? 'WHERE 1 = 0' : '1 = 0';
5502
	}
5503
5504
	$sql = '( '. implode( ' OR ', $post_type_clauses ) . ' )';
5505
5506
	if ( null !== $post_author ) {
5507
		$sql .= $wpdb->prepare( ' AND post_author = %d', $post_author );
5508
	}
5509
5510
	if ( $full ) {
5511
		$sql = 'WHERE ' . $sql;
5512
	}
5513
5514
	return $sql;
5515
}
5516
5517
/**
5518
 * Retrieve the date that the last post was published.
5519
 *
5520
 * The server timezone is the default and is the difference between GMT and
5521
 * server time. The 'blog' value is the date when the last post was posted. The
5522
 * 'gmt' is when the last post was posted in GMT formatted date.
5523
 *
5524
 * @since 0.71
5525
 * @since 4.4.0 The `$post_type` argument was added.
5526
 *
5527
 * @param string $timezone  Optional. The timezone for the timestamp. Accepts 'server', 'blog', or 'gmt'.
5528
 *                          'server' uses the server's internal timezone.
5529
 *                          'blog' uses the `post_modified` field, which proxies to the timezone set for the site.
5530
 *                          'gmt' uses the `post_modified_gmt` field.
5531
 *                          Default 'server'.
5532
 * @param string $post_type Optional. The post type to check. Default 'any'.
5533
 * @return string The date of the last post.
5534
 */
5535
function get_lastpostdate( $timezone = 'server', $post_type = 'any' ) {
5536
	/**
5537
	 * Filters the date the last post was published.
5538
	 *
5539
	 * @since 2.3.0
5540
	 *
5541
	 * @param string $date     Date the last post was published.
5542
	 * @param string $timezone Location to use for getting the post published date.
5543
	 *                         See get_lastpostdate() for accepted `$timezone` values.
5544
	 */
5545
	return apply_filters( 'get_lastpostdate', _get_last_post_time( $timezone, 'date', $post_type ), $timezone );
5546
}
5547
5548
/**
5549
 * Get the timestamp of the last time any post was modified.
5550
 *
5551
 * The server timezone is the default and is the difference between GMT and
5552
 * server time. The 'blog' value is just when the last post was modified. The
5553
 * 'gmt' is when the last post was modified in GMT time.
5554
 *
5555
 * @since 1.2.0
5556
 * @since 4.4.0 The `$post_type` argument was added.
5557
 *
5558
 * @param string $timezone  Optional. The timezone for the timestamp. See get_lastpostdate()
5559
 *                          for information on accepted values.
5560
 *                          Default 'server'.
5561
 * @param string $post_type Optional. The post type to check. Default 'any'.
5562
 * @return string The timestamp.
5563
 */
5564
function get_lastpostmodified( $timezone = 'server', $post_type = 'any' ) {
5565
	/**
5566
	 * Pre-filter the return value of get_lastpostmodified() before the query is run.
5567
	 *
5568
	 * @since 4.4.0
5569
	 *
5570
	 * @param string $lastpostmodified Date the last post was modified.
5571
	 *                                 Returning anything other than false will short-circuit the function.
5572
	 * @param string $timezone         Location to use for getting the post modified date.
5573
	 *                                 See get_lastpostdate() for accepted `$timezone` values.
5574
	 * @param string $post_type        The post type to check.
5575
	 */
5576
	$lastpostmodified = apply_filters( 'pre_get_lastpostmodified', false, $timezone, $post_type );
5577
	if ( false !== $lastpostmodified ) {
5578
		return $lastpostmodified;
5579
	}
5580
5581
	$lastpostmodified = _get_last_post_time( $timezone, 'modified', $post_type );
5582
5583
	$lastpostdate = get_lastpostdate($timezone);
5584
	if ( $lastpostdate > $lastpostmodified ) {
5585
		$lastpostmodified = $lastpostdate;
5586
	}
5587
5588
	/**
5589
	 * Filters the date the last post was modified.
5590
	 *
5591
	 * @since 2.3.0
5592
	 *
5593
	 * @param string $lastpostmodified Date the last post was modified.
5594
	 * @param string $timezone         Location to use for getting the post modified date.
5595
	 *                                 See get_lastpostdate() for accepted `$timezone` values.
5596
	 */
5597
	return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone );
5598
}
5599
5600
/**
5601
 * Get the timestamp of the last time any post was modified or published.
5602
 *
5603
 * @since 3.1.0
5604
 * @since 4.4.0 The `$post_type` argument was added.
5605
 * @access private
5606
 *
5607
 * @global wpdb $wpdb WordPress database abstraction object.
5608
 *
5609
 * @param string $timezone  The timezone for the timestamp. See get_lastpostdate().
5610
 *                          for information on accepted values.
5611
 * @param string $field     Post field to check. Accepts 'date' or 'modified'.
5612
 * @param string $post_type Optional. The post type to check. Default 'any'.
5613
 * @return string|false The timestamp.
5614
 */
5615
function _get_last_post_time( $timezone, $field, $post_type = 'any' ) {
5616
	global $wpdb;
5617
5618
	if ( ! in_array( $field, array( 'date', 'modified' ) ) ) {
5619
		return false;
5620
	}
5621
5622
	$timezone = strtolower( $timezone );
5623
5624
	$key = "lastpost{$field}:$timezone";
5625
	if ( 'any' !== $post_type ) {
5626
		$key .= ':' . sanitize_key( $post_type );
5627
	}
5628
5629
	$date = wp_cache_get( $key, 'timeinfo' );
5630
	if ( false !== $date ) {
5631
		return $date;
5632
	}
5633
5634
	if ( 'any' === $post_type ) {
5635
		$post_types = get_post_types( array( 'public' => true ) );
5636
		array_walk( $post_types, array( $wpdb, 'escape_by_ref' ) );
5637
		$post_types = "'" . implode( "', '", $post_types ) . "'";
5638
	} else {
5639
		$post_types = "'" . sanitize_key( $post_type ) . "'";
5640
	}
5641
5642
	switch ( $timezone ) {
5643
		case 'gmt':
5644
			$date = $wpdb->get_var("SELECT post_{$field}_gmt FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1");
5645
			break;
5646
		case 'blog':
5647
			$date = $wpdb->get_var("SELECT post_{$field} FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1");
5648
			break;
5649
		case 'server':
5650
			$add_seconds_server = date( 'Z' );
5651
			$date = $wpdb->get_var("SELECT DATE_ADD(post_{$field}_gmt, INTERVAL '$add_seconds_server' SECOND) FROM $wpdb->posts WHERE post_status = 'publish' AND post_type IN ({$post_types}) ORDER BY post_{$field}_gmt DESC LIMIT 1");
5652
			break;
5653
	}
5654
5655
	if ( $date ) {
5656
		wp_cache_set( $key, $date, 'timeinfo' );
5657
5658
		return $date;
5659
	}
5660
5661
	return false;
5662
}
5663
5664
/**
5665
 * Updates posts in cache.
5666
 *
5667
 * @since 1.5.1
5668
 *
5669
 * @param array $posts Array of post objects, passed by reference.
5670
 */
5671
function update_post_cache( &$posts ) {
5672
	if ( ! $posts )
0 ignored issues
show
Bug Best Practice introduced by
The expression $posts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
5673
		return;
5674
5675
	foreach ( $posts as $post )
5676
		wp_cache_add( $post->ID, $post, 'posts' );
5677
}
5678
5679
/**
5680
 * Will clean the post in the cache.
5681
 *
5682
 * Cleaning means delete from the cache of the post. Will call to clean the term
5683
 * object cache associated with the post ID.
5684
 *
5685
 * This function not run if $_wp_suspend_cache_invalidation is not empty. See
5686
 * wp_suspend_cache_invalidation().
5687
 *
5688
 * @since 2.0.0
5689
 *
5690
 * @global bool $_wp_suspend_cache_invalidation
5691
 *
5692
 * @param int|WP_Post $post Post ID or post object to remove from the cache.
5693
 */
5694
function clean_post_cache( $post ) {
5695
	global $_wp_suspend_cache_invalidation;
5696
5697
	if ( ! empty( $_wp_suspend_cache_invalidation ) )
5698
		return;
5699
5700
	$post = get_post( $post );
5701
	if ( empty( $post ) )
5702
		return;
5703
5704
	wp_cache_delete( $post->ID, 'posts' );
5705
	wp_cache_delete( $post->ID, 'post_meta' );
5706
5707
	clean_object_term_cache( $post->ID, $post->post_type );
5708
5709
	wp_cache_delete( 'wp_get_archives', 'general' );
5710
5711
	/**
5712
	 * Fires immediately after the given post's cache is cleaned.
5713
	 *
5714
	 * @since 2.5.0
5715
	 *
5716
	 * @param int     $post_id Post ID.
5717
	 * @param WP_Post $post    Post object.
5718
	 */
5719
	do_action( 'clean_post_cache', $post->ID, $post );
5720
5721
	if ( 'page' == $post->post_type ) {
5722
		wp_cache_delete( 'all_page_ids', 'posts' );
5723
5724
		/**
5725
		 * Fires immediately after the given page's cache is cleaned.
5726
		 *
5727
		 * @since 2.5.0
5728
		 *
5729
		 * @param int $post_id Post ID.
5730
		 */
5731
		do_action( 'clean_page_cache', $post->ID );
5732
	}
5733
5734
	wp_cache_set( 'last_changed', microtime(), 'posts' );
5735
}
5736
5737
/**
5738
 * Call major cache updating functions for list of Post objects.
5739
 *
5740
 * @since 1.5.0
5741
 *
5742
 * @param array  $posts             Array of Post objects
5743
 * @param string $post_type         Optional. Post type. Default 'post'.
5744
 * @param bool   $update_term_cache Optional. Whether to update the term cache. Default true.
5745
 * @param bool   $update_meta_cache Optional. Whether to update the meta cache. Default true.
5746
 */
5747
function update_post_caches( &$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true ) {
5748
	// No point in doing all this work if we didn't match any posts.
5749
	if ( !$posts )
0 ignored issues
show
Bug Best Practice introduced by
The expression $posts of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
5750
		return;
5751
5752
	update_post_cache($posts);
5753
5754
	$post_ids = array();
5755
	foreach ( $posts as $post )
5756
		$post_ids[] = $post->ID;
5757
5758
	if ( ! $post_type )
5759
		$post_type = 'any';
5760
5761
	if ( $update_term_cache ) {
5762
		if ( is_array($post_type) ) {
5763
			$ptypes = $post_type;
5764
		} elseif ( 'any' == $post_type ) {
5765
			$ptypes = array();
5766
			// Just use the post_types in the supplied posts.
5767
			foreach ( $posts as $post ) {
5768
				$ptypes[] = $post->post_type;
5769
			}
5770
			$ptypes = array_unique($ptypes);
5771
		} else {
5772
			$ptypes = array($post_type);
5773
		}
5774
5775
		if ( ! empty($ptypes) )
5776
			update_object_term_cache($post_ids, $ptypes);
5777
	}
5778
5779
	if ( $update_meta_cache )
5780
		update_postmeta_cache($post_ids);
5781
}
5782
5783
/**
5784
 * Updates metadata cache for list of post IDs.
5785
 *
5786
 * Performs SQL query to retrieve the metadata for the post IDs and updates the
5787
 * metadata cache for the posts. Therefore, the functions, which call this
5788
 * function, do not need to perform SQL queries on their own.
5789
 *
5790
 * @since 2.1.0
5791
 *
5792
 * @param array $post_ids List of post IDs.
5793
 * @return array|false Returns false if there is nothing to update or an array
5794
 *                     of metadata.
5795
 */
5796
function update_postmeta_cache( $post_ids ) {
5797
	return update_meta_cache('post', $post_ids);
5798
}
5799
5800
/**
5801
 * Will clean the attachment in the cache.
5802
 *
5803
 * Cleaning means delete from the cache. Optionally will clean the term
5804
 * object cache associated with the attachment ID.
5805
 *
5806
 * This function will not run if $_wp_suspend_cache_invalidation is not empty.
5807
 *
5808
 * @since 3.0.0
5809
 *
5810
 * @global bool $_wp_suspend_cache_invalidation
5811
 *
5812
 * @param int  $id          The attachment ID in the cache to clean.
5813
 * @param bool $clean_terms Optional. Whether to clean terms cache. Default false.
5814
 */
5815
function clean_attachment_cache( $id, $clean_terms = false ) {
5816
	global $_wp_suspend_cache_invalidation;
5817
5818
	if ( !empty($_wp_suspend_cache_invalidation) )
5819
		return;
5820
5821
	$id = (int) $id;
5822
5823
	wp_cache_delete($id, 'posts');
5824
	wp_cache_delete($id, 'post_meta');
5825
5826
	if ( $clean_terms )
5827
		clean_object_term_cache($id, 'attachment');
5828
5829
	/**
5830
	 * Fires after the given attachment's cache is cleaned.
5831
	 *
5832
	 * @since 3.0.0
5833
	 *
5834
	 * @param int $id Attachment ID.
5835
	 */
5836
	do_action( 'clean_attachment_cache', $id );
5837
}
5838
5839
//
5840
// Hooks
5841
//
5842
5843
/**
5844
 * Hook for managing future post transitions to published.
5845
 *
5846
 * @since 2.3.0
5847
 * @access private
5848
 *
5849
 * @see wp_clear_scheduled_hook()
5850
 * @global wpdb $wpdb WordPress database abstraction object.
5851
 *
5852
 * @param string  $new_status New post status.
5853
 * @param string  $old_status Previous post status.
5854
 * @param WP_Post $post       Post object.
5855
 */
5856
function _transition_post_status( $new_status, $old_status, $post ) {
5857
	global $wpdb;
5858
5859
	if ( $old_status != 'publish' && $new_status == 'publish' ) {
5860
		// Reset GUID if transitioning to publish and it is empty.
5861
		if ( '' == get_the_guid($post->ID) )
5862
			$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) );
5863
5864
		/**
5865
		 * Fires when a post's status is transitioned from private to published.
5866
		 *
5867
		 * @since 1.5.0
5868
		 * @deprecated 2.3.0 Use 'private_to_publish' instead.
5869
		 *
5870
		 * @param int $post_id Post ID.
5871
		 */
5872
		do_action('private_to_published', $post->ID);
5873
	}
5874
5875
	// If published posts changed clear the lastpostmodified cache.
5876
	if ( 'publish' == $new_status || 'publish' == $old_status) {
5877
		foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
5878
			wp_cache_delete( "lastpostmodified:$timezone", 'timeinfo' );
5879
			wp_cache_delete( "lastpostdate:$timezone", 'timeinfo' );
5880
			wp_cache_delete( "lastpostdate:$timezone:{$post->post_type}", 'timeinfo' );
5881
		}
5882
	}
5883
5884
	if ( $new_status !== $old_status ) {
5885
		wp_cache_delete( _count_posts_cache_key( $post->post_type ), 'counts' );
5886
		wp_cache_delete( _count_posts_cache_key( $post->post_type, 'readable' ), 'counts' );
5887
	}
5888
5889
	// Always clears the hook in case the post status bounced from future to draft.
5890
	wp_clear_scheduled_hook('publish_future_post', array( $post->ID ) );
5891
}
5892
5893
/**
5894
 * Hook used to schedule publication for a post marked for the future.
5895
 *
5896
 * The $post properties used and must exist are 'ID' and 'post_date_gmt'.
5897
 *
5898
 * @since 2.3.0
5899
 * @access private
5900
 *
5901
 * @param int     $deprecated Not used. Can be set to null. Never implemented. Not marked
5902
 *                            as deprecated with _deprecated_argument() as it conflicts with
5903
 *                            wp_transition_post_status() and the default filter for _future_post_hook().
5904
 * @param WP_Post $post       Post object.
5905
 */
5906
function _future_post_hook( $deprecated, $post ) {
0 ignored issues
show
The parameter $deprecated is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
5907
	wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
5908
	wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT') , 'publish_future_post', array( $post->ID ) );
5909
}
5910
5911
/**
5912
 * Hook to schedule pings and enclosures when a post is published.
5913
 *
5914
 * Uses XMLRPC_REQUEST and WP_IMPORTING constants.
5915
 *
5916
 * @since 2.3.0
5917
 * @access private
5918
 *
5919
 * @param int $post_id The ID in the database table of the post being published.
5920
 */
5921
function _publish_post_hook( $post_id ) {
5922
	if ( defined( 'XMLRPC_REQUEST' ) ) {
5923
		/**
5924
		 * Fires when _publish_post_hook() is called during an XML-RPC request.
5925
		 *
5926
		 * @since 2.1.0
5927
		 *
5928
		 * @param int $post_id Post ID.
5929
		 */
5930
		do_action( 'xmlrpc_publish_post', $post_id );
5931
	}
5932
5933
	if ( defined('WP_IMPORTING') )
5934
		return;
5935
5936
	if ( get_option('default_pingback_flag') )
5937
		add_post_meta( $post_id, '_pingme', '1' );
5938
	add_post_meta( $post_id, '_encloseme', '1' );
5939
5940
	if ( ! wp_next_scheduled( 'do_pings' ) ) {
5941
		wp_schedule_single_event( time(), 'do_pings' );
5942
	}
5943
}
5944
5945
/**
5946
 * Return the post's parent's post_ID
5947
 *
5948
 * @since 3.1.0
5949
 *
5950
 * @param int $post_ID
5951
 *
5952
 * @return int|false Post parent ID, otherwise false.
5953
 */
5954
function wp_get_post_parent_id( $post_ID ) {
5955
	$post = get_post( $post_ID );
5956
	if ( !$post || is_wp_error( $post ) )
5957
		return false;
5958
	return (int) $post->post_parent;
5959
}
5960
5961
/**
5962
 * Check the given subset of the post hierarchy for hierarchy loops.
5963
 *
5964
 * Prevents loops from forming and breaks those that it finds. Attached
5965
 * to the {@see 'wp_insert_post_parent'} filter.
5966
 *
5967
 * @since 3.1.0
5968
 *
5969
 * @see wp_find_hierarchy_loop()
5970
 *
5971
 * @param int $post_parent ID of the parent for the post we're checking.
5972
 * @param int $post_ID     ID of the post we're checking.
5973
 * @return int The new post_parent for the post, 0 otherwise.
5974
 */
5975 View Code Duplication
function wp_check_post_hierarchy_for_loops( $post_parent, $post_ID ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
5976
	// Nothing fancy here - bail.
5977
	if ( !$post_parent )
5978
		return 0;
5979
5980
	// New post can't cause a loop.
5981
	if ( empty( $post_ID ) )
5982
		return $post_parent;
5983
5984
	// Can't be its own parent.
5985
	if ( $post_parent == $post_ID )
5986
		return 0;
5987
5988
	// Now look for larger loops.
5989
	if ( !$loop = wp_find_hierarchy_loop( 'wp_get_post_parent_id', $post_ID, $post_parent ) )
5990
		return $post_parent; // No loop
5991
5992
	// Setting $post_parent to the given value causes a loop.
5993
	if ( isset( $loop[$post_ID] ) )
5994
		return 0;
5995
5996
	// There's a loop, but it doesn't contain $post_ID. Break the loop.
5997
	foreach ( array_keys( $loop ) as $loop_member )
5998
		wp_update_post( array( 'ID' => $loop_member, 'post_parent' => 0 ) );
5999
6000
	return $post_parent;
6001
}
6002
6003
/**
6004
 * Set a post thumbnail.
6005
 *
6006
 * @since 3.1.0
6007
 *
6008
 * @param int|WP_Post $post         Post ID or post object where thumbnail should be attached.
6009
 * @param int         $thumbnail_id Thumbnail to attach.
6010
 * @return int|bool True on success, false on failure.
6011
 */
6012
function set_post_thumbnail( $post, $thumbnail_id ) {
6013
	$post = get_post( $post );
6014
	$thumbnail_id = absint( $thumbnail_id );
6015
	if ( $post && $thumbnail_id && get_post( $thumbnail_id ) ) {
6016
		if ( wp_get_attachment_image( $thumbnail_id, 'thumbnail' ) )
6017
			return update_post_meta( $post->ID, '_thumbnail_id', $thumbnail_id );
6018
		else
6019
			return delete_post_meta( $post->ID, '_thumbnail_id' );
6020
	}
6021
	return false;
6022
}
6023
6024
/**
6025
 * Remove a post thumbnail.
6026
 *
6027
 * @since 3.3.0
6028
 *
6029
 * @param int|WP_Post $post Post ID or post object where thumbnail should be removed from.
6030
 * @return bool True on success, false on failure.
6031
 */
6032
function delete_post_thumbnail( $post ) {
6033
	$post = get_post( $post );
6034
	if ( $post )
6035
		return delete_post_meta( $post->ID, '_thumbnail_id' );
6036
	return false;
6037
}
6038
6039
/**
6040
 * Delete auto-drafts for new posts that are > 7 days old.
6041
 *
6042
 * @since 3.4.0
6043
 *
6044
 * @global wpdb $wpdb WordPress database abstraction object.
6045
 */
6046
function wp_delete_auto_drafts() {
6047
	global $wpdb;
6048
6049
	// Cleanup old auto-drafts more than 7 days old.
6050
	$old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND DATE_SUB( NOW(), INTERVAL 7 DAY ) > post_date" );
6051
	foreach ( (array) $old_posts as $delete ) {
6052
		// Force delete.
6053
		wp_delete_post( $delete, true );
6054
	}
6055
}
6056
6057
/**
6058
 * Queues posts for lazy-loading of term meta.
6059
 *
6060
 * @since 4.5.0
6061
 *
6062
 * @param array $posts Array of WP_Post objects.
6063
 */
6064
function wp_queue_posts_for_term_meta_lazyload( $posts ) {
6065
	$post_type_taxonomies = $term_ids = array();
6066
	foreach ( $posts as $post ) {
6067
		if ( ! ( $post instanceof WP_Post ) ) {
6068
			continue;
6069
		}
6070
6071
		if ( ! isset( $post_type_taxonomies[ $post->post_type ] ) ) {
6072
			$post_type_taxonomies[ $post->post_type ] = get_object_taxonomies( $post->post_type );
6073
		}
6074
6075
		foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) {
6076
			// Term cache should already be primed by `update_post_term_cache()`.
6077
			$terms = get_object_term_cache( $post->ID, $taxonomy );
6078
			if ( false !== $terms ) {
6079
				foreach ( $terms as $term ) {
0 ignored issues
show
The expression $terms of type array|object<WP_Term>|object<WP_Error>|null is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

  1. If you want to be on the safe side, you can add an additional type-check:

    $collection = json_decode($data, true);
    if ( ! is_array($collection)) {
        throw new \RuntimeException('$collection must be an array.');
    }
    
    foreach ($collection as $item) { /** ... */ }
    
  2. If you are sure that the expression is traversable, you might want to add a doc comment cast to improve IDE auto-completion and static analysis:

    /** @var array $collection */
    $collection = json_decode($data, true);
    
    foreach ($collection as $item) { /** .. */ }
    
  3. Mark the issue as a false-positive: Just hover the remove button, in the top-right corner of this issue for more options.

Loading history...
6080
					if ( ! isset( $term_ids[ $term->term_id ] ) ) {
6081
						$term_ids[] = $term->term_id;
6082
					}
6083
				}
6084
			}
6085
		}
6086
	}
6087
6088
	if ( $term_ids ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $term_ids of type array is implicitly converted to a boolean; are you sure this is intended? If so, consider using ! empty($expr) instead to make it clear that you intend to check for an array without elements.

This check marks implicit conversions of arrays to boolean values in a comparison. While in PHP an empty array is considered to be equal (but not identical) to false, this is not always apparent.

Consider making the comparison explicit by using empty(..) or ! empty(...) instead.

Loading history...
6089
		$lazyloader = wp_metadata_lazyloader();
6090
		$lazyloader->queue_objects( 'term', $term_ids );
6091
	}
6092
}
6093
6094
/**
6095
 * Update the custom taxonomies' term counts when a post's status is changed.
6096
 *
6097
 * For example, default posts term counts (for custom taxonomies) don't include
6098
 * private / draft posts.
6099
 *
6100
 * @since 3.3.0
6101
 * @access private
6102
 *
6103
 * @param string  $new_status New post status.
6104
 * @param string  $old_status Old post status.
6105
 * @param WP_Post $post       Post object.
6106
 */
6107
function _update_term_count_on_transition_post_status( $new_status, $old_status, $post ) {
0 ignored issues
show
The parameter $new_status is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
The parameter $old_status is not used and could be removed.

This check looks from parameters that have been defined for a function or method, but which are not used in the method body.

Loading history...
6108
	// Update counts for the post's terms.
6109
	foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
6110
		$tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) );
6111
		wp_update_term_count( $tt_ids, $taxonomy );
6112
	}
6113
}
6114
6115
/**
6116
 * Adds any posts from the given ids to the cache that do not already exist in cache
6117
 *
6118
 * @since 3.4.0
6119
 * @access private
6120
 *
6121
 * @see update_post_caches()
6122
 *
6123
 * @global wpdb $wpdb WordPress database abstraction object.
6124
 *
6125
 * @param array $ids               ID list.
6126
 * @param bool  $update_term_cache Optional. Whether to update the term cache. Default true.
6127
 * @param bool  $update_meta_cache Optional. Whether to update the meta cache. Default true.
6128
 */
6129 View Code Duplication
function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache = true ) {
0 ignored issues
show
This function seems to be duplicated in your project.

Duplicated code is one of the most pungent code smells. If you need to duplicate the same code in three or more different places, we strongly encourage you to look into extracting the code into a single class or operation.

You can also find more detailed suggestions in the “Code” section of your repository.

Loading history...
6130
	global $wpdb;
6131
6132
	$non_cached_ids = _get_non_cached_ids( $ids, 'posts' );
6133
	if ( !empty( $non_cached_ids ) ) {
6134
		$fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", join( ",", $non_cached_ids ) ) );
6135
6136
		update_post_caches( $fresh_posts, 'any', $update_term_cache, $update_meta_cache );
6137
	}
6138
}
6139
6140
/**
6141
 * Adds a suffix if any trashed posts have a given slug.
6142
 *
6143
 * Store its desired (i.e. current) slug so it can try to reclaim it
6144
 * if the post is untrashed.
6145
 *
6146
 * For internal use.
6147
 *
6148
 * @since 4.5.0
6149
 * @access private
6150
 *
6151
 * @param string $post_name Slug.
6152
 * @param string $post_ID   Optional. Post ID that should be ignored. Default 0.
6153
 */
6154
function wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID = 0 ) {
6155
	$trashed_posts_with_desired_slug = get_posts( array(
6156
		'name' => $post_name,
6157
		'post_status' => 'trash',
6158
		'post_type' => 'any',
6159
		'nopaging' => true,
6160
		'post__not_in' => array( $post_ID )
6161
	) );
6162
6163
	if ( ! empty( $trashed_posts_with_desired_slug ) ) {
6164
		foreach ( $trashed_posts_with_desired_slug as $_post ) {
6165
			wp_add_trashed_suffix_to_post_name_for_post( $_post );
6166
		}
6167
	}
6168
}
6169
6170
/**
6171
 * Adds a trashed suffix for a given post.
6172
 *
6173
 * Store its desired (i.e. current) slug so it can try to reclaim it
6174
 * if the post is untrashed.
6175
 *
6176
 * For internal use.
6177
 *
6178
 * @since 4.5.0
6179
 * @access private
6180
 *
6181
 * @param WP_Post $post The post.
6182
 * @return string New slug for the post.
6183
 */
6184
function wp_add_trashed_suffix_to_post_name_for_post( $post ) {
6185
	global $wpdb;
6186
6187
	$post = get_post( $post );
6188
6189
	if ( '__trashed' === substr( $post->post_name, -9 ) ) {
6190
		return $post->post_name;
6191
	}
6192
	add_post_meta( $post->ID, '_wp_desired_post_slug', $post->post_name );
6193
	$post_name = _truncate_post_slug( $post->post_name, 191 ) . '__trashed';
6194
	$wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
6195
	clean_post_cache( $post->ID );
6196
	return $post_name;
6197
}
6198
6199
/**
6200
 * Filter the SQL clauses of an attachment query to include filenames.
6201
 *
6202
 * @since 4.7.0
6203
 * @access private
6204
 *
6205
 * @global wpdb $wpdb WordPress database abstraction object.
6206
 *
6207
 * @param array $clauses An array including WHERE, GROUP BY, JOIN, ORDER BY,
6208
 *                       DISTINCT, fields (SELECT), and LIMITS clauses.
6209
 * @return array The modified clauses.
6210
 */
6211
function _filter_query_attachment_filenames( $clauses ) {
6212
	global $wpdb;
6213
	remove_filter( 'posts_clauses', __FUNCTION__ );
6214
6215
	// Add a LEFT JOIN of the postmeta table so we don't trample existing JOINs.
6216
	$clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS sq1 ON ( {$wpdb->posts}.ID = sq1.post_id AND sq1.meta_key = '_wp_attached_file' )";
6217
6218
	$clauses['groupby'] = "{$wpdb->posts}.ID";
6219
6220
	$clauses['where'] = preg_replace(
6221
		"/\({$wpdb->posts}.post_content (NOT LIKE|LIKE) (\'[^']+\')\)/",
6222
		"$0 OR ( sq1.meta_value $1 $2 )",
6223
		$clauses['where'] );
6224
6225
	return $clauses;
6226
}
6227