Issues (2010)

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.

wp-includes/post.php (37 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

Code
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
	) );
37
38
	register_post_type( 'page', array(
39
		'labels' => array(
40
			'name_admin_bar' => _x( 'Page', 'add new from admin bar' ),
41
		),
42
		'public' => true,
43
		'publicly_queryable' => false,
44
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
45
		'_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
46
		'capability_type' => 'page',
47
		'map_meta_cap' => true,
48
		'menu_position' => 20,
49
		'hierarchical' => true,
50
		'rewrite' => false,
51
		'query_var' => false,
52
		'delete_with_user' => true,
53
		'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ),
54
	) );
55
56
	register_post_type( 'attachment', array(
57
		'labels' => array(
58
			'name' => _x('Media', 'post type general name'),
59
			'name_admin_bar' => _x( 'Media', 'add new from admin bar' ),
60
			'add_new' => _x( 'Add New', 'add new media' ),
61
 			'edit_item' => __( 'Edit Media' ),
62
 			'view_item' => __( 'View Attachment Page' ),
63
		),
64
		'public' => true,
65
		'show_ui' => true,
66
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
67
		'_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
68
		'capability_type' => 'post',
69
		'capabilities' => array(
70
			'create_posts' => 'upload_files',
71
		),
72
		'map_meta_cap' => true,
73
		'hierarchical' => false,
74
		'rewrite' => false,
75
		'query_var' => false,
76
		'show_in_nav_menus' => false,
77
		'delete_with_user' => true,
78
		'supports' => array( 'title', 'author', 'comments' ),
79
	) );
80
	add_post_type_support( 'attachment:audio', 'thumbnail' );
81
	add_post_type_support( 'attachment:video', 'thumbnail' );
82
83
	register_post_type( 'revision', array(
84
		'labels' => array(
85
			'name' => __( 'Revisions' ),
86
			'singular_name' => __( 'Revision' ),
87
		),
88
		'public' => false,
89
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
90
		'_edit_link' => 'revision.php?revision=%d', /* internal use only. don't use this when registering your own post type. */
91
		'capability_type' => 'post',
92
		'map_meta_cap' => true,
93
		'hierarchical' => false,
94
		'rewrite' => false,
95
		'query_var' => false,
96
		'can_export' => false,
97
		'delete_with_user' => true,
98
		'supports' => array( 'author' ),
99
	) );
100
101
	register_post_type( 'nav_menu_item', array(
102
		'labels' => array(
103
			'name' => __( 'Navigation Menu Items' ),
104
			'singular_name' => __( 'Navigation Menu Item' ),
105
		),
106
		'public' => false,
107
		'_builtin' => true, /* internal use only. don't use this when registering your own post type. */
108
		'hierarchical' => false,
109
		'rewrite' => false,
110
		'delete_with_user' => false,
111
		'query_var' => false,
112
	) );
113
114
	register_post_status( 'publish', array(
115
		'label'       => _x( 'Published', 'post status' ),
116
		'public'      => true,
117
		'_builtin'    => true, /* internal use only. */
118
		'label_count' => _n_noop( 'Published <span class="count">(%s)</span>', 'Published <span class="count">(%s)</span>' ),
119
	) );
120
121
	register_post_status( 'future', array(
122
		'label'       => _x( 'Scheduled', 'post status' ),
123
		'protected'   => true,
124
		'_builtin'    => true, /* internal use only. */
125
		'label_count' => _n_noop('Scheduled <span class="count">(%s)</span>', 'Scheduled <span class="count">(%s)</span>' ),
126
	) );
127
128
	register_post_status( 'draft', array(
129
		'label'       => _x( 'Draft', 'post status' ),
130
		'protected'   => true,
131
		'_builtin'    => true, /* internal use only. */
132
		'label_count' => _n_noop( 'Draft <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>' ),
133
	) );
134
135
	register_post_status( 'pending', array(
136
		'label'       => _x( 'Pending', 'post status' ),
137
		'protected'   => true,
138
		'_builtin'    => true, /* internal use only. */
139
		'label_count' => _n_noop( 'Pending <span class="count">(%s)</span>', 'Pending <span class="count">(%s)</span>' ),
140
	) );
141
142
	register_post_status( 'private', array(
143
		'label'       => _x( 'Private', 'post status' ),
144
		'private'     => true,
145
		'_builtin'    => true, /* internal use only. */
146
		'label_count' => _n_noop( 'Private <span class="count">(%s)</span>', 'Private <span class="count">(%s)</span>' ),
147
	) );
148
149
	register_post_status( 'trash', array(
150
		'label'       => _x( 'Trash', 'post status' ),
151
		'internal'    => true,
152
		'_builtin'    => true, /* internal use only. */
153
		'label_count' => _n_noop( 'Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>' ),
154
		'show_in_admin_status_list' => true,
155
	) );
156
157
	register_post_status( 'auto-draft', array(
158
		'label'    => 'auto-draft',
159
		'internal' => true,
160
		'_builtin' => true, /* internal use only. */
161
	) );
162
163
	register_post_status( 'inherit', array(
164
		'label'    => 'inherit',
165
		'internal' => true,
166
		'_builtin' => true, /* internal use only. */
167
		'exclude_from_search' => false,
168
	) );
169
}
170
171
/**
172
 * Retrieve attached file path based on attachment ID.
173
 *
174
 * By default the path will go through the 'get_attached_file' filter, but
175
 * passing a true to the $unfiltered argument of get_attached_file() will
176
 * return the file path unfiltered.
177
 *
178
 * The function works by getting the single post meta name, named
179
 * '_wp_attached_file' and returning it. This is a convenience function to
180
 * prevent looking up the meta name and provide a mechanism for sending the
181
 * attached filename through a filter.
182
 *
183
 * @since 2.0.0
184
 *
185
 * @param int  $attachment_id Attachment ID.
186
 * @param bool $unfiltered    Optional. Whether to apply filters. Default false.
187
 * @return string|false The file path to where the attached file should be, false otherwise.
188
 */
189
function get_attached_file( $attachment_id, $unfiltered = false ) {
190
	$file = get_post_meta( $attachment_id, '_wp_attached_file', true );
191
192
	// If the file is relative, prepend upload dir.
193
	if ( $file && 0 !== strpos( $file, '/' ) && ! preg_match( '|^.:\\\|', $file ) && ( ( $uploads = wp_get_upload_dir() ) && false === $uploads['error'] ) ) {
194
		$file = $uploads['basedir'] . "/$file";
195
	}
196
197
	if ( $unfiltered ) {
198
		return $file;
199
	}
200
201
	/**
202
	 * Filters the attached file based on the given ID.
203
	 *
204
	 * @since 2.1.0
205
	 *
206
	 * @param string $file          Path to attached file.
207
	 * @param int    $attachment_id Attachment ID.
208
	 */
209
	return apply_filters( 'get_attached_file', $file, $attachment_id );
210
}
211
212
/**
213
 * Update attachment file path based on attachment ID.
214
 *
215
 * Used to update the file path of the attachment, which uses post meta name
216
 * '_wp_attached_file' to store the path of the attachment.
217
 *
218
 * @since 2.1.0
219
 *
220
 * @param int    $attachment_id Attachment ID.
221
 * @param string $file          File path for the attachment.
222
 * @return bool True on success, false on failure.
223
 */
224
function update_attached_file( $attachment_id, $file ) {
225
	if ( !get_post( $attachment_id ) )
226
		return false;
227
228
	/**
229
	 * Filters the path to the attached file to update.
230
	 *
231
	 * @since 2.1.0
232
	 *
233
	 * @param string $file          Path to the attached file to update.
234
	 * @param int    $attachment_id Attachment ID.
235
	 */
236
	$file = apply_filters( 'update_attached_file', $file, $attachment_id );
237
238
	if ( $file = _wp_relative_upload_path( $file ) )
239
		return update_post_meta( $attachment_id, '_wp_attached_file', $file );
240
	else
241
		return delete_post_meta( $attachment_id, '_wp_attached_file' );
242
}
243
244
/**
245
 * Return relative path to an uploaded file.
246
 *
247
 * The path is relative to the current upload dir.
248
 *
249
 * @since 2.9.0
250
 *
251
 * @param string $path Full path to the file.
252
 * @return string Relative path on success, unchanged path on failure.
253
 */
254
function _wp_relative_upload_path( $path ) {
255
	$new_path = $path;
256
257
	$uploads = wp_get_upload_dir();
258
	if ( 0 === strpos( $new_path, $uploads['basedir'] ) ) {
259
			$new_path = str_replace( $uploads['basedir'], '', $new_path );
260
			$new_path = ltrim( $new_path, '/' );
261
	}
262
263
	/**
264
	 * Filters the relative path to an uploaded file.
265
	 *
266
	 * @since 2.9.0
267
	 *
268
	 * @param string $new_path Relative path to the file.
269
	 * @param string $path     Full path to the file.
270
	 */
271
	return apply_filters( '_wp_relative_upload_path', $new_path, $path );
272
}
273
274
/**
275
 * Retrieve all children of the post parent ID.
276
 *
277
 * Normally, without any enhancements, the children would apply to pages. In the
278
 * context of the inner workings of WordPress, pages, posts, and attachments
279
 * share the same table, so therefore the functionality could apply to any one
280
 * of them. It is then noted that while this function does not work on posts, it
281
 * does not mean that it won't work on posts. It is recommended that you know
282
 * what context you wish to retrieve the children of.
283
 *
284
 * Attachments may also be made the child of a post, so if that is an accurate
285
 * statement (which needs to be verified), it would then be possible to get
286
 * all of the attachments for a post. Attachments have since changed since
287
 * version 2.5, so this is most likely inaccurate, but serves generally as an
288
 * example of what is possible.
289
 *
290
 * The arguments listed as defaults are for this function and also of the
291
 * get_posts() function. The arguments are combined with the get_children defaults
292
 * and are then passed to the get_posts() function, which accepts additional arguments.
293
 * You can replace the defaults in this function, listed below and the additional
294
 * arguments listed in the get_posts() function.
295
 *
296
 * The 'post_parent' is the most important argument and important attention
297
 * needs to be paid to the $args parameter. If you pass either an object or an
298
 * integer (number), then just the 'post_parent' is grabbed and everything else
299
 * is lost. If you don't specify any arguments, then it is assumed that you are
300
 * in The Loop and the post parent will be grabbed for from the current post.
301
 *
302
 * The 'post_parent' argument is the ID to get the children. The 'numberposts'
303
 * is the amount of posts to retrieve that has a default of '-1', which is
304
 * used to get all of the posts. Giving a number higher than 0 will only
305
 * retrieve that amount of posts.
306
 *
307
 * The 'post_type' and 'post_status' arguments can be used to choose what
308
 * criteria of posts to retrieve. The 'post_type' can be anything, but WordPress
309
 * post types are 'post', 'pages', and 'attachments'. The 'post_status'
310
 * argument will accept any post status within the write administration panels.
311
 *
312
 * @since 2.0.0
313
 *
314
 * @see get_posts()
315
 * @todo Check validity of description.
316
 *
317
 * @global WP_Post $post
318
 *
319
 * @param mixed  $args   Optional. User defined arguments for replacing the defaults. Default empty.
320
 * @param string $output Optional. Constant for return type. Accepts OBJECT, ARRAY_A, ARRAY_N.
321
 *                       Default OBJECT.
322
 * @return array Array of children, where the type of each element is determined by $output parameter.
323
 *               Empty array on failure.
324
 */
325
function get_children( $args = '', $output = OBJECT ) {
326
	$kids = array();
327
	if ( empty( $args ) ) {
328
		if ( isset( $GLOBALS['post'] ) ) {
329
			$args = array('post_parent' => (int) $GLOBALS['post']->post_parent );
330
		} else {
331
			return $kids;
332
		}
333
	} elseif ( is_object( $args ) ) {
334
		$args = array('post_parent' => (int) $args->post_parent );
335
	} elseif ( is_numeric( $args ) ) {
336
		$args = array('post_parent' => (int) $args);
337
	}
338
339
	$defaults = array(
340
		'numberposts' => -1, 'post_type' => 'any',
341
		'post_status' => 'any', 'post_parent' => 0,
342
	);
343
344
	$r = wp_parse_args( $args, $defaults );
345
346
	$children = get_posts( $r );
347
348
	if ( ! $children )
349
		return $kids;
350
351
	if ( ! empty( $r['fields'] ) )
352
		return $children;
353
354
	update_post_cache($children);
355
356
	foreach ( $children as $key => $child )
357
		$kids[$child->ID] = $children[$key];
358
359
	if ( $output == OBJECT ) {
360
		return $kids;
361
	} elseif ( $output == ARRAY_A ) {
362
		$weeuns = array();
363
		foreach ( (array) $kids as $kid ) {
364
			$weeuns[$kid->ID] = get_object_vars($kids[$kid->ID]);
365
		}
366
		return $weeuns;
367
	} elseif ( $output == ARRAY_N ) {
368
		$babes = array();
369
		foreach ( (array) $kids as $kid ) {
370
			$babes[$kid->ID] = array_values(get_object_vars($kids[$kid->ID]));
371
		}
372
		return $babes;
373
	} else {
374
		return $kids;
375
	}
376
}
377
378
/**
379
 * Get extended entry info (<!--more-->).
380
 *
381
 * There should not be any space after the second dash and before the word
382
 * 'more'. There can be text or space(s) after the word 'more', but won't be
383
 * referenced.
384
 *
385
 * The returned array has 'main', 'extended', and 'more_text' keys. Main has the text before
386
 * the `<!--more-->`. The 'extended' key has the content after the
387
 * `<!--more-->` comment. The 'more_text' key has the custom "Read More" text.
388
 *
389
 * @since 1.0.0
390
 *
391
 * @param string $post Post content.
392
 * @return array Post before ('main'), after ('extended'), and custom read more ('more_text').
393
 */
394
function get_extended( $post ) {
395
	//Match the new style more links.
396
	if ( preg_match('/<!--more(.*?)?-->/', $post, $matches) ) {
397
		list($main, $extended) = explode($matches[0], $post, 2);
398
		$more_text = $matches[1];
399
	} else {
400
		$main = $post;
401
		$extended = '';
402
		$more_text = '';
403
	}
404
405
	//  leading and trailing whitespace.
406
	$main = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $main);
407
	$extended = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $extended);
408
	$more_text = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $more_text);
409
410
	return array( 'main' => $main, 'extended' => $extended, 'more_text' => $more_text );
411
}
412
413
/**
414
 * Retrieves post data given a post ID or post object.
415
 *
416
 * See sanitize_post() for optional $filter values. Also, the parameter
417
 * `$post`, must be given as a variable, since it is passed by reference.
418
 *
419
 * @since 1.5.1
420
 *
421
 * @global WP_Post $post
422
 *
423
 * @param int|WP_Post|null $post   Optional. Post ID or post object. Defaults to global $post.
424
 * @param string           $output Optional, default is Object. Accepts OBJECT, ARRAY_A, or ARRAY_N.
425
 *                                 Default OBJECT.
426
 * @param string           $filter Optional. Type of filter to apply. Accepts 'raw', 'edit', 'db',
427
 *                                 or 'display'. Default 'raw'.
428
 * @return WP_Post|array|null Type corresponding to $output on success or null on failure.
429
 *                            When $output is OBJECT, a `WP_Post` instance is returned.
430
 */
431
function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) {
432
	if ( empty( $post ) && isset( $GLOBALS['post'] ) )
433
		$post = $GLOBALS['post'];
434
435 View Code Duplication
	if ( $post instanceof WP_Post ) {
436
		$_post = $post;
437
	} elseif ( is_object( $post ) ) {
438
		if ( empty( $post->filter ) ) {
439
			$_post = sanitize_post( $post, 'raw' );
440
			$_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...
441
		} elseif ( 'raw' == $post->filter ) {
442
			$_post = new WP_Post( $post );
443
		} else {
444
			$_post = WP_Post::get_instance( $post->ID );
445
		}
446
	} else {
447
		$_post = WP_Post::get_instance( $post );
448
	}
449
450
	if ( ! $_post )
451
		return null;
452
453
	$_post = $_post->filter( $filter );
454
455 View Code Duplication
	if ( $output == ARRAY_A )
456
		return $_post->to_array();
457
	elseif ( $output == ARRAY_N )
458
		return array_values( $_post->to_array() );
459
460
	return $_post;
461
}
462
463
/**
464
 * Retrieve ancestors of a post.
465
 *
466
 * @since 2.5.0
467
 *
468
 * @param int|WP_Post $post Post ID or post object.
469
 * @return array Ancestor IDs or empty array if none are found.
470
 */
471
function get_post_ancestors( $post ) {
472
	$post = get_post( $post );
473
474
	if ( ! $post || empty( $post->post_parent ) || $post->post_parent == $post->ID )
475
		return array();
476
477
	$ancestors = array();
478
479
	$id = $ancestors[] = $post->post_parent;
480
481
	while ( $ancestor = get_post( $id ) ) {
482
		// Loop detection: If the ancestor has been seen before, break.
483
		if ( empty( $ancestor->post_parent ) || ( $ancestor->post_parent == $post->ID ) || in_array( $ancestor->post_parent, $ancestors ) )
484
			break;
485
486
		$id = $ancestors[] = $ancestor->post_parent;
487
	}
488
489
	return $ancestors;
490
}
491
492
/**
493
 * Retrieve data from a post field based on Post ID.
494
 *
495
 * Examples of the post field will be, 'post_type', 'post_status', 'post_content',
496
 * etc and based off of the post object property or key names.
497
 *
498
 * The context values are based off of the taxonomy filter functions and
499
 * supported values are found within those functions.
500
 *
501
 * @since 2.3.0
502
 * @since 4.5.0 The `$post` parameter was made optional.
503
 *
504
 * @see sanitize_post_field()
505
 *
506
 * @param string      $field   Post field name.
507
 * @param int|WP_Post $post    Optional. Post ID or post object. Defaults to current post.
508
 * @param string      $context Optional. How to filter the field. Accepts 'raw', 'edit', 'db',
509
 *                             or 'display'. Default 'display'.
510
 * @return string The value of the post field on success, empty string on failure.
511
 */
512
function get_post_field( $field, $post = null, $context = 'display' ) {
513
	$post = get_post( $post );
514
515
	if ( !$post )
516
		return '';
517
518
	if ( !isset($post->$field) )
519
		return '';
520
521
	return sanitize_post_field($field, $post->$field, $post->ID, $context);
522
}
523
524
/**
525
 * Retrieve the mime type of an attachment based on the ID.
526
 *
527
 * This function can be used with any post type, but it makes more sense with
528
 * attachments.
529
 *
530
 * @since 2.0.0
531
 *
532
 * @param int|WP_Post $ID Optional. Post ID or post object. Default empty.
533
 * @return string|false The mime type on success, false on failure.
534
 */
535
function get_post_mime_type( $ID = '' ) {
536
	$post = get_post($ID);
537
538
	if ( is_object($post) )
539
		return $post->post_mime_type;
540
541
	return false;
542
}
543
544
/**
545
 * Retrieve the post status based on the Post ID.
546
 *
547
 * If the post ID is of an attachment, then the parent post status will be given
548
 * instead.
549
 *
550
 * @since 2.0.0
551
 *
552
 * @param int|WP_Post $ID Optional. Post ID or post object. Default empty.
553
 * @return string|false Post status on success, false on failure.
554
 */
555
function get_post_status( $ID = '' ) {
556
	$post = get_post($ID);
557
558
	if ( !is_object($post) )
559
		return false;
560
561
	if ( 'attachment' == $post->post_type ) {
562
		if ( 'private' == $post->post_status )
563
			return 'private';
564
565
		// Unattached attachments are assumed to be published.
566
		if ( ( 'inherit' == $post->post_status ) && ( 0 == $post->post_parent) )
567
			return 'publish';
568
569
		// Inherit status from the parent.
570
		if ( $post->post_parent && ( $post->ID != $post->post_parent ) ) {
571
			$parent_post_status = get_post_status( $post->post_parent );
572
			if ( 'trash' == $parent_post_status ) {
573
				return get_post_meta( $post->post_parent, '_wp_trash_meta_status', true );
574
			} else {
575
				return $parent_post_status;
576
			}
577
		}
578
579
	}
580
581
	/**
582
	 * Filters the post status.
583
	 *
584
	 * @since 4.4.0
585
	 *
586
	 * @param string  $post_status The post status.
587
	 * @param WP_Post $post        The post object.
588
	 */
589
	return apply_filters( 'get_post_status', $post->post_status, $post );
590
}
591
592
/**
593
 * Retrieve all of the WordPress supported post statuses.
594
 *
595
 * Posts have a limited set of valid status values, this provides the
596
 * post_status values and descriptions.
597
 *
598
 * @since 2.5.0
599
 *
600
 * @return array List of post statuses.
601
 */
602 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...
603
	$status = array(
604
		'draft'   => __( 'Draft' ),
605
		'pending' => __( 'Pending Review' ),
606
		'private' => __( 'Private' ),
607
		'publish' => __( 'Published' )
608
	);
609
610
	return $status;
611
}
612
613
/**
614
 * Retrieve all of the WordPress support page statuses.
615
 *
616
 * Pages have a limited set of valid status values, this provides the
617
 * post_status values and descriptions.
618
 *
619
 * @since 2.5.0
620
 *
621
 * @return array List of page statuses.
622
 */
623 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...
624
	$status = array(
625
		'draft'   => __( 'Draft' ),
626
		'private' => __( 'Private' ),
627
		'publish' => __( 'Published' )
628
	);
629
630
	return $status;
631
}
632
633
/**
634
 * Register a post status. Do not use before init.
635
 *
636
 * A simple function for creating or modifying a post status based on the
637
 * parameters given. The function will accept an array (second optional
638
 * parameter), along with a string for the post status name.
639
 *
640
 * Arguments prefixed with an _underscore shouldn't be used by plugins and themes.
641
 *
642
 * @since 3.0.0
643
 * @global array $wp_post_statuses Inserts new post status object into the list
644
 *
645
 * @param string $post_status Name of the post status.
646
 * @param array|string $args {
647
 *     Optional. Array or string of post status arguments.
648
 *
649
 *     @type bool|string $label                     A descriptive name for the post status marked
650
 *                                                  for translation. Defaults to value of $post_status.
651
 *     @type bool|array  $label_count               Descriptive text to use for nooped plurals.
652
 *                                                  Default array of $label, twice
653
 *     @type bool        $exclude_from_search       Whether to exclude posts with this post status
654
 *                                                  from search results. Default is value of $internal.
655
 *     @type bool        $_builtin                  Whether the status is built-in. Core-use only.
656
 *                                                  Default false.
657
 *     @type bool        $public                    Whether posts of this status should be shown
658
 *                                                  in the front end of the site. Default false.
659
 *     @type bool        $internal                  Whether the status is for internal use only.
660
 *                                                  Default false.
661
 *     @type bool        $protected                 Whether posts with this status should be protected.
662
 *                                                  Default false.
663
 *     @type bool        $private                   Whether posts with this status should be private.
664
 *                                                  Default false.
665
 *     @type bool        $publicly_queryable        Whether posts with this status should be publicly-
666
 *                                                  queryable. Default is value of $public.
667
 *     @type bool        $show_in_admin_all_list    Whether to include posts in the edit listing for
668
 *                                                  their post type. Default is value of $internal.
669
 *     @type bool        $show_in_admin_status_list Show in the list of statuses with post counts at
670
 *                                                  the top of the edit listings,
671
 *                                                  e.g. All (12) | Published (9) | My Custom Status (2)
672
 *                                                  Default is value of $internal.
673
 * }
674
 * @return object
675
 */
676
function register_post_status( $post_status, $args = array() ) {
677
	global $wp_post_statuses;
678
679
	if (!is_array($wp_post_statuses))
680
		$wp_post_statuses = array();
681
682
	// Args prefixed with an underscore are reserved for internal use.
683
	$defaults = array(
684
		'label' => false,
685
		'label_count' => false,
686
		'exclude_from_search' => null,
687
		'_builtin' => false,
688
		'public' => null,
689
		'internal' => null,
690
		'protected' => null,
691
		'private' => null,
692
		'publicly_queryable' => null,
693
		'show_in_admin_status_list' => null,
694
		'show_in_admin_all_list' => null,
695
	);
696
	$args = wp_parse_args($args, $defaults);
697
	$args = (object) $args;
698
699
	$post_status = sanitize_key($post_status);
700
	$args->name = $post_status;
701
702
	// Set various defaults.
703
	if ( null === $args->public && null === $args->internal && null === $args->protected && null === $args->private )
704
		$args->internal = true;
705
706
	if ( null === $args->public  )
707
		$args->public = false;
708
709
	if ( null === $args->private  )
710
		$args->private = false;
711
712
	if ( null === $args->protected  )
713
		$args->protected = false;
714
715
	if ( null === $args->internal  )
716
		$args->internal = false;
717
718
	if ( null === $args->publicly_queryable )
719
		$args->publicly_queryable = $args->public;
720
721
	if ( null === $args->exclude_from_search )
722
		$args->exclude_from_search = $args->internal;
723
724
	if ( null === $args->show_in_admin_all_list )
725
		$args->show_in_admin_all_list = !$args->internal;
726
727
	if ( null === $args->show_in_admin_status_list )
728
		$args->show_in_admin_status_list = !$args->internal;
729
730
	if ( false === $args->label )
731
		$args->label = $post_status;
732
733
	if ( false === $args->label_count )
734
		$args->label_count = array( $args->label, $args->label );
735
736
	$wp_post_statuses[$post_status] = $args;
737
738
	return $args;
739
}
740
741
/**
742
 * Retrieve a post status object by name.
743
 *
744
 * @since 3.0.0
745
 *
746
 * @global array $wp_post_statuses List of post statuses.
747
 *
748
 * @see register_post_status()
749
 *
750
 * @param string $post_status The name of a registered post status.
751
 * @return object|null A post status object.
752
 */
753
function get_post_status_object( $post_status ) {
754
	global $wp_post_statuses;
755
756
	if ( empty($wp_post_statuses[$post_status]) )
757
		return null;
758
759
	return $wp_post_statuses[$post_status];
760
}
761
762
/**
763
 * Get a list of post statuses.
764
 *
765
 * @since 3.0.0
766
 *
767
 * @global array $wp_post_statuses List of post statuses.
768
 *
769
 * @see register_post_status()
770
 *
771
 * @param array|string $args     Optional. Array or string of post status arguments to compare against
772
 *                               properties of the global `$wp_post_statuses objects`. Default empty array.
773
 * @param string       $output   Optional. The type of output to return, either 'names' or 'objects'. Default 'names'.
774
 * @param string       $operator Optional. The logical operation to perform. 'or' means only one element
775
 *                               from the array needs to match; 'and' means all elements must match.
776
 *                               Default 'and'.
777
 * @return array A list of post status names or objects.
778
 */
779 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...
780
	global $wp_post_statuses;
781
782
	$field = ('names' == $output) ? 'name' : false;
783
784
	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 779 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...
785
}
786
787
/**
788
 * Whether the post type is hierarchical.
789
 *
790
 * A false return value might also mean that the post type does not exist.
791
 *
792
 * @since 3.0.0
793
 *
794
 * @see get_post_type_object()
795
 *
796
 * @param string $post_type Post type name
797
 * @return bool Whether post type is hierarchical.
798
 */
799
function is_post_type_hierarchical( $post_type ) {
800
	if ( ! post_type_exists( $post_type ) )
801
		return false;
802
803
	$post_type = get_post_type_object( $post_type );
804
	return $post_type->hierarchical;
805
}
806
807
/**
808
 * Check if a post type is registered.
809
 *
810
 * @since 3.0.0
811
 *
812
 * @see get_post_type_object()
813
 *
814
 * @param string $post_type Post type name.
815
 * @return bool Whether post type is registered.
816
 */
817
function post_type_exists( $post_type ) {
818
	return (bool) get_post_type_object( $post_type );
819
}
820
821
/**
822
 * Retrieves the post type of the current post or of a given post.
823
 *
824
 * @since 2.1.0
825
 *
826
 * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
827
 * @return string|false          Post type on success, false on failure.
828
 */
829
function get_post_type( $post = null ) {
830
	if ( $post = get_post( $post ) )
831
		return $post->post_type;
832
833
	return false;
834
}
835
836
/**
837
 * Retrieves a post type object by name.
838
 *
839
 * @since 3.0.0
840
 * @since 4.6.0 Object returned is now an instance of WP_Post_Type.
841
 *
842
 * @global array $wp_post_types List of post types.
843
 *
844
 * @see register_post_type()
845
 *
846
 * @param string $post_type The name of a registered post type.
847
 * @return WP_Post_Type|null WP_Post_Type object if it exists, null otherwise.
848
 */
849
function get_post_type_object( $post_type ) {
850
	global $wp_post_types;
851
852
	if ( ! is_scalar( $post_type ) || empty( $wp_post_types[ $post_type ] ) ) {
853
		return null;
854
	}
855
856
	return $wp_post_types[ $post_type ];
857
}
858
859
/**
860
 * Get a list of all registered post type objects.
861
 *
862
 * @since 2.9.0
863
 *
864
 * @global array $wp_post_types List of post types.
865
 *
866
 * @see register_post_type() for accepted arguments.
867
 *
868
 * @param array|string $args     Optional. An array of key => value arguments to match against
869
 *                               the post type objects. Default empty array.
870
 * @param string       $output   Optional. The type of output to return. Accepts post type 'names'
871
 *                               or 'objects'. Default 'names'.
872
 * @param string       $operator Optional. The logical operation to perform. 'or' means only one
873
 *                               element from the array needs to match; 'and' means all elements
874
 *                               must match; 'not' means no elements may match. Default 'and'.
875
 * @return array A list of post type names or objects.
876
 */
877 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...
878
	global $wp_post_types;
879
880
	$field = ('names' == $output) ? 'name' : false;
881
882
	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 877 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...
883
}
884
885
/**
886
 * Registers a post type.
887
 *
888
 * Note: Post type registrations should not be hooked before the
889
 * {@see 'init'} action. Also, any taxonomy connections should be
890
 * registered via the `$taxonomies` argument to ensure consistency
891
 * when hooks such as {@see 'parse_query'} or {@see 'pre_get_posts'}
892
 * are used.
893
 *
894
 * Post types can support any number of built-in core features such
895
 * as meta boxes, custom fields, post thumbnails, post statuses,
896
 * comments, and more. See the `$supports` argument for a complete
897
 * list of supported features.
898
 *
899
 * @since 2.9.0
900
 * @since 3.0.0 The `show_ui` argument is now enforced on the new post screen.
901
 * @since 4.4.0 The `show_ui` argument is now enforced on the post type listing
902
 *              screen and post editing screen.
903
 * @since 4.6.0 Post type object returned is now an instance of WP_Post_Type.
904
 *
905
 * @global array $wp_post_types List of post types.
906
 *
907
 * @param string $post_type Post type key. Must not exceed 20 characters and may
908
 *                          only contain lowercase alphanumeric characters, dashes,
909
 *                          and underscores. See sanitize_key().
910
 * @param array|string $args {
911
 *     Array or string of arguments for registering a post type.
912
 *
913
 *     @type string      $label                Name of the post type shown in the menu. Usually plural.
914
 *                                             Default is value of $labels['name'].
915
 *     @type array       $labels               An array of labels for this post type. If not set, post
916
 *                                             labels are inherited for non-hierarchical types and page
917
 *                                             labels for hierarchical ones. See get_post_type_labels() for a full
918
 *                                             list of supported labels.
919
 *     @type string      $description          A short descriptive summary of what the post type is.
920
 *                                             Default empty.
921
 *     @type bool        $public               Whether a post type is intended for use publicly either via
922
 *                                             the admin interface or by front-end users. While the default
923
 *                                             settings of $exclude_from_search, $publicly_queryable, $show_ui,
924
 *                                             and $show_in_nav_menus are inherited from public, each does not
925
 *                                             rely on this relationship and controls a very specific intention.
926
 *                                             Default false.
927
 *     @type bool        $hierarchical         Whether the post type is hierarchical (e.g. page). Default false.
928
 *     @type bool        $exclude_from_search  Whether to exclude posts with this post type from front end search
929
 *                                             results. Default is the opposite value of $public.
930
 *     @type bool        $publicly_queryable   Whether queries can be performed on the front end for the post type
931
 *                                             as part of parse_request(). Endpoints would include:
932
 *                                             * ?post_type={post_type_key}
933
 *                                             * ?{post_type_key}={single_post_slug}
934
 *                                             * ?{post_type_query_var}={single_post_slug}
935
 *                                             If not set, the default is inherited from $public.
936
 *     @type bool        $show_ui              Whether to generate and allow a UI for managing this post type in the
937
 *                                             admin. Default is value of $public.
938
 *     @type bool        $show_in_menu         Where to show the post type in the admin menu. To work, $show_ui
939
 *                                             must be true. If true, the post type is shown in its own top level
940
 *                                             menu. If false, no menu is shown. If a string of an existing top
941
 *                                             level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post
942
 *                                             type will be placed as a sub-menu of that.
943
 *                                             Default is value of $show_ui.
944
 *     @type bool        $show_in_nav_menus    Makes this post type available for selection in navigation menus.
945
 *                                             Default is value $public.
946
 *     @type bool        $show_in_admin_bar    Makes this post type available via the admin bar. Default is value
947
 *                                             of $show_in_menu.
948
 *     @type int         $menu_position        The position in the menu order the post type should appear. To work,
949
 *                                             $show_in_menu must be true. Default null (at the bottom).
950
 *     @type string      $menu_icon            The url to the icon to be used for this menu. Pass a base64-encoded
951
 *                                             SVG using a data URI, which will be colored to match the color scheme
952
 *                                             -- this should begin with 'data:image/svg+xml;base64,'. Pass the name
953
 *                                             of a Dashicons helper class to use a font icon, e.g.
954
 *                                             'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty
955
 *                                             so an icon can be added via CSS. Defaults to use the posts icon.
956
 *     @type string      $capability_type      The string to use to build the read, edit, and delete capabilities.
957
 *                                             May be passed as an array to allow for alternative plurals when using
958
 *                                             this argument as a base to construct the capabilities, e.g.
959
 *                                             array('story', 'stories'). Default 'post'.
960
 *     @type array       $capabilities         Array of capabilities for this post type. $capability_type is used
961
 *                                             as a base to construct capabilities by default.
962
 *                                             See get_post_type_capabilities().
963
 *     @type bool        $map_meta_cap         Whether to use the internal default meta capability handling.
964
 *                                             Default false.
965
 *     @type array       $supports             Core feature(s) the post type supports. Serves as an alias for calling
966
 *                                             add_post_type_support() directly. Core features include 'title',
967
 *                                             'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt',
968
 *                                             'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'.
969
 *                                             Additionally, the 'revisions' feature dictates whether the post type
970
 *                                             will store revisions, and the 'comments' feature dictates whether the
971
 *                                             comments count will show on the edit screen. Defaults is an array
972
 *                                             containing 'title' and 'editor'.
973
 *     @type callable    $register_meta_box_cb Provide a callback function that sets up the meta boxes for the
974
 *                                             edit form. Do remove_meta_box() and add_meta_box() calls in the
975
 *                                             callback. Default null.
976
 *     @type array       $taxonomies           An array of taxonomy identifiers that will be registered for the
977
 *                                             post type. Taxonomies can be registered later with register_taxonomy()
978
 *                                             or register_taxonomy_for_object_type().
979
 *                                             Default empty array.
980
 *     @type bool|string $has_archive          Whether there should be post type archives, or if a string, the
981
 *                                             archive slug to use. Will generate the proper rewrite rules if
982
 *                                             $rewrite is enabled. Default false.
983
 *     @type bool|array  $rewrite              {
984
 *         Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
985
 *         Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be
986
 *         passed with any of these keys:
987
 *
988
 *         @type string $slug       Customize the permastruct slug. Defaults to $post_type key.
989
 *         @type bool   $with_front Whether the permastruct should be prepended with WP_Rewrite::$front.
990
 *                                  Default true.
991
 *         @type bool   $feeds      Whether the feed permastruct should be built for this post type.
992
 *                                  Default is value of $has_archive.
993
 *         @type bool   $pages      Whether the permastruct should provide for pagination. Default true.
994
 *         @type const  $ep_mask    Endpoint mask to assign. If not specified and permalink_epmask is set,
995
 *                                  inherits from $permalink_epmask. If not specified and permalink_epmask
996
 *                                  is not set, defaults to EP_PERMALINK.
997
 *     }
998
 *     @type string|bool $query_var            Sets the query_var key for this post type. Defaults to $post_type
999
 *                                             key. If false, a post type cannot be loaded at
1000
 *                                             ?{query_var}={post_slug}. If specified as a string, the query
1001
 *                                             ?{query_var_string}={post_slug} will be valid.
1002
 *     @type bool        $can_export           Whether to allow this post type to be exported. Default true.
1003
 *     @type bool        $delete_with_user     Whether to delete posts of this type when deleting a user. If true,
1004
 *                                             posts of this type belonging to the user will be moved to trash
1005
 *                                             when then user is deleted. If false, posts of this type belonging
1006
 *                                             to the user will *not* be trashed or deleted. If not set (the default),
1007
 *                                             posts are trashed if post_type_supports('author'). Otherwise posts
1008
 *                                             are not trashed or deleted. Default null.
1009
 *     @type bool        $_builtin             FOR INTERNAL USE ONLY! True if this post type is a native or
1010
 *                                             "built-in" post_type. Default false.
1011
 *     @type string      $_edit_link           FOR INTERNAL USE ONLY! URL segment to use for edit link of
1012
 *                                             this post type. Default 'post.php?post=%d'.
1013
 * }
1014
 * @return WP_Post_Type|WP_Error The registered post type object, or an error object.
1015
 */
1016
function register_post_type( $post_type, $args = array() ) {
1017
	global $wp_post_types;
1018
1019
	if ( ! is_array( $wp_post_types ) ) {
1020
		$wp_post_types = array();
1021
	}
1022
1023
	// Sanitize post type name
1024
	$post_type = sanitize_key( $post_type );
1025
1026 View Code Duplication
	if ( empty( $post_type ) || strlen( $post_type ) > 20 ) {
1027
		_doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' );
1028
		return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) );
1029
	}
1030
1031
	$post_type_object = new WP_Post_Type( $post_type, $args );
1032
	$post_type_object->add_supports();
1033
	$post_type_object->add_rewrite_rules();
1034
	$post_type_object->register_meta_boxes();
1035
1036
	$wp_post_types[ $post_type ] = $post_type_object;
1037
1038
	$post_type_object->add_hooks();
1039
	$post_type_object->register_taxonomies();
1040
1041
	/**
1042
	 * Fires after a post type is registered.
1043
	 *
1044
	 * @since 3.3.0
1045
	 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1046
	 *
1047
	 * @param string       $post_type        Post type.
1048
	 * @param WP_Post_Type $post_type_object Arguments used to register the post type.
1049
	 */
1050
	do_action( 'registered_post_type', $post_type, $post_type_object );
1051
1052
	return $post_type_object;
1053
}
1054
1055
/**
1056
 * Unregisters a post type.
1057
 *
1058
 * Can not be used to unregister built-in post types.
1059
 *
1060
 * @since 4.5.0
1061
 *
1062
 * @global array $wp_post_types List of post types.
1063
 *
1064
 * @param string $post_type Post type to unregister.
1065
 * @return bool|WP_Error True on success, WP_Error on failure or if the post type doesn't exist.
1066
 */
1067
function unregister_post_type( $post_type ) {
1068
	global $wp_post_types;
1069
1070
	if ( ! post_type_exists( $post_type ) ) {
1071
		return new WP_Error( 'invalid_post_type', __( 'Invalid post type.' ) );
1072
	}
1073
1074
	$post_type_object = get_post_type_object( $post_type );
1075
1076
	// Do not allow unregistering internal post types.
1077
	if ( $post_type_object->_builtin ) {
1078
		return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) );
1079
	}
1080
1081
	$post_type_object->remove_supports();
1082
	$post_type_object->remove_rewrite_rules();
1083
	$post_type_object->unregister_meta_boxes();
1084
	$post_type_object->remove_hooks();
1085
	$post_type_object->unregister_taxonomies();
1086
1087
	unset( $wp_post_types[ $post_type ] );
1088
1089
	/**
1090
	 * Fires after a post type was unregistered.
1091
	 *
1092
	 * @since 4.5.0
1093
	 *
1094
	 * @param string $post_type Post type key.
1095
	 */
1096
	do_action( 'unregistered_post_type', $post_type );
1097
1098
	return true;
1099
}
1100
1101
/**
1102
 * Build an object with all post type capabilities out of a post type object
1103
 *
1104
 * Post type capabilities use the 'capability_type' argument as a base, if the
1105
 * capability is not set in the 'capabilities' argument array or if the
1106
 * 'capabilities' argument is not supplied.
1107
 *
1108
 * The capability_type argument can optionally be registered as an array, with
1109
 * the first value being singular and the second plural, e.g. array('story, 'stories')
1110
 * Otherwise, an 's' will be added to the value for the plural form. After
1111
 * registration, capability_type will always be a string of the singular value.
1112
 *
1113
 * By default, seven keys are accepted as part of the capabilities array:
1114
 *
1115
 * - edit_post, read_post, and delete_post are meta capabilities, which are then
1116
 *   generally mapped to corresponding primitive capabilities depending on the
1117
 *   context, which would be the post being edited/read/deleted and the user or
1118
 *   role being checked. Thus these capabilities would generally not be granted
1119
 *   directly to users or roles.
1120
 *
1121
 * - edit_posts - Controls whether objects of this post type can be edited.
1122
 * - edit_others_posts - Controls whether objects of this type owned by other users
1123
 *   can be edited. If the post type does not support an author, then this will
1124
 *   behave like edit_posts.
1125
 * - publish_posts - Controls publishing objects of this post type.
1126
 * - read_private_posts - Controls whether private objects can be read.
1127
 *
1128
 * These four primitive capabilities are checked in core in various locations.
1129
 * There are also seven other primitive capabilities which are not referenced
1130
 * directly in core, except in map_meta_cap(), which takes the three aforementioned
1131
 * meta capabilities and translates them into one or more primitive capabilities
1132
 * that must then be checked against the user or role, depending on the context.
1133
 *
1134
 * - read - Controls whether objects of this post type can be read.
1135
 * - delete_posts - Controls whether objects of this post type can be deleted.
1136
 * - delete_private_posts - Controls whether private objects can be deleted.
1137
 * - delete_published_posts - Controls whether published objects can be deleted.
1138
 * - delete_others_posts - Controls whether objects owned by other users can be
1139
 *   can be deleted. If the post type does not support an author, then this will
1140
 *   behave like delete_posts.
1141
 * - edit_private_posts - Controls whether private objects can be edited.
1142
 * - edit_published_posts - Controls whether published objects can be edited.
1143
 *
1144
 * These additional capabilities are only used in map_meta_cap(). Thus, they are
1145
 * only assigned by default if the post type is registered with the 'map_meta_cap'
1146
 * argument set to true (default is false).
1147
 *
1148
 * @since 3.0.0
1149
 *
1150
 * @see register_post_type()
1151
 * @see map_meta_cap()
1152
 *
1153
 * @param object $args Post type registration arguments.
1154
 * @return object object with all the capabilities as member variables.
1155
 */
1156
function get_post_type_capabilities( $args ) {
1157
	if ( ! is_array( $args->capability_type ) )
1158
		$args->capability_type = array( $args->capability_type, $args->capability_type . 's' );
1159
1160
	// Singular base for meta capabilities, plural base for primitive capabilities.
1161
	list( $singular_base, $plural_base ) = $args->capability_type;
1162
1163
	$default_capabilities = array(
1164
		// Meta capabilities
1165
		'edit_post'          => 'edit_'         . $singular_base,
1166
		'read_post'          => 'read_'         . $singular_base,
1167
		'delete_post'        => 'delete_'       . $singular_base,
1168
		// Primitive capabilities used outside of map_meta_cap():
1169
		'edit_posts'         => 'edit_'         . $plural_base,
1170
		'edit_others_posts'  => 'edit_others_'  . $plural_base,
1171
		'publish_posts'      => 'publish_'      . $plural_base,
1172
		'read_private_posts' => 'read_private_' . $plural_base,
1173
	);
1174
1175
	// Primitive capabilities used within map_meta_cap():
1176
	if ( $args->map_meta_cap ) {
1177
		$default_capabilities_for_mapping = array(
1178
			'read'                   => 'read',
1179
			'delete_posts'           => 'delete_'           . $plural_base,
1180
			'delete_private_posts'   => 'delete_private_'   . $plural_base,
1181
			'delete_published_posts' => 'delete_published_' . $plural_base,
1182
			'delete_others_posts'    => 'delete_others_'    . $plural_base,
1183
			'edit_private_posts'     => 'edit_private_'     . $plural_base,
1184
			'edit_published_posts'   => 'edit_published_'   . $plural_base,
1185
		);
1186
		$default_capabilities = array_merge( $default_capabilities, $default_capabilities_for_mapping );
1187
	}
1188
1189
	$capabilities = array_merge( $default_capabilities, $args->capabilities );
1190
1191
	// Post creation capability simply maps to edit_posts by default:
1192
	if ( ! isset( $capabilities['create_posts'] ) )
1193
		$capabilities['create_posts'] = $capabilities['edit_posts'];
1194
1195
	// Remember meta capabilities for future reference.
1196
	if ( $args->map_meta_cap )
1197
		_post_type_meta_capabilities( $capabilities );
1198
1199
	return (object) $capabilities;
1200
}
1201
1202
/**
1203
 * Store or return a list of post type meta caps for map_meta_cap().
1204
 *
1205
 * @since 3.1.0
1206
 * @access private
1207
 *
1208
 * @global array $post_type_meta_caps Used to store meta capabilities.
1209
 *
1210
 * @param array $capabilities Post type meta capabilities.
1211
 */
1212
function _post_type_meta_capabilities( $capabilities = null ) {
1213
	global $post_type_meta_caps;
1214
1215
	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...
1216
		if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ) ) ) {
1217
			$post_type_meta_caps[ $custom ] = $core;
1218
		}
1219
	}
1220
}
1221
1222
/**
1223
 * Builds an object with all post type labels out of a post type object.
1224
 *
1225
 * Accepted keys of the label array in the post type object:
1226
 *
1227
 * - `name` - General name for the post type, usually plural. The same and overridden
1228
 *          by `$post_type_object->label`. Default is 'Posts' / 'Pages'.
1229
 * - `singular_name` - Name for one object of this post type. Default is 'Post' / 'Page'.
1230
 * - `add_new` - Default is 'Add New' for both hierarchical and non-hierarchical types.
1231
 *             When internationalizing this string, please use a {@link https://codex.wordpress.org/I18n_for_WordPress_Developers#Disambiguation_by_context gettext context}
1232
 *             matching your post type. Example: `_x( 'Add New', 'product', 'textdomain' );`.
1233
 * - `add_new_item` - Label for adding a new singular item. Default is 'Add New Post' / 'Add New Page'.
1234
 * - `edit_item` - Label for editing a singular item. Default is 'Edit Post' / 'Edit Page'.
1235
 * - `new_item` - Label for the new item page title. Default is 'New Post' / 'New Page'.
1236
 * - `view_item` - Label for viewing a singular item. Default is 'View Post' / 'View Page'.
1237
 * - `search_items` - Label for searching plural items. Default is 'Search Posts' / 'Search Pages'.
1238
 * - `not_found` - Label used when no items are found. Default is 'No posts found' / 'No pages found'.
1239
 * - `not_found_in_trash` - Label used when no items are in the trash. Default is 'No posts found in Trash' /
1240
 *                        'No pages found in Trash'.
1241
 * - `parent_item_colon` - Label used to prefix parents of hierarchical items. Not used on non-hierarchical
1242
 *                       post types. Default is 'Parent Page:'.
1243
 * - `all_items` - Label to signify all items in a submenu link. Default is 'All Posts' / 'All Pages'.
1244
 * - `archives` - Label for archives in nav menus. Default is 'Post Archives' / 'Page Archives'.
1245
 * - `insert_into_item` - Label for the media frame button. Default is 'Insert into post' / 'Insert into page'.
1246
 * - `uploaded_to_this_item` - Label for the media frame filter. Default is 'Uploaded to this post' /
1247
 *                           'Uploaded to this page'.
1248
 * - `featured_image` - Label for the Featured Image meta box title. Default is 'Featured Image'.
1249
 * - `set_featured_image` - Label for setting the featured image. Default is 'Set featured image'.
1250
 * - `remove_featured_image` - Label for removing the featured image. Default is 'Remove featured image'.
1251
 * - `use_featured_image` - Label in the media frame for using a featured image. Default is 'Use as featured image'.
1252
 * - `menu_name` - Label for the menu name. Default is the same as `name`.
1253
 * - `filter_items_list` - Label for the table views hidden heading. Default is 'Filter posts list' /
1254
 *                       'Filter pages list'.
1255
 * - `items_list_navigation` - Label for the table pagination hidden heading. Default is 'Posts list navigation' /
1256
 *                           'Pages list navigation'.
1257
 * - `items_list` - Label for the table hidden heading. Default is 'Posts list' / 'Pages list'.
1258
 *
1259
 * Above, the first default value is for non-hierarchical post types (like posts)
1260
 * and the second one is for hierarchical post types (like pages).
1261
 *
1262
 * Note: To set labels used in post type admin notices, see the {@see 'post_updated_messages'} filter.
1263
 *
1264
 * @since 3.0.0
1265
 * @since 4.3.0 Added the `featured_image`, `set_featured_image`, `remove_featured_image`,
1266
 *              and `use_featured_image` labels.
1267
 * @since 4.4.0 Added the `insert_into_item`, `uploaded_to_this_item`, `filter_items_list`,
1268
 *              `items_list_navigation`, and `items_list` labels.
1269
 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1270
 *
1271
 * @access private
1272
 *
1273
 * @param object|WP_Post_Type $post_type_object Post type object.
1274
 * @return object Object with all the labels as member variables.
1275
 */
1276
function get_post_type_labels( $post_type_object ) {
1277
	$nohier_vs_hier_defaults = array(
1278
		'name' => array( _x('Posts', 'post type general name'), _x('Pages', 'post type general name') ),
1279
		'singular_name' => array( _x('Post', 'post type singular name'), _x('Page', 'post type singular name') ),
1280
		'add_new' => array( _x('Add New', 'post'), _x('Add New', 'page') ),
1281
		'add_new_item' => array( __('Add New Post'), __('Add New Page') ),
1282
		'edit_item' => array( __('Edit Post'), __('Edit Page') ),
1283
		'new_item' => array( __('New Post'), __('New Page') ),
1284
		'view_item' => array( __('View Post'), __('View Page') ),
1285
		'search_items' => array( __('Search Posts'), __('Search Pages') ),
1286
		'not_found' => array( __('No posts found.'), __('No pages found.') ),
1287
		'not_found_in_trash' => array( __('No posts found in Trash.'), __('No pages found in Trash.') ),
1288
		'parent_item_colon' => array( null, __('Parent Page:') ),
1289
		'all_items' => array( __( 'All Posts' ), __( 'All Pages' ) ),
1290
		'archives' => array( __( 'Post Archives' ), __( 'Page Archives' ) ),
1291
		'insert_into_item' => array( __( 'Insert into post' ), __( 'Insert into page' ) ),
1292
		'uploaded_to_this_item' => array( __( 'Uploaded to this post' ), __( 'Uploaded to this page' ) ),
1293
		'featured_image' => array( __( 'Featured Image' ), __( 'Featured Image' ) ),
1294
		'set_featured_image' => array( __( 'Set featured image' ), __( 'Set featured image' ) ),
1295
		'remove_featured_image' => array( __( 'Remove featured image' ), __( 'Remove featured image' ) ),
1296
		'use_featured_image' => array( __( 'Use as featured image' ), __( 'Use as featured image' ) ),
1297
		'filter_items_list' => array( __( 'Filter posts list' ), __( 'Filter pages list' ) ),
1298
		'items_list_navigation' => array( __( 'Posts list navigation' ), __( 'Pages list navigation' ) ),
1299
		'items_list' => array( __( 'Posts list' ), __( 'Pages list' ) ),
1300
	);
1301
	$nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
1302
1303
	$labels = _get_custom_object_labels( $post_type_object, $nohier_vs_hier_defaults );
1304
1305
	$post_type = $post_type_object->name;
1306
1307
	$default_labels = clone $labels;
1308
1309
	/**
1310
	 * Filters the labels of a specific post type.
1311
	 *
1312
	 * The dynamic portion of the hook name, `$post_type`, refers to
1313
	 * the post type slug.
1314
	 *
1315
	 * @since 3.5.0
1316
	 *
1317
	 * @see get_post_type_labels() for the full list of labels.
1318
	 *
1319
	 * @param object $labels Object with labels for the post type as member variables.
1320
	 */
1321
	$labels = apply_filters( "post_type_labels_{$post_type}", $labels );
1322
1323
	// Ensure that the filtered labels contain all required default values.
1324
	$labels = (object) array_merge( (array) $default_labels, (array) $labels );
1325
1326
	return $labels;
1327
}
1328
1329
/**
1330
 * Build an object with custom-something object (post type, taxonomy) labels
1331
 * out of a custom-something object
1332
 *
1333
 * @since 3.0.0
1334
 * @access private
1335
 *
1336
 * @param object $object                  A custom-something object.
1337
 * @param array  $nohier_vs_hier_defaults Hierarchical vs non-hierarchical default labels.
1338
 * @return object Object containing labels for the given custom-something object.
1339
 */
1340
function _get_custom_object_labels( $object, $nohier_vs_hier_defaults ) {
1341
	$object->labels = (array) $object->labels;
1342
1343
	if ( isset( $object->label ) && empty( $object->labels['name'] ) )
1344
		$object->labels['name'] = $object->label;
1345
1346 View Code Duplication
	if ( !isset( $object->labels['singular_name'] ) && isset( $object->labels['name'] ) )
1347
		$object->labels['singular_name'] = $object->labels['name'];
1348
1349 View Code Duplication
	if ( ! isset( $object->labels['name_admin_bar'] ) )
1350
		$object->labels['name_admin_bar'] = isset( $object->labels['singular_name'] ) ? $object->labels['singular_name'] : $object->name;
1351
1352 View Code Duplication
	if ( !isset( $object->labels['menu_name'] ) && isset( $object->labels['name'] ) )
1353
		$object->labels['menu_name'] = $object->labels['name'];
1354
1355 View Code Duplication
	if ( !isset( $object->labels['all_items'] ) && isset( $object->labels['menu_name'] ) )
1356
		$object->labels['all_items'] = $object->labels['menu_name'];
1357
1358 View Code Duplication
	if ( !isset( $object->labels['archives'] ) && isset( $object->labels['all_items'] ) ) {
1359
		$object->labels['archives'] = $object->labels['all_items'];
1360
	}
1361
1362
	$defaults = array();
1363
	foreach ( $nohier_vs_hier_defaults as $key => $value ) {
1364
		$defaults[$key] = $object->hierarchical ? $value[1] : $value[0];
1365
	}
1366
	$labels = array_merge( $defaults, $object->labels );
1367
	$object->labels = (object) $object->labels;
1368
1369
	return (object) $labels;
1370
}
1371
1372
/**
1373
 * Add submenus for post types.
1374
 *
1375
 * @access private
1376
 * @since 3.1.0
1377
 */
1378
function _add_post_type_submenus() {
1379
	foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
1380
		$ptype_obj = get_post_type_object( $ptype );
1381
		// Sub-menus only.
1382
		if ( ! $ptype_obj->show_in_menu || $ptype_obj->show_in_menu === true )
1383
			continue;
1384
		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" );
1385
	}
1386
}
1387
1388
/**
1389
 * Register support of certain features for a post type.
1390
 *
1391
 * All core features are directly associated with a functional area of the edit
1392
 * screen, such as the editor or a meta box. Features include: 'title', 'editor',
1393
 * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
1394
 * 'thumbnail', 'custom-fields', and 'post-formats'.
1395
 *
1396
 * Additionally, the 'revisions' feature dictates whether the post type will
1397
 * store revisions, and the 'comments' feature dictates whether the comments
1398
 * count will show on the edit screen.
1399
 *
1400
 * @since 3.0.0
1401
 *
1402
 * @global array $_wp_post_type_features
1403
 *
1404
 * @param string       $post_type The post type for which to add the feature.
1405
 * @param string|array $feature   The feature being added, accepts an array of
1406
 *                                feature strings or a single string.
1407
 */
1408
function add_post_type_support( $post_type, $feature ) {
1409
	global $_wp_post_type_features;
1410
1411
	$features = (array) $feature;
1412
	foreach ($features as $feature) {
1413
		if ( func_num_args() == 2 )
1414
			$_wp_post_type_features[$post_type][$feature] = true;
1415
		else
1416
			$_wp_post_type_features[$post_type][$feature] = array_slice( func_get_args(), 2 );
1417
	}
1418
}
1419
1420
/**
1421
 * Remove support for a feature from a post type.
1422
 *
1423
 * @since 3.0.0
1424
 *
1425
 * @global array $_wp_post_type_features
1426
 *
1427
 * @param string $post_type The post type for which to remove the feature.
1428
 * @param string $feature   The feature being removed.
1429
 */
1430
function remove_post_type_support( $post_type, $feature ) {
1431
	global $_wp_post_type_features;
1432
1433
	unset( $_wp_post_type_features[ $post_type ][ $feature ] );
1434
}
1435
1436
/**
1437
 * Get all the post type features
1438
 *
1439
 * @since 3.4.0
1440
 *
1441
 * @global array $_wp_post_type_features
1442
 *
1443
 * @param string $post_type The post type.
1444
 * @return array Post type supports list.
1445
 */
1446
function get_all_post_type_supports( $post_type ) {
1447
	global $_wp_post_type_features;
1448
1449
	if ( isset( $_wp_post_type_features[$post_type] ) )
1450
		return $_wp_post_type_features[$post_type];
1451
1452
	return array();
1453
}
1454
1455
/**
1456
 * Check a post type's support for a given feature.
1457
 *
1458
 * @since 3.0.0
1459
 *
1460
 * @global array $_wp_post_type_features
1461
 *
1462
 * @param string $post_type The post type being checked.
1463
 * @param string $feature   The feature being checked.
1464
 * @return bool Whether the post type supports the given feature.
1465
 */
1466
function post_type_supports( $post_type, $feature ) {
1467
	global $_wp_post_type_features;
1468
1469
	return ( isset( $_wp_post_type_features[$post_type][$feature] ) );
1470
}
1471
1472
/**
1473
 * Retrieves a list of post type names that support a specific feature.
1474
 *
1475
 * @since 4.5.0
1476
 *
1477
 * @global array $_wp_post_type_features Post type features
1478
 *
1479
 * @param array|string $feature  Single feature or an array of features the post types should support.
1480
 * @param string       $operator Optional. The logical operation to perform. 'or' means
1481
 *                               only one element from the array needs to match; 'and'
1482
 *                               means all elements must match; 'not' means no elements may
1483
 *                               match. Default 'and'.
1484
 * @return array A list of post type names.
1485
 */
1486
function get_post_types_by_support( $feature, $operator = 'and' ) {
1487
	global $_wp_post_type_features;
1488
1489
	$features = array_fill_keys( (array) $feature, true );
1490
1491
	return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
1492
}
1493
1494
/**
1495
 * Update the post type for the post ID.
1496
 *
1497
 * The page or post cache will be cleaned for the post ID.
1498
 *
1499
 * @since 2.5.0
1500
 *
1501
 * @global wpdb $wpdb WordPress database abstraction object.
1502
 *
1503
 * @param int    $post_id   Optional. Post ID to change post type. Default 0.
1504
 * @param string $post_type Optional. Post type. Accepts 'post' or 'page' to
1505
 *                          name a few. Default 'post'.
1506
 * @return int|false Amount of rows changed. Should be 1 for success and 0 for failure.
1507
 */
1508
function set_post_type( $post_id = 0, $post_type = 'post' ) {
1509
	global $wpdb;
1510
1511
	$post_type = sanitize_post_field('post_type', $post_type, $post_id, 'db');
1512
	$return = $wpdb->update( $wpdb->posts, array('post_type' => $post_type), array('ID' => $post_id) );
1513
1514
	clean_post_cache( $post_id );
1515
1516
	return $return;
1517
}
1518
1519
/**
1520
 * Determines whether a post type is considered "viewable".
1521
 *
1522
 * For built-in post types such as posts and pages, the 'public' value will be evaluated.
1523
 * For all others, the 'publicly_queryable' value will be used.
1524
 *
1525
 * @since 4.4.0
1526
 * @since 4.5.0 Added the ability to pass a post type name in addition to object.
1527
 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1528
 *
1529
 * @param string|WP_Post_Type $post_type Post type name or object.
1530
 * @return bool Whether the post type should be considered viewable.
1531
 */
1532
function is_post_type_viewable( $post_type ) {
1533
	if ( is_scalar( $post_type ) ) {
1534
		$post_type = get_post_type_object( $post_type );
1535
		if ( ! $post_type ) {
1536
			return false;
1537
		}
1538
	}
1539
1540
	return $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
1541
}
1542
1543
/**
1544
 * Retrieve list of latest posts or posts matching criteria.
1545
 *
1546
 * The defaults are as follows:
1547
 *
1548
 * @since 1.2.0
1549
 *
1550
 * @see WP_Query::parse_query()
1551
 *
1552
 * @param array $args {
1553
 *     Optional. Arguments to retrieve posts. See WP_Query::parse_query() for all
1554
 *     available arguments.
1555
 *
1556
 *     @type int        $numberposts      Total number of posts to retrieve. Is an alias of $posts_per_page
1557
 *                                        in WP_Query. Accepts -1 for all. Default 5.
1558
 *     @type int|string $category         Category ID or comma-separated list of IDs (this or any children).
1559
 *                                        Is an alias of $cat in WP_Query. Default 0.
1560
 *     @type array      $include          An array of post IDs to retrieve, sticky posts will be included.
1561
 *                                        Is an alias of $post__in in WP_Query. Default empty array.
1562
 *     @type array      $exclude          An array of post IDs not to retrieve. Default empty array.
1563
 *     @type bool       $suppress_filters Whether to suppress filters. Default true.
1564
 * }
1565
 * @return array List of posts.
1566
 */
1567
function get_posts( $args = null ) {
1568
	$defaults = array(
1569
		'numberposts' => 5,
1570
		'category' => 0, 'orderby' => 'date',
1571
		'order' => 'DESC', 'include' => array(),
1572
		'exclude' => array(), 'meta_key' => '',
1573
		'meta_value' =>'', 'post_type' => 'post',
1574
		'suppress_filters' => true
1575
	);
1576
1577
	$r = wp_parse_args( $args, $defaults );
0 ignored issues
show
It seems like $args defined by parameter $args on line 1567 can also be of type null; however, wp_parse_args() does only seem to accept string|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...
1578
	if ( empty( $r['post_status'] ) )
1579
		$r['post_status'] = ( 'attachment' == $r['post_type'] ) ? 'inherit' : 'publish';
1580
	if ( ! empty($r['numberposts']) && empty($r['posts_per_page']) )
1581
		$r['posts_per_page'] = $r['numberposts'];
1582
	if ( ! empty($r['category']) )
1583
		$r['cat'] = $r['category'];
1584
	if ( ! empty($r['include']) ) {
1585
		$incposts = wp_parse_id_list( $r['include'] );
1586
		$r['posts_per_page'] = count($incposts);  // only the number of posts included
1587
		$r['post__in'] = $incposts;
1588
	} elseif ( ! empty($r['exclude']) )
1589
		$r['post__not_in'] = wp_parse_id_list( $r['exclude'] );
1590
1591
	$r['ignore_sticky_posts'] = true;
1592
	$r['no_found_rows'] = true;
1593
1594
	$get_posts = new WP_Query;
1595
	return $get_posts->query($r);
0 ignored issues
show
$r is of type array<string,?,{"no_found_rows":"boolean"}>, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1596
1597
}
1598
1599
//
1600
// Post meta functions
1601
//
1602
1603
/**
1604
 * Add meta data field to a post.
1605
 *
1606
 * Post meta data is called "Custom Fields" on the Administration Screen.
1607
 *
1608
 * @since 1.5.0
1609
 *
1610
 * @param int    $post_id    Post ID.
1611
 * @param string $meta_key   Metadata name.
1612
 * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
1613
 * @param bool   $unique     Optional. Whether the same key should not be added.
1614
 *                           Default false.
1615
 * @return int|false Meta ID on success, false on failure.
1616
 */
1617
function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
1618
	// Make sure meta is added to the post, not a revision.
1619
	if ( $the_post = wp_is_post_revision($post_id) )
1620
		$post_id = $the_post;
1621
1622
	return add_metadata('post', $post_id, $meta_key, $meta_value, $unique);
1623
}
1624
1625
/**
1626
 * Remove metadata matching criteria from a post.
1627
 *
1628
 * You can match based on the key, or key and value. Removing based on key and
1629
 * value, will keep from removing duplicate metadata with the same key. It also
1630
 * allows removing all metadata matching key, if needed.
1631
 *
1632
 * @since 1.5.0
1633
 *
1634
 * @param int    $post_id    Post ID.
1635
 * @param string $meta_key   Metadata name.
1636
 * @param mixed  $meta_value Optional. Metadata value. Must be serializable if
1637
 *                           non-scalar. Default empty.
1638
 * @return bool True on success, false on failure.
1639
 */
1640
function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
1641
	// Make sure meta is added to the post, not a revision.
1642
	if ( $the_post = wp_is_post_revision($post_id) )
1643
		$post_id = $the_post;
1644
1645
	return delete_metadata('post', $post_id, $meta_key, $meta_value);
1646
}
1647
1648
/**
1649
 * Retrieve post meta field for a post.
1650
 *
1651
 * @since 1.5.0
1652
 *
1653
 * @param int    $post_id Post ID.
1654
 * @param string $key     Optional. The meta key to retrieve. By default, returns
1655
 *                        data for all keys. Default empty.
1656
 * @param bool   $single  Optional. Whether to return a single value. Default false.
1657
 * @return mixed Will be an array if $single is false. Will be value of meta data
1658
 *               field if $single is true.
1659
 */
1660
function get_post_meta( $post_id, $key = '', $single = false ) {
1661
	return get_metadata('post', $post_id, $key, $single);
1662
}
1663
1664
/**
1665
 * Update post meta field based on post ID.
1666
 *
1667
 * Use the $prev_value parameter to differentiate between meta fields with the
1668
 * same key and post ID.
1669
 *
1670
 * If the meta field for the post does not exist, it will be added.
1671
 *
1672
 * @since 1.5.0
1673
 *
1674
 * @param int    $post_id    Post ID.
1675
 * @param string $meta_key   Metadata key.
1676
 * @param mixed  $meta_value Metadata value. Must be serializable if non-scalar.
1677
 * @param mixed  $prev_value Optional. Previous value to check before removing.
1678
 *                           Default empty.
1679
 * @return int|bool Meta ID if the key didn't exist, true on successful update,
1680
 *                  false on failure.
1681
 */
1682
function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
1683
	// Make sure meta is added to the post, not a revision.
1684
	if ( $the_post = wp_is_post_revision($post_id) )
1685
		$post_id = $the_post;
1686
1687
	return update_metadata('post', $post_id, $meta_key, $meta_value, $prev_value);
1688
}
1689
1690
/**
1691
 * Delete everything from post meta matching meta key.
1692
 *
1693
 * @since 2.3.0
1694
 *
1695
 * @param string $post_meta_key Key to search for when deleting.
1696
 * @return bool Whether the post meta key was deleted from the database.
1697
 */
1698
function delete_post_meta_by_key( $post_meta_key ) {
1699
	return delete_metadata( 'post', null, $post_meta_key, '', true );
1700
}
1701
1702
/**
1703
 * Retrieve post meta fields, based on post ID.
1704
 *
1705
 * The post meta fields are retrieved from the cache where possible,
1706
 * so the function is optimized to be called more than once.
1707
 *
1708
 * @since 1.2.0
1709
 *
1710
 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
1711
 * @return array Post meta for the given post.
1712
 */
1713
function get_post_custom( $post_id = 0 ) {
1714
	$post_id = absint( $post_id );
1715
	if ( ! $post_id )
1716
		$post_id = get_the_ID();
1717
1718
	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...
1719
}
1720
1721
/**
1722
 * Retrieve meta field names for a post.
1723
 *
1724
 * If there are no meta fields, then nothing (null) will be returned.
1725
 *
1726
 * @since 1.2.0
1727
 *
1728
 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
1729
 * @return array|void Array of the keys, if retrieved.
1730
 */
1731
function get_post_custom_keys( $post_id = 0 ) {
1732
	$custom = get_post_custom( $post_id );
1733
1734
	if ( !is_array($custom) )
1735
		return;
1736
1737
	if ( $keys = array_keys($custom) )
1738
		return $keys;
1739
}
1740
1741
/**
1742
 * Retrieve values for a custom post field.
1743
 *
1744
 * The parameters must not be considered optional. All of the post meta fields
1745
 * will be retrieved and only the meta field key values returned.
1746
 *
1747
 * @since 1.2.0
1748
 *
1749
 * @param string $key     Optional. Meta field key. Default empty.
1750
 * @param int    $post_id Optional. Post ID. Default is ID of the global $post.
1751
 * @return array|null Meta field values.
1752
 */
1753
function get_post_custom_values( $key = '', $post_id = 0 ) {
1754
	if ( !$key )
1755
		return null;
1756
1757
	$custom = get_post_custom($post_id);
1758
1759
	return isset($custom[$key]) ? $custom[$key] : null;
1760
}
1761
1762
/**
1763
 * Check if post is sticky.
1764
 *
1765
 * Sticky posts should remain at the top of The Loop. If the post ID is not
1766
 * given, then The Loop ID for the current post will be used.
1767
 *
1768
 * @since 2.7.0
1769
 *
1770
 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
1771
 * @return bool Whether post is sticky.
1772
 */
1773
function is_sticky( $post_id = 0 ) {
1774
	$post_id = absint( $post_id );
1775
1776
	if ( ! $post_id )
1777
		$post_id = get_the_ID();
1778
1779
	$stickies = get_option( 'sticky_posts' );
1780
1781
	if ( ! is_array( $stickies ) )
1782
		return false;
1783
1784
	if ( in_array( $post_id, $stickies ) )
1785
		return true;
1786
1787
	return false;
1788
}
1789
1790
/**
1791
 * Sanitize every post field.
1792
 *
1793
 * If the context is 'raw', then the post object or array will get minimal
1794
 * sanitization of the integer fields.
1795
 *
1796
 * @since 2.3.0
1797
 *
1798
 * @see sanitize_post_field()
1799
 *
1800
 * @param object|WP_Post|array $post    The Post Object or Array
1801
 * @param string               $context Optional. How to sanitize post fields.
1802
 *                                      Accepts 'raw', 'edit', 'db', or 'display'.
1803
 *                                      Default 'display'.
1804
 * @return object|WP_Post|array The now sanitized Post Object or Array (will be the
1805
 *                              same type as $post).
1806
 */
1807
function sanitize_post( $post, $context = 'display' ) {
1808
	if ( is_object($post) ) {
1809
		// Check if post already filtered for this context.
1810
		if ( isset($post->filter) && $context == $post->filter )
1811
			return $post;
1812
		if ( !isset($post->ID) )
1813
			$post->ID = 0;
1814
		foreach ( array_keys(get_object_vars($post)) as $field )
1815
			$post->$field = sanitize_post_field($field, $post->$field, $post->ID, $context);
1816
		$post->filter = $context;
1817
	} elseif ( is_array( $post ) ) {
1818
		// Check if post already filtered for this context.
1819
		if ( isset($post['filter']) && $context == $post['filter'] )
1820
			return $post;
1821
		if ( !isset($post['ID']) )
1822
			$post['ID'] = 0;
1823
		foreach ( array_keys($post) as $field )
1824
			$post[$field] = sanitize_post_field($field, $post[$field], $post['ID'], $context);
1825
		$post['filter'] = $context;
1826
	}
1827
	return $post;
1828
}
1829
1830
/**
1831
 * Sanitize post field based on context.
1832
 *
1833
 * Possible context values are:  'raw', 'edit', 'db', 'display', 'attribute' and
1834
 * 'js'. The 'display' context is used by default. 'attribute' and 'js' contexts
1835
 * are treated like 'display' when calling filters.
1836
 *
1837
 * @since 2.3.0
1838
 * @since 4.4.0 Like `sanitize_post()`, `$context` defaults to 'display'.
1839
 *
1840
 * @param string $field   The Post Object field name.
1841
 * @param mixed  $value   The Post Object value.
1842
 * @param int    $post_id Post ID.
1843
 * @param string $context Optional. How to sanitize post fields. Looks for 'raw', 'edit',
1844
 *                        'db', 'display', 'attribute' and 'js'. Default 'display'.
1845
 * @return mixed Sanitized value.
1846
 */
1847
function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
1848
	$int_fields = array('ID', 'post_parent', 'menu_order');
1849
	if ( in_array($field, $int_fields) )
1850
		$value = (int) $value;
1851
1852
	// Fields which contain arrays of integers.
1853
	$array_int_fields = array( 'ancestors' );
1854
	if ( in_array($field, $array_int_fields) ) {
1855
		$value = array_map( 'absint', $value);
1856
		return $value;
1857
	}
1858
1859
	if ( 'raw' == $context )
1860
		return $value;
1861
1862
	$prefixed = false;
1863
	if ( false !== strpos($field, 'post_') ) {
1864
		$prefixed = true;
1865
		$field_no_prefix = str_replace('post_', '', $field);
1866
	}
1867
1868
	if ( 'edit' == $context ) {
1869
		$format_to_edit = array('post_content', 'post_excerpt', 'post_title', 'post_password');
1870
1871
		if ( $prefixed ) {
1872
1873
			/**
1874
			 * Filters the value of a specific post field to edit.
1875
			 *
1876
			 * The dynamic portion of the hook name, `$field`, refers to the post
1877
			 * field name.
1878
			 *
1879
			 * @since 2.3.0
1880
			 *
1881
			 * @param mixed $value   Value of the post field.
1882
			 * @param int   $post_id Post ID.
1883
			 */
1884
			$value = apply_filters( "edit_{$field}", $value, $post_id );
1885
1886
			/**
1887
			 * Filters the value of a specific post field to edit.
1888
			 *
1889
			 * The dynamic portion of the hook name, `$field_no_prefix`, refers to
1890
			 * the post field name.
1891
			 *
1892
			 * @since 2.3.0
1893
			 *
1894
			 * @param mixed $value   Value of the post field.
1895
			 * @param int   $post_id Post ID.
1896
			 */
1897
			$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...
1898
		} else {
1899
			$value = apply_filters( "edit_post_{$field}", $value, $post_id );
1900
		}
1901
1902
		if ( in_array($field, $format_to_edit) ) {
1903
			if ( 'post_content' == $field )
1904
				$value = format_to_edit($value, user_can_richedit());
1905
			else
1906
				$value = format_to_edit($value);
1907
		} else {
1908
			$value = esc_attr($value);
1909
		}
1910 View Code Duplication
	} elseif ( 'db' == $context ) {
1911
		if ( $prefixed ) {
1912
1913
			/**
1914
			 * Filters the value of a specific post field before saving.
1915
			 *
1916
			 * The dynamic portion of the hook name, `$field`, refers to the post
1917
			 * field name.
1918
			 *
1919
			 * @since 2.3.0
1920
			 *
1921
			 * @param mixed $value Value of the post field.
1922
			 */
1923
			$value = apply_filters( "pre_{$field}", $value );
1924
1925
			/**
1926
			 * Filters the value of a specific field before saving.
1927
			 *
1928
			 * The dynamic portion of the hook name, `$field_no_prefix`, refers
1929
			 * to the post field name.
1930
			 *
1931
			 * @since 2.3.0
1932
			 *
1933
			 * @param mixed $value Value of the post field.
1934
			 */
1935
			$value = apply_filters( "{$field_no_prefix}_save_pre", $value );
1936
		} else {
1937
			$value = apply_filters( "pre_post_{$field}", $value );
1938
1939
			/**
1940
			 * Filters the value of a specific post field before saving.
1941
			 *
1942
			 * The dynamic portion of the hook name, `$field`, refers to the post
1943
			 * field name.
1944
			 *
1945
			 * @since 2.3.0
1946
			 *
1947
			 * @param mixed $value Value of the post field.
1948
			 */
1949
			$value = apply_filters( "{$field}_pre", $value );
1950
		}
1951
	} else {
1952
1953
		// Use display filters by default.
1954
		if ( $prefixed ) {
1955
1956
			/**
1957
			 * Filters the value of a specific post field for display.
1958
			 *
1959
			 * The dynamic portion of the hook name, `$field`, refers to the post
1960
			 * field name.
1961
			 *
1962
			 * @since 2.3.0
1963
			 *
1964
			 * @param mixed  $value   Value of the prefixed post field.
1965
			 * @param int    $post_id Post ID.
1966
			 * @param string $context Context for how to sanitize the field. Possible
1967
			 *                        values include 'raw', 'edit', 'db', 'display',
1968
			 *                        'attribute' and 'js'.
1969
			 */
1970
			$value = apply_filters( $field, $value, $post_id, $context );
1971
		} else {
1972
			$value = apply_filters( "post_{$field}", $value, $post_id, $context );
1973
		}
1974
1975 View Code Duplication
		if ( 'attribute' == $context ) {
1976
			$value = esc_attr( $value );
1977
		} elseif ( 'js' == $context ) {
1978
			$value = esc_js( $value );
1979
		}
1980
	}
1981
1982
	return $value;
1983
}
1984
1985
/**
1986
 * Make a post sticky.
1987
 *
1988
 * Sticky posts should be displayed at the top of the front page.
1989
 *
1990
 * @since 2.7.0
1991
 *
1992
 * @param int $post_id Post ID.
1993
 */
1994
function stick_post( $post_id ) {
1995
	$stickies = get_option('sticky_posts');
1996
1997
	if ( !is_array($stickies) )
1998
		$stickies = array($post_id);
1999
2000
	if ( ! in_array($post_id, $stickies) )
2001
		$stickies[] = $post_id;
2002
2003
	$updated = update_option( 'sticky_posts', $stickies );
2004
2005
	if ( $updated ) {
2006
		/**
2007
		 * Fires once a post has been added to the sticky list.
2008
		 *
2009
		 * @since 4.6.0
2010
		 *
2011
		 * @param int $post_id ID of the post that was stuck.
2012
		 */
2013
		do_action( 'post_stuck', $post_id );
2014
	}
2015
}
2016
2017
/**
2018
 * Un-stick a post.
2019
 *
2020
 * Sticky posts should be displayed at the top of the front page.
2021
 *
2022
 * @since 2.7.0
2023
 *
2024
 * @param int $post_id Post ID.
2025
 */
2026
function unstick_post( $post_id ) {
2027
	$stickies = get_option('sticky_posts');
2028
2029
	if ( !is_array($stickies) )
2030
		return;
2031
2032
	if ( ! in_array($post_id, $stickies) )
2033
		return;
2034
2035
	$offset = array_search($post_id, $stickies);
2036
	if ( false === $offset )
2037
		return;
2038
2039
	array_splice($stickies, $offset, 1);
2040
2041
	$updated = update_option( 'sticky_posts', $stickies );
2042
2043
	if ( $updated ) {
2044
		/**
2045
		 * Fires once a post has been removed from the sticky list.
2046
		 *
2047
		 * @since 4.6.0
2048
		 *
2049
		 * @param int $post_id ID of the post that was unstuck.
2050
		 */
2051
		do_action( 'post_unstuck', $post_id );
2052
	}
2053
}
2054
2055
/**
2056
 * Return the cache key for wp_count_posts() based on the passed arguments.
2057
 *
2058
 * @since 3.9.0
2059
 *
2060
 * @param string $type Optional. Post type to retrieve count Default 'post'.
2061
 * @param string $perm Optional. 'readable' or empty. Default empty.
2062
 * @return string The cache key.
2063
 */
2064
function _count_posts_cache_key( $type = 'post', $perm = '' ) {
2065
	$cache_key = 'posts-' . $type;
2066
	if ( 'readable' == $perm && is_user_logged_in() ) {
2067
		$post_type_object = get_post_type_object( $type );
2068
		if ( $post_type_object && ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2069
			$cache_key .= '_' . $perm . '_' . get_current_user_id();
2070
		}
2071
	}
2072
	return $cache_key;
2073
}
2074
2075
/**
2076
 * Count number of posts of a post type and if user has permissions to view.
2077
 *
2078
 * This function provides an efficient method of finding the amount of post's
2079
 * type a blog has. Another method is to count the amount of items in
2080
 * get_posts(), but that method has a lot of overhead with doing so. Therefore,
2081
 * when developing for 2.5+, use this function instead.
2082
 *
2083
 * The $perm parameter checks for 'readable' value and if the user can read
2084
 * private posts, it will display that for the user that is signed in.
2085
 *
2086
 * @since 2.5.0
2087
 *
2088
 * @global wpdb $wpdb WordPress database abstraction object.
2089
 *
2090
 * @param string $type Optional. Post type to retrieve count. Default 'post'.
2091
 * @param string $perm Optional. 'readable' or empty. Default empty.
2092
 * @return object Number of posts for each status.
2093
 */
2094
function wp_count_posts( $type = 'post', $perm = '' ) {
2095
	global $wpdb;
2096
2097
	if ( ! post_type_exists( $type ) )
2098
		return new stdClass;
2099
2100
	$cache_key = _count_posts_cache_key( $type, $perm );
2101
2102
	$counts = wp_cache_get( $cache_key, 'counts' );
2103
	if ( false !== $counts ) {
2104
		/** This filter is documented in wp-includes/post.php */
2105
		return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2106
	}
2107
2108
	$query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
2109
	if ( 'readable' == $perm && is_user_logged_in() ) {
2110
		$post_type_object = get_post_type_object($type);
2111
		if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2112
			$query .= $wpdb->prepare( " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
2113
				get_current_user_id()
2114
			);
2115
		}
2116
	}
2117
	$query .= ' GROUP BY post_status';
2118
2119
	$results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
2120
	$counts = array_fill_keys( get_post_stati(), 0 );
2121
2122
	foreach ( $results as $row ) {
2123
		$counts[ $row['post_status'] ] = $row['num_posts'];
2124
	}
2125
2126
	$counts = (object) $counts;
2127
	wp_cache_set( $cache_key, $counts, 'counts' );
2128
2129
	/**
2130
	 * Modify returned post counts by status for the current post type.
2131
	 *
2132
	 * @since 3.7.0
2133
	 *
2134
	 * @param object $counts An object containing the current post_type's post
2135
	 *                       counts by status.
2136
	 * @param string $type   Post type.
2137
	 * @param string $perm   The permission to determine if the posts are 'readable'
2138
	 *                       by the current user.
2139
	 */
2140
	return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2141
}
2142
2143
/**
2144
 * Count number of attachments for the mime type(s).
2145
 *
2146
 * If you set the optional mime_type parameter, then an array will still be
2147
 * returned, but will only have the item you are looking for. It does not give
2148
 * you the number of attachments that are children of a post. You can get that
2149
 * by counting the number of children that post has.
2150
 *
2151
 * @since 2.5.0
2152
 *
2153
 * @global wpdb $wpdb WordPress database abstraction object.
2154
 *
2155
 * @param string|array $mime_type Optional. Array or comma-separated list of
2156
 *                                MIME patterns. Default empty.
2157
 * @return object An object containing the attachment counts by mime type.
2158
 */
2159
function wp_count_attachments( $mime_type = '' ) {
2160
	global $wpdb;
2161
2162
	$and = wp_post_mime_type_where( $mime_type );
2163
	$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 );
2164
2165
	$counts = array();
2166
	foreach ( (array) $count as $row ) {
2167
		$counts[ $row['post_mime_type'] ] = $row['num_posts'];
2168
	}
2169
	$counts['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and");
2170
2171
	/**
2172
	 * Modify returned attachment counts by mime type.
2173
	 *
2174
	 * @since 3.7.0
2175
	 *
2176
	 * @param object $counts    An object containing the attachment counts by
2177
	 *                          mime type.
2178
	 * @param string $mime_type The mime type pattern used to filter the attachments
2179
	 *                          counted.
2180
	 */
2181
	return apply_filters( 'wp_count_attachments', (object) $counts, $mime_type );
2182
}
2183
2184
/**
2185
 * Get default post mime types.
2186
 *
2187
 * @since 2.9.0
2188
 *
2189
 * @return array List of post mime types.
2190
 */
2191
function get_post_mime_types() {
2192
	$post_mime_types = array(	//	array( adj, noun )
2193
		'image' => array(__('Images'), __('Manage Images'), _n_noop('Image <span class="count">(%s)</span>', 'Images <span class="count">(%s)</span>')),
2194
		'audio' => array(__('Audio'), __('Manage Audio'), _n_noop('Audio <span class="count">(%s)</span>', 'Audio <span class="count">(%s)</span>')),
2195
		'video' => array(__('Video'), __('Manage Video'), _n_noop('Video <span class="count">(%s)</span>', 'Video <span class="count">(%s)</span>')),
2196
	);
2197
2198
	/**
2199
	 * Filters the default list of post mime types.
2200
	 *
2201
	 * @since 2.5.0
2202
	 *
2203
	 * @param array $post_mime_types Default list of post mime types.
2204
	 */
2205
	return apply_filters( 'post_mime_types', $post_mime_types );
2206
}
2207
2208
/**
2209
 * Check a MIME-Type against a list.
2210
 *
2211
 * If the wildcard_mime_types parameter is a string, it must be comma separated
2212
 * list. If the real_mime_types is a string, it is also comma separated to
2213
 * create the list.
2214
 *
2215
 * @since 2.5.0
2216
 *
2217
 * @param string|array $wildcard_mime_types Mime types, e.g. audio/mpeg or image (same as image/*)
2218
 *                                          or flash (same as *flash*).
2219
 * @param string|array $real_mime_types     Real post mime type values.
2220
 * @return array array(wildcard=>array(real types)).
2221
 */
2222
function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) {
2223
	$matches = array();
2224
	if ( is_string( $wildcard_mime_types ) ) {
2225
		$wildcard_mime_types = array_map( 'trim', explode( ',', $wildcard_mime_types ) );
2226
	}
2227
	if ( is_string( $real_mime_types ) ) {
2228
		$real_mime_types = array_map( 'trim', explode( ',', $real_mime_types ) );
2229
	}
2230
2231
	$patternses = array();
2232
	$wild = '[-._a-z0-9]*';
2233
2234
	foreach ( (array) $wildcard_mime_types as $type ) {
2235
		$mimes = array_map( 'trim', explode( ',', $type ) );
2236
		foreach ( $mimes as $mime ) {
2237
			$regex = str_replace( '__wildcard__', $wild, preg_quote( str_replace( '*', '__wildcard__', $mime ) ) );
2238
			$patternses[][$type] = "^$regex$";
2239
			if ( false === strpos( $mime, '/' ) ) {
2240
				$patternses[][$type] = "^$regex/";
2241
				$patternses[][$type] = $regex;
2242
			}
2243
		}
2244
	}
2245
	asort( $patternses );
2246
2247
	foreach ( $patternses as $patterns ) {
2248
		foreach ( $patterns as $type => $pattern ) {
2249
			foreach ( (array) $real_mime_types as $real ) {
2250
				if ( preg_match( "#$pattern#", $real ) && ( empty( $matches[$type] ) || false === array_search( $real, $matches[$type] ) ) ) {
2251
					$matches[$type][] = $real;
2252
				}
2253
			}
2254
		}
2255
	}
2256
	return $matches;
2257
}
2258
2259
/**
2260
 * Convert MIME types into SQL.
2261
 *
2262
 * @since 2.5.0
2263
 *
2264
 * @param string|array $post_mime_types List of mime types or comma separated string
2265
 *                                      of mime types.
2266
 * @param string       $table_alias     Optional. Specify a table alias, if needed.
2267
 *                                      Default empty.
2268
 * @return string The SQL AND clause for mime searching.
2269
 */
2270
function wp_post_mime_type_where( $post_mime_types, $table_alias = '' ) {
2271
	$where = '';
2272
	$wildcards = array('', '%', '%/%');
2273
	if ( is_string($post_mime_types) )
2274
		$post_mime_types = array_map('trim', explode(',', $post_mime_types));
2275
2276
	$wheres = array();
2277
2278
	foreach ( (array) $post_mime_types as $mime_type ) {
2279
		$mime_type = preg_replace('/\s/', '', $mime_type);
2280
		$slashpos = strpos($mime_type, '/');
2281
		if ( false !== $slashpos ) {
2282
			$mime_group = preg_replace('/[^-*.a-zA-Z0-9]/', '', substr($mime_type, 0, $slashpos));
2283
			$mime_subgroup = preg_replace('/[^-*.+a-zA-Z0-9]/', '', substr($mime_type, $slashpos + 1));
2284
			if ( empty($mime_subgroup) )
2285
				$mime_subgroup = '*';
2286
			else
2287
				$mime_subgroup = str_replace('/', '', $mime_subgroup);
2288
			$mime_pattern = "$mime_group/$mime_subgroup";
2289
		} else {
2290
			$mime_pattern = preg_replace('/[^-*.a-zA-Z0-9]/', '', $mime_type);
2291
			if ( false === strpos($mime_pattern, '*') )
2292
				$mime_pattern .= '/*';
2293
		}
2294
2295
		$mime_pattern = preg_replace('/\*+/', '%', $mime_pattern);
2296
2297
		if ( in_array( $mime_type, $wildcards ) )
2298
			return '';
2299
2300
		if ( false !== strpos($mime_pattern, '%') )
2301
			$wheres[] = empty($table_alias) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
2302
		else
2303
			$wheres[] = empty($table_alias) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
2304
	}
2305
	if ( !empty($wheres) )
2306
		$where = ' AND (' . join(' OR ', $wheres) . ') ';
2307
	return $where;
2308
}
2309
2310
/**
2311
 * Trash or delete a post or page.
2312
 *
2313
 * When the post and page is permanently deleted, everything that is tied to
2314
 * it is deleted also. This includes comments, post meta fields, and terms
2315
 * associated with the post.
2316
 *
2317
 * The post or page is moved to trash instead of permanently deleted unless
2318
 * trash is disabled, item is already in the trash, or $force_delete is true.
2319
 *
2320
 * @since 1.0.0
2321
 *
2322
 * @global wpdb $wpdb WordPress database abstraction object.
2323
 * @see wp_delete_attachment()
2324
 * @see wp_trash_post()
2325
 *
2326
 * @param int  $postid       Optional. Post ID. Default 0.
2327
 * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
2328
 *                           Default false.
2329
 * @return array|false|WP_Post False on failure.
2330
 */
2331
function wp_delete_post( $postid = 0, $force_delete = false ) {
2332
	global $wpdb;
2333
2334
	if ( !$post = $wpdb->get_row($wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $postid)) )
2335
		return $post;
2336
2337
	if ( !$force_delete && ( $post->post_type == 'post' || $post->post_type == 'page') && get_post_status( $postid ) != 'trash' && EMPTY_TRASH_DAYS )
2338
		return wp_trash_post( $postid );
2339
2340
	if ( $post->post_type == 'attachment' )
2341
		return wp_delete_attachment( $postid, $force_delete );
2342
2343
	/**
2344
	 * Filters whether a post deletion should take place.
2345
	 *
2346
	 * @since 4.4.0
2347
	 *
2348
	 * @param bool    $delete       Whether to go forward with deletion.
2349
	 * @param WP_Post $post         Post object.
2350
	 * @param bool    $force_delete Whether to bypass the trash.
2351
	 */
2352
	$check = apply_filters( 'pre_delete_post', null, $post, $force_delete );
2353
	if ( null !== $check ) {
2354
		return $check;
2355
	}
2356
2357
	/**
2358
	 * Fires before a post is deleted, at the start of wp_delete_post().
2359
	 *
2360
	 * @since 3.2.0
2361
	 *
2362
	 * @see wp_delete_post()
2363
	 *
2364
	 * @param int $postid Post ID.
2365
	 */
2366
	do_action( 'before_delete_post', $postid );
2367
2368
	delete_post_meta($postid,'_wp_trash_meta_status');
2369
	delete_post_meta($postid,'_wp_trash_meta_time');
2370
2371
	wp_delete_object_term_relationships($postid, get_object_taxonomies($post->post_type));
2372
2373
	$parent_data = array( 'post_parent' => $post->post_parent );
2374
	$parent_where = array( 'post_parent' => $postid );
2375
2376
	if ( is_post_type_hierarchical( $post->post_type ) ) {
2377
		// Point children of this page to its parent, also clean the cache of affected children.
2378
		$children_query = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type = %s", $postid, $post->post_type );
2379
		$children = $wpdb->get_results( $children_query );
2380
		if ( $children ) {
2381
			$wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => $post->post_type ) );
2382
		}
2383
	}
2384
2385
	// Do raw query. wp_get_post_revisions() is filtered.
2386
	$revision_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $postid ) );
2387
	// Use wp_delete_post (via wp_delete_post_revision) again. Ensures any meta/misplaced data gets cleaned up.
2388
	foreach ( $revision_ids as $revision_id )
2389
		wp_delete_post_revision( $revision_id );
2390
2391
	// Point all attachments to this post up one level.
2392
	$wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
2393
2394
	wp_defer_comment_counting( true );
2395
2396
	$comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ));
2397
	foreach ( $comment_ids as $comment_id ) {
2398
		wp_delete_comment( $comment_id, true );
2399
	}
2400
2401
	wp_defer_comment_counting( false );
2402
2403
	$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ));
2404
	foreach ( $post_meta_ids as $mid )
2405
		delete_metadata_by_mid( 'post', $mid );
2406
2407
	/**
2408
	 * Fires immediately before a post is deleted from the database.
2409
	 *
2410
	 * @since 1.2.0
2411
	 *
2412
	 * @param int $postid Post ID.
2413
	 */
2414
	do_action( 'delete_post', $postid );
2415
	$result = $wpdb->delete( $wpdb->posts, array( 'ID' => $postid ) );
2416
	if ( ! $result ) {
2417
		return false;
2418
	}
2419
2420
	/**
2421
	 * Fires immediately after a post is deleted from the database.
2422
	 *
2423
	 * @since 2.2.0
2424
	 *
2425
	 * @param int $postid Post ID.
2426
	 */
2427
	do_action( 'deleted_post', $postid );
2428
2429
	clean_post_cache( $post );
2430
2431
	if ( is_post_type_hierarchical( $post->post_type ) && $children ) {
2432
		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...
2433
			clean_post_cache( $child );
2434
	}
2435
2436
	wp_clear_scheduled_hook('publish_future_post', array( $postid ) );
2437
2438
	/**
2439
	 * Fires after a post is deleted, at the conclusion of wp_delete_post().
2440
	 *
2441
	 * @since 3.2.0
2442
	 *
2443
	 * @see wp_delete_post()
2444
	 *
2445
	 * @param int $postid Post ID.
2446
	 */
2447
	do_action( 'after_delete_post', $postid );
2448
2449
	return $post;
2450
}
2451
2452
/**
2453
 * Reset the page_on_front, show_on_front, and page_for_post settings when
2454
 * a linked page is deleted or trashed.
2455
 *
2456
 * Also ensures the post is no longer sticky.
2457
 *
2458
 * @since 3.7.0
2459
 * @access private
2460
 *
2461
 * @param int $post_id Post ID.
2462
 */
2463
function _reset_front_page_settings_for_post( $post_id ) {
2464
	$post = get_post( $post_id );
2465
	if ( 'page' == $post->post_type ) {
2466
	 	/*
2467
	 	 * If the page is defined in option page_on_front or post_for_posts,
2468
	 	 * adjust the corresponding options.
2469
	 	 */
2470
		if ( get_option( 'page_on_front' ) == $post->ID ) {
2471
			update_option( 'show_on_front', 'posts' );
2472
			update_option( 'page_on_front', 0 );
2473
		}
2474
		if ( get_option( 'page_for_posts' ) == $post->ID ) {
2475
			delete_option( 'page_for_posts', 0 );
2476
		}
2477
	}
2478
	unstick_post( $post->ID );
2479
}
2480
2481
/**
2482
 * Move a post or page to the Trash
2483
 *
2484
 * If trash is disabled, the post or page is permanently deleted.
2485
 *
2486
 * @since 2.9.0
2487
 *
2488
 * @see wp_delete_post()
2489
 *
2490
 * @param int $post_id Optional. Post ID. Default is ID of the global $post
2491
 *                     if EMPTY_TRASH_DAYS equals true.
2492
 * @return false|array|WP_Post|null Post data array, otherwise false.
2493
 */
2494
function wp_trash_post( $post_id = 0 ) {
2495
	if ( !EMPTY_TRASH_DAYS )
2496
		return wp_delete_post($post_id, true);
2497
2498
	if ( !$post = get_post($post_id, ARRAY_A) )
2499
		return $post;
2500
2501
	if ( $post['post_status'] == 'trash' )
2502
		return false;
2503
2504
	/**
2505
	 * Fires before a post is sent to the trash.
2506
	 *
2507
	 * @since 3.3.0
2508
	 *
2509
	 * @param int $post_id Post ID.
2510
	 */
2511
	do_action( 'wp_trash_post', $post_id );
2512
2513
	add_post_meta($post_id,'_wp_trash_meta_status', $post['post_status']);
2514
	add_post_meta($post_id,'_wp_trash_meta_time', time());
2515
2516
	$post['post_status'] = 'trash';
2517
	wp_insert_post( wp_slash( $post ) );
0 ignored issues
show
It seems like $post defined by get_post($post_id, ARRAY_A) on line 2498 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...
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...
2518
2519
	wp_trash_post_comments($post_id);
2520
2521
	/**
2522
	 * Fires after a post is sent to the trash.
2523
	 *
2524
	 * @since 2.9.0
2525
	 *
2526
	 * @param int $post_id Post ID.
2527
	 */
2528
	do_action( 'trashed_post', $post_id );
2529
2530
	return $post;
2531
}
2532
2533
/**
2534
 * Restore a post or page from the Trash.
2535
 *
2536
 * @since 2.9.0
2537
 *
2538
 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2539
 * @return WP_Post|false WP_Post object. False on failure.
2540
 */
2541
function wp_untrash_post( $post_id = 0 ) {
2542
	if ( !$post = get_post($post_id, ARRAY_A) )
2543
		return $post;
2544
2545
	if ( $post['post_status'] != 'trash' )
2546
		return false;
2547
2548
	/**
2549
	 * Fires before a post is restored from the trash.
2550
	 *
2551
	 * @since 2.9.0
2552
	 *
2553
	 * @param int $post_id Post ID.
2554
	 */
2555
	do_action( 'untrash_post', $post_id );
2556
2557
	$post_status = get_post_meta($post_id, '_wp_trash_meta_status', true);
2558
2559
	$post['post_status'] = $post_status;
2560
2561
	delete_post_meta($post_id, '_wp_trash_meta_status');
2562
	delete_post_meta($post_id, '_wp_trash_meta_time');
2563
2564
	wp_insert_post( wp_slash( $post ) );
0 ignored issues
show
It seems like $post defined by get_post($post_id, ARRAY_A) on line 2542 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...
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...
2565
2566
	wp_untrash_post_comments($post_id);
2567
2568
	/**
2569
	 * Fires after a post is restored from the trash.
2570
	 *
2571
	 * @since 2.9.0
2572
	 *
2573
	 * @param int $post_id Post ID.
2574
	 */
2575
	do_action( 'untrashed_post', $post_id );
2576
2577
	return $post;
2578
}
2579
2580
/**
2581
 * Moves comments for a post to the trash.
2582
 *
2583
 * @since 2.9.0
2584
 *
2585
 * @global wpdb $wpdb WordPress database abstraction object.
2586
 *
2587
 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
2588
 * @return mixed|void False on failure.
2589
 */
2590
function wp_trash_post_comments( $post = null ) {
2591
	global $wpdb;
2592
2593
	$post = get_post($post);
2594
	if ( empty($post) )
2595
		return;
2596
2597
	$post_id = $post->ID;
2598
2599
	/**
2600
	 * Fires before comments are sent to the trash.
2601
	 *
2602
	 * @since 2.9.0
2603
	 *
2604
	 * @param int $post_id Post ID.
2605
	 */
2606
	do_action( 'trash_post_comments', $post_id );
2607
2608
	$comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id) );
2609
	if ( empty($comments) )
2610
		return;
2611
2612
	// Cache current status for each comment.
2613
	$statuses = array();
2614
	foreach ( $comments as $comment )
2615
		$statuses[$comment->comment_ID] = $comment->comment_approved;
2616
	add_post_meta($post_id, '_wp_trash_meta_comments_status', $statuses);
2617
2618
	// Set status for all comments to post-trashed.
2619
	$result = $wpdb->update($wpdb->comments, array('comment_approved' => 'post-trashed'), array('comment_post_ID' => $post_id));
2620
2621
	clean_comment_cache( array_keys($statuses) );
2622
2623
	/**
2624
	 * Fires after comments are sent to the trash.
2625
	 *
2626
	 * @since 2.9.0
2627
	 *
2628
	 * @param int   $post_id  Post ID.
2629
	 * @param array $statuses Array of comment statuses.
2630
	 */
2631
	do_action( 'trashed_post_comments', $post_id, $statuses );
2632
2633
	return $result;
2634
}
2635
2636
/**
2637
 * Restore comments for a post from the trash.
2638
 *
2639
 * @since 2.9.0
2640
 *
2641
 * @global wpdb $wpdb WordPress database abstraction object.
2642
 *
2643
 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
2644
 * @return true|void
2645
 */
2646
function wp_untrash_post_comments( $post = null ) {
2647
	global $wpdb;
2648
2649
	$post = get_post($post);
2650
	if ( empty($post) )
2651
		return;
2652
2653
	$post_id = $post->ID;
2654
2655
	$statuses = get_post_meta($post_id, '_wp_trash_meta_comments_status', true);
2656
2657
	if ( empty($statuses) )
2658
		return true;
2659
2660
	/**
2661
	 * Fires before comments are restored for a post from the trash.
2662
	 *
2663
	 * @since 2.9.0
2664
	 *
2665
	 * @param int $post_id Post ID.
2666
	 */
2667
	do_action( 'untrash_post_comments', $post_id );
2668
2669
	// Restore each comment to its original status.
2670
	$group_by_status = array();
2671
	foreach ( $statuses as $comment_id => $comment_status )
2672
		$group_by_status[$comment_status][] = $comment_id;
2673
2674
	foreach ( $group_by_status as $status => $comments ) {
2675
		// Sanity check. This shouldn't happen.
2676
		if ( 'post-trashed' == $status ) {
2677
			$status = '0';
2678
		}
2679
		$comments_in = implode( ', ', array_map( 'intval', $comments ) );
2680
		$wpdb->query( $wpdb->prepare( "UPDATE $wpdb->comments SET comment_approved = %s WHERE comment_ID IN ($comments_in)", $status ) );
2681
	}
2682
2683
	clean_comment_cache( array_keys($statuses) );
2684
2685
	delete_post_meta($post_id, '_wp_trash_meta_comments_status');
2686
2687
	/**
2688
	 * Fires after comments are restored for a post from the trash.
2689
	 *
2690
	 * @since 2.9.0
2691
	 *
2692
	 * @param int $post_id Post ID.
2693
	 */
2694
	do_action( 'untrashed_post_comments', $post_id );
2695
}
2696
2697
/**
2698
 * Retrieve the list of categories for a post.
2699
 *
2700
 * Compatibility layer for themes and plugins. Also an easy layer of abstraction
2701
 * away from the complexity of the taxonomy layer.
2702
 *
2703
 * @since 2.1.0
2704
 *
2705
 * @see wp_get_object_terms()
2706
 *
2707
 * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
2708
 *                       global $post. Default 0.
2709
 * @param array $args    Optional. Category arguments. See wp_get_object_terms(). Default empty.
2710
 * @return array List of categories. If the `$fields` argument passed via `$args` is 'all' or
2711
 *               'all_with_object_id', an array of WP_Term objects will be returned. If `$fields`
2712
 *               is 'ids', an array of category ids. If `$fields` is 'names', an array of category names.
2713
 */
2714 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...
2715
	$post_id = (int) $post_id;
2716
2717
	$defaults = array('fields' => 'ids');
2718
	$args = wp_parse_args( $args, $defaults );
2719
2720
	$cats = wp_get_object_terms($post_id, 'category', $args);
2721
	return $cats;
2722
}
2723
2724
/**
2725
 * Retrieve the tags for a post.
2726
 *
2727
 * There is only one default for this function, called 'fields' and by default
2728
 * is set to 'all'. There are other defaults that can be overridden in
2729
 * wp_get_object_terms().
2730
 *
2731
 * @since 2.3.0
2732
 *
2733
 * @param int   $post_id Optional. The Post ID. Does not default to the ID of the
2734
 *                       global $post. Default 0.
2735
 * @param array $args Optional. Overwrite the defaults
2736
 * @return array List of post tags.
2737
 */
2738
function wp_get_post_tags( $post_id = 0, $args = array() ) {
2739
	return wp_get_post_terms( $post_id, 'post_tag', $args);
2740
}
2741
2742
/**
2743
 * Retrieve the terms for a post.
2744
 *
2745
 * There is only one default for this function, called 'fields' and by default
2746
 * is set to 'all'. There are other defaults that can be overridden in
2747
 * wp_get_object_terms().
2748
 *
2749
 * @since 2.8.0
2750
 *
2751
 * @param int    $post_id  Optional. The Post ID. Does not default to the ID of the
2752
 *                         global $post. Default 0.
2753
 * @param string $taxonomy Optional. The taxonomy for which to retrieve terms. Default 'post_tag'.
2754
 * @param array  $args     Optional. wp_get_object_terms() arguments. Default empty array.
2755
 * @return array|WP_Error  List of post terms or empty array if no terms were found. WP_Error object
2756
 *                         if `$taxonomy` doesn't exist.
2757
 */
2758 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...
2759
	$post_id = (int) $post_id;
2760
2761
	$defaults = array('fields' => 'all');
2762
	$args = wp_parse_args( $args, $defaults );
2763
2764
	$tags = wp_get_object_terms($post_id, $taxonomy, $args);
2765
2766
	return $tags;
2767
}
2768
2769
/**
2770
 * Retrieve a number of recent posts.
2771
 *
2772
 * @since 1.0.0
2773
 *
2774
 * @see get_posts()
2775
 *
2776
 * @param array  $args       Optional. Arguments to retrieve posts. Default empty array.
2777
 * @param string $output     Optional. Type of output. Accepts ARRAY_A or ''. Default ARRAY_A.
2778
 * @return array|false Associative array if $output equals ARRAY_A, array or false if no results.
2779
 */
2780
function wp_get_recent_posts( $args = array(), $output = ARRAY_A ) {
2781
2782
	if ( is_numeric( $args ) ) {
2783
		_deprecated_argument( __FUNCTION__, '3.1.0', __( 'Passing an integer number of posts is deprecated. Pass an array of arguments instead.' ) );
2784
		$args = array( 'numberposts' => absint( $args ) );
2785
	}
2786
2787
	// Set default arguments.
2788
	$defaults = array(
2789
		'numberposts' => 10, 'offset' => 0,
2790
		'category' => 0, 'orderby' => 'post_date',
2791
		'order' => 'DESC', 'include' => '',
2792
		'exclude' => '', 'meta_key' => '',
2793
		'meta_value' =>'', 'post_type' => 'post', 'post_status' => 'draft, publish, future, pending, private',
2794
		'suppress_filters' => true
2795
	);
2796
2797
	$r = wp_parse_args( $args, $defaults );
2798
2799
	$results = get_posts( $r );
2800
2801
	// Backward compatibility. Prior to 3.1 expected posts to be returned in array.
2802
	if ( ARRAY_A == $output ){
2803
		foreach ( $results as $key => $result ) {
2804
			$results[$key] = get_object_vars( $result );
2805
		}
2806
		return $results ? $results : array();
2807
	}
2808
2809
	return $results ? $results : false;
2810
2811
}
2812
2813
/**
2814
 * Insert or update a post.
2815
 *
2816
 * If the $postarr parameter has 'ID' set to a value, then post will be updated.
2817
 *
2818
 * You can set the post date manually, by setting the values for 'post_date'
2819
 * and 'post_date_gmt' keys. You can close the comments or open the comments by
2820
 * setting the value for 'comment_status' key.
2821
 *
2822
 * @since 1.0.0
2823
 * @since 4.2.0 Support was added for encoding emoji in the post title, content, and excerpt.
2824
 * @since 4.4.0 A 'meta_input' array can now be passed to `$postarr` to add post meta data.
2825
 *
2826
 * @see sanitize_post()
2827
 * @global wpdb $wpdb WordPress database abstraction object.
2828
 *
2829
 * @param array $postarr {
2830
 *     An array of elements that make up a post to update or insert.
2831
 *
2832
 *     @type int    $ID                    The post ID. If equal to something other than 0,
2833
 *                                         the post with that ID will be updated. Default 0.
2834
 *     @type int    $post_author           The ID of the user who added the post. Default is
2835
 *                                         the current user ID.
2836
 *     @type string $post_date             The date of the post. Default is the current time.
2837
 *     @type string $post_date_gmt         The date of the post in the GMT timezone. Default is
2838
 *                                         the value of `$post_date`.
2839
 *     @type mixed  $post_content          The post content. Default empty.
2840
 *     @type string $post_content_filtered The filtered post content. Default empty.
2841
 *     @type string $post_title            The post title. Default empty.
2842
 *     @type string $post_excerpt          The post excerpt. Default empty.
2843
 *     @type string $post_status           The post status. Default 'draft'.
2844
 *     @type string $post_type             The post type. Default 'post'.
2845
 *     @type string $comment_status        Whether the post can accept comments. Accepts 'open' or 'closed'.
2846
 *                                         Default is the value of 'default_comment_status' option.
2847
 *     @type string $ping_status           Whether the post can accept pings. Accepts 'open' or 'closed'.
2848
 *                                         Default is the value of 'default_ping_status' option.
2849
 *     @type string $post_password         The password to access the post. Default empty.
2850
 *     @type string $post_name             The post name. Default is the sanitized post title
2851
 *                                         when creating a new post.
2852
 *     @type string $to_ping               Space or carriage return-separated list of URLs to ping.
2853
 *                                         Default empty.
2854
 *     @type string $pinged                Space or carriage return-separated list of URLs that have
2855
 *                                         been pinged. Default empty.
2856
 *     @type string $post_modified         The date when the post was last modified. Default is
2857
 *                                         the current time.
2858
 *     @type string $post_modified_gmt     The date when the post was last modified in the GMT
2859
 *                                         timezone. Default is the current time.
2860
 *     @type int    $post_parent           Set this for the post it belongs to, if any. Default 0.
2861
 *     @type int    $menu_order            The order the post should be displayed in. Default 0.
2862
 *     @type string $post_mime_type        The mime type of the post. Default empty.
2863
 *     @type string $guid                  Global Unique ID for referencing the post. Default empty.
2864
 *     @type array  $post_category         Array of category names, slugs, or IDs.
2865
 *                                         Defaults to value of the 'default_category' option.
2866
 *     @type array  $tax_input             Array of taxonomy terms keyed by their taxonomy name. Default empty.
2867
 *     @type array  $meta_input            Array of post meta values keyed by their post meta key. Default empty.
2868
 * }
2869
 * @param bool  $wp_error Optional. Whether to allow return of WP_Error on failure. Default false.
2870
 * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
2871
 */
2872
function wp_insert_post( $postarr, $wp_error = false ) {
2873
	global $wpdb;
2874
2875
	$user_id = get_current_user_id();
2876
2877
	$defaults = array(
2878
		'post_author' => $user_id,
2879
		'post_content' => '',
2880
		'post_content_filtered' => '',
2881
		'post_title' => '',
2882
		'post_excerpt' => '',
2883
		'post_status' => 'draft',
2884
		'post_type' => 'post',
2885
		'comment_status' => '',
2886
		'ping_status' => '',
2887
		'post_password' => '',
2888
		'to_ping' =>  '',
2889
		'pinged' => '',
2890
		'post_parent' => 0,
2891
		'menu_order' => 0,
2892
		'guid' => '',
2893
		'import_id' => 0,
2894
		'context' => '',
2895
	);
2896
2897
	$postarr = wp_parse_args($postarr, $defaults);
2898
2899
	unset( $postarr[ 'filter' ] );
2900
2901
	$postarr = sanitize_post($postarr, 'db');
2902
2903
	// Are we updating or creating?
2904
	$post_ID = 0;
2905
	$update = false;
2906
	$guid = $postarr['guid'];
2907
2908
	if ( ! empty( $postarr['ID'] ) ) {
2909
		$update = true;
2910
2911
		// Get the post ID and GUID.
2912
		$post_ID = $postarr['ID'];
2913
		$post_before = get_post( $post_ID );
2914
		if ( is_null( $post_before ) ) {
2915
			if ( $wp_error ) {
2916
				return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
2917
			}
2918
			return 0;
2919
		}
2920
2921
		$guid = get_post_field( 'guid', $post_ID );
2922
		$previous_status = get_post_field('post_status', $post_ID );
2923
	} else {
2924
		$previous_status = 'new';
2925
	}
2926
2927
	$post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
2928
2929
	$post_title = $postarr['post_title'];
2930
	$post_content = $postarr['post_content'];
2931
	$post_excerpt = $postarr['post_excerpt'];
2932
	if ( isset( $postarr['post_name'] ) ) {
2933
		$post_name = $postarr['post_name'];
2934
	} elseif ( $update ) {
2935
		// For an update, don't modify the post_name if it wasn't supplied as an argument.
2936
		$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...
2937
	}
2938
2939
	$maybe_empty = 'attachment' !== $post_type
2940
		&& ! $post_content && ! $post_title && ! $post_excerpt
2941
		&& post_type_supports( $post_type, 'editor' )
2942
		&& post_type_supports( $post_type, 'title' )
2943
		&& post_type_supports( $post_type, 'excerpt' );
2944
2945
	/**
2946
	 * Filters whether the post should be considered "empty".
2947
	 *
2948
	 * The post is considered "empty" if both:
2949
	 * 1. The post type supports the title, editor, and excerpt fields
2950
	 * 2. The title, editor, and excerpt fields are all empty
2951
	 *
2952
	 * Returning a truthy value to the filter will effectively short-circuit
2953
	 * the new post being inserted, returning 0. If $wp_error is true, a WP_Error
2954
	 * will be returned instead.
2955
	 *
2956
	 * @since 3.3.0
2957
	 *
2958
	 * @param bool  $maybe_empty Whether the post should be considered "empty".
2959
	 * @param array $postarr     Array of post data.
2960
	 */
2961
	if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
2962
		if ( $wp_error ) {
2963
			return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
2964
		} else {
2965
			return 0;
2966
		}
2967
	}
2968
2969
	$post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
2970
	if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash' ) ) ) {
2971
		$post_status = 'inherit';
2972
	}
2973
2974
	if ( ! empty( $postarr['post_category'] ) ) {
2975
		// Filter out empty terms.
2976
		$post_category = array_filter( $postarr['post_category'] );
2977
	}
2978
2979
	// Make sure we set a valid category.
2980
	if ( empty( $post_category ) || 0 == count( $post_category ) || ! is_array( $post_category ) ) {
2981
		// 'post' requires at least one category.
2982
		if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
2983
			$post_category = array( get_option('default_category') );
2984
		} else {
2985
			$post_category = array();
2986
		}
2987
	}
2988
2989
	// Don't allow contributors to set the post slug for pending review posts.
2990
	if ( 'pending' == $post_status && !current_user_can( 'publish_posts' ) ) {
2991
		$post_name = '';
2992
	}
2993
2994
	/*
2995
	 * Create a valid post name. Drafts and pending posts are allowed to have
2996
	 * an empty post name.
2997
	 */
2998
	if ( empty($post_name) ) {
2999 View Code Duplication
		if ( !in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3000
			$post_name = sanitize_title($post_title);
3001
		} else {
3002
			$post_name = '';
3003
		}
3004
	} else {
3005
		// On updates, we need to check to see if it's using the old, fixed sanitization context.
3006
		$check_name = sanitize_title( $post_name, '', 'old-save' );
3007
		if ( $update && strtolower( urlencode( $post_name ) ) == $check_name && get_post_field( 'post_name', $post_ID ) == $check_name ) {
3008
			$post_name = $check_name;
3009
		} else { // new post, or slug has changed.
3010
			$post_name = sanitize_title($post_name);
3011
		}
3012
	}
3013
3014
	/*
3015
	 * If the post date is empty (due to having been new or a draft) and status
3016
	 * is not 'draft' or 'pending', set date to now.
3017
	 */
3018
	if ( empty( $postarr['post_date'] ) || '0000-00-00 00:00:00' == $postarr['post_date'] ) {
3019
		if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3020
			$post_date = current_time( 'mysql' );
3021
		} else {
3022
			$post_date = get_date_from_gmt( $postarr['post_date_gmt'] );
3023
		}
3024
	} else {
3025
		$post_date = $postarr['post_date'];
3026
	}
3027
3028
	// Validate the date.
3029
	$mm = substr( $post_date, 5, 2 );
3030
	$jj = substr( $post_date, 8, 2 );
3031
	$aa = substr( $post_date, 0, 4 );
3032
	$valid_date = wp_checkdate( $mm, $jj, $aa, $post_date );
3033
	if ( ! $valid_date ) {
3034
		if ( $wp_error ) {
3035
			return new WP_Error( 'invalid_date', __( 'Whoops, the provided date is invalid.' ) );
3036
		} else {
3037
			return 0;
3038
		}
3039
	}
3040
3041
	if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3042 View Code Duplication
		if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3043
			$post_date_gmt = get_gmt_from_date( $post_date );
3044
		} else {
3045
			$post_date_gmt = '0000-00-00 00:00:00';
3046
		}
3047
	} else {
3048
		$post_date_gmt = $postarr['post_date_gmt'];
3049
	}
3050
3051
	if ( $update || '0000-00-00 00:00:00' == $post_date ) {
3052
		$post_modified     = current_time( 'mysql' );
3053
		$post_modified_gmt = current_time( 'mysql', 1 );
3054
	} else {
3055
		$post_modified     = $post_date;
3056
		$post_modified_gmt = $post_date_gmt;
3057
	}
3058
3059
	if ( 'attachment' !== $post_type ) {
3060
		if ( 'publish' == $post_status ) {
3061
			$now = gmdate('Y-m-d H:i:59');
3062 View Code Duplication
			if ( mysql2date('U', $post_date_gmt, false) > mysql2date('U', $now, false) ) {
3063
				$post_status = 'future';
3064
			}
3065
		} elseif ( 'future' == $post_status ) {
3066
			$now = gmdate('Y-m-d H:i:59');
3067 View Code Duplication
			if ( mysql2date('U', $post_date_gmt, false) <= mysql2date('U', $now, false) ) {
3068
				$post_status = 'publish';
3069
			}
3070
		}
3071
	}
3072
3073
	// Comment status.
3074
	if ( empty( $postarr['comment_status'] ) ) {
3075
		if ( $update ) {
3076
			$comment_status = 'closed';
3077
		} else {
3078
			$comment_status = get_default_comment_status( $post_type );
3079
		}
3080
	} else {
3081
		$comment_status = $postarr['comment_status'];
3082
	}
3083
3084
	// These variables are needed by compact() later.
3085
	$post_content_filtered = $postarr['post_content_filtered'];
3086
	$post_author = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
3087
	$ping_status = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
3088
	$to_ping = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
3089
	$pinged = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
3090
	$import_id = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
3091
3092
	/*
3093
	 * The 'wp_insert_post_parent' filter expects all variables to be present.
3094
	 * Previously, these variables would have already been extracted
3095
	 */
3096
	if ( isset( $postarr['menu_order'] ) ) {
3097
		$menu_order = (int) $postarr['menu_order'];
3098
	} else {
3099
		$menu_order = 0;
3100
	}
3101
3102
	$post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
3103
	if ( 'private' == $post_status ) {
3104
		$post_password = '';
3105
	}
3106
3107
	if ( isset( $postarr['post_parent'] ) ) {
3108
		$post_parent = (int) $postarr['post_parent'];
3109
	} else {
3110
		$post_parent = 0;
3111
	}
3112
3113
	/**
3114
	 * Filters the post parent -- used to check for and prevent hierarchy loops.
3115
	 *
3116
	 * @since 3.1.0
3117
	 *
3118
	 * @param int   $post_parent Post parent ID.
3119
	 * @param int   $post_ID     Post ID.
3120
	 * @param array $new_postarr Array of parsed post data.
3121
	 * @param array $postarr     Array of sanitized, but otherwise unmodified post data.
3122
	 */
3123
	$post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, compact( array_keys( $postarr ) ), $postarr );
3124
3125
	/*
3126
	 * If the post is being untrashed and it has a desired slug stored in post meta,
3127
	 * reassign it.
3128
	 */
3129
	if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
3130
		$desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
3131
		if ( $desired_post_slug ) {
3132
			delete_post_meta( $post_ID, '_wp_desired_post_slug' );
3133
			$post_name = $desired_post_slug;
3134
		}
3135
	}
3136
3137
	// If a trashed post has the desired slug, change it and let this post have it.
3138
	if ( 'trash' !== $post_status && $post_name ) {
3139
		wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID );
3140
	}
3141
3142
	// When trashing an existing post, change its slug to allow non-trashed posts to use it.
3143
	if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
3144
		$post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
3145
	}
3146
3147
	$post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
3148
3149
	// Don't unslash.
3150
	$post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
3151
3152
	// Expected_slashed (everything!).
3153
	$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' );
3154
3155
	$emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
3156
3157
	foreach ( $emoji_fields as $emoji_field ) {
3158
		if ( isset( $data[ $emoji_field ] ) ) {
3159
			$charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
3160
			if ( 'utf8' === $charset ) {
3161
				$data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
3162
			}
3163
		}
3164
	}
3165
3166
	if ( 'attachment' === $post_type ) {
3167
		/**
3168
		 * Filters attachment post data before it is updated in or added to the database.
3169
		 *
3170
		 * @since 3.9.0
3171
		 *
3172
		 * @param array $data    An array of sanitized attachment post data.
3173
		 * @param array $postarr An array of unsanitized attachment post data.
3174
		 */
3175
		$data = apply_filters( 'wp_insert_attachment_data', $data, $postarr );
3176
	} else {
3177
		/**
3178
		 * Filters slashed post data just before it is inserted into the database.
3179
		 *
3180
		 * @since 2.7.0
3181
		 *
3182
		 * @param array $data    An array of slashed post data.
3183
		 * @param array $postarr An array of sanitized, but otherwise unmodified post data.
3184
		 */
3185
		$data = apply_filters( 'wp_insert_post_data', $data, $postarr );
3186
	}
3187
	$data = wp_unslash( $data );
3188
	$where = array( 'ID' => $post_ID );
3189
3190
	if ( $update ) {
3191
		/**
3192
		 * Fires immediately before an existing post is updated in the database.
3193
		 *
3194
		 * @since 2.5.0
3195
		 *
3196
		 * @param int   $post_ID Post ID.
3197
		 * @param array $data    Array of unslashed post data.
3198
		 */
3199
		do_action( 'pre_post_update', $post_ID, $data );
3200
		if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
3201
			if ( $wp_error ) {
3202
				return new WP_Error('db_update_error', __('Could not update post in the database'), $wpdb->last_error);
3203
			} else {
3204
				return 0;
3205
			}
3206
		}
3207
	} else {
3208
		// If there is a suggested ID, use it if not already present.
3209
		if ( ! empty( $import_id ) ) {
3210
			$import_id = (int) $import_id;
3211
			if ( ! $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id) ) ) {
3212
				$data['ID'] = $import_id;
3213
			}
3214
		}
3215 View Code Duplication
		if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
3216
			if ( $wp_error ) {
3217
				return new WP_Error('db_insert_error', __('Could not insert post into the database'), $wpdb->last_error);
3218
			} else {
3219
				return 0;
3220
			}
3221
		}
3222
		$post_ID = (int) $wpdb->insert_id;
3223
3224
		// Use the newly generated $post_ID.
3225
		$where = array( 'ID' => $post_ID );
3226
	}
3227
3228
	if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {
3229
		$data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data['post_status'], $post_type, $post_parent );
3230
		$wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
3231
		clean_post_cache( $post_ID );
3232
	}
3233
3234
	if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
3235
		wp_set_post_categories( $post_ID, $post_category );
3236
	}
3237
3238
	if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
3239
		wp_set_post_tags( $post_ID, $postarr['tags_input'] );
3240
	}
3241
3242
	// New-style support for all custom taxonomies.
3243
	if ( ! empty( $postarr['tax_input'] ) ) {
3244
		foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
3245
			$taxonomy_obj = get_taxonomy($taxonomy);
3246
			if ( ! $taxonomy_obj ) {
3247
				/* translators: %s: taxonomy name */
3248
				_doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
3249
				continue;
3250
			}
3251
3252
			// array = hierarchical, string = non-hierarchical.
3253
			if ( is_array( $tags ) ) {
3254
				$tags = array_filter($tags);
3255
			}
3256
			if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
3257
				wp_set_post_terms( $post_ID, $tags, $taxonomy );
3258
			}
3259
		}
3260
	}
3261
3262
	if ( ! empty( $postarr['meta_input'] ) ) {
3263
		foreach ( $postarr['meta_input'] as $field => $value ) {
3264
			update_post_meta( $post_ID, $field, $value );
3265
		}
3266
	}
3267
3268
	$current_guid = get_post_field( 'guid', $post_ID );
3269
3270
	// Set GUID.
3271
	if ( ! $update && '' == $current_guid ) {
3272
		$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
3273
	}
3274
3275
	if ( 'attachment' === $postarr['post_type'] ) {
3276
		if ( ! empty( $postarr['file'] ) ) {
3277
			update_attached_file( $post_ID, $postarr['file'] );
3278
		}
3279
3280
		if ( ! empty( $postarr['context'] ) ) {
3281
			add_post_meta( $post_ID, '_wp_attachment_context', $postarr['context'], true );
3282
		}
3283
	}
3284
3285
	// Set or remove featured image.
3286
	if ( isset( $postarr['_thumbnail_id'] ) ) {
3287
		$thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
3288 View Code Duplication
		if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
3289
			if ( wp_attachment_is( 'audio', $post_ID ) ) {
3290
				$thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
3291
			} elseif ( wp_attachment_is( 'video', $post_ID ) ) {
3292
				$thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
3293
			}
3294
		}
3295
3296
		if ( $thumbnail_support ) {
3297
			$thumbnail_id = intval( $postarr['_thumbnail_id'] );
3298
			if ( -1 === $thumbnail_id ) {
3299
				delete_post_thumbnail( $post_ID );
3300
			} else {
3301
				set_post_thumbnail( $post_ID, $thumbnail_id );
3302
			}
3303
		}
3304
	}
3305
3306
	clean_post_cache( $post_ID );
3307
3308
	$post = get_post( $post_ID );
3309
3310
	if ( ! empty( $postarr['page_template'] ) && 'page' == $data['post_type'] ) {
3311
		$post->page_template = $postarr['page_template'];
3312
		$page_templates = wp_get_theme()->get_page_templates( $post );
0 ignored issues
show
It seems like $post defined by get_post($post_ID) on line 3308 can also be of type array; however, WP_Theme::get_page_templates() does only seem to accept object<WP_Post>|null, 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...
3313
		if ( 'default' != $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
3314
			if ( $wp_error ) {
3315
				return new WP_Error('invalid_page_template', __('The page template is invalid.'));
3316
			}
3317
			update_post_meta( $post_ID, '_wp_page_template', 'default' );
3318
		} else {
3319
			update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
3320
		}
3321
	}
3322
3323
	if ( 'attachment' !== $postarr['post_type'] ) {
3324
		wp_transition_post_status( $data['post_status'], $previous_status, $post );
0 ignored issues
show
It seems like $post defined by get_post($post_ID) on line 3308 can also be of type array or null; however, wp_transition_post_status() does only seem to accept object<WP_Post>, 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...
3325
	} else {
3326
		if ( $update ) {
3327
			/**
3328
			 * Fires once an existing attachment has been updated.
3329
			 *
3330
			 * @since 2.0.0
3331
			 *
3332
			 * @param int $post_ID Attachment ID.
3333
			 */
3334
			do_action( 'edit_attachment', $post_ID );
3335
			$post_after = get_post( $post_ID );
3336
3337
			/**
3338
			 * Fires once an existing attachment has been updated.
3339
			 *
3340
			 * @since 4.4.0
3341
			 *
3342
			 * @param int     $post_ID      Post ID.
3343
			 * @param WP_Post $post_after   Post object following the update.
3344
			 * @param WP_Post $post_before  Post object before the update.
3345
			 */
3346
			do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
3347
		} else {
3348
3349
			/**
3350
			 * Fires once an attachment has been added.
3351
			 *
3352
			 * @since 2.0.0
3353
			 *
3354
			 * @param int $post_ID Attachment ID.
3355
			 */
3356
			do_action( 'add_attachment', $post_ID );
3357
		}
3358
3359
		return $post_ID;
3360
	}
3361
3362
	if ( $update ) {
3363
		/**
3364
		 * Fires once an existing post has been updated.
3365
		 *
3366
		 * @since 1.2.0
3367
		 *
3368
		 * @param int     $post_ID Post ID.
3369
		 * @param WP_Post $post    Post object.
3370
		 */
3371
		do_action( 'edit_post', $post_ID, $post );
3372
		$post_after = get_post($post_ID);
3373
3374
		/**
3375
		 * Fires once an existing post has been updated.
3376
		 *
3377
		 * @since 3.0.0
3378
		 *
3379
		 * @param int     $post_ID      Post ID.
3380
		 * @param WP_Post $post_after   Post object following the update.
3381
		 * @param WP_Post $post_before  Post object before the update.
3382
		 */
3383
		do_action( 'post_updated', $post_ID, $post_after, $post_before);
3384
	}
3385
3386
	/**
3387
	 * Fires once a post has been saved.
3388
	 *
3389
	 * The dynamic portion of the hook name, `$post->post_type`, refers to
3390
	 * the post type slug.
3391
	 *
3392
	 * @since 3.7.0
3393
	 *
3394
	 * @param int     $post_ID Post ID.
3395
	 * @param WP_Post $post    Post object.
3396
	 * @param bool    $update  Whether this is an existing post being updated or not.
3397
	 */
3398
	do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );
3399
3400
	/**
3401
	 * Fires once a post has been saved.
3402
	 *
3403
	 * @since 1.5.0
3404
	 *
3405
	 * @param int     $post_ID Post ID.
3406
	 * @param WP_Post $post    Post object.
3407
	 * @param bool    $update  Whether this is an existing post being updated or not.
3408
	 */
3409
	do_action( 'save_post', $post_ID, $post, $update );
3410
3411
	/**
3412
	 * Fires once a post has been saved.
3413
	 *
3414
	 * @since 2.0.0
3415
	 *
3416
	 * @param int     $post_ID Post ID.
3417
	 * @param WP_Post $post    Post object.
3418
	 * @param bool    $update  Whether this is an existing post being updated or not.
3419
	 */
3420
	do_action( 'wp_insert_post', $post_ID, $post, $update );
3421
3422
	return $post_ID;
3423
}
3424
3425
/**
3426
 * Update a post with new post data.
3427
 *
3428
 * The date does not have to be set for drafts. You can set the date and it will
3429
 * not be overridden.
3430
 *
3431
 * @since 1.0.0
3432
 *
3433
 * @param array|object $postarr  Optional. Post data. Arrays are expected to be escaped,
3434
 *                               objects are not. Default array.
3435
 * @param bool         $wp_error Optional. Allow return of WP_Error on failure. Default false.
3436
 * @return int|WP_Error The value 0 or WP_Error on failure. The post ID on success.
3437
 */
3438
function wp_update_post( $postarr = array(), $wp_error = false ) {
3439
	if ( is_object($postarr) ) {
3440
		// Non-escaped post was passed.
3441
		$postarr = get_object_vars($postarr);
3442
		$postarr = wp_slash($postarr);
3443
	}
3444
3445
	// First, get all of the original fields.
3446
	$post = get_post($postarr['ID'], ARRAY_A);
3447
3448
	if ( is_null( $post ) ) {
3449
		if ( $wp_error )
3450
			return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3451
		return 0;
3452
	}
3453
3454
	// Escape data pulled from DB.
3455
	$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...
3456
3457
	// Passed post category list overwrites existing category list if not empty.
3458
	if ( isset($postarr['post_category']) && is_array($postarr['post_category'])
3459
			 && 0 != count($postarr['post_category']) )
3460
		$post_cats = $postarr['post_category'];
3461
	else
3462
		$post_cats = $post['post_category'];
3463
3464
	// Drafts shouldn't be assigned a date unless explicitly done so by the user.
3465
	if ( isset( $post['post_status'] ) && in_array($post['post_status'], array('draft', 'pending', 'auto-draft')) && empty($postarr['edit_date']) &&
3466
			 ('0000-00-00 00:00:00' == $post['post_date_gmt']) )
3467
		$clear_date = true;
3468
	else
3469
		$clear_date = false;
3470
3471
	// Merge old and new fields with new fields overwriting old ones.
3472
	$postarr = array_merge($post, $postarr);
3473
	$postarr['post_category'] = $post_cats;
3474
	if ( $clear_date ) {
3475
		$postarr['post_date'] = current_time('mysql');
3476
		$postarr['post_date_gmt'] = '';
3477
	}
3478
3479
	if ($postarr['post_type'] == 'attachment')
3480
		return wp_insert_attachment($postarr);
3481
3482
	return wp_insert_post( $postarr, $wp_error );
3483
}
3484
3485
/**
3486
 * Publish a post by transitioning the post status.
3487
 *
3488
 * @since 2.1.0
3489
 *
3490
 * @global wpdb $wpdb WordPress database abstraction object.
3491
 *
3492
 * @param int|WP_Post $post Post ID or post object.
3493
 */
3494
function wp_publish_post( $post ) {
3495
	global $wpdb;
3496
3497
	if ( ! $post = get_post( $post ) )
3498
		return;
3499
3500
	if ( 'publish' == $post->post_status )
3501
		return;
3502
3503
	$wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
3504
3505
	clean_post_cache( $post->ID );
3506
3507
	$old_status = $post->post_status;
3508
	$post->post_status = 'publish';
3509
	wp_transition_post_status( 'publish', $old_status, $post );
0 ignored issues
show
It seems like $post defined by get_post($post) on line 3497 can also be of type array; however, wp_transition_post_status() does only seem to accept object<WP_Post>, 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...
3510
3511
	/** This action is documented in wp-includes/post.php */
3512
	do_action( 'edit_post', $post->ID, $post );
3513
3514
	/** This action is documented in wp-includes/post.php */
3515
	do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
3516
3517
	/** This action is documented in wp-includes/post.php */
3518
	do_action( 'save_post', $post->ID, $post, true );
3519
3520
	/** This action is documented in wp-includes/post.php */
3521
	do_action( 'wp_insert_post', $post->ID, $post, true );
3522
}
3523
3524
/**
3525
 * Publish future post and make sure post ID has future post status.
3526
 *
3527
 * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
3528
 * from publishing drafts, etc.
3529
 *
3530
 * @since 2.5.0
3531
 *
3532
 * @param int|WP_Post $post_id Post ID or post object.
3533
 */
3534
function check_and_publish_future_post( $post_id ) {
3535
	$post = get_post($post_id);
3536
3537
	if ( empty($post) )
3538
		return;
3539
3540
	if ( 'future' != $post->post_status )
3541
		return;
3542
3543
	$time = strtotime( $post->post_date_gmt . ' GMT' );
3544
3545
	// Uh oh, someone jumped the gun!
3546
	if ( $time > time() ) {
3547
		wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // clear anything else in the system
3548
		wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
3549
		return;
3550
	}
3551
3552
	// wp_publish_post() returns no meaningful value.
3553
	wp_publish_post( $post_id );
3554
}
3555
3556
/**
3557
 * Computes a unique slug for the post, when given the desired slug and some post details.
3558
 *
3559
 * @since 2.8.0
3560
 *
3561
 * @global wpdb       $wpdb WordPress database abstraction object.
3562
 * @global WP_Rewrite $wp_rewrite
3563
 *
3564
 * @param string $slug        The desired slug (post_name).
3565
 * @param int    $post_ID     Post ID.
3566
 * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
3567
 * @param string $post_type   Post type.
3568
 * @param int    $post_parent Post parent ID.
3569
 * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
3570
 */
3571
function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
3572
	if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) || ( 'inherit' == $post_status && 'revision' == $post_type ) )
3573
		return $slug;
3574
3575
	global $wpdb, $wp_rewrite;
3576
3577
	$original_slug = $slug;
3578
3579
	$feeds = $wp_rewrite->feeds;
3580
	if ( ! is_array( $feeds ) )
3581
		$feeds = array();
3582
3583
	if ( 'attachment' == $post_type ) {
3584
		// Attachment slugs must be unique across all types.
3585
		$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
3586
		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
3587
3588
		/**
3589
		 * Filters whether the post slug would make a bad attachment slug.
3590
		 *
3591
		 * @since 3.1.0
3592
		 *
3593
		 * @param bool   $bad_slug Whether the slug would be bad as an attachment slug.
3594
		 * @param string $slug     The post slug.
3595
		 */
3596
		if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
3597
			$suffix = 2;
3598 View Code Duplication
			do {
3599
				$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
3600
				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID ) );
3601
				$suffix++;
3602
			} while ( $post_name_check );
3603
			$slug = $alt_post_name;
3604
		}
3605
	} elseif ( is_post_type_hierarchical( $post_type ) ) {
3606
		if ( 'nav_menu_item' == $post_type )
3607
			return $slug;
3608
3609
		/*
3610
		 * Page slugs must be unique within their own trees. Pages are in a separate
3611
		 * namespace than posts so page slugs are allowed to overlap post slugs.
3612
		 */
3613
		$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";
3614
		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
3615
3616
		/**
3617
		 * Filters whether the post slug would make a bad hierarchical post slug.
3618
		 *
3619
		 * @since 3.1.0
3620
		 *
3621
		 * @param bool   $bad_slug    Whether the post slug would be bad in a hierarchical post context.
3622
		 * @param string $slug        The post slug.
3623
		 * @param string $post_type   Post type.
3624
		 * @param int    $post_parent Post parent ID.
3625
		 */
3626
		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 ) ) {
3627
			$suffix = 2;
3628 View Code Duplication
			do {
3629
				$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
3630
				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
3631
				$suffix++;
3632
			} while ( $post_name_check );
3633
			$slug = $alt_post_name;
3634
		}
3635
	} else {
3636
		// Post slugs must be unique across all posts.
3637
		$check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
3638
		$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
3639
3640
		// Prevent new post slugs that could result in URLs that conflict with date archives.
3641
		$post = get_post( $post_ID );
3642
		$conflicts_with_date_archive = false;
3643
		if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) && $slug_num = intval( $slug ) ) {
3644
			$permastructs   = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
3645
			$postname_index = array_search( '%postname%', $permastructs );
3646
3647
			/*
3648
			 * Potential date clashes are as follows:
3649
			 *
3650
			 * - Any integer in the first permastruct position could be a year.
3651
			 * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
3652
			 * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
3653
			 */
3654
			if ( 0 === $postname_index ||
3655
				( $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...
3656
				( $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...
3657
			) {
3658
				$conflicts_with_date_archive = true;
3659
			}
3660
		}
3661
3662
		/**
3663
		 * Filters whether the post slug would be bad as a flat slug.
3664
		 *
3665
		 * @since 3.1.0
3666
		 *
3667
		 * @param bool   $bad_slug  Whether the post slug would be bad as a flat slug.
3668
		 * @param string $slug      The post slug.
3669
		 * @param string $post_type Post type.
3670
		 */
3671
		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 ) ) {
3672
			$suffix = 2;
3673 View Code Duplication
			do {
3674
				$alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
3675
				$post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
3676
				$suffix++;
3677
			} while ( $post_name_check );
3678
			$slug = $alt_post_name;
3679
		}
3680
	}
3681
3682
	/**
3683
	 * Filters the unique post slug.
3684
	 *
3685
	 * @since 3.3.0
3686
	 *
3687
	 * @param string $slug          The post slug.
3688
	 * @param int    $post_ID       Post ID.
3689
	 * @param string $post_status   The post status.
3690
	 * @param string $post_type     Post type.
3691
	 * @param int    $post_parent   Post parent ID
3692
	 * @param string $original_slug The original post slug.
3693
	 */
3694
	return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
3695
}
3696
3697
/**
3698
 * Truncate a post slug.
3699
 *
3700
 * @since 3.6.0
3701
 * @access private
3702
 *
3703
 * @see utf8_uri_encode()
3704
 *
3705
 * @param string $slug   The slug to truncate.
3706
 * @param int    $length Optional. Max length of the slug. Default 200 (characters).
3707
 * @return string The truncated slug.
3708
 */
3709
function _truncate_post_slug( $slug, $length = 200 ) {
3710
	if ( strlen( $slug ) > $length ) {
3711
		$decoded_slug = urldecode( $slug );
3712
		if ( $decoded_slug === $slug )
3713
			$slug = substr( $slug, 0, $length );
3714
		else
3715
			$slug = utf8_uri_encode( $decoded_slug, $length );
3716
	}
3717
3718
	return rtrim( $slug, '-' );
3719
}
3720
3721
/**
3722
 * Add tags to a post.
3723
 *
3724
 * @see wp_set_post_tags()
3725
 *
3726
 * @since 2.3.0
3727
 *
3728
 * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
3729
 * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
3730
 *                              separated by commas. Default empty.
3731
 * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
3732
 */
3733
function wp_add_post_tags( $post_id = 0, $tags = '' ) {
3734
	return wp_set_post_tags($post_id, $tags, true);
3735
}
3736
3737
/**
3738
 * Set the tags for a post.
3739
 *
3740
 * @since 2.3.0
3741
 *
3742
 * @see wp_set_object_terms()
3743
 *
3744
 * @param int          $post_id Optional. The Post ID. Does not default to the ID of the global $post.
3745
 * @param string|array $tags    Optional. An array of tags to set for the post, or a string of tags
3746
 *                              separated by commas. Default empty.
3747
 * @param bool         $append  Optional. If true, don't delete existing tags, just add on. If false,
3748
 *                              replace the tags with the new tags. Default false.
3749
 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
3750
 */
3751
function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
3752
	return wp_set_post_terms( $post_id, $tags, 'post_tag', $append);
3753
}
3754
3755
/**
3756
 * Set the terms for a post.
3757
 *
3758
 * @since 2.8.0
3759
 *
3760
 * @see wp_set_object_terms()
3761
 *
3762
 * @param int          $post_id  Optional. The Post ID. Does not default to the ID of the global $post.
3763
 * @param string|array $tags     Optional. An array of terms to set for the post, or a string of terms
3764
 *                               separated by commas. Default empty.
3765
 * @param string       $taxonomy Optional. Taxonomy name. Default 'post_tag'.
3766
 * @param bool         $append   Optional. If true, don't delete existing terms, just add on. If false,
3767
 *                               replace the terms with the new terms. Default false.
3768
 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
3769
 */
3770
function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
3771
	$post_id = (int) $post_id;
3772
3773
	if ( !$post_id )
3774
		return false;
3775
3776
	if ( empty($tags) )
3777
		$tags = array();
3778
3779 View Code Duplication
	if ( ! is_array( $tags ) ) {
3780
		$comma = _x( ',', 'tag delimiter' );
3781
		if ( ',' !== $comma )
3782
			$tags = str_replace( $comma, ',', $tags );
3783
		$tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
3784
	}
3785
3786
	/*
3787
	 * Hierarchical taxonomies must always pass IDs rather than names so that
3788
	 * children with the same names but different parents aren't confused.
3789
	 */
3790
	if ( is_taxonomy_hierarchical( $taxonomy ) ) {
3791
		$tags = array_unique( array_map( 'intval', $tags ) );
3792
	}
3793
3794
	return wp_set_object_terms( $post_id, $tags, $taxonomy, $append );
3795
}
3796
3797
/**
3798
 * Set categories for a post.
3799
 *
3800
 * If the post categories parameter is not set, then the default category is
3801
 * going used.
3802
 *
3803
 * @since 2.1.0
3804
 *
3805
 * @param int       $post_ID         Optional. The Post ID. Does not default to the ID
3806
 *                                   of the global $post. Default 0.
3807
 * @param array|int $post_categories Optional. List of categories or ID of category.
3808
 *                                   Default empty array.
3809
 * @param bool      $append         If true, don't delete existing categories, just add on.
3810
 *                                  If false, replace the categories with the new categories.
3811
 * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
3812
 */
3813
function wp_set_post_categories( $post_ID = 0, $post_categories = array(), $append = false ) {
3814
	$post_ID = (int) $post_ID;
3815
	$post_type = get_post_type( $post_ID );
3816
	$post_status = get_post_status( $post_ID );
3817
	// If $post_categories isn't already an array, make it one:
3818
	$post_categories = (array) $post_categories;
3819
	if ( empty( $post_categories ) ) {
3820
		if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
3821
			$post_categories = array( get_option('default_category') );
3822
			$append = false;
3823
		} else {
3824
			$post_categories = array();
3825
		}
3826
	} elseif ( 1 == count( $post_categories ) && '' == reset( $post_categories ) ) {
3827
		return true;
3828
	}
3829
3830
	return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
3831
}
3832
3833
/**
3834
 * Fires actions related to the transitioning of a post's status.
3835
 *
3836
 * When a post is saved, the post status is "transitioned" from one status to another,
3837
 * though this does not always mean the status has actually changed before and after
3838
 * the save. This function fires a number of action hooks related to that transition:
3839
 * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
3840
 * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
3841
 * that the function does not transition the post object in the database.
3842
 *
3843
 * For instance: When publishing a post for the first time, the post status may transition
3844
 * from 'draft' – or some other status – to 'publish'. However, if a post is already
3845
 * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
3846
 * before and after the transition.
3847
 *
3848
 * @since 2.3.0
3849
 *
3850
 * @param string  $new_status Transition to this post status.
3851
 * @param string  $old_status Previous post status.
3852
 * @param WP_Post $post Post data.
3853
 */
3854
function wp_transition_post_status( $new_status, $old_status, $post ) {
3855
	/**
3856
	 * Fires when a post is transitioned from one status to another.
3857
	 *
3858
	 * @since 2.3.0
3859
	 *
3860
	 * @param string  $new_status New post status.
3861
	 * @param string  $old_status Old post status.
3862
	 * @param WP_Post $post       Post object.
3863
	 */
3864
	do_action( 'transition_post_status', $new_status, $old_status, $post );
3865
3866
	/**
3867
	 * Fires when a post is transitioned from one status to another.
3868
	 *
3869
	 * The dynamic portions of the hook name, `$new_status` and `$old status`,
3870
	 * refer to the old and new post statuses, respectively.
3871
	 *
3872
	 * @since 2.3.0
3873
	 *
3874
	 * @param WP_Post $post Post object.
3875
	 */
3876
	do_action( "{$old_status}_to_{$new_status}", $post );
3877
3878
	/**
3879
	 * Fires when a post is transitioned from one status to another.
3880
	 *
3881
	 * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
3882
	 * refer to the new post status and post type, respectively.
3883
	 *
3884
	 * Please note: When this action is hooked using a particular post status (like
3885
	 * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
3886
	 * first transitioned to that status from something else, as well as upon
3887
	 * subsequent post updates (old and new status are both the same).
3888
	 *
3889
	 * Therefore, if you are looking to only fire a callback when a post is first
3890
	 * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
3891
	 *
3892
	 * @since 2.3.0
3893
	 *
3894
	 * @param int     $post_id Post ID.
3895
	 * @param WP_Post $post    Post object.
3896
	 */
3897
	do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
3898
}
3899
3900
//
3901
// Comment, trackback, and pingback functions.
3902
//
3903
3904
/**
3905
 * Add a URL to those already pinged.
3906
 *
3907
 * @since 1.5.0
3908
 *
3909
 * @global wpdb $wpdb WordPress database abstraction object.
3910
 *
3911
 * @param int    $post_id Post ID.
3912
 * @param string $uri     Ping URI.
3913
 * @return int|false How many rows were updated.
3914
 */
3915
function add_ping( $post_id, $uri ) {
3916
	global $wpdb;
3917
	$pung = $wpdb->get_var( $wpdb->prepare( "SELECT pinged FROM $wpdb->posts WHERE ID = %d", $post_id ));
3918
	$pung = trim($pung);
3919
	$pung = preg_split('/\s/', $pung);
3920
	$pung[] = $uri;
3921
	$new = implode("\n", $pung);
3922
3923
	/**
3924
	 * Filters the new ping URL to add for the given post.
3925
	 *
3926
	 * @since 2.0.0
3927
	 *
3928
	 * @param string $new New ping URL to add.
3929
	 */
3930
	$new = apply_filters( 'add_ping', $new );
3931
3932
	// expected_slashed ($new).
3933
	$new = wp_unslash($new);
3934
	return $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post_id ) );
3935
}
3936
3937
/**
3938
 * Retrieve enclosures already enclosed for a post.
3939
 *
3940
 * @since 1.5.0
3941
 *
3942
 * @param int $post_id Post ID.
3943
 * @return array List of enclosures.
3944
 */
3945
function get_enclosed( $post_id ) {
3946
	$custom_fields = get_post_custom( $post_id );
3947
	$pung = array();
3948
	if ( !is_array( $custom_fields ) )
3949
		return $pung;
3950
3951
	foreach ( $custom_fields as $key => $val ) {
3952
		if ( 'enclosure' != $key || !is_array( $val ) )
3953
			continue;
3954
		foreach ( $val as $enc ) {
3955
			$enclosure = explode( "\n", $enc );
3956
			$pung[] = trim( $enclosure[ 0 ] );
3957
		}
3958
	}
3959
3960
	/**
3961
	 * Filters the list of enclosures already enclosed for the given post.
3962
	 *
3963
	 * @since 2.0.0
3964
	 *
3965
	 * @param array $pung    Array of enclosures for the given post.
3966
	 * @param int   $post_id Post ID.
3967
	 */
3968
	return apply_filters( 'get_enclosed', $pung, $post_id );
3969
}
3970
3971
/**
3972
 * Retrieve URLs already pinged for a post.
3973
 *
3974
 * @since 1.5.0
3975
 *
3976
 * @global wpdb $wpdb WordPress database abstraction object.
3977
 *
3978
 * @param int $post_id Post ID.
3979
 * @return array
3980
 */
3981 View Code Duplication
function get_pung( $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...
3982
	global $wpdb;
3983
	$pung = $wpdb->get_var( $wpdb->prepare( "SELECT pinged FROM $wpdb->posts WHERE ID = %d", $post_id ));
3984
	$pung = trim($pung);
3985
	$pung = preg_split('/\s/', $pung);
3986
3987
	/**
3988
	 * Filters the list of already-pinged URLs for the given post.
3989
	 *
3990
	 * @since 2.0.0
3991
	 *
3992
	 * @param array $pung Array of URLs already pinged for the given post.
3993
	 */
3994
	return apply_filters( 'get_pung', $pung );
3995
}
3996
3997
/**
3998
 * Retrieve URLs that need to be pinged.
3999
 *
4000
 * @since 1.5.0
4001
 *
4002
 * @global wpdb $wpdb WordPress database abstraction object.
4003
 *
4004
 * @param int $post_id Post ID
4005
 * @return array
4006
 */
4007 View Code Duplication
function get_to_ping( $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...
4008
	global $wpdb;
4009
	$to_ping = $wpdb->get_var( $wpdb->prepare( "SELECT to_ping FROM $wpdb->posts WHERE ID = %d", $post_id ));
4010
	$to_ping = sanitize_trackback_urls( $to_ping );
4011
	$to_ping = preg_split('/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY);
4012
4013
	/**
4014
	 * Filters the list of URLs yet to ping for the given post.
4015
	 *
4016
	 * @since 2.0.0
4017
	 *
4018
	 * @param array $to_ping List of URLs yet to ping.
4019
	 */
4020
	return apply_filters( 'get_to_ping', $to_ping );
4021
}
4022
4023
/**
4024
 * Do trackbacks for a list of URLs.
4025
 *
4026
 * @since 1.0.0
4027
 *
4028
 * @param string $tb_list Comma separated list of URLs.
4029
 * @param int    $post_id Post ID.
4030
 */
4031
function trackback_url_list( $tb_list, $post_id ) {
4032
	if ( ! empty( $tb_list ) ) {
4033
		// Get post data.
4034
		$postdata = get_post( $post_id, ARRAY_A );
4035
4036
		// Form an excerpt.
4037
		$excerpt = strip_tags( $postdata['post_excerpt'] ? $postdata['post_excerpt'] : $postdata['post_content'] );
4038
4039
		if ( strlen( $excerpt ) > 255 ) {
4040
			$excerpt = substr( $excerpt, 0, 252 ) . '&hellip;';
4041
		}
4042
4043
		$trackback_urls = explode( ',', $tb_list );
4044
		foreach ( (array) $trackback_urls as $tb_url ) {
4045
			$tb_url = trim( $tb_url );
4046
			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...
4047
		}
4048
	}
4049
}
4050
4051
//
4052
// Page functions
4053
//
4054
4055
/**
4056
 * Get a list of page IDs.
4057
 *
4058
 * @since 2.0.0
4059
 *
4060
 * @global wpdb $wpdb WordPress database abstraction object.
4061
 *
4062
 * @return array List of page IDs.
4063
 */
4064
function get_all_page_ids() {
4065
	global $wpdb;
4066
4067
	$page_ids = wp_cache_get('all_page_ids', 'posts');
4068
	if ( ! is_array( $page_ids ) ) {
4069
		$page_ids = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_type = 'page'");
4070
		wp_cache_add('all_page_ids', $page_ids, 'posts');
4071
	}
4072
4073
	return $page_ids;
4074
}
4075
4076
/**
4077
 * Retrieves page data given a page ID or page object.
4078
 *
4079
 * Use get_post() instead of get_page().
4080
 *
4081
 * @since 1.5.1
4082
 * @deprecated 3.5.0 Use get_post()
4083
 *
4084
 * @param mixed  $page   Page object or page ID. Passed by reference.
4085
 * @param string $output Optional. What to output. Accepts OBJECT, ARRAY_A, or ARRAY_N.
4086
 *                       Default OBJECT.
4087
 * @param string $filter Optional. How the return value should be filtered. Accepts 'raw',
4088
 *                       'edit', 'db', 'display'. Default 'raw'.
4089
 * @return WP_Post|array|null WP_Post on success or null on failure.
4090
 */
4091
function get_page( $page, $output = OBJECT, $filter = 'raw') {
4092
	return get_post( $page, $output, $filter );
4093
}
4094
4095
/**
4096
 * Retrieves a page given its path.
4097
 *
4098
 * @since 2.1.0
4099
 *
4100
 * @global wpdb $wpdb WordPress database abstraction object.
4101
 *
4102
 * @param string       $page_path Page path.
4103
 * @param string       $output    Optional. Output type. Accepts OBJECT, ARRAY_N, or ARRAY_A.
4104
 *                                Default OBJECT.
4105
 * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4106
 * @return WP_Post|array|void WP_Post on success.
4107
 */
4108
function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4109
	global $wpdb;
4110
4111
	$last_changed = wp_cache_get( 'last_changed', 'posts' );
4112
	if ( false === $last_changed ) {
4113
		$last_changed = microtime();
4114
		wp_cache_set( 'last_changed', $last_changed, 'posts' );
4115
	}
4116
4117
	$hash = md5( $page_path . serialize( $post_type ) );
4118
	$cache_key = "get_page_by_path:$hash:$last_changed";
4119
	$cached = wp_cache_get( $cache_key, 'posts' );
4120
	if ( false !== $cached ) {
4121
		// Special case: '0' is a bad `$page_path`.
4122
		if ( '0' === $cached || 0 === $cached ) {
4123
			return;
4124
		} else {
4125
			return get_post( $cached, $output );
4126
		}
4127
	}
4128
4129
	$page_path = rawurlencode(urldecode($page_path));
4130
	$page_path = str_replace('%2F', '/', $page_path);
4131
	$page_path = str_replace('%20', ' ', $page_path);
4132
	$parts = explode( '/', trim( $page_path, '/' ) );
4133
	$parts = esc_sql( $parts );
4134
	$parts = array_map( 'sanitize_title_for_query', $parts );
4135
4136
	$in_string = "'" . implode( "','", $parts ) . "'";
4137
4138
	if ( is_array( $post_type ) ) {
4139
		$post_types = $post_type;
4140
	} else {
4141
		$post_types = array( $post_type, 'attachment' );
4142
	}
4143
4144
	$post_types = esc_sql( $post_types );
4145
	$post_type_in_string = "'" . implode( "','", $post_types ) . "'";
4146
	$sql = "
4147
		SELECT ID, post_name, post_parent, post_type
4148
		FROM $wpdb->posts
4149
		WHERE post_name IN ($in_string)
4150
		AND post_type IN ($post_type_in_string)
4151
	";
4152
4153
	$pages = $wpdb->get_results( $sql, OBJECT_K );
4154
4155
	$revparts = array_reverse( $parts );
4156
4157
	$foundid = 0;
4158
	foreach ( (array) $pages as $page ) {
4159
		if ( $page->post_name == $revparts[0] ) {
4160
			$count = 0;
4161
			$p = $page;
4162
4163
			/*
4164
			 * Loop through the given path parts from right to left,
4165
			 * ensuring each matches the post ancestry.
4166
			 */
4167
			while ( $p->post_parent != 0 && isset( $pages[ $p->post_parent ] ) ) {
4168
				$count++;
4169
				$parent = $pages[ $p->post_parent ];
4170
				if ( ! isset( $revparts[ $count ] ) || $parent->post_name != $revparts[ $count ] )
4171
					break;
4172
				$p = $parent;
4173
			}
4174
4175
			if ( $p->post_parent == 0 && $count+1 == count( $revparts ) && $p->post_name == $revparts[ $count ] ) {
4176
				$foundid = $page->ID;
4177
				if ( $page->post_type == $post_type )
4178
					break;
4179
			}
4180
		}
4181
	}
4182
4183
	// We cache misses as well as hits.
4184
	wp_cache_set( $cache_key, $foundid, 'posts' );
4185
4186
	if ( $foundid ) {
4187
		return get_post( $foundid, $output );
4188
	}
4189
}
4190
4191
/**
4192
 * Retrieve a page given its title.
4193
 *
4194
 * @since 2.1.0
4195
 *
4196
 * @global wpdb $wpdb WordPress database abstraction object.
4197
 *
4198
 * @param string       $page_title Page title
4199
 * @param string       $output     Optional. Output type. OBJECT, ARRAY_N, or ARRAY_A.
4200
 *                                 Default OBJECT.
4201
 * @param string|array $post_type  Optional. Post type or array of post types. Default 'page'.
4202
 * @return WP_Post|array|void WP_Post on success or null on failure
4203
 */
4204
function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
4205
	global $wpdb;
4206
4207
	if ( is_array( $post_type ) ) {
4208
		$post_type = esc_sql( $post_type );
4209
		$post_type_in_string = "'" . implode( "','", $post_type ) . "'";
4210
		$sql = $wpdb->prepare( "
4211
			SELECT ID
4212
			FROM $wpdb->posts
4213
			WHERE post_title = %s
4214
			AND post_type IN ($post_type_in_string)
4215
		", $page_title );
4216
	} else {
4217
		$sql = $wpdb->prepare( "
4218
			SELECT ID
4219
			FROM $wpdb->posts
4220
			WHERE post_title = %s
4221
			AND post_type = %s
4222
		", $page_title, $post_type );
4223
	}
4224
4225
	$page = $wpdb->get_var( $sql );
4226
4227
	if ( $page ) {
4228
		return get_post( $page, $output );
4229
	}
4230
}
4231
4232
/**
4233
 * Identify descendants of a given page ID in a list of page objects.
4234
 *
4235
 * Descendants are identified from the `$pages` array passed to the function. No database queries are performed.
4236
 *
4237
 * @since 1.5.1
4238
 *
4239
 * @param int   $page_id Page ID.
4240
 * @param array $pages   List of page objects from which descendants should be identified.
4241
 * @return array List of page children.
4242
 */
4243
function get_page_children( $page_id, $pages ) {
4244
	// Build a hash of ID -> children.
4245
	$children = array();
4246
	foreach ( (array) $pages as $page ) {
4247
		$children[ intval( $page->post_parent ) ][] = $page;
4248
	}
4249
4250
	$page_list = array();
4251
4252
	// Start the search by looking at immediate children.
4253
	if ( isset( $children[ $page_id ] ) ) {
4254
		// Always start at the end of the stack in order to preserve original `$pages` order.
4255
		$to_look = array_reverse( $children[ $page_id ] );
4256
4257
		while ( $to_look ) {
4258
			$p = array_pop( $to_look );
4259
			$page_list[] = $p;
4260
			if ( isset( $children[ $p->ID ] ) ) {
4261
				foreach ( array_reverse( $children[ $p->ID ] ) as $child ) {
4262
					// Append to the `$to_look` stack to descend the tree.
4263
					$to_look[] = $child;
4264
				}
4265
			}
4266
		}
4267
	}
4268
4269
	return $page_list;
4270
}
4271
4272
/**
4273
 * Order the pages with children under parents in a flat list.
4274
 *
4275
 * It uses auxiliary structure to hold parent-children relationships and
4276
 * runs in O(N) complexity
4277
 *
4278
 * @since 2.0.0
4279
 *
4280
 * @param array $pages   Posts array, passed by reference.
4281
 * @param int   $page_id Optional. Parent page ID. Default 0.
4282
 * @return array A list arranged by hierarchy. Children immediately follow their parents.
4283
 */
4284
function get_page_hierarchy( &$pages, $page_id = 0 ) {
4285
	if ( empty( $pages ) ) {
4286
		return array();
4287
	}
4288
4289
	$children = array();
4290
	foreach ( (array) $pages as $p ) {
4291
		$parent_id = intval( $p->post_parent );
4292
		$children[ $parent_id ][] = $p;
4293
	}
4294
4295
	$result = array();
4296
	_page_traverse_name( $page_id, $children, $result );
4297
4298
	return $result;
4299
}
4300
4301
/**
4302
 * Traverse and return all the nested children post names of a root page.
4303
 *
4304
 * $children contains parent-children relations
4305
 *
4306
 * @since 2.9.0
4307
 *
4308
 * @see _page_traverse_name()
4309
 *
4310
 * @param int   $page_id   Page ID.
4311
 * @param array $children  Parent-children relations, passed by reference.
4312
 * @param array $result    Result, passed by reference.
4313
 */
4314
function _page_traverse_name( $page_id, &$children, &$result ){
4315
	if ( isset( $children[ $page_id ] ) ){
4316
		foreach ( (array)$children[ $page_id ] as $child ) {
4317
			$result[ $child->ID ] = $child->post_name;
4318
			_page_traverse_name( $child->ID, $children, $result );
4319
		}
4320
	}
4321
}
4322
4323
/**
4324
 * Build the URI path for a page.
4325
 *
4326
 * Sub pages will be in the "directory" under the parent page post name.
4327
 *
4328
 * @since 1.5.0
4329
 * @since 4.6.0 Converted the `$page` parameter to optional.
4330
 *
4331
 * @param WP_Post|object|int $page Optional. Page ID or WP_Post object. Default is global $post.
4332
 * @return string|false Page URI, false on error.
4333
 */
4334
function get_page_uri( $page = 0 ) {
4335
	if ( ! $page instanceof WP_Post ) {
4336
		$page = get_post( $page );
4337
	}
4338
4339
	if ( ! $page )
4340
		return false;
4341
4342
	$uri = $page->post_name;
4343
4344
	foreach ( $page->ancestors as $parent ) {
4345
		$parent = get_post( $parent );
4346
		if ( $parent && $parent->post_name ) {
4347
			$uri = $parent->post_name . '/' . $uri;
4348
		}
4349
	}
4350
4351
	/**
4352
	 * Filters the URI for a page.
4353
	 *
4354
	 * @since 4.4.0
4355
	 *
4356
	 * @param string  $uri  Page URI.
4357
	 * @param WP_Post $page Page object.
4358
	 */
4359
	return apply_filters( 'get_page_uri', $uri, $page );
4360
}
4361
4362
/**
4363
 * Retrieve a list of pages.
4364
 *
4365
 * @global wpdb $wpdb WordPress database abstraction object.
4366
 *
4367
 * @since 1.5.0
4368
 *
4369
 * @param array|string $args {
4370
 *     Optional. Array or string of arguments to retrieve pages.
4371
 *
4372
 *     @type int          $child_of     Page ID to return child and grandchild pages of. Note: The value
4373
 *                                      of `$hierarchical` has no bearing on whether `$child_of` returns
4374
 *                                      hierarchical results. Default 0, or no restriction.
4375
 *     @type string       $sort_order   How to sort retrieved pages. Accepts 'ASC', 'DESC'. Default 'ASC'.
4376
 *     @type string       $sort_column  What columns to sort pages by, comma-separated. Accepts 'post_author',
4377
 *                                      'post_date', 'post_title', 'post_name', 'post_modified', 'menu_order',
4378
 *                                      'post_modified_gmt', 'post_parent', 'ID', 'rand', 'comment_count'.
4379
 *                                      'post_' can be omitted for any values that start with it.
4380
 *                                      Default 'post_title'.
4381
 *     @type bool         $hierarchical Whether to return pages hierarchically. If false in conjunction with
4382
 *                                      `$child_of` also being false, both arguments will be disregarded.
4383
 *                                      Default true.
4384
 *     @type array        $exclude      Array of page IDs to exclude. Default empty array.
4385
 *     @type array        $include      Array of page IDs to include. Cannot be used with `$child_of`,
4386
 *                                      `$parent`, `$exclude`, `$meta_key`, `$meta_value`, or `$hierarchical`.
4387
 *                                      Default empty array.
4388
 *     @type string       $meta_key     Only include pages with this meta key. Default empty.
4389
 *     @type string       $meta_value   Only include pages with this meta value. Requires `$meta_key`.
4390
 *                                      Default empty.
4391
 *     @type string       $authors      A comma-separated list of author IDs. Default empty.
4392
 *     @type int          $parent       Page ID to return direct children of. Default -1, or no restriction.
4393
 *     @type string|array $exclude_tree Comma-separated string or array of page IDs to exclude.
4394
 *                                      Default empty array.
4395
 *     @type int          $number       The number of pages to return. Default 0, or all pages.
4396
 *     @type int          $offset       The number of pages to skip before returning. Requires `$number`.
4397
 *                                      Default 0.
4398
 *     @type string       $post_type    The post type to query. Default 'page'.
4399
 *     @type string       $post_status  A comma-separated list of post status types to include.
4400
 *                                      Default 'publish'.
4401
 * }
4402
 * @return array|false List of pages matching defaults or `$args`.
4403
 */
4404
function get_pages( $args = array() ) {
4405
	global $wpdb;
4406
4407
	$defaults = array(
4408
		'child_of' => 0, 'sort_order' => 'ASC',
4409
		'sort_column' => 'post_title', 'hierarchical' => 1,
4410
		'exclude' => array(), 'include' => array(),
4411
		'meta_key' => '', 'meta_value' => '',
4412
		'authors' => '', 'parent' => -1, 'exclude_tree' => array(),
4413
		'number' => '', 'offset' => 0,
4414
		'post_type' => 'page', 'post_status' => 'publish',
4415
	);
4416
4417
	$r = wp_parse_args( $args, $defaults );
4418
4419
	$number = (int) $r['number'];
4420
	$offset = (int) $r['offset'];
4421
	$child_of = (int) $r['child_of'];
4422
	$hierarchical = $r['hierarchical'];
4423
	$exclude = $r['exclude'];
4424
	$meta_key = $r['meta_key'];
4425
	$meta_value = $r['meta_value'];
4426
	$parent = $r['parent'];
4427
	$post_status = $r['post_status'];
4428
4429
	// Make sure the post type is hierarchical.
4430
	$hierarchical_post_types = get_post_types( array( 'hierarchical' => true ) );
4431
	if ( ! in_array( $r['post_type'], $hierarchical_post_types ) ) {
4432
		return false;
4433
	}
4434
4435
	if ( $parent > 0 && ! $child_of ) {
4436
		$hierarchical = false;
4437
	}
4438
4439
	// Make sure we have a valid post status.
4440
	if ( ! is_array( $post_status ) ) {
4441
		$post_status = explode( ',', $post_status );
4442
	}
4443
	if ( array_diff( $post_status, get_post_stati() ) ) {
4444
		return false;
4445
	}
4446
4447
	// $args can be whatever, only use the args defined in defaults to compute the key.
4448
	$key = md5( serialize( wp_array_slice_assoc( $r, array_keys( $defaults ) ) ) );
4449
	$last_changed = wp_cache_get( 'last_changed', 'posts' );
4450
	if ( ! $last_changed ) {
4451
		$last_changed = microtime();
4452
		wp_cache_set( 'last_changed', $last_changed, 'posts' );
4453
	}
4454
4455
	$cache_key = "get_pages:$key:$last_changed";
4456
	if ( $cache = wp_cache_get( $cache_key, 'posts' ) ) {
4457
		// Convert to WP_Post instances.
4458
		$pages = array_map( 'get_post', $cache );
4459
		/** This filter is documented in wp-includes/post.php */
4460
		$pages = apply_filters( 'get_pages', $pages, $r );
4461
		return $pages;
4462
	}
4463
4464
	$inclusions = '';
4465
	if ( ! empty( $r['include'] ) ) {
4466
		$child_of = 0; //ignore child_of, parent, exclude, meta_key, and meta_value params if using include
4467
		$parent = -1;
4468
		$exclude = '';
4469
		$meta_key = '';
4470
		$meta_value = '';
4471
		$hierarchical = false;
4472
		$incpages = wp_parse_id_list( $r['include'] );
4473
		if ( ! empty( $incpages ) ) {
4474
			$inclusions = ' AND ID IN (' . implode( ',', $incpages ) .  ')';
4475
		}
4476
	}
4477
4478
	$exclusions = '';
4479
	if ( ! empty( $exclude ) ) {
4480
		$expages = wp_parse_id_list( $exclude );
4481
		if ( ! empty( $expages ) ) {
4482
			$exclusions = ' AND ID NOT IN (' . implode( ',', $expages ) .  ')';
4483
		}
4484
	}
4485
4486
	$author_query = '';
4487
	if ( ! empty( $r['authors'] ) ) {
4488
		$post_authors = preg_split( '/[\s,]+/', $r['authors'] );
4489
4490
		if ( ! empty( $post_authors ) ) {
4491
			foreach ( $post_authors as $post_author ) {
4492
				//Do we have an author id or an author login?
4493
				if ( 0 == intval($post_author) ) {
4494
					$post_author = get_user_by('login', $post_author);
4495
					if ( empty( $post_author ) ) {
4496
						continue;
4497
					}
4498
					if ( empty( $post_author->ID ) ) {
4499
						continue;
4500
					}
4501
					$post_author = $post_author->ID;
4502
				}
4503
4504
				if ( '' == $author_query ) {
4505
					$author_query = $wpdb->prepare(' post_author = %d ', $post_author);
4506
				} else {
4507
					$author_query .= $wpdb->prepare(' OR post_author = %d ', $post_author);
4508
				}
4509
			}
4510
			if ( '' != $author_query ) {
4511
				$author_query = " AND ($author_query)";
4512
			}
4513
		}
4514
	}
4515
4516
	$join = '';
4517
	$where = "$exclusions $inclusions ";
4518
	if ( '' !== $meta_key || '' !== $meta_value ) {
4519
		$join = " LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )";
4520
4521
		// meta_key and meta_value might be slashed
4522
		$meta_key = wp_unslash($meta_key);
4523
		$meta_value = wp_unslash($meta_value);
4524
		if ( '' !== $meta_key ) {
4525
			$where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_key = %s", $meta_key);
4526
		}
4527
		if ( '' !== $meta_value ) {
4528
			$where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_value = %s", $meta_value);
4529
		}
4530
4531
	}
4532
4533
	if ( is_array( $parent ) ) {
4534
		$post_parent__in = implode( ',', array_map( 'absint', (array) $parent ) );
4535
		if ( ! empty( $post_parent__in ) ) {
4536
			$where .= " AND post_parent IN ($post_parent__in)";
4537
		}
4538
	} elseif ( $parent >= 0 ) {
4539
		$where .= $wpdb->prepare(' AND post_parent = %d ', $parent);
4540
	}
4541
4542
	if ( 1 == count( $post_status ) ) {
4543
		$where_post_type = $wpdb->prepare( "post_type = %s AND post_status = %s", $r['post_type'], reset( $post_status ) );
4544
	} else {
4545
		$post_status = implode( "', '", $post_status );
4546
		$where_post_type = $wpdb->prepare( "post_type = %s AND post_status IN ('$post_status')", $r['post_type'] );
4547
	}
4548
4549
	$orderby_array = array();
4550
	$allowed_keys = array( 'author', 'post_author', 'date', 'post_date', 'title', 'post_title', 'name', 'post_name', 'modified',
4551
		'post_modified', 'modified_gmt', 'post_modified_gmt', 'menu_order', 'parent', 'post_parent',
4552
		'ID', 'rand', 'comment_count' );
4553
4554
	foreach ( explode( ',', $r['sort_column'] ) as $orderby ) {
4555
		$orderby = trim( $orderby );
4556
		if ( ! in_array( $orderby, $allowed_keys ) ) {
4557
			continue;
4558
		}
4559
4560
		switch ( $orderby ) {
4561
			case 'menu_order':
4562
				break;
4563
			case 'ID':
4564
				$orderby = "$wpdb->posts.ID";
4565
				break;
4566
			case 'rand':
4567
				$orderby = 'RAND()';
4568
				break;
4569
			case 'comment_count':
4570
				$orderby = "$wpdb->posts.comment_count";
4571
				break;
4572
			default:
4573
				if ( 0 === strpos( $orderby, 'post_' ) ) {
4574
					$orderby = "$wpdb->posts." . $orderby;
4575
				} else {
4576
					$orderby = "$wpdb->posts.post_" . $orderby;
4577
				}
4578
		}
4579
4580
		$orderby_array[] = $orderby;
4581
4582
	}
4583
	$sort_column = ! empty( $orderby_array ) ? implode( ',', $orderby_array ) : "$wpdb->posts.post_title";
4584
4585
	$sort_order = strtoupper( $r['sort_order'] );
4586
	if ( '' !== $sort_order && ! in_array( $sort_order, array( 'ASC', 'DESC' ) ) ) {
4587
		$sort_order = 'ASC';
4588
	}
4589
4590
	$query = "SELECT * FROM $wpdb->posts $join WHERE ($where_post_type) $where ";
4591
	$query .= $author_query;
4592
	$query .= " ORDER BY " . $sort_column . " " . $sort_order ;
4593
4594
	if ( ! empty( $number ) ) {
4595
		$query .= ' LIMIT ' . $offset . ',' . $number;
4596
	}
4597
4598
	$pages = $wpdb->get_results($query);
4599
4600
	if ( empty($pages) ) {
4601
		/** This filter is documented in wp-includes/post.php */
4602
		$pages = apply_filters( 'get_pages', array(), $r );
4603
		return $pages;
4604
	}
4605
4606
	// Sanitize before caching so it'll only get done once.
4607
	$num_pages = count($pages);
4608
	for ($i = 0; $i < $num_pages; $i++) {
4609
		$pages[$i] = sanitize_post($pages[$i], 'raw');
4610
	}
4611
4612
	// Update cache.
4613
	update_post_cache( $pages );
4614
4615
	if ( $child_of || $hierarchical ) {
4616
		$pages = get_page_children($child_of, $pages);
4617
	}
4618
4619
	if ( ! empty( $r['exclude_tree'] ) ) {
4620
		$exclude = wp_parse_id_list( $r['exclude_tree'] );
4621
		foreach ( $exclude as $id ) {
4622
			$children = get_page_children( $id, $pages );
4623
			foreach ( $children as $child ) {
4624
				$exclude[] = $child->ID;
4625
			}
4626
		}
4627
4628
		$num_pages = count( $pages );
4629
		for ( $i = 0; $i < $num_pages; $i++ ) {
4630
			if ( in_array( $pages[$i]->ID, $exclude ) ) {
4631
				unset( $pages[$i] );
4632
			}
4633
		}
4634
	}
4635
4636
	$page_structure = array();
4637
	foreach ( $pages as $page ) {
4638
		$page_structure[] = $page->ID;
4639
	}
4640
4641
	wp_cache_set( $cache_key, $page_structure, 'posts' );
4642
4643
	// Convert to WP_Post instances
4644
	$pages = array_map( 'get_post', $pages );
4645
4646
	/**
4647
	 * Filters the retrieved list of pages.
4648
	 *
4649
	 * @since 2.1.0
4650
	 *
4651
	 * @param array $pages List of pages to retrieve.
4652
	 * @param array $r     Array of get_pages() arguments.
4653
	 */
4654
	return apply_filters( 'get_pages', $pages, $r );
4655
}
4656
4657
//
4658
// Attachment functions
4659
//
4660
4661
/**
4662
 * Check if the attachment URI is local one and is really an attachment.
4663
 *
4664
 * @since 2.0.0
4665
 *
4666
 * @param string $url URL to check
4667
 * @return bool True on success, false on failure.
4668
 */
4669
function is_local_attachment($url) {
4670
	if (strpos($url, home_url()) === false)
4671
		return false;
4672
	if (strpos($url, home_url('/?attachment_id=')) !== false)
4673
		return true;
4674
	if ( $id = url_to_postid($url) ) {
4675
		$post = get_post($id);
4676
		if ( 'attachment' == $post->post_type )
4677
			return true;
4678
	}
4679
	return false;
4680
}
4681
4682
/**
4683
 * Insert an attachment.
4684
 *
4685
 * If you set the 'ID' in the $args parameter, it will mean that you are
4686
 * updating and attempt to update the attachment. You can also set the
4687
 * attachment name or title by setting the key 'post_name' or 'post_title'.
4688
 *
4689
 * You can set the dates for the attachment manually by setting the 'post_date'
4690
 * and 'post_date_gmt' keys' values.
4691
 *
4692
 * By default, the comments will use the default settings for whether the
4693
 * comments are allowed. You can close them manually or keep them open by
4694
 * setting the value for the 'comment_status' key.
4695
 *
4696
 * @since 2.0.0
4697
 *
4698
 * @see wp_insert_post()
4699
 *
4700
 * @param string|array $args   Arguments for inserting an attachment.
4701
 * @param string       $file   Optional. Filename.
4702
 * @param int          $parent Optional. Parent post ID.
4703
 * @return int Attachment ID.
4704
 */
4705
function wp_insert_attachment( $args, $file = false, $parent = 0 ) {
4706
	$defaults = array(
4707
		'file'        => $file,
4708
		'post_parent' => 0
4709
	);
4710
4711
	$data = wp_parse_args( $args, $defaults );
4712
4713
	if ( ! empty( $parent ) ) {
4714
		$data['post_parent'] = $parent;
4715
	}
4716
4717
	$data['post_type'] = 'attachment';
4718
4719
	return wp_insert_post( $data );
4720
}
4721
4722
/**
4723
 * Trash or delete an attachment.
4724
 *
4725
 * When an attachment is permanently deleted, the file will also be removed.
4726
 * Deletion removes all post meta fields, taxonomy, comments, etc. associated
4727
 * with the attachment (except the main post).
4728
 *
4729
 * The attachment is moved to the trash instead of permanently deleted unless trash
4730
 * for media is disabled, item is already in the trash, or $force_delete is true.
4731
 *
4732
 * @since 2.0.0
4733
 *
4734
 * @global wpdb $wpdb WordPress database abstraction object.
4735
 *
4736
 * @param int  $post_id      Attachment ID.
4737
 * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
4738
 *                           Default false.
4739
 * @return mixed False on failure. Post data on success.
4740
 */
4741
function wp_delete_attachment( $post_id, $force_delete = false ) {
4742
	global $wpdb;
4743
4744
	if ( !$post = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id) ) )
4745
		return $post;
4746
4747
	if ( 'attachment' != $post->post_type )
4748
		return false;
4749
4750
	if ( !$force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' != $post->post_status )
4751
		return wp_trash_post( $post_id );
4752
4753
	delete_post_meta($post_id, '_wp_trash_meta_status');
4754
	delete_post_meta($post_id, '_wp_trash_meta_time');
4755
4756
	$meta = wp_get_attachment_metadata( $post_id );
4757
	$backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
4758
	$file = get_attached_file( $post_id );
4759
4760
	if ( is_multisite() )
4761
		delete_transient( 'dirsize_cache' );
4762
4763
	/**
4764
	 * Fires before an attachment is deleted, at the start of wp_delete_attachment().
4765
	 *
4766
	 * @since 2.0.0
4767
	 *
4768
	 * @param int $post_id Attachment ID.
4769
	 */
4770
	do_action( 'delete_attachment', $post_id );
4771
4772
	wp_delete_object_term_relationships($post_id, array('category', 'post_tag'));
4773
	wp_delete_object_term_relationships($post_id, get_object_taxonomies($post->post_type));
4774
4775
	// Delete all for any posts.
4776
	delete_metadata( 'post', null, '_thumbnail_id', $post_id, true );
4777
4778
	wp_defer_comment_counting( true );
4779
4780
	$comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ));
4781
	foreach ( $comment_ids as $comment_id ) {
4782
		wp_delete_comment( $comment_id, true );
4783
	}
4784
4785
	wp_defer_comment_counting( false );
4786
4787
	$post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ));
4788
	foreach ( $post_meta_ids as $mid )
4789
		delete_metadata_by_mid( 'post', $mid );
4790
4791
	/** This action is documented in wp-includes/post.php */
4792
	do_action( 'delete_post', $post_id );
4793
	$result = $wpdb->delete( $wpdb->posts, array( 'ID' => $post_id ) );
4794
	if ( ! $result ) {
4795
		return false;
4796
	}
4797
	/** This action is documented in wp-includes/post.php */
4798
	do_action( 'deleted_post', $post_id );
4799
4800
	$uploadpath = wp_get_upload_dir();
4801
4802
	if ( ! empty($meta['thumb']) ) {
4803
		// Don't delete the thumb if another attachment uses it.
4804
		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)) ) {
4805
			$thumbfile = str_replace(basename($file), $meta['thumb'], $file);
4806
			/** This filter is documented in wp-includes/functions.php */
4807
			$thumbfile = apply_filters( 'wp_delete_file', $thumbfile );
4808
			@ unlink( path_join($uploadpath['basedir'], $thumbfile) );
4809
		}
4810
	}
4811
4812
	// Remove intermediate and backup images if there are any.
4813
	if ( isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) {
4814
		foreach ( $meta['sizes'] as $size => $sizeinfo ) {
4815
			$intermediate_file = str_replace( basename( $file ), $sizeinfo['file'], $file );
4816
			/** This filter is documented in wp-includes/functions.php */
4817
			$intermediate_file = apply_filters( 'wp_delete_file', $intermediate_file );
4818
			@ unlink( path_join( $uploadpath['basedir'], $intermediate_file ) );
4819
		}
4820
	}
4821
4822
	if ( is_array($backup_sizes) ) {
4823
		foreach ( $backup_sizes as $size ) {
4824
			$del_file = path_join( dirname($meta['file']), $size['file'] );
4825
			/** This filter is documented in wp-includes/functions.php */
4826
			$del_file = apply_filters( 'wp_delete_file', $del_file );
4827
			@ unlink( path_join($uploadpath['basedir'], $del_file) );
4828
		}
4829
	}
4830
4831
	wp_delete_file( $file );
0 ignored issues
show
It seems like $file defined by get_attached_file($post_id) on line 4758 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...
4832
4833
	clean_post_cache( $post );
4834
4835
	return $post;
4836
}
4837
4838
/**
4839
 * Retrieve attachment meta field for attachment ID.
4840
 *
4841
 * @since 2.1.0
4842
 *
4843
 * @param int  $post_id    Attachment ID. Default 0.
4844
 * @param bool $unfiltered Optional. If true, filters are not run. Default false.
4845
 * @return mixed Attachment meta field. False on failure.
4846
 */
4847 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...
4848
	$post_id = (int) $post_id;
4849
	if ( !$post = get_post( $post_id ) )
4850
		return false;
4851
4852
	$data = get_post_meta( $post->ID, '_wp_attachment_metadata', true );
4853
4854
	if ( $unfiltered )
4855
		return $data;
4856
4857
	/**
4858
	 * Filters the attachment meta data.
4859
	 *
4860
	 * @since 2.1.0
4861
	 *
4862
	 * @param array|bool $data    Array of meta data for the given attachment, or false
4863
	 *                            if the object does not exist.
4864
	 * @param int        $post_id Attachment ID.
4865
	 */
4866
	return apply_filters( 'wp_get_attachment_metadata', $data, $post->ID );
4867
}
4868
4869
/**
4870
 * Update metadata for an attachment.
4871
 *
4872
 * @since 2.1.0
4873
 *
4874
 * @param int   $post_id Attachment ID.
4875
 * @param array $data    Attachment data.
4876
 * @return int|bool False if $post is invalid.
4877
 */
4878 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...
4879
	$post_id = (int) $post_id;
4880
	if ( !$post = get_post( $post_id ) )
4881
		return false;
4882
4883
	/**
4884
	 * Filters the updated attachment meta data.
4885
	 *
4886
	 * @since 2.1.0
4887
	 *
4888
	 * @param array $data    Array of updated attachment meta data.
4889
	 * @param int   $post_id Attachment ID.
4890
	 */
4891
	if ( $data = apply_filters( 'wp_update_attachment_metadata', $data, $post->ID ) )
4892
		return update_post_meta( $post->ID, '_wp_attachment_metadata', $data );
4893
	else
4894
		return delete_post_meta( $post->ID, '_wp_attachment_metadata' );
4895
}
4896
4897
/**
4898
 * Retrieve the URL for an attachment.
4899
 *
4900
 * @since 2.1.0
4901
 *
4902
 * @global string $pagenow
4903
 *
4904
 * @param int $post_id Optional. Attachment ID. Default 0.
4905
 * @return string|false Attachment URL, otherwise false.
4906
 */
4907
function wp_get_attachment_url( $post_id = 0 ) {
4908
	$post_id = (int) $post_id;
4909
	if ( !$post = get_post( $post_id ) )
4910
		return false;
4911
4912
	if ( 'attachment' != $post->post_type )
4913
		return false;
4914
4915
	$url = '';
4916
	// Get attached file.
4917
	if ( $file = get_post_meta( $post->ID, '_wp_attached_file', true ) ) {
4918
		// Get upload directory.
4919
		if ( ( $uploads = wp_get_upload_dir() ) && false === $uploads['error'] ) {
4920
			// Check that the upload base exists in the file location.
4921
			if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
4922
				// Replace file location with url location.
4923
				$url = str_replace($uploads['basedir'], $uploads['baseurl'], $file);
4924
			} elseif ( false !== strpos($file, 'wp-content/uploads') ) {
4925
				// Get the directory name relative to the basedir (back compat for pre-2.7 uploads)
4926
				$url = trailingslashit( $uploads['baseurl'] . '/' . _wp_get_attachment_relative_path( $file ) ) . basename( $file );
4927
			} else {
4928
				// It's a newly-uploaded file, therefore $file is relative to the basedir.
4929
				$url = $uploads['baseurl'] . "/$file";
4930
			}
4931
		}
4932
	}
4933
4934
	/*
4935
	 * If any of the above options failed, Fallback on the GUID as used pre-2.7,
4936
	 * not recommended to rely upon this.
4937
	 */
4938
	if ( empty($url) ) {
4939
		$url = get_the_guid( $post->ID );
4940
	}
4941
4942
	// On SSL front end, URLs should be HTTPS.
4943
	if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $GLOBALS['pagenow'] ) {
4944
		$url = set_url_scheme( $url );
4945
	}
4946
4947
	/**
4948
	 * Filters the attachment URL.
4949
	 *
4950
	 * @since 2.1.0
4951
	 *
4952
	 * @param string $url     URL for the given attachment.
4953
	 * @param int    $post_id Attachment ID.
4954
	 */
4955
	$url = apply_filters( 'wp_get_attachment_url', $url, $post->ID );
4956
4957
	if ( empty( $url ) )
4958
		return false;
4959
4960
	return $url;
4961
}
4962
4963
/**
4964
 * Retrieves the caption for an attachment.
4965
 *
4966
 * @since 4.6.0
4967
 *
4968
 * @param int $post_id Optional. Attachment ID. Default is the ID of the global `$post`.
4969
 * @return string|false False on failure. Attachment caption on success.
4970
 */
4971 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...
4972
	$post_id = (int) $post_id;
4973
	if ( ! $post = get_post( $post_id ) ) {
4974
		return false;
4975
	}
4976
4977
	if ( 'attachment' !== $post->post_type ) {
4978
		return false;
4979
	}
4980
4981
	$caption = $post->post_excerpt;
4982
4983
	/**
4984
	 * Filters the attachment caption.
4985
	 *
4986
	 * @since 4.6.0
4987
	 *
4988
	 * @param string $caption Caption for the given attachment.
4989
	 * @param int    $post_id Attachment ID.
4990
	 */
4991
	return apply_filters( 'wp_get_attachment_caption', $caption, $post->ID );
4992
}
4993
4994
/**
4995
 * Retrieve thumbnail for an attachment.
4996
 *
4997
 * @since 2.1.0
4998
 *
4999
 * @param int $post_id Optional. Attachment ID. Default 0.
5000
 * @return string|false False on failure. Thumbnail file path on success.
5001
 */
5002
function wp_get_attachment_thumb_file( $post_id = 0 ) {
5003
	$post_id = (int) $post_id;
5004
	if ( !$post = get_post( $post_id ) )
5005
		return false;
5006
	if ( !is_array( $imagedata = wp_get_attachment_metadata( $post->ID ) ) )
5007
		return false;
5008
5009
	$file = get_attached_file( $post->ID );
5010
5011
	if ( !empty($imagedata['thumb']) && ($thumbfile = str_replace(basename($file), $imagedata['thumb'], $file)) && file_exists($thumbfile) ) {
5012
		/**
5013
		 * Filters the attachment thumbnail file path.
5014
		 *
5015
		 * @since 2.1.0
5016
		 *
5017
		 * @param string $thumbfile File path to the attachment thumbnail.
5018
		 * @param int    $post_id   Attachment ID.
5019
		 */
5020
		return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
5021
	}
5022
	return false;
5023
}
5024
5025
/**
5026
 * Retrieve URL for an attachment thumbnail.
5027
 *
5028
 * @since 2.1.0
5029
 *
5030
 * @param int $post_id Optional. Attachment ID. Default 0.
5031
 * @return string|false False on failure. Thumbnail URL on success.
5032
 */
5033
function wp_get_attachment_thumb_url( $post_id = 0 ) {
5034
	$post_id = (int) $post_id;
5035
	if ( !$post = get_post( $post_id ) )
5036
		return false;
5037
	if ( !$url = wp_get_attachment_url( $post->ID ) )
5038
		return false;
5039
5040
	$sized = image_downsize( $post_id, 'thumbnail' );
5041
	if ( $sized )
5042
		return $sized[0];
5043
5044
	if ( !$thumb = wp_get_attachment_thumb_file( $post->ID ) )
5045
		return false;
5046
5047
	$url = str_replace(basename($url), basename($thumb), $url);
5048
5049
	/**
5050
	 * Filters the attachment thumbnail URL.
5051
	 *
5052
	 * @since 2.1.0
5053
	 *
5054
	 * @param string $url     URL for the attachment thumbnail.
5055
	 * @param int    $post_id Attachment ID.
5056
	 */
5057
	return apply_filters( 'wp_get_attachment_thumb_url', $url, $post->ID );
5058
}
5059
5060
/**
5061
 * Verifies an attachment is of a given type.
5062
 *
5063
 * @since 4.2.0
5064
 *
5065
 * @param string      $type Attachment type. Accepts 'image', 'audio', or 'video'.
5066
 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
5067
 * @return bool True if one of the accepted types, false otherwise.
5068
 */
5069
function wp_attachment_is( $type, $post = null ) {
5070
	if ( ! $post = get_post( $post ) ) {
5071
		return false;
5072
	}
5073
5074
	if ( ! $file = get_attached_file( $post->ID ) ) {
5075
		return false;
5076
	}
5077
5078
	if ( 0 === strpos( $post->post_mime_type, $type . '/' ) ) {
5079
		return true;
5080
	}
5081
5082
	$check = wp_check_filetype( $file );
5083
	if ( empty( $check['ext'] ) ) {
5084
		return false;
5085
	}
5086
5087
	$ext = $check['ext'];
5088
5089
	if ( 'import' !== $post->post_mime_type ) {
5090
		return $type === $ext;
5091
	}
5092
5093
	switch ( $type ) {
5094
	case 'image':
5095
		$image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png' );
5096
		return in_array( $ext, $image_exts );
5097
5098
	case 'audio':
5099
		return in_array( $ext, wp_get_audio_extensions() );
5100
5101
	case 'video':
5102
		return in_array( $ext, wp_get_video_extensions() );
5103
5104
	default:
5105
		return $type === $ext;
5106
	}
5107
}
5108
5109
/**
5110
 * Checks if the attachment is an image.
5111
 *
5112
 * @since 2.1.0
5113
 * @since 4.2.0 Modified into wrapper for wp_attachment_is() and
5114
 *              allowed WP_Post object to be passed.
5115
 *
5116
 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
5117
 * @return bool Whether the attachment is an image.
5118
 */
5119
function wp_attachment_is_image( $post = null ) {
5120
	return wp_attachment_is( 'image', $post );
5121
}
5122
5123
/**
5124
 * Retrieve the icon for a MIME type.
5125
 *
5126
 * @since 2.1.0
5127
 *
5128
 * @param string|int $mime MIME type or attachment ID.
5129
 * @return string|false Icon, false otherwise.
5130
 */
5131
function wp_mime_type_icon( $mime = 0 ) {
5132
	if ( !is_numeric($mime) )
5133
		$icon = wp_cache_get("mime_type_icon_$mime");
5134
5135
	$post_id = 0;
5136
	if ( empty($icon) ) {
5137
		$post_mimes = array();
5138
		if ( is_numeric($mime) ) {
5139
			$mime = (int) $mime;
5140
			if ( $post = get_post( $mime ) ) {
5141
				$post_id = (int) $post->ID;
5142
				$file = get_attached_file( $post_id );
5143
				$ext = preg_replace('/^.+?\.([^.]+)$/', '$1', $file);
5144
				if ( !empty($ext) ) {
5145
					$post_mimes[] = $ext;
5146
					if ( $ext_type = wp_ext2type( $ext ) )
5147
						$post_mimes[] = $ext_type;
5148
				}
5149
				$mime = $post->post_mime_type;
5150
			} else {
5151
				$mime = 0;
5152
			}
5153
		} else {
5154
			$post_mimes[] = $mime;
5155
		}
5156
5157
		$icon_files = wp_cache_get('icon_files');
5158
5159
		if ( !is_array($icon_files) ) {
5160
			/**
5161
			 * Filters the icon directory path.
5162
			 *
5163
			 * @since 2.0.0
5164
			 *
5165
			 * @param string $path Icon directory absolute path.
5166
			 */
5167
			$icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
5168
5169
			/**
5170
			 * Filters the icon directory URI.
5171
			 *
5172
			 * @since 2.0.0
5173
			 *
5174
			 * @param string $uri Icon directory URI.
5175
			 */
5176
			$icon_dir_uri = apply_filters( 'icon_dir_uri', includes_url( 'images/media' ) );
5177
5178
			/**
5179
			 * Filters the list of icon directory URIs.
5180
			 *
5181
			 * @since 2.5.0
5182
			 *
5183
			 * @param array $uris List of icon directory URIs.
5184
			 */
5185
			$dirs = apply_filters( 'icon_dirs', array( $icon_dir => $icon_dir_uri ) );
5186
			$icon_files = array();
5187
			while ( $dirs ) {
5188
				$keys = array_keys( $dirs );
5189
				$dir = array_shift( $keys );
5190
				$uri = array_shift($dirs);
5191
				if ( $dh = opendir($dir) ) {
5192
					while ( false !== $file = readdir($dh) ) {
5193
						$file = basename($file);
5194
						if ( substr($file, 0, 1) == '.' )
5195
							continue;
5196
						if ( !in_array(strtolower(substr($file, -4)), array('.png', '.gif', '.jpg') ) ) {
5197
							if ( is_dir("$dir/$file") )
5198
								$dirs["$dir/$file"] = "$uri/$file";
5199
							continue;
5200
						}
5201
						$icon_files["$dir/$file"] = "$uri/$file";
5202
					}
5203
					closedir($dh);
5204
				}
5205
			}
5206
			wp_cache_add( 'icon_files', $icon_files, 'default', 600 );
5207
		}
5208
5209
		$types = array();
5210
		// Icon basename - extension = MIME wildcard.
5211
		foreach ( $icon_files as $file => $uri )
5212
			$types[ preg_replace('/^([^.]*).*$/', '$1', basename($file)) ] =& $icon_files[$file];
5213
5214
		if ( ! empty($mime) ) {
5215
			$post_mimes[] = substr($mime, 0, strpos($mime, '/'));
5216
			$post_mimes[] = substr($mime, strpos($mime, '/') + 1);
5217
			$post_mimes[] = str_replace('/', '_', $mime);
5218
		}
5219
5220
		$matches = wp_match_mime_types(array_keys($types), $post_mimes);
5221
		$matches['default'] = array('default');
5222
5223
		foreach ( $matches as $match => $wilds ) {
5224
			foreach ( $wilds as $wild ) {
5225
				if ( ! isset( $types[ $wild ] ) ) {
5226
					continue;
5227
				}
5228
5229
				$icon = $types[ $wild ];
5230
				if ( ! is_numeric( $mime ) ) {
5231
					wp_cache_add( "mime_type_icon_$mime", $icon );
5232
				}
5233
				break 2;
5234
			}
5235
		}
5236
	}
5237
5238
	/**
5239
	 * Filters the mime type icon.
5240
	 *
5241
	 * @since 2.1.0
5242
	 *
5243
	 * @param string $icon    Path to the mime type icon.
5244
	 * @param string $mime    Mime type.
5245
	 * @param int    $post_id Attachment ID. Will equal 0 if the function passed
5246
	 *                        the mime type.
5247
	 */
5248
	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...
5249
}
5250
5251
/**
5252
 * Check for changed slugs for published post objects and save the old slug.
5253
 *
5254
 * The function is used when a post object of any type is updated,
5255
 * by comparing the current and previous post objects.
5256
 *
5257
 * If the slug was changed and not already part of the old slugs then it will be
5258
 * added to the post meta field ('_wp_old_slug') for storing old slugs for that
5259
 * post.
5260
 *
5261
 * The most logically usage of this function is redirecting changed post objects, so
5262
 * that those that linked to an changed post will be redirected to the new post.
5263
 *
5264
 * @since 2.1.0
5265
 *
5266
 * @param int     $post_id     Post ID.
5267
 * @param WP_Post $post        The Post Object
5268
 * @param WP_Post $post_before The Previous Post Object
5269
 */
5270
function wp_check_for_changed_slugs( $post_id, $post, $post_before ) {
5271
	// Don't bother if it hasn't changed.
5272
	if ( $post->post_name == $post_before->post_name ) {
5273
		return;
5274
	}
5275
5276
	// We're only concerned with published, non-hierarchical objects.
5277
	if ( ! ( 'publish' === $post->post_status || ( 'attachment' === get_post_type( $post ) && 'inherit' === $post->post_status ) ) || is_post_type_hierarchical( $post->post_type ) ) {
5278
		return;
5279
	}
5280
5281
	$old_slugs = (array) get_post_meta( $post_id, '_wp_old_slug' );
5282
5283
	// If we haven't added this old slug before, add it now.
5284
	if ( ! empty( $post_before->post_name ) && ! in_array( $post_before->post_name, $old_slugs ) ) {
5285
		add_post_meta( $post_id, '_wp_old_slug', $post_before->post_name );
5286
	}
5287
5288
	// If the new slug was used previously, delete it from the list.
5289
	if ( in_array( $post->post_name, $old_slugs ) ) {
5290
		delete_post_meta( $post_id, '_wp_old_slug', $post->post_name );
5291
	}
5292
}
5293
5294
/**
5295
 * Retrieve the private post SQL based on capability.
5296
 *
5297
 * This function provides a standardized way to appropriately select on the
5298
 * post_status of a post type. The function will return a piece of SQL code
5299
 * that can be added to a WHERE clause; this SQL is constructed to allow all
5300
 * published posts, and all private posts to which the user has access.
5301
 *
5302
 * @since 2.2.0
5303
 * @since 4.3.0 Added the ability to pass an array to `$post_type`.
5304
 *
5305
 * @param string|array $post_type Single post type or an array of post types. Currently only supports 'post' or 'page'.
5306
 * @return string SQL code that can be added to a where clause.
5307
 */
5308
function get_private_posts_cap_sql( $post_type ) {
5309
	return get_posts_by_author_sql( $post_type, false );
5310
}
5311
5312
/**
5313
 * Retrieve the post SQL based on capability, author, and type.
5314
 *
5315
 * @since 3.0.0
5316
 * @since 4.3.0 Introduced the ability to pass an array of post types to `$post_type`.
5317
 *
5318
 * @see get_private_posts_cap_sql()
5319
 * @global wpdb $wpdb WordPress database abstraction object.
5320
 *
5321
 * @param array|string   $post_type   Single post type or an array of post types.
5322
 * @param bool           $full        Optional. Returns a full WHERE statement instead of just
5323
 *                                    an 'andalso' term. Default true.
5324
 * @param int            $post_author Optional. Query posts having a single author ID. Default null.
5325
 * @param bool           $public_only Optional. Only return public posts. Skips cap checks for
5326
 *                                    $current_user.  Default false.
5327
 * @return string SQL WHERE code that can be added to a query.
5328
 */
5329
function get_posts_by_author_sql( $post_type, $full = true, $post_author = null, $public_only = false ) {
5330
	global $wpdb;
5331
5332
	if ( is_array( $post_type ) ) {
5333
		$post_types = $post_type;
5334
	} else {
5335
		$post_types = array( $post_type );
5336
	}
5337
5338
	$post_type_clauses = array();
5339
	foreach ( $post_types as $post_type ) {
5340
		$post_type_obj = get_post_type_object( $post_type );
5341
		if ( ! $post_type_obj ) {
5342
			continue;
5343
		}
5344
5345
		/**
5346
		 * Filters the capability to read private posts for a custom post type
5347
		 * when generating SQL for getting posts by author.
5348
		 *
5349
		 * @since 2.2.0
5350
		 * @deprecated 3.2.0 The hook transitioned from "somewhat useless" to "totally useless".
5351
		 *
5352
		 * @param string $cap Capability.
5353
		 */
5354
		if ( ! $cap = apply_filters( 'pub_priv_sql_capability', '' ) ) {
5355
			$cap = current_user_can( $post_type_obj->cap->read_private_posts );
5356
		}
5357
5358
		// Only need to check the cap if $public_only is false.
5359
		$post_status_sql = "post_status = 'publish'";
5360
		if ( false === $public_only ) {
5361
			if ( $cap ) {
5362
				// Does the user have the capability to view private posts? Guess so.
5363
				$post_status_sql .= " OR post_status = 'private'";
5364
			} elseif ( is_user_logged_in() ) {
5365
				// Users can view their own private posts.
5366
				$id = get_current_user_id();
5367
				if ( null === $post_author || ! $full ) {
5368
					$post_status_sql .= " OR post_status = 'private' AND post_author = $id";
5369
				} elseif ( $id == (int) $post_author ) {
5370
					$post_status_sql .= " OR post_status = 'private'";
5371
				} // else none
5372
			} // else none
5373
		}
5374
5375
		$post_type_clauses[] = "( post_type = '" . $post_type . "' AND ( $post_status_sql ) )";
5376
	}
5377
5378
	if ( empty( $post_type_clauses ) ) {
5379
		return $full ? 'WHERE 1 = 0' : '1 = 0';
5380
	}
5381
5382
	$sql = '( '. implode( ' OR ', $post_type_clauses ) . ' )';
5383
5384
	if ( null !== $post_author ) {
5385
		$sql .= $wpdb->prepare( ' AND post_author = %d', $post_author );
5386
	}
5387
5388
	if ( $full ) {
5389
		$sql = 'WHERE ' . $sql;
5390
	}
5391
5392
	return $sql;
5393
}
5394
5395
/**
5396
 * Retrieve the date that the last post was published.
5397
 *
5398
 * The server timezone is the default and is the difference between GMT and
5399
 * server time. The 'blog' value is the date when the last post was posted. The
5400
 * 'gmt' is when the last post was posted in GMT formatted date.
5401
 *
5402
 * @since 0.71
5403
 * @since 4.4.0 The `$post_type` argument was added.
5404
 *
5405
 * @param string $timezone  Optional. The timezone for the timestamp. Accepts 'server', 'blog', or 'gmt'.
5406
 *                          'server' uses the server's internal timezone.
5407
 *                          'blog' uses the `post_modified` field, which proxies to the timezone set for the site.
5408
 *                          'gmt' uses the `post_modified_gmt` field.
5409
 *                          Default 'server'.
5410
 * @param string $post_type Optional. The post type to check. Default 'any'.
5411
 * @return string The date of the last post.
5412
 */
5413
function get_lastpostdate( $timezone = 'server', $post_type = 'any' ) {
5414
	/**
5415
	 * Filters the date the last post was published.
5416
	 *
5417
	 * @since 2.3.0
5418
	 *
5419
	 * @param string $date     Date the last post was published.
5420
	 * @param string $timezone Location to use for getting the post published date.
5421
	 *                         See get_lastpostdate() for accepted `$timezone` values.
5422
	 */
5423
	return apply_filters( 'get_lastpostdate', _get_last_post_time( $timezone, 'date', $post_type ), $timezone );
5424
}
5425
5426
/**
5427
 * Get the timestamp of the last time any post was modified.
5428
 *
5429
 * The server timezone is the default and is the difference between GMT and
5430
 * server time. The 'blog' value is just when the last post was modified. The
5431
 * 'gmt' is when the last post was modified in GMT time.
5432
 *
5433
 * @since 1.2.0
5434
 * @since 4.4.0 The `$post_type` argument was added.
5435
 *
5436
 * @param string $timezone  Optional. The timezone for the timestamp. See get_lastpostdate()
5437
 *                          for information on accepted values.
5438
 *                          Default 'server'.
5439
 * @param string $post_type Optional. The post type to check. Default 'any'.
5440
 * @return string The timestamp.
5441
 */
5442
function get_lastpostmodified( $timezone = 'server', $post_type = 'any' ) {
5443
	/**
5444
	 * Pre-filter the return value of get_lastpostmodified() before the query is run.
5445
	 *
5446
	 * @since 4.4.0
5447
	 *
5448
	 * @param string $lastpostmodified Date the last post was modified.
5449
	 *                                 Returning anything other than false will short-circuit the function.
5450
	 * @param string $timezone         Location to use for getting the post modified date.
5451
	 *                                 See get_lastpostdate() for accepted `$timezone` values.
5452
	 * @param string $post_type        The post type to check.
5453
	 */
5454
	$lastpostmodified = apply_filters( 'pre_get_lastpostmodified', false, $timezone, $post_type );
5455
	if ( false !== $lastpostmodified ) {
5456
		return $lastpostmodified;
5457
	}
5458
5459
	$lastpostmodified = _get_last_post_time( $timezone, 'modified', $post_type );
5460
5461
	$lastpostdate = get_lastpostdate($timezone);
5462
	if ( $lastpostdate > $lastpostmodified ) {
5463
		$lastpostmodified = $lastpostdate;
5464
	}
5465
5466
	/**
5467
	 * Filters the date the last post was modified.
5468
	 *
5469
	 * @since 2.3.0
5470
	 *
5471
	 * @param string $lastpostmodified Date the last post was modified.
5472
	 * @param string $timezone         Location to use for getting the post modified date.
5473
	 *                                 See get_lastpostdate() for accepted `$timezone` values.
5474
	 */
5475
	return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone );
5476
}
5477
5478
/**
5479
 * Get the timestamp of the last time any post was modified or published.
5480
 *
5481
 * @since 3.1.0
5482
 * @since 4.4.0 The `$post_type` argument was added.
5483
 * @access private
5484
 *
5485
 * @global wpdb $wpdb WordPress database abstraction object.
5486
 *
5487
 * @param string $timezone  The timezone for the timestamp. See get_lastpostdate().
5488
 *                          for information on accepted values.
5489
 * @param string $field     Post field to check. Accepts 'date' or 'modified'.
5490
 * @param string $post_type Optional. The post type to check. Default 'any'.
5491
 * @return string|false The timestamp.
5492
 */
5493
function _get_last_post_time( $timezone, $field, $post_type = 'any' ) {
5494
	global $wpdb;
5495
5496
	if ( ! in_array( $field, array( 'date', 'modified' ) ) ) {
5497
		return false;
5498
	}
5499
5500
	$timezone = strtolower( $timezone );
5501
5502
	$key = "lastpost{$field}:$timezone";
5503
	if ( 'any' !== $post_type ) {
5504
		$key .= ':' . sanitize_key( $post_type );
5505
	}
5506
5507
	$date = wp_cache_get( $key, 'timeinfo' );
5508
5509
	if ( ! $date ) {
5510
		if ( 'any' === $post_type ) {
5511
			$post_types = get_post_types( array( 'public' => true ) );
5512
			array_walk( $post_types, array( $wpdb, 'escape_by_ref' ) );
5513
			$post_types = "'" . implode( "', '", $post_types ) . "'";
5514
		} else {
5515
			$post_types = "'" . sanitize_key( $post_type ) . "'";
5516
		}
5517
5518
		switch ( $timezone ) {
5519
			case 'gmt':
5520
				$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");
5521
				break;
5522
			case 'blog':
5523
				$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");
5524
				break;
5525
			case 'server':
5526
				$add_seconds_server = date( 'Z' );
5527
				$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");
5528
				break;
5529
		}
5530
5531
		if ( $date ) {
5532
			wp_cache_set( $key, $date, 'timeinfo' );
5533
		}
5534
	}
5535
5536
	return $date;
5537
}
5538
5539
/**
5540
 * Updates posts in cache.
5541
 *
5542
 * @since 1.5.1
5543
 *
5544
 * @param array $posts Array of post objects, passed by reference.
5545
 */
5546
function update_post_cache( &$posts ) {
5547
	if ( ! $posts )
5548
		return;
5549
5550
	foreach ( $posts as $post )
5551
		wp_cache_add( $post->ID, $post, 'posts' );
5552
}
5553
5554
/**
5555
 * Will clean the post in the cache.
5556
 *
5557
 * Cleaning means delete from the cache of the post. Will call to clean the term
5558
 * object cache associated with the post ID.
5559
 *
5560
 * This function not run if $_wp_suspend_cache_invalidation is not empty. See
5561
 * wp_suspend_cache_invalidation().
5562
 *
5563
 * @since 2.0.0
5564
 *
5565
 * @global bool $_wp_suspend_cache_invalidation
5566
 *
5567
 * @param int|WP_Post $post Post ID or post object to remove from the cache.
5568
 */
5569
function clean_post_cache( $post ) {
5570
	global $_wp_suspend_cache_invalidation;
5571
5572
	if ( ! empty( $_wp_suspend_cache_invalidation ) )
5573
		return;
5574
5575
	$post = get_post( $post );
5576
	if ( empty( $post ) )
5577
		return;
5578
5579
	wp_cache_delete( $post->ID, 'posts' );
5580
	wp_cache_delete( $post->ID, 'post_meta' );
5581
5582
	clean_object_term_cache( $post->ID, $post->post_type );
5583
5584
	wp_cache_delete( 'wp_get_archives', 'general' );
5585
5586
	/**
5587
	 * Fires immediately after the given post's cache is cleaned.
5588
	 *
5589
	 * @since 2.5.0
5590
	 *
5591
	 * @param int     $post_id Post ID.
5592
	 * @param WP_Post $post    Post object.
5593
	 */
5594
	do_action( 'clean_post_cache', $post->ID, $post );
5595
5596
	if ( 'page' == $post->post_type ) {
5597
		wp_cache_delete( 'all_page_ids', 'posts' );
5598
5599
		/**
5600
		 * Fires immediately after the given page's cache is cleaned.
5601
		 *
5602
		 * @since 2.5.0
5603
		 *
5604
		 * @param int $post_id Post ID.
5605
		 */
5606
		do_action( 'clean_page_cache', $post->ID );
5607
	}
5608
5609
	wp_cache_set( 'last_changed', microtime(), 'posts' );
5610
}
5611
5612
/**
5613
 * Call major cache updating functions for list of Post objects.
5614
 *
5615
 * @since 1.5.0
5616
 *
5617
 * @param array  $posts             Array of Post objects
5618
 * @param string $post_type         Optional. Post type. Default 'post'.
5619
 * @param bool   $update_term_cache Optional. Whether to update the term cache. Default true.
5620
 * @param bool   $update_meta_cache Optional. Whether to update the meta cache. Default true.
5621
 */
5622
function update_post_caches( &$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true ) {
5623
	// No point in doing all this work if we didn't match any posts.
5624
	if ( !$posts )
5625
		return;
5626
5627
	update_post_cache($posts);
5628
5629
	$post_ids = array();
5630
	foreach ( $posts as $post )
5631
		$post_ids[] = $post->ID;
5632
5633
	if ( ! $post_type )
5634
		$post_type = 'any';
5635
5636
	if ( $update_term_cache ) {
5637
		if ( is_array($post_type) ) {
5638
			$ptypes = $post_type;
5639
		} elseif ( 'any' == $post_type ) {
5640
			$ptypes = array();
5641
			// Just use the post_types in the supplied posts.
5642
			foreach ( $posts as $post ) {
5643
				$ptypes[] = $post->post_type;
5644
			}
5645
			$ptypes = array_unique($ptypes);
5646
		} else {
5647
			$ptypes = array($post_type);
5648
		}
5649
5650
		if ( ! empty($ptypes) )
5651
			update_object_term_cache($post_ids, $ptypes);
5652
	}
5653
5654
	if ( $update_meta_cache )
5655
		update_postmeta_cache($post_ids);
5656
}
5657
5658
/**
5659
 * Updates metadata cache for list of post IDs.
5660
 *
5661
 * Performs SQL query to retrieve the metadata for the post IDs and updates the
5662
 * metadata cache for the posts. Therefore, the functions, which call this
5663
 * function, do not need to perform SQL queries on their own.
5664
 *
5665
 * @since 2.1.0
5666
 *
5667
 * @param array $post_ids List of post IDs.
5668
 * @return array|false Returns false if there is nothing to update or an array
5669
 *                     of metadata.
5670
 */
5671
function update_postmeta_cache( $post_ids ) {
5672
	return update_meta_cache('post', $post_ids);
5673
}
5674
5675
/**
5676
 * Will clean the attachment in the cache.
5677
 *
5678
 * Cleaning means delete from the cache. Optionally will clean the term
5679
 * object cache associated with the attachment ID.
5680
 *
5681
 * This function will not run if $_wp_suspend_cache_invalidation is not empty.
5682
 *
5683
 * @since 3.0.0
5684
 *
5685
 * @global bool $_wp_suspend_cache_invalidation
5686
 *
5687
 * @param int  $id          The attachment ID in the cache to clean.
5688
 * @param bool $clean_terms Optional. Whether to clean terms cache. Default false.
5689
 */
5690
function clean_attachment_cache( $id, $clean_terms = false ) {
5691
	global $_wp_suspend_cache_invalidation;
5692
5693
	if ( !empty($_wp_suspend_cache_invalidation) )
5694
		return;
5695
5696
	$id = (int) $id;
5697
5698
	wp_cache_delete($id, 'posts');
5699
	wp_cache_delete($id, 'post_meta');
5700
5701
	if ( $clean_terms )
5702
		clean_object_term_cache($id, 'attachment');
5703
5704
	/**
5705
	 * Fires after the given attachment's cache is cleaned.
5706
	 *
5707
	 * @since 3.0.0
5708
	 *
5709
	 * @param int $id Attachment ID.
5710
	 */
5711
	do_action( 'clean_attachment_cache', $id );
5712
}
5713
5714
//
5715
// Hooks
5716
//
5717
5718
/**
5719
 * Hook for managing future post transitions to published.
5720
 *
5721
 * @since 2.3.0
5722
 * @access private
5723
 *
5724
 * @see wp_clear_scheduled_hook()
5725
 * @global wpdb $wpdb WordPress database abstraction object.
5726
 *
5727
 * @param string  $new_status New post status.
5728
 * @param string  $old_status Previous post status.
5729
 * @param WP_Post $post       Post object.
5730
 */
5731
function _transition_post_status( $new_status, $old_status, $post ) {
5732
	global $wpdb;
5733
5734
	if ( $old_status != 'publish' && $new_status == 'publish' ) {
5735
		// Reset GUID if transitioning to publish and it is empty.
5736
		if ( '' == get_the_guid($post->ID) )
5737
			$wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) );
5738
5739
		/**
5740
		 * Fires when a post's status is transitioned from private to published.
5741
		 *
5742
		 * @since 1.5.0
5743
		 * @deprecated 2.3.0 Use 'private_to_publish' instead.
5744
		 *
5745
		 * @param int $post_id Post ID.
5746
		 */
5747
		do_action('private_to_published', $post->ID);
5748
	}
5749
5750
	// If published posts changed clear the lastpostmodified cache.
5751
	if ( 'publish' == $new_status || 'publish' == $old_status) {
5752
		foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
5753
			wp_cache_delete( "lastpostmodified:$timezone", 'timeinfo' );
5754
			wp_cache_delete( "lastpostdate:$timezone", 'timeinfo' );
5755
			wp_cache_delete( "lastpostdate:$timezone:{$post->post_type}", 'timeinfo' );
5756
		}
5757
	}
5758
5759
	if ( $new_status !== $old_status ) {
5760
		wp_cache_delete( _count_posts_cache_key( $post->post_type ), 'counts' );
5761
		wp_cache_delete( _count_posts_cache_key( $post->post_type, 'readable' ), 'counts' );
5762
	}
5763
5764
	// Always clears the hook in case the post status bounced from future to draft.
5765
	wp_clear_scheduled_hook('publish_future_post', array( $post->ID ) );
5766
}
5767
5768
/**
5769
 * Hook used to schedule publication for a post marked for the future.
5770
 *
5771
 * The $post properties used and must exist are 'ID' and 'post_date_gmt'.
5772
 *
5773
 * @since 2.3.0
5774
 * @access private
5775
 *
5776
 * @param int     $deprecated Not used. Can be set to null. Never implemented. Not marked
5777
 *                            as deprecated with _deprecated_argument() as it conflicts with
5778
 *                            wp_transition_post_status() and the default filter for _future_post_hook().
5779
 * @param WP_Post $post       Post object.
5780
 */
5781
function _future_post_hook( $deprecated, $post ) {
5782
	wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
5783
	wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT') , 'publish_future_post', array( $post->ID ) );
5784
}
5785
5786
/**
5787
 * Hook to schedule pings and enclosures when a post is published.
5788
 *
5789
 * Uses XMLRPC_REQUEST and WP_IMPORTING constants.
5790
 *
5791
 * @since 2.3.0
5792
 * @access private
5793
 *
5794
 * @param int $post_id The ID in the database table of the post being published.
5795
 */
5796
function _publish_post_hook( $post_id ) {
5797
	if ( defined( 'XMLRPC_REQUEST' ) ) {
5798
		/**
5799
		 * Fires when _publish_post_hook() is called during an XML-RPC request.
5800
		 *
5801
		 * @since 2.1.0
5802
		 *
5803
		 * @param int $post_id Post ID.
5804
		 */
5805
		do_action( 'xmlrpc_publish_post', $post_id );
5806
	}
5807
5808
	if ( defined('WP_IMPORTING') )
5809
		return;
5810
5811
	if ( get_option('default_pingback_flag') )
5812
		add_post_meta( $post_id, '_pingme', '1' );
5813
	add_post_meta( $post_id, '_encloseme', '1' );
5814
5815
	wp_schedule_single_event(time(), 'do_pings');
5816
}
5817
5818
/**
5819
 * Return the post's parent's post_ID
5820
 *
5821
 * @since 3.1.0
5822
 *
5823
 * @param int $post_ID
5824
 *
5825
 * @return int|false Post parent ID, otherwise false.
5826
 */
5827
function wp_get_post_parent_id( $post_ID ) {
5828
	$post = get_post( $post_ID );
5829
	if ( !$post || is_wp_error( $post ) )
5830
		return false;
5831
	return (int) $post->post_parent;
5832
}
5833
5834
/**
5835
 * Check the given subset of the post hierarchy for hierarchy loops.
5836
 *
5837
 * Prevents loops from forming and breaks those that it finds. Attached
5838
 * to the {@see 'wp_insert_post_parent'} filter.
5839
 *
5840
 * @since 3.1.0
5841
 *
5842
 * @see wp_find_hierarchy_loop()
5843
 *
5844
 * @param int $post_parent ID of the parent for the post we're checking.
5845
 * @param int $post_ID     ID of the post we're checking.
5846
 * @return int The new post_parent for the post, 0 otherwise.
5847
 */
5848 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...
5849
	// Nothing fancy here - bail.
5850
	if ( !$post_parent )
5851
		return 0;
5852
5853
	// New post can't cause a loop.
5854
	if ( empty( $post_ID ) )
5855
		return $post_parent;
5856
5857
	// Can't be its own parent.
5858
	if ( $post_parent == $post_ID )
5859
		return 0;
5860
5861
	// Now look for larger loops.
5862
	if ( !$loop = wp_find_hierarchy_loop( 'wp_get_post_parent_id', $post_ID, $post_parent ) )
5863
		return $post_parent; // No loop
5864
5865
	// Setting $post_parent to the given value causes a loop.
5866
	if ( isset( $loop[$post_ID] ) )
5867
		return 0;
5868
5869
	// There's a loop, but it doesn't contain $post_ID. Break the loop.
5870
	foreach ( array_keys( $loop ) as $loop_member )
5871
		wp_update_post( array( 'ID' => $loop_member, 'post_parent' => 0 ) );
5872
5873
	return $post_parent;
5874
}
5875
5876
/**
5877
 * Set a post thumbnail.
5878
 *
5879
 * @since 3.1.0
5880
 *
5881
 * @param int|WP_Post $post         Post ID or post object where thumbnail should be attached.
5882
 * @param int         $thumbnail_id Thumbnail to attach.
5883
 * @return int|bool True on success, false on failure.
5884
 */
5885
function set_post_thumbnail( $post, $thumbnail_id ) {
5886
	$post = get_post( $post );
5887
	$thumbnail_id = absint( $thumbnail_id );
5888
	if ( $post && $thumbnail_id && get_post( $thumbnail_id ) ) {
5889
		if ( wp_get_attachment_image( $thumbnail_id, 'thumbnail' ) )
5890
			return update_post_meta( $post->ID, '_thumbnail_id', $thumbnail_id );
5891
		else
5892
			return delete_post_meta( $post->ID, '_thumbnail_id' );
5893
	}
5894
	return false;
5895
}
5896
5897
/**
5898
 * Remove a post thumbnail.
5899
 *
5900
 * @since 3.3.0
5901
 *
5902
 * @param int|WP_Post $post Post ID or post object where thumbnail should be removed from.
5903
 * @return bool True on success, false on failure.
5904
 */
5905
function delete_post_thumbnail( $post ) {
5906
	$post = get_post( $post );
5907
	if ( $post )
5908
		return delete_post_meta( $post->ID, '_thumbnail_id' );
5909
	return false;
5910
}
5911
5912
/**
5913
 * Delete auto-drafts for new posts that are > 7 days old.
5914
 *
5915
 * @since 3.4.0
5916
 *
5917
 * @global wpdb $wpdb WordPress database abstraction object.
5918
 */
5919
function wp_delete_auto_drafts() {
5920
	global $wpdb;
5921
5922
	// Cleanup old auto-drafts more than 7 days old.
5923
	$old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND DATE_SUB( NOW(), INTERVAL 7 DAY ) > post_date" );
5924
	foreach ( (array) $old_posts as $delete ) {
5925
		// Force delete.
5926
		wp_delete_post( $delete, true );
5927
	}
5928
}
5929
5930
/**
5931
 * Queues posts for lazy-loading of term meta.
5932
 *
5933
 * @since 4.5.0
5934
 *
5935
 * @param array $posts Array of WP_Post objects.
5936
 */
5937
function wp_queue_posts_for_term_meta_lazyload( $posts ) {
5938
	$post_type_taxonomies = $term_ids = array();
5939
	foreach ( $posts as $post ) {
5940
		if ( ! ( $post instanceof WP_Post ) ) {
5941
			continue;
5942
		}
5943
5944
		if ( ! isset( $post_type_taxonomies[ $post->post_type ] ) ) {
5945
			$post_type_taxonomies[ $post->post_type ] = get_object_taxonomies( $post->post_type );
5946
		}
5947
5948
		foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) {
5949
			// Term cache should already be primed by `update_post_term_cache()`.
5950
			$terms = get_object_term_cache( $post->ID, $taxonomy );
5951
			if ( false !== $terms ) {
5952
				foreach ( $terms as $term ) {
5953
					if ( ! isset( $term_ids[ $term->term_id ] ) ) {
5954
						$term_ids[] = $term->term_id;
5955
					}
5956
				}
5957
			}
5958
		}
5959
	}
5960
5961
	if ( $term_ids ) {
5962
		$lazyloader = wp_metadata_lazyloader();
5963
		$lazyloader->queue_objects( 'term', $term_ids );
5964
	}
5965
}
5966
5967
/**
5968
 * Update the custom taxonomies' term counts when a post's status is changed.
5969
 *
5970
 * For example, default posts term counts (for custom taxonomies) don't include
5971
 * private / draft posts.
5972
 *
5973
 * @since 3.3.0
5974
 * @access private
5975
 *
5976
 * @param string  $new_status New post status.
5977
 * @param string  $old_status Old post status.
5978
 * @param WP_Post $post       Post object.
5979
 */
5980
function _update_term_count_on_transition_post_status( $new_status, $old_status, $post ) {
5981
	// Update counts for the post's terms.
5982
	foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
5983
		$tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) );
5984
		wp_update_term_count( $tt_ids, $taxonomy );
0 ignored issues
show
It seems like $tt_ids defined by wp_get_object_terms($pos...('fields' => 'tt_ids')) on line 5983 can also be of type object<WP_Error>; however, wp_update_term_count() does only seem to accept integer|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...
5985
	}
5986
}
5987
5988
/**
5989
 * Adds any posts from the given ids to the cache that do not already exist in cache
5990
 *
5991
 * @since 3.4.0
5992
 * @access private
5993
 *
5994
 * @see update_post_caches()
5995
 *
5996
 * @global wpdb $wpdb WordPress database abstraction object.
5997
 *
5998
 * @param array $ids               ID list.
5999
 * @param bool  $update_term_cache Optional. Whether to update the term cache. Default true.
6000
 * @param bool  $update_meta_cache Optional. Whether to update the meta cache. Default true.
6001
 */
6002 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...
6003
	global $wpdb;
6004
6005
	$non_cached_ids = _get_non_cached_ids( $ids, 'posts' );
6006
	if ( !empty( $non_cached_ids ) ) {
6007
		$fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", join( ",", $non_cached_ids ) ) );
6008
6009
		update_post_caches( $fresh_posts, 'any', $update_term_cache, $update_meta_cache );
6010
	}
6011
}
6012
6013
/**
6014
 * Adds a suffix if any trashed posts have a given slug.
6015
 *
6016
 * Store its desired (i.e. current) slug so it can try to reclaim it
6017
 * if the post is untrashed.
6018
 *
6019
 * For internal use.
6020
 *
6021
 * @since 4.5.0
6022
 * @access private
6023
 *
6024
 * @param string $post_name Slug.
6025
 * @param string $post_ID   Optional. Post ID that should be ignored. Default 0.
6026
 */
6027
function wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID = 0 ) {
6028
	$trashed_posts_with_desired_slug = get_posts( array(
6029
		'name' => $post_name,
6030
		'post_status' => 'trash',
6031
		'post_type' => 'any',
6032
		'nopaging' => true,
6033
		'post__not_in' => array( $post_ID )
6034
	) );
6035
6036
	if ( ! empty( $trashed_posts_with_desired_slug ) ) {
6037
		foreach ( $trashed_posts_with_desired_slug as $_post ) {
6038
			wp_add_trashed_suffix_to_post_name_for_post( $_post );
6039
		}
6040
	}
6041
}
6042
6043
/**
6044
 * Adds a trashed suffix for a given post.
6045
 *
6046
 * Store its desired (i.e. current) slug so it can try to reclaim it
6047
 * if the post is untrashed.
6048
 *
6049
 * For internal use.
6050
 *
6051
 * @since 4.5.0
6052
 * @access private
6053
 *
6054
 * @param WP_Post $post The post.
6055
 * @return string New slug for the post.
6056
 */
6057
function wp_add_trashed_suffix_to_post_name_for_post( $post ) {
6058
	global $wpdb;
6059
6060
	$post = get_post( $post );
6061
6062
	if ( '__trashed' === substr( $post->post_name, -9 ) ) {
6063
		return $post->post_name;
6064
	}
6065
	add_post_meta( $post->ID, '_wp_desired_post_slug', $post->post_name );
6066
	$post_name = _truncate_post_slug( $post->post_name, 191 ) . '__trashed';
6067
	$wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
6068
	clean_post_cache( $post->ID );
6069
	return $post_name;
6070
}
6071