at path:ROOT / wp-includes / post.php
run:R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:52
R W Run
DIR
2026-03-11 16:18:51
R W Run
DIR
2026-03-11 16:18:51
R W Run
23.8 KB
2026-03-11 16:18:51
R W Run
7.8 KB
2026-03-11 16:18:52
R W Run
36.1 KB
2026-03-11 16:18:51
R W Run
11.9 KB
2026-03-11 16:18:52
R W Run
18.94 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:52
R W Run
28.6 KB
2026-03-11 16:18:51
R W Run
316 By
2026-03-11 16:18:51
R W Run
12.9 KB
2026-03-11 16:18:51
R W Run
61.02 KB
2026-03-11 16:18:52
R W Run
15 KB
2026-03-11 16:18:51
R W Run
112.05 KB
2026-03-11 16:18:51
R W Run
12.47 KB
2026-03-11 16:18:51
R W Run
15.07 KB
2026-03-11 16:18:52
R W Run
9.84 KB
2026-03-11 16:18:52
R W Run
13.17 KB
2026-03-11 16:18:52
R W Run
33.83 KB
2026-03-11 16:18:51
R W Run
42.63 KB
2026-03-11 16:18:51
R W Run
55.71 KB
2026-03-11 16:18:52
R W Run
12.53 KB
2026-03-11 16:18:51
R W Run
2.55 KB
2026-03-11 16:18:52
R W Run
28.92 KB
2026-03-11 16:18:52
R W Run
539 By
2026-03-11 16:18:51
R W Run
367 By
2026-03-11 16:18:52
R W Run
42.65 KB
2026-03-11 16:18:51
R W Run
401 By
2026-03-11 16:18:51
R W Run
6.61 KB
2026-03-11 16:18:51
R W Run
664 By
2026-03-11 16:18:52
R W Run
20.63 KB
2026-03-11 16:18:51
R W Run
2.18 KB
2026-03-11 16:18:52
R W Run
453 By
2026-03-11 16:18:52
R W Run
457 By
2026-03-11 16:18:51
R W Run
36.83 KB
2026-03-11 16:18:52
R W Run
2.41 KB
2026-03-11 16:18:52
R W Run
8.28 KB
2026-03-11 16:18:51
R W Run
13.89 KB
2026-03-11 16:18:51
R W Run
11.76 KB
2026-03-11 16:18:51
R W Run
2.65 KB
2026-03-11 16:18:51
R W Run
7.43 KB
2026-03-11 16:18:51
R W Run
17.46 KB
2026-03-11 16:18:51
R W Run
5.14 KB
2026-03-11 16:18:52
R W Run
16.7 KB
2026-03-11 16:18:51
R W Run
8.28 KB
2026-03-11 16:18:52
R W Run
2.92 KB
2026-03-11 16:18:52
R W Run
1.32 KB
2026-03-11 16:18:51
R W Run
4.6 KB
2026-03-11 16:18:52
R W Run
11.62 KB
2026-03-11 16:18:52
R W Run
2.5 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
11.25 KB
2026-03-11 16:18:52
R W Run
5.32 KB
2026-03-11 16:18:51
R W Run
10.99 KB
2026-03-11 16:18:52
R W Run
68.32 KB
2026-03-11 16:18:51
R W Run
6.34 KB
2026-03-11 16:18:51
R W Run
5.49 KB
2026-03-11 16:18:51
R W Run
1.99 KB
2026-03-11 16:18:52
R W Run
7.02 KB
2026-03-11 16:18:51
R W Run
4.91 KB
2026-03-11 16:18:52
R W Run
16.86 KB
2026-03-11 16:18:51
R W Run
24.23 KB
2026-03-11 16:18:51
R W Run
3.97 KB
2026-03-11 16:18:51
R W Run
47.66 KB
2026-03-11 16:18:51
R W Run
9.22 KB
2026-03-11 16:18:51
R W Run
25.51 KB
2026-03-11 16:18:51
R W Run
198.38 KB
2026-03-11 16:18:52
R W Run
56.65 KB
2026-03-11 16:18:51
R W Run
10.46 KB
2026-03-11 16:18:51
R W Run
10.95 KB
2026-03-11 16:18:52
R W Run
29.26 KB
2026-03-11 16:18:51
R W Run
70.91 KB
2026-03-11 16:18:52
R W Run
35.3 KB
2026-03-11 16:18:52
R W Run
16.61 KB
2026-03-11 16:18:52
R W Run
2.57 KB
2026-03-11 16:18:52
R W Run
39.83 KB
2026-03-11 16:18:51
R W Run
70.64 KB
2026-03-11 16:18:51
R W Run
15.56 KB
2026-03-11 16:18:52
R W Run
7.33 KB
2026-03-11 16:18:52
R W Run
253 By
2026-03-11 16:18:51
R W Run
7.96 KB
2026-03-11 16:18:52
R W Run
3.23 KB
2026-03-11 16:18:52
R W Run
969 By
2026-03-11 16:18:52
R W Run
16.28 KB
2026-03-11 16:18:51
R W Run
7.22 KB
2026-03-11 16:18:51
R W Run
12.95 KB
2026-03-11 16:18:51
R W Run
6.53 KB
2026-03-11 16:18:51
R W Run
3.42 KB
2026-03-11 16:18:52
R W Run
5.84 KB
2026-03-11 16:18:51
R W Run
1.97 KB
2026-03-11 16:18:51
R W Run
4.3 KB
2026-03-11 16:18:52
R W Run
2.91 KB
2026-03-11 16:18:51
R W Run
16.46 KB
2026-03-11 16:18:52
R W Run
40.6 KB
2026-03-11 16:18:51
R W Run
20.22 KB
2026-03-11 16:18:51
R W Run
36.11 KB
2026-03-11 16:18:52
R W Run
17.01 KB
2026-03-11 16:18:51
R W Run
7.27 KB
2026-03-11 16:18:52
R W Run
6.62 KB
2026-03-11 16:18:52
R W Run
16.49 KB
2026-03-11 16:18:52
R W Run
1.79 KB
2026-03-11 16:18:52
R W Run
29.82 KB
2026-03-11 16:18:51
R W Run
6.67 KB
2026-03-11 16:18:52
R W Run
8.98 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:51
R W Run
12.01 KB
2026-03-11 16:18:51
R W Run
17.11 KB
2026-03-11 16:18:51
R W Run
6.74 KB
2026-03-11 16:18:52
R W Run
30.93 KB
2026-03-11 16:18:51
R W Run
4.99 KB
2026-03-11 16:18:51
R W Run
4.25 KB
2026-03-11 16:18:51
R W Run
24.72 KB
2026-03-11 16:18:51
R W Run
29.96 KB
2026-03-11 16:18:52
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
160 KB
2026-03-11 16:18:51
R W Run
6.72 KB
2026-03-11 16:18:52
R W Run
10.92 KB
2026-03-11 16:18:51
R W Run
4.77 KB
2026-03-11 16:18:51
R W Run
3.38 KB
2026-03-11 16:18:51
R W Run
11.18 KB
2026-03-11 16:18:51
R W Run
62.19 KB
2026-03-11 16:18:51
R W Run
2.46 KB
2026-03-11 16:18:51
R W Run
9.17 KB
2026-03-11 16:18:51
R W Run
32.15 KB
2026-03-11 16:18:51
R W Run
34.05 KB
2026-03-11 16:18:52
R W Run
7.15 KB
2026-03-11 16:18:51
R W Run
3.47 KB
2026-03-11 16:18:52
R W Run
1.87 KB
2026-03-11 16:18:52
R W Run
30.91 KB
2026-03-11 16:18:51
R W Run
7.29 KB
2026-03-11 16:18:52
R W Run
7.35 KB
2026-03-11 16:18:51
R W Run
12.54 KB
2026-03-11 16:18:51
R W Run
19.12 KB
2026-03-11 16:18:51
R W Run
18.12 KB
2026-03-11 16:18:52
R W Run
39.99 KB
2026-03-11 16:18:52
R W Run
5.17 KB
2026-03-11 16:18:52
R W Run
979 By
2026-03-11 16:18:51
R W Run
18.44 KB
2026-03-11 16:18:52
R W Run
10.24 KB
2026-03-11 16:18:51
R W Run
1.77 KB
2026-03-11 16:18:52
R W Run
34.9 KB
2026-03-11 16:18:51
R W Run
7.19 KB
2026-03-11 16:18:52
R W Run
160.5 KB
2026-03-11 16:18:51
R W Run
64.27 KB
2026-03-11 16:18:51
R W Run
27.95 KB
2026-03-11 16:18:51
R W Run
4.69 KB
2026-03-11 16:18:51
R W Run
2.94 KB
2026-03-11 16:18:51
R W Run
43.13 KB
2026-03-11 16:18:52
R W Run
2.25 KB
2026-03-11 16:18:52
R W Run
22.5 KB
2026-03-11 16:18:51
R W Run
13.01 KB
2026-03-11 16:18:52
R W Run
3.27 KB
2026-03-11 16:18:51
R W Run
18 KB
2026-03-11 16:18:51
R W Run
210.4 KB
2026-03-11 16:18:52
R W Run
25.86 KB
2026-03-11 16:18:52
R W Run
115.85 KB
2026-03-11 16:18:51
R W Run
373 By
2026-03-11 16:18:52
R W Run
343 By
2026-03-11 16:18:52
R W Run
338 By
2026-03-11 16:18:51
R W Run
100.73 KB
2026-03-11 16:18:52
R W Run
130.93 KB
2026-03-11 16:18:51
R W Run
19.1 KB
2026-03-11 16:18:51
R W Run
17.41 KB
2026-03-11 16:18:52
R W Run
41.98 KB
2026-03-11 16:18:52
R W Run
400 By
2026-03-11 16:18:52
R W Run
11.1 KB
2026-03-11 16:18:52
R W Run
37.02 KB
2026-03-11 16:18:51
R W Run
2.24 KB
2026-03-11 16:18:51
R W Run
188.13 KB
2026-03-11 16:18:51
R W Run
338 By
2026-03-11 16:18:51
R W Run
38 KB
2026-03-11 16:18:51
R W Run
4.02 KB
2026-03-11 16:18:52
R W Run
5.38 KB
2026-03-11 16:18:51
R W Run
3.05 KB
2026-03-11 16:18:52
R W Run
2.61 KB
2026-03-11 16:18:51
R W Run
1.16 KB
2026-03-11 16:18:52
R W Run
4.04 KB
2026-03-11 16:18:51
R W Run
3.71 KB
2026-03-11 16:18:51
R W Run
24.6 KB
2026-03-11 16:18:51
R W Run
9.56 KB
2026-03-11 16:18:51
R W Run
346.43 KB
2026-03-11 16:18:52
R W Run
281.84 KB
2026-03-11 16:18:52
R W Run
14.95 KB
2026-03-11 16:18:51
R W Run
8.44 KB
2026-03-11 16:18:52
R W Run
168.95 KB
2026-03-11 16:18:52
R W Run
20.71 KB
2026-03-11 16:18:52
R W Run
25.27 KB
2026-03-11 16:18:51
R W Run
5.72 KB
2026-03-11 16:18:51
R W Run
4.63 KB
2026-03-11 16:18:52
R W Run
81.73 KB
2026-03-11 16:18:51
R W Run
67.18 KB
2026-03-11 16:18:51
R W Run
156.36 KB
2026-03-11 16:18:52
R W Run
55.19 KB
2026-03-11 16:18:51
R W Run
162 By
2026-03-11 16:18:51
R W Run
61.72 KB
2026-03-11 16:18:51
R W Run
216.06 KB
2026-03-11 16:18:52
R W Run
65.09 KB
2026-03-11 16:18:51
R W Run
25.24 KB
2026-03-11 16:18:52
R W Run
4.81 KB
2026-03-11 16:18:51
R W Run
6.48 KB
2026-03-11 16:18:52
R W Run
21.25 KB
2026-03-11 16:18:51
R W Run
2.79 KB
2026-03-11 16:18:52
R W Run
89.69 KB
2026-03-11 16:18:52
R W Run
19.42 KB
2026-03-11 16:18:52
R W Run
3.69 KB
2026-03-11 16:18:52
R W Run
4.11 KB
2026-03-11 16:18:51
R W Run
40.74 KB
2026-03-11 16:18:51
R W Run
25.38 KB
2026-03-11 16:18:51
R W Run
43.31 KB
2026-03-11 16:18:52
R W Run
102.57 KB
2026-03-11 16:18:52
R W Run
6.18 KB
2026-03-11 16:18:51
R W Run
124.47 KB
2026-03-11 16:18:52
R W Run
35.65 KB
2026-03-11 16:18:52
R W Run
6.94 KB
2026-03-11 16:18:52
R W Run
67.04 KB
2026-03-11 16:18:52
R W Run
10.62 KB
2026-03-11 16:18:51
R W Run
289.35 KB
2026-03-11 16:18:52
R W Run
36.23 KB
2026-03-11 16:18:51
R W Run
200 By
2026-03-11 16:18:52
R W Run
200 By
2026-03-11 16:18:52
R W Run
98.29 KB
2026-03-11 16:18:52
R W Run
30.02 KB
2026-03-11 16:18:52
R W Run
19.03 KB
2026-03-11 16:18:52
R W Run
5.06 KB
2026-03-11 16:18:52
R W Run
255 By
2026-03-11 16:18:51
R W Run
22.66 KB
2026-03-11 16:18:52
R W Run
154.63 KB
2026-03-11 16:18:51
R W Run
9.68 KB
2026-03-11 16:18:51
R W Run
258 By
2026-03-11 16:18:51
R W Run
23.49 KB
2026-03-11 16:18:51
R W Run
3.16 KB
2026-03-11 16:18:51
R W Run
8.4 KB
2026-03-11 16:18:52
R W Run
441 By
2026-03-11 16:18:51
R W Run
7.39 KB
2026-03-11 16:18:51
R W Run
173 KB
2026-03-11 16:18:52
R W Run
544 By
2026-03-11 16:18:52
R W Run
4.17 KB
2026-03-11 16:18:51
R W Run
35.97 KB
2026-03-11 16:18:52
R W Run
1.69 KB
2026-03-11 16:18:51
R W Run
2.84 KB
2026-03-11 16:18:52
R W Run
6.09 KB
2026-03-11 16:18:51
R W Run
8.71 KB
2026-03-11 16:18:51
R W Run
131.84 KB
2026-03-11 16:18:51
R W Run
37.45 KB
2026-03-11 16:18:51
R W Run
173.89 KB
2026-03-11 16:18:51
R W Run
7.09 KB
2026-03-11 16:18:51
R W Run
6.41 KB
2026-03-11 16:18:51
R W Run
1.08 KB
2026-03-11 16:18:51
R W Run
69.46 KB
2026-03-11 16:18:52
R W Run
445 By
2026-03-11 16:18:51
R W Run
799 By
2026-03-11 16:18:52
R W Run
error_log
📄post.php
1<?php
2/**
3 * Core Post API
4 *
5 * @package WordPress
6 * @subpackage Post
7 */
8
9//
10// Post Type registration.
11//
12
13/**
14 * Creates the initial post types when 'init' action is fired.
15 *
16 * See {@see 'init'}.
17 *
18 * @since 2.9.0
19 */
20function create_initial_post_types() {
21 WP_Post_Type::reset_default_labels();
22
23 register_post_type(
24 'post',
25 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 'menu_icon' => 'dashicons-admin-post',
36 'hierarchical' => false,
37 'rewrite' => false,
38 'query_var' => false,
39 'delete_with_user' => true,
40 'supports' => array(
41 'title',
42 'editor' => array( 'notes' => true ),
43 'author',
44 'thumbnail',
45 'excerpt',
46 'trackbacks',
47 'custom-fields',
48 'comments',
49 'revisions',
50 'post-formats',
51 ),
52 'show_in_rest' => true,
53 'rest_base' => 'posts',
54 'rest_controller_class' => 'WP_REST_Posts_Controller',
55 )
56 );
57
58 register_post_type(
59 'page',
60 array(
61 'labels' => array(
62 'name_admin_bar' => _x( 'Page', 'add new from admin bar' ),
63 ),
64 'public' => true,
65 'publicly_queryable' => false,
66 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
67 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
68 'capability_type' => 'page',
69 'map_meta_cap' => true,
70 'menu_position' => 20,
71 'menu_icon' => 'dashicons-admin-page',
72 'hierarchical' => true,
73 'rewrite' => false,
74 'query_var' => false,
75 'delete_with_user' => true,
76 'supports' => array(
77 'title',
78 'editor' => array( 'notes' => true ),
79 'author',
80 'thumbnail',
81 'page-attributes',
82 'custom-fields',
83 'comments',
84 'revisions',
85 ),
86 'show_in_rest' => true,
87 'rest_base' => 'pages',
88 'rest_controller_class' => 'WP_REST_Posts_Controller',
89 )
90 );
91
92 register_post_type(
93 'attachment',
94 array(
95 'labels' => array(
96 'name' => _x( 'Media', 'post type general name' ),
97 'name_admin_bar' => _x( 'Media', 'add new from admin bar' ),
98 'add_new' => __( 'Add Media File' ),
99 'add_new_item' => __( 'Add Media File' ),
100 'edit_item' => __( 'Edit Media' ),
101 'view_item' => ( '1' === get_option( 'wp_attachment_pages_enabled' ) ) ? __( 'View Attachment Page' ) : __( 'View Media File' ),
102 'attributes' => __( 'Attachment Attributes' ),
103 ),
104 'public' => true,
105 'show_ui' => true,
106 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
107 '_edit_link' => 'post.php?post=%d', /* internal use only. don't use this when registering your own post type. */
108 'capability_type' => 'post',
109 'capabilities' => array(
110 'create_posts' => 'upload_files',
111 ),
112 'map_meta_cap' => true,
113 'menu_icon' => 'dashicons-admin-media',
114 'hierarchical' => false,
115 'rewrite' => false,
116 'query_var' => false,
117 'show_in_nav_menus' => false,
118 'delete_with_user' => true,
119 'supports' => array( 'title', 'author', 'comments' ),
120 'show_in_rest' => true,
121 'rest_base' => 'media',
122 'rest_controller_class' => 'WP_REST_Attachments_Controller',
123 )
124 );
125 add_post_type_support( 'attachment:audio', 'thumbnail' );
126 add_post_type_support( 'attachment:video', 'thumbnail' );
127
128 register_post_type(
129 'revision',
130 array(
131 'labels' => array(
132 'name' => __( 'Revisions' ),
133 'singular_name' => __( 'Revision' ),
134 ),
135 'public' => false,
136 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
137 '_edit_link' => 'revision.php?revision=%d', /* internal use only. don't use this when registering your own post type. */
138 'capability_type' => 'post',
139 'map_meta_cap' => true,
140 'hierarchical' => false,
141 'rewrite' => false,
142 'query_var' => false,
143 'can_export' => false,
144 'delete_with_user' => true,
145 'supports' => array( 'author' ),
146 )
147 );
148
149 register_post_type(
150 'nav_menu_item',
151 array(
152 'labels' => array(
153 'name' => __( 'Navigation Menu Items' ),
154 'singular_name' => __( 'Navigation Menu Item' ),
155 ),
156 'public' => false,
157 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
158 'hierarchical' => false,
159 'rewrite' => false,
160 'delete_with_user' => false,
161 'query_var' => false,
162 'map_meta_cap' => true,
163 'capability_type' => array( 'edit_theme_options', 'edit_theme_options' ),
164 'capabilities' => array(
165 // Meta Capabilities.
166 'edit_post' => 'edit_post',
167 'read_post' => 'read_post',
168 'delete_post' => 'delete_post',
169 // Primitive Capabilities.
170 'edit_posts' => 'edit_theme_options',
171 'edit_others_posts' => 'edit_theme_options',
172 'delete_posts' => 'edit_theme_options',
173 'publish_posts' => 'edit_theme_options',
174 'read_private_posts' => 'edit_theme_options',
175 'read' => 'read',
176 'delete_private_posts' => 'edit_theme_options',
177 'delete_published_posts' => 'edit_theme_options',
178 'delete_others_posts' => 'edit_theme_options',
179 'edit_private_posts' => 'edit_theme_options',
180 'edit_published_posts' => 'edit_theme_options',
181 ),
182 'show_in_rest' => true,
183 'rest_base' => 'menu-items',
184 'rest_controller_class' => 'WP_REST_Menu_Items_Controller',
185 )
186 );
187
188 register_post_type(
189 'custom_css',
190 array(
191 'labels' => array(
192 'name' => __( 'Custom CSS' ),
193 'singular_name' => __( 'Custom CSS' ),
194 ),
195 'public' => false,
196 'hierarchical' => false,
197 'rewrite' => false,
198 'query_var' => false,
199 'delete_with_user' => false,
200 'can_export' => true,
201 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
202 'supports' => array( 'title', 'revisions' ),
203 'capabilities' => array(
204 'delete_posts' => 'edit_theme_options',
205 'delete_post' => 'edit_theme_options',
206 'delete_published_posts' => 'edit_theme_options',
207 'delete_private_posts' => 'edit_theme_options',
208 'delete_others_posts' => 'edit_theme_options',
209 'edit_post' => 'edit_css',
210 'edit_posts' => 'edit_css',
211 'edit_others_posts' => 'edit_css',
212 'edit_published_posts' => 'edit_css',
213 'read_post' => 'read',
214 'read_private_posts' => 'read',
215 'publish_posts' => 'edit_theme_options',
216 ),
217 )
218 );
219
220 register_post_type(
221 'customize_changeset',
222 array(
223 'labels' => array(
224 'name' => _x( 'Changesets', 'post type general name' ),
225 'singular_name' => _x( 'Changeset', 'post type singular name' ),
226 'add_new' => __( 'Add Changeset' ),
227 'add_new_item' => __( 'Add Changeset' ),
228 'new_item' => __( 'New Changeset' ),
229 'edit_item' => __( 'Edit Changeset' ),
230 'view_item' => __( 'View Changeset' ),
231 'all_items' => __( 'All Changesets' ),
232 'search_items' => __( 'Search Changesets' ),
233 'not_found' => __( 'No changesets found.' ),
234 'not_found_in_trash' => __( 'No changesets found in Trash.' ),
235 ),
236 'public' => false,
237 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
238 'map_meta_cap' => true,
239 'hierarchical' => false,
240 'rewrite' => false,
241 'query_var' => false,
242 'can_export' => false,
243 'delete_with_user' => false,
244 'supports' => array( 'title', 'author' ),
245 'capability_type' => 'customize_changeset',
246 'capabilities' => array(
247 'create_posts' => 'customize',
248 'delete_others_posts' => 'customize',
249 'delete_post' => 'customize',
250 'delete_posts' => 'customize',
251 'delete_private_posts' => 'customize',
252 'delete_published_posts' => 'customize',
253 'edit_others_posts' => 'customize',
254 'edit_post' => 'customize',
255 'edit_posts' => 'customize',
256 'edit_private_posts' => 'customize',
257 'edit_published_posts' => 'do_not_allow',
258 'publish_posts' => 'customize',
259 'read' => 'read',
260 'read_post' => 'customize',
261 'read_private_posts' => 'customize',
262 ),
263 )
264 );
265
266 register_post_type(
267 'oembed_cache',
268 array(
269 'labels' => array(
270 'name' => __( 'oEmbed Responses' ),
271 'singular_name' => __( 'oEmbed Response' ),
272 ),
273 'public' => false,
274 'hierarchical' => false,
275 'rewrite' => false,
276 'query_var' => false,
277 'delete_with_user' => false,
278 'can_export' => false,
279 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
280 'supports' => array(),
281 )
282 );
283
284 register_post_type(
285 'user_request',
286 array(
287 'labels' => array(
288 'name' => __( 'User Requests' ),
289 'singular_name' => __( 'User Request' ),
290 ),
291 'public' => false,
292 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
293 'hierarchical' => false,
294 'rewrite' => false,
295 'query_var' => false,
296 'can_export' => false,
297 'delete_with_user' => false,
298 'supports' => array(),
299 )
300 );
301
302 register_post_type(
303 'wp_block',
304 array(
305 'labels' => array(
306 'name' => _x( 'Patterns', 'post type general name' ),
307 'singular_name' => _x( 'Pattern', 'post type singular name' ),
308 'add_new' => __( 'Add Pattern' ),
309 'add_new_item' => __( 'Add Pattern' ),
310 'new_item' => __( 'New Pattern' ),
311 'edit_item' => __( 'Edit Block Pattern' ),
312 'view_item' => __( 'View Pattern' ),
313 'view_items' => __( 'View Patterns' ),
314 'all_items' => __( 'All Patterns' ),
315 'search_items' => __( 'Search Patterns' ),
316 'not_found' => __( 'No patterns found.' ),
317 'not_found_in_trash' => __( 'No patterns found in Trash.' ),
318 'filter_items_list' => __( 'Filter patterns list' ),
319 'items_list_navigation' => __( 'Patterns list navigation' ),
320 'items_list' => __( 'Patterns list' ),
321 'item_published' => __( 'Pattern published.' ),
322 'item_published_privately' => __( 'Pattern published privately.' ),
323 'item_reverted_to_draft' => __( 'Pattern reverted to draft.' ),
324 'item_scheduled' => __( 'Pattern scheduled.' ),
325 'item_updated' => __( 'Pattern updated.' ),
326 ),
327 'public' => false,
328 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
329 'show_ui' => true,
330 'show_in_menu' => false,
331 'rewrite' => false,
332 'show_in_rest' => true,
333 'rest_base' => 'blocks',
334 'rest_controller_class' => 'WP_REST_Blocks_Controller',
335 'capability_type' => 'block',
336 'capabilities' => array(
337 // You need to be able to edit posts, in order to read blocks in their raw form.
338 'read' => 'edit_posts',
339 // You need to be able to publish posts, in order to create blocks.
340 'create_posts' => 'publish_posts',
341 'edit_posts' => 'edit_posts',
342 'edit_published_posts' => 'edit_published_posts',
343 'delete_published_posts' => 'delete_published_posts',
344 // Enables trashing draft posts as well.
345 'delete_posts' => 'delete_posts',
346 'edit_others_posts' => 'edit_others_posts',
347 'delete_others_posts' => 'delete_others_posts',
348 ),
349 'map_meta_cap' => true,
350 'supports' => array(
351 'title',
352 'excerpt',
353 'editor',
354 'revisions',
355 'custom-fields',
356 ),
357 )
358 );
359
360 $template_edit_link = 'site-editor.php?' . build_query(
361 array(
362 'postType' => '%s',
363 'postId' => '%s',
364 'canvas' => 'edit',
365 )
366 );
367
368 register_post_type(
369 'wp_template',
370 array(
371 'labels' => array(
372 'name' => _x( 'Templates', 'post type general name' ),
373 'singular_name' => _x( 'Template', 'post type singular name' ),
374 'add_new' => __( 'Add Template' ),
375 'add_new_item' => __( 'Add Template' ),
376 'new_item' => __( 'New Template' ),
377 'edit_item' => __( 'Edit Template' ),
378 'view_item' => __( 'View Template' ),
379 'all_items' => __( 'Templates' ),
380 'search_items' => __( 'Search Templates' ),
381 'parent_item_colon' => __( 'Parent Template:' ),
382 'not_found' => __( 'No templates found.' ),
383 'not_found_in_trash' => __( 'No templates found in Trash.' ),
384 'archives' => __( 'Template archives' ),
385 'insert_into_item' => __( 'Insert into template' ),
386 'uploaded_to_this_item' => __( 'Uploaded to this template' ),
387 'filter_items_list' => __( 'Filter templates list' ),
388 'items_list_navigation' => __( 'Templates list navigation' ),
389 'items_list' => __( 'Templates list' ),
390 'item_updated' => __( 'Template updated.' ),
391 ),
392 'description' => __( 'Templates to include in your theme.' ),
393 'public' => false,
394 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
395 '_edit_link' => $template_edit_link, /* internal use only. don't use this when registering your own post type. */
396 'has_archive' => false,
397 'show_ui' => false,
398 'show_in_menu' => false,
399 'show_in_rest' => true,
400 'rewrite' => false,
401 'rest_base' => 'templates',
402 'rest_controller_class' => 'WP_REST_Templates_Controller',
403 'autosave_rest_controller_class' => 'WP_REST_Template_Autosaves_Controller',
404 'revisions_rest_controller_class' => 'WP_REST_Template_Revisions_Controller',
405 'late_route_registration' => true,
406 'capability_type' => array( 'template', 'templates' ),
407 'capabilities' => array(
408 'create_posts' => 'edit_theme_options',
409 'delete_posts' => 'edit_theme_options',
410 'delete_others_posts' => 'edit_theme_options',
411 'delete_private_posts' => 'edit_theme_options',
412 'delete_published_posts' => 'edit_theme_options',
413 'edit_posts' => 'edit_theme_options',
414 'edit_others_posts' => 'edit_theme_options',
415 'edit_private_posts' => 'edit_theme_options',
416 'edit_published_posts' => 'edit_theme_options',
417 'publish_posts' => 'edit_theme_options',
418 'read' => 'edit_theme_options',
419 'read_private_posts' => 'edit_theme_options',
420 ),
421 'map_meta_cap' => true,
422 'supports' => array(
423 'title',
424 'slug',
425 'excerpt',
426 'editor',
427 'revisions',
428 'author',
429 ),
430 )
431 );
432
433 register_post_type(
434 'wp_template_part',
435 array(
436 'labels' => array(
437 'name' => _x( 'Template Parts', 'post type general name' ),
438 'singular_name' => _x( 'Template Part', 'post type singular name' ),
439 'add_new' => __( 'Add Template Part' ),
440 'add_new_item' => __( 'Add Template Part' ),
441 'new_item' => __( 'New Template Part' ),
442 'edit_item' => __( 'Edit Template Part' ),
443 'view_item' => __( 'View Template Part' ),
444 'all_items' => __( 'Template Parts' ),
445 'search_items' => __( 'Search Template Parts' ),
446 'parent_item_colon' => __( 'Parent Template Part:' ),
447 'not_found' => __( 'No template parts found.' ),
448 'not_found_in_trash' => __( 'No template parts found in Trash.' ),
449 'archives' => __( 'Template part archives' ),
450 'insert_into_item' => __( 'Insert into template part' ),
451 'uploaded_to_this_item' => __( 'Uploaded to this template part' ),
452 'filter_items_list' => __( 'Filter template parts list' ),
453 'items_list_navigation' => __( 'Template parts list navigation' ),
454 'items_list' => __( 'Template parts list' ),
455 'item_updated' => __( 'Template part updated.' ),
456 ),
457 'description' => __( 'Template parts to include in your templates.' ),
458 'public' => false,
459 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
460 '_edit_link' => $template_edit_link, /* internal use only. don't use this when registering your own post type. */
461 'has_archive' => false,
462 'show_ui' => false,
463 'show_in_menu' => false,
464 'show_in_rest' => true,
465 'rewrite' => false,
466 'rest_base' => 'template-parts',
467 'rest_controller_class' => 'WP_REST_Templates_Controller',
468 'autosave_rest_controller_class' => 'WP_REST_Template_Autosaves_Controller',
469 'revisions_rest_controller_class' => 'WP_REST_Template_Revisions_Controller',
470 'late_route_registration' => true,
471 'map_meta_cap' => true,
472 'capabilities' => array(
473 'create_posts' => 'edit_theme_options',
474 'delete_posts' => 'edit_theme_options',
475 'delete_others_posts' => 'edit_theme_options',
476 'delete_private_posts' => 'edit_theme_options',
477 'delete_published_posts' => 'edit_theme_options',
478 'edit_posts' => 'edit_theme_options',
479 'edit_others_posts' => 'edit_theme_options',
480 'edit_private_posts' => 'edit_theme_options',
481 'edit_published_posts' => 'edit_theme_options',
482 'publish_posts' => 'edit_theme_options',
483 'read' => 'edit_theme_options',
484 'read_private_posts' => 'edit_theme_options',
485 ),
486 'supports' => array(
487 'title',
488 'slug',
489 'excerpt',
490 'editor',
491 'revisions',
492 'author',
493 ),
494 )
495 );
496
497 register_post_type(
498 'wp_global_styles',
499 array(
500 'label' => _x( 'Global Styles', 'post type general name' ),
501 'description' => __( 'Global styles to include in themes.' ),
502 'public' => false,
503 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
504 '_edit_link' => '/site-editor.php?canvas=edit', /* internal use only. don't use this when registering your own post type. */
505 'show_ui' => false,
506 'show_in_rest' => true,
507 'rewrite' => false,
508 'rest_base' => 'global-styles',
509 'rest_controller_class' => 'WP_REST_Global_Styles_Controller',
510 'revisions_rest_controller_class' => 'WP_REST_Global_Styles_Revisions_Controller',
511 'late_route_registration' => true,
512 'capabilities' => array(
513 'read' => 'edit_posts',
514 'create_posts' => 'edit_theme_options',
515 'edit_posts' => 'edit_theme_options',
516 'edit_published_posts' => 'edit_theme_options',
517 'delete_published_posts' => 'edit_theme_options',
518 'edit_others_posts' => 'edit_theme_options',
519 'delete_others_posts' => 'edit_theme_options',
520 ),
521 'map_meta_cap' => true,
522 'supports' => array(
523 'title',
524 'editor',
525 'revisions',
526 ),
527 )
528 );
529 // Disable autosave endpoints for global styles.
530 remove_post_type_support( 'wp_global_styles', 'autosave' );
531
532 $navigation_post_edit_link = 'site-editor.php?' . build_query(
533 array(
534 'postId' => '%s',
535 'postType' => 'wp_navigation',
536 'canvas' => 'edit',
537 )
538 );
539
540 register_post_type(
541 'wp_navigation',
542 array(
543 'labels' => array(
544 'name' => _x( 'Navigation Menus', 'post type general name' ),
545 'singular_name' => _x( 'Navigation Menu', 'post type singular name' ),
546 'add_new' => __( 'Add Navigation Menu' ),
547 'add_new_item' => __( 'Add Navigation Menu' ),
548 'new_item' => __( 'New Navigation Menu' ),
549 'edit_item' => __( 'Edit Navigation Menu' ),
550 'view_item' => __( 'View Navigation Menu' ),
551 'all_items' => __( 'Navigation Menus' ),
552 'search_items' => __( 'Search Navigation Menus' ),
553 'parent_item_colon' => __( 'Parent Navigation Menu:' ),
554 'not_found' => __( 'No Navigation Menu found.' ),
555 'not_found_in_trash' => __( 'No Navigation Menu found in Trash.' ),
556 'archives' => __( 'Navigation Menu archives' ),
557 'insert_into_item' => __( 'Insert into Navigation Menu' ),
558 'uploaded_to_this_item' => __( 'Uploaded to this Navigation Menu' ),
559 'filter_items_list' => __( 'Filter Navigation Menu list' ),
560 'items_list_navigation' => __( 'Navigation Menus list navigation' ),
561 'items_list' => __( 'Navigation Menus list' ),
562 ),
563 'description' => __( 'Navigation menus that can be inserted into your site.' ),
564 'public' => false,
565 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
566 '_edit_link' => $navigation_post_edit_link, /* internal use only. don't use this when registering your own post type. */
567 'has_archive' => false,
568 'show_ui' => true,
569 'show_in_menu' => false,
570 'show_in_admin_bar' => false,
571 'show_in_rest' => true,
572 'rewrite' => false,
573 'map_meta_cap' => true,
574 'capabilities' => array(
575 'edit_others_posts' => 'edit_theme_options',
576 'delete_posts' => 'edit_theme_options',
577 'publish_posts' => 'edit_theme_options',
578 'create_posts' => 'edit_theme_options',
579 'read_private_posts' => 'edit_theme_options',
580 'delete_private_posts' => 'edit_theme_options',
581 'delete_published_posts' => 'edit_theme_options',
582 'delete_others_posts' => 'edit_theme_options',
583 'edit_private_posts' => 'edit_theme_options',
584 'edit_published_posts' => 'edit_theme_options',
585 'edit_posts' => 'edit_theme_options',
586 ),
587 'rest_base' => 'navigation',
588 'rest_controller_class' => 'WP_REST_Posts_Controller',
589 'supports' => array(
590 'title',
591 'editor',
592 'revisions',
593 ),
594 )
595 );
596
597 register_post_type(
598 'wp_font_family',
599 array(
600 'labels' => array(
601 'name' => __( 'Font Families' ),
602 'singular_name' => __( 'Font Family' ),
603 ),
604 'public' => false,
605 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
606 'hierarchical' => false,
607 'capabilities' => array(
608 'read' => 'edit_theme_options',
609 'read_private_posts' => 'edit_theme_options',
610 'create_posts' => 'edit_theme_options',
611 'publish_posts' => 'edit_theme_options',
612 'edit_posts' => 'edit_theme_options',
613 'edit_others_posts' => 'edit_theme_options',
614 'edit_published_posts' => 'edit_theme_options',
615 'delete_posts' => 'edit_theme_options',
616 'delete_others_posts' => 'edit_theme_options',
617 'delete_published_posts' => 'edit_theme_options',
618 ),
619 'map_meta_cap' => true,
620 'query_var' => false,
621 'rewrite' => false,
622 'show_in_rest' => true,
623 'rest_base' => 'font-families',
624 'rest_controller_class' => 'WP_REST_Font_Families_Controller',
625 'supports' => array( 'title' ),
626 )
627 );
628
629 register_post_type(
630 'wp_font_face',
631 array(
632 'labels' => array(
633 'name' => __( 'Font Faces' ),
634 'singular_name' => __( 'Font Face' ),
635 ),
636 'public' => false,
637 '_builtin' => true, /* internal use only. don't use this when registering your own post type. */
638 'hierarchical' => false,
639 'capabilities' => array(
640 'read' => 'edit_theme_options',
641 'read_private_posts' => 'edit_theme_options',
642 'create_posts' => 'edit_theme_options',
643 'publish_posts' => 'edit_theme_options',
644 'edit_posts' => 'edit_theme_options',
645 'edit_others_posts' => 'edit_theme_options',
646 'edit_published_posts' => 'edit_theme_options',
647 'delete_posts' => 'edit_theme_options',
648 'delete_others_posts' => 'edit_theme_options',
649 'delete_published_posts' => 'edit_theme_options',
650 ),
651 'map_meta_cap' => true,
652 'query_var' => false,
653 'rewrite' => false,
654 'show_in_rest' => true,
655 'rest_base' => 'font-families/(?P<font_family_id>[\d]+)/font-faces',
656 'rest_controller_class' => 'WP_REST_Font_Faces_Controller',
657 'supports' => array( 'title' ),
658 )
659 );
660
661 register_post_status(
662 'publish',
663 array(
664 'label' => _x( 'Published', 'post status' ),
665 'public' => true,
666 '_builtin' => true, /* internal use only. */
667 /* translators: %s: Number of published posts. */
668 'label_count' => _n_noop(
669 'Published <span class="count">(%s)</span>',
670 'Published <span class="count">(%s)</span>'
671 ),
672 )
673 );
674
675 register_post_status(
676 'future',
677 array(
678 'label' => _x( 'Scheduled', 'post status' ),
679 'protected' => true,
680 '_builtin' => true, /* internal use only. */
681 /* translators: %s: Number of scheduled posts. */
682 'label_count' => _n_noop(
683 'Scheduled <span class="count">(%s)</span>',
684 'Scheduled <span class="count">(%s)</span>'
685 ),
686 )
687 );
688
689 register_post_status(
690 'draft',
691 array(
692 'label' => _x( 'Draft', 'post status' ),
693 'protected' => true,
694 '_builtin' => true, /* internal use only. */
695 /* translators: %s: Number of draft posts. */
696 'label_count' => _n_noop(
697 'Draft <span class="count">(%s)</span>',
698 'Drafts <span class="count">(%s)</span>'
699 ),
700 'date_floating' => true,
701 )
702 );
703
704 register_post_status(
705 'pending',
706 array(
707 'label' => _x( 'Pending', 'post status' ),
708 'protected' => true,
709 '_builtin' => true, /* internal use only. */
710 /* translators: %s: Number of pending posts. */
711 'label_count' => _n_noop(
712 'Pending <span class="count">(%s)</span>',
713 'Pending <span class="count">(%s)</span>'
714 ),
715 'date_floating' => true,
716 )
717 );
718
719 register_post_status(
720 'private',
721 array(
722 'label' => _x( 'Private', 'post status' ),
723 'private' => true,
724 '_builtin' => true, /* internal use only. */
725 /* translators: %s: Number of private posts. */
726 'label_count' => _n_noop(
727 'Private <span class="count">(%s)</span>',
728 'Private <span class="count">(%s)</span>'
729 ),
730 )
731 );
732
733 register_post_status(
734 'trash',
735 array(
736 'label' => _x( 'Trash', 'post status' ),
737 'internal' => true,
738 '_builtin' => true, /* internal use only. */
739 /* translators: %s: Number of trashed posts. */
740 'label_count' => _n_noop(
741 'Trash <span class="count">(%s)</span>',
742 'Trash <span class="count">(%s)</span>'
743 ),
744 'show_in_admin_status_list' => true,
745 )
746 );
747
748 register_post_status(
749 'auto-draft',
750 array(
751 'label' => 'auto-draft',
752 'internal' => true,
753 '_builtin' => true, /* internal use only. */
754 'date_floating' => true,
755 )
756 );
757
758 register_post_status(
759 'inherit',
760 array(
761 'label' => 'inherit',
762 'internal' => true,
763 '_builtin' => true, /* internal use only. */
764 'exclude_from_search' => false,
765 )
766 );
767
768 register_post_status(
769 'request-pending',
770 array(
771 'label' => _x( 'Pending', 'request status' ),
772 'internal' => true,
773 '_builtin' => true, /* internal use only. */
774 /* translators: %s: Number of pending requests. */
775 'label_count' => _n_noop(
776 'Pending <span class="count">(%s)</span>',
777 'Pending <span class="count">(%s)</span>'
778 ),
779 'exclude_from_search' => false,
780 )
781 );
782
783 register_post_status(
784 'request-confirmed',
785 array(
786 'label' => _x( 'Confirmed', 'request status' ),
787 'internal' => true,
788 '_builtin' => true, /* internal use only. */
789 /* translators: %s: Number of confirmed requests. */
790 'label_count' => _n_noop(
791 'Confirmed <span class="count">(%s)</span>',
792 'Confirmed <span class="count">(%s)</span>'
793 ),
794 'exclude_from_search' => false,
795 )
796 );
797
798 register_post_status(
799 'request-failed',
800 array(
801 'label' => _x( 'Failed', 'request status' ),
802 'internal' => true,
803 '_builtin' => true, /* internal use only. */
804 /* translators: %s: Number of failed requests. */
805 'label_count' => _n_noop(
806 'Failed <span class="count">(%s)</span>',
807 'Failed <span class="count">(%s)</span>'
808 ),
809 'exclude_from_search' => false,
810 )
811 );
812
813 register_post_status(
814 'request-completed',
815 array(
816 'label' => _x( 'Completed', 'request status' ),
817 'internal' => true,
818 '_builtin' => true, /* internal use only. */
819 /* translators: %s: Number of completed requests. */
820 'label_count' => _n_noop(
821 'Completed <span class="count">(%s)</span>',
822 'Completed <span class="count">(%s)</span>'
823 ),
824 'exclude_from_search' => false,
825 )
826 );
827}
828
829/**
830 * Retrieves attached file path based on attachment ID.
831 *
832 * By default the path will go through the {@see 'get_attached_file'} filter, but
833 * passing `true` to the `$unfiltered` argument will return the file path unfiltered.
834 *
835 * The function works by retrieving the `_wp_attached_file` post meta value.
836 * This is a convenience function to prevent looking up the meta name and provide
837 * a mechanism for sending the attached filename through a filter.
838 *
839 * @since 2.0.0
840 *
841 * @param int $attachment_id Attachment ID.
842 * @param bool $unfiltered Optional. Whether to skip the {@see 'get_attached_file'} filter.
843 * Default false.
844 * @return string|false The file path to where the attached file should be, false otherwise.
845 */
846function get_attached_file( $attachment_id, $unfiltered = false ) {
847 $file = get_post_meta( $attachment_id, '_wp_attached_file', true );
848
849 // If the file is relative, prepend upload dir.
850 if ( $file && ! str_starts_with( $file, '/' ) && ! preg_match( '|^.:\\\|', $file ) ) {
851 $uploads = wp_get_upload_dir();
852 if ( false === $uploads['error'] ) {
853 $file = $uploads['basedir'] . "/$file";
854 }
855 }
856
857 if ( $unfiltered ) {
858 return $file;
859 }
860
861 /**
862 * Filters the attached file based on the given ID.
863 *
864 * @since 2.1.0
865 *
866 * @param string|false $file The file path to where the attached file should be, false otherwise.
867 * @param int $attachment_id Attachment ID.
868 */
869 return apply_filters( 'get_attached_file', $file, $attachment_id );
870}
871
872/**
873 * Updates attachment file path based on attachment ID.
874 *
875 * Used to update the file path of the attachment, which uses post meta name
876 * `_wp_attached_file` to store the path of the attachment.
877 *
878 * @since 2.1.0
879 *
880 * @param int $attachment_id Attachment ID.
881 * @param string $file File path for the attachment.
882 * @return int|bool Meta ID if the `_wp_attached_file` key didn't exist for the attachment.
883 * True on successful update, false on failure or if the `$file` value passed
884 * to the function is the same as the one that is already in the database.
885 */
886function update_attached_file( $attachment_id, $file ) {
887 if ( ! get_post( $attachment_id ) ) {
888 return false;
889 }
890
891 /**
892 * Filters the path to the attached file to update.
893 *
894 * @since 2.1.0
895 *
896 * @param string $file Path to the attached file to update.
897 * @param int $attachment_id Attachment ID.
898 */
899 $file = apply_filters( 'update_attached_file', $file, $attachment_id );
900
901 $file = _wp_relative_upload_path( $file );
902 if ( $file ) {
903 return update_post_meta( $attachment_id, '_wp_attached_file', $file );
904 } else {
905 return delete_post_meta( $attachment_id, '_wp_attached_file' );
906 }
907}
908
909/**
910 * Returns relative path to an uploaded file.
911 *
912 * The path is relative to the current upload dir.
913 *
914 * @since 2.9.0
915 * @access private
916 *
917 * @param string $path Full path to the file.
918 * @return string Relative path on success, unchanged path on failure.
919 */
920function _wp_relative_upload_path( $path ) {
921 $new_path = $path;
922
923 $uploads = wp_get_upload_dir();
924 if ( str_starts_with( $new_path, $uploads['basedir'] ) ) {
925 $new_path = str_replace( $uploads['basedir'], '', $new_path );
926 $new_path = ltrim( $new_path, '/' );
927 }
928
929 /**
930 * Filters the relative path to an uploaded file.
931 *
932 * @since 2.9.0
933 *
934 * @param string $new_path Relative path to the file.
935 * @param string $path Full path to the file.
936 */
937 return apply_filters( '_wp_relative_upload_path', $new_path, $path );
938}
939
940/**
941 * Retrieves all children of the post parent ID.
942 *
943 * Normally, without any enhancements, the children would apply to pages. In the
944 * context of the inner workings of WordPress, pages, posts, and attachments
945 * share the same table, so therefore the functionality could apply to any one
946 * of them. It is then noted that while this function does not work on posts, it
947 * does not mean that it won't work on posts. It is recommended that you know
948 * what context you wish to retrieve the children of.
949 *
950 * Attachments may also be made the child of a post, so if that is an accurate
951 * statement (which needs to be verified), it would then be possible to get
952 * all of the attachments for a post. Attachments have since changed since
953 * version 2.5, so this is most likely inaccurate, but serves generally as an
954 * example of what is possible.
955 *
956 * The arguments listed as defaults are for this function and also of the
957 * get_posts() function. The arguments are combined with the get_children defaults
958 * and are then passed to the get_posts() function, which accepts additional arguments.
959 * You can replace the defaults in this function, listed below and the additional
960 * arguments listed in the get_posts() function.
961 *
962 * The 'post_parent' is the most important argument and important attention
963 * needs to be paid to the $args parameter. If you pass either an object or an
964 * integer (number), then just the 'post_parent' is grabbed and everything else
965 * is lost. If you don't specify any arguments, then it is assumed that you are
966 * in The Loop and the post parent will be grabbed for from the current post.
967 *
968 * The 'post_parent' argument is the ID to get the children. The 'numberposts'
969 * is the amount of posts to retrieve that has a default of '-1', which is
970 * used to get all of the posts. Giving a number higher than 0 will only
971 * retrieve that amount of posts.
972 *
973 * The 'post_type' and 'post_status' arguments can be used to choose what
974 * criteria of posts to retrieve. The 'post_type' can be anything, but WordPress
975 * post types are 'post', 'pages', and 'attachments'. The 'post_status'
976 * argument will accept any post status within the write administration panels.
977 *
978 * @since 2.0.0
979 *
980 * @see get_posts()
981 * @todo Check validity of description.
982 *
983 * @global WP_Post $post Global post object.
984 *
985 * @param mixed $args Optional. User defined arguments for replacing the defaults. Default empty.
986 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
987 * correspond to a WP_Post object, an associative array, or a numeric array,
988 * respectively. Default OBJECT.
989 * @return WP_Post[]|array[]|int[] Array of post objects, arrays, or IDs, depending on `$output`.
990 */
991function get_children( $args = '', $output = OBJECT ) {
992 $kids = array();
993 if ( empty( $args ) ) {
994 if ( isset( $GLOBALS['post'] ) ) {
995 $args = array( 'post_parent' => (int) $GLOBALS['post']->post_parent );
996 } else {
997 return $kids;
998 }
999 } elseif ( is_object( $args ) ) {
1000 $args = array( 'post_parent' => (int) $args->post_parent );
1001 } elseif ( is_numeric( $args ) ) {
1002 $args = array( 'post_parent' => (int) $args );
1003 }
1004
1005 $defaults = array(
1006 'numberposts' => -1,
1007 'post_type' => 'any',
1008 'post_status' => 'any',
1009 'post_parent' => 0,
1010 );
1011
1012 $parsed_args = wp_parse_args( $args, $defaults );
1013
1014 $children = get_posts( $parsed_args );
1015
1016 if ( ! $children ) {
1017 return $kids;
1018 }
1019
1020 if ( ! empty( $parsed_args['fields'] ) ) {
1021 return $children;
1022 }
1023
1024 update_post_cache( $children );
1025
1026 foreach ( $children as $key => $child ) {
1027 $kids[ $child->ID ] = $children[ $key ];
1028 }
1029
1030 if ( OBJECT === $output ) {
1031 return $kids;
1032 } elseif ( ARRAY_A === $output ) {
1033 $weeuns = array();
1034 foreach ( (array) $kids as $kid ) {
1035 $weeuns[ $kid->ID ] = get_object_vars( $kids[ $kid->ID ] );
1036 }
1037 return $weeuns;
1038 } elseif ( ARRAY_N === $output ) {
1039 $babes = array();
1040 foreach ( (array) $kids as $kid ) {
1041 $babes[ $kid->ID ] = array_values( get_object_vars( $kids[ $kid->ID ] ) );
1042 }
1043 return $babes;
1044 } else {
1045 return $kids;
1046 }
1047}
1048
1049/**
1050 * Gets extended entry info (<!--more-->).
1051 *
1052 * There should not be any space after the second dash and before the word
1053 * 'more'. There can be text or space(s) after the word 'more', but won't be
1054 * referenced.
1055 *
1056 * The returned array has 'main', 'extended', and 'more_text' keys. Main has the text before
1057 * the `<!--more-->`. The 'extended' key has the content after the
1058 * `<!--more-->` comment. The 'more_text' key has the custom "Read More" text.
1059 *
1060 * @since 1.0.0
1061 *
1062 * @param string $post Post content.
1063 * @return string[] {
1064 * Extended entry info.
1065 *
1066 * @type string $main Content before the more tag.
1067 * @type string $extended Content after the more tag.
1068 * @type string $more_text Custom read more text, or empty string.
1069 * }
1070 */
1071function get_extended( $post ) {
1072 // Match the new style more links.
1073 if ( preg_match( '/<!--more(.*?)?-->/', $post, $matches ) ) {
1074 list($main, $extended) = explode( $matches[0], $post, 2 );
1075 $more_text = $matches[1];
1076 } else {
1077 $main = $post;
1078 $extended = '';
1079 $more_text = '';
1080 }
1081
1082 // Leading and trailing whitespace.
1083 $main = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $main );
1084 $extended = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $extended );
1085 $more_text = preg_replace( '/^[\s]*(.*)[\s]*$/', '\\1', $more_text );
1086
1087 return array(
1088 'main' => $main,
1089 'extended' => $extended,
1090 'more_text' => $more_text,
1091 );
1092}
1093
1094/**
1095 * Retrieves post data given a post ID or post object.
1096 *
1097 * See sanitize_post() for optional $filter values. Also, the parameter
1098 * `$post`, must be given as a variable, since it is passed by reference.
1099 *
1100 * @since 1.5.1
1101 *
1102 * @global WP_Post $post Global post object.
1103 *
1104 * @param int|WP_Post|null $post Optional. Post ID or post object. `null`, `false`, `0` and other PHP falsey values
1105 * return the current global post inside the loop. A numerically valid post ID that
1106 * points to a non-existent post returns `null`. Defaults to global $post.
1107 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
1108 * correspond to a WP_Post object, an associative array, or a numeric array,
1109 * respectively. Default OBJECT.
1110 * @param string $filter Optional. Type of filter to apply. Accepts 'raw', 'edit', 'db',
1111 * or 'display'. Default 'raw'.
1112 * @return WP_Post|array|null Type corresponding to $output on success or null on failure.
1113 * When $output is OBJECT, a `WP_Post` instance is returned.
1114 */
1115function get_post( $post = null, $output = OBJECT, $filter = 'raw' ) {
1116 if ( empty( $post ) && isset( $GLOBALS['post'] ) ) {
1117 $post = $GLOBALS['post'];
1118 }
1119
1120 if ( $post instanceof WP_Post ) {
1121 $_post = $post;
1122 } elseif ( is_object( $post ) ) {
1123 if ( empty( $post->filter ) ) {
1124 $_post = sanitize_post( $post, 'raw' );
1125 $_post = new WP_Post( $_post );
1126 } elseif ( 'raw' === $post->filter ) {
1127 $_post = new WP_Post( $post );
1128 } else {
1129 $_post = WP_Post::get_instance( $post->ID );
1130 }
1131 } else {
1132 $_post = WP_Post::get_instance( $post );
1133 }
1134
1135 if ( ! $_post ) {
1136 return null;
1137 }
1138
1139 $_post = $_post->filter( $filter );
1140
1141 if ( ARRAY_A === $output ) {
1142 return $_post->to_array();
1143 } elseif ( ARRAY_N === $output ) {
1144 return array_values( $_post->to_array() );
1145 }
1146
1147 return $_post;
1148}
1149
1150/**
1151 * Retrieves the IDs of the ancestors of a post.
1152 *
1153 * @since 2.5.0
1154 *
1155 * @param int|WP_Post $post Post ID or post object.
1156 * @return int[] Array of ancestor IDs or empty array if there are none.
1157 */
1158function get_post_ancestors( $post ) {
1159 $post = get_post( $post );
1160
1161 if ( ! $post || empty( $post->post_parent ) || $post->post_parent === $post->ID ) {
1162 return array();
1163 }
1164
1165 $ancestors = array();
1166
1167 $id = $post->post_parent;
1168 $ancestors[] = $id;
1169
1170 while ( $ancestor = get_post( $id ) ) {
1171 // Loop detection: If the ancestor has been seen before, break.
1172 if ( empty( $ancestor->post_parent ) || $ancestor->post_parent === $post->ID
1173 || in_array( $ancestor->post_parent, $ancestors, true )
1174 ) {
1175 break;
1176 }
1177
1178 $id = $ancestor->post_parent;
1179 $ancestors[] = $id;
1180 }
1181
1182 return $ancestors;
1183}
1184
1185/**
1186 * Retrieves data from a post field based on Post ID.
1187 *
1188 * Examples of the post field will be, 'post_type', 'post_status', 'post_content',
1189 * etc and based off of the post object property or key names.
1190 *
1191 * The context values are based off of the taxonomy filter functions and
1192 * supported values are found within those functions.
1193 *
1194 * @since 2.3.0
1195 * @since 4.5.0 The `$post` parameter was made optional.
1196 *
1197 * @see sanitize_post_field()
1198 *
1199 * @param string $field Post field name.
1200 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post.
1201 * @param string $context Optional. How to filter the field. Accepts 'raw', 'edit', 'db',
1202 * or 'display'. Default 'display'.
1203 * @return int|string|int[] The value of the post field on success, empty string on failure.
1204 */
1205function get_post_field( $field, $post = null, $context = 'display' ) {
1206 $post = get_post( $post );
1207
1208 if ( ! $post ) {
1209 return '';
1210 }
1211
1212 if ( ! isset( $post->$field ) ) {
1213 return '';
1214 }
1215
1216 return sanitize_post_field( $field, $post->$field, $post->ID, $context );
1217}
1218
1219/**
1220 * Retrieves the mime type of an attachment based on the ID.
1221 *
1222 * This function can be used with any post type, but it makes more sense with
1223 * attachments.
1224 *
1225 * @since 2.0.0
1226 *
1227 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post.
1228 * @return string|false The mime type on success, false on failure.
1229 */
1230function get_post_mime_type( $post = null ) {
1231 $post = get_post( $post );
1232
1233 if ( is_object( $post ) ) {
1234 return $post->post_mime_type;
1235 }
1236
1237 return false;
1238}
1239
1240/**
1241 * Retrieves the post status based on the post ID.
1242 *
1243 * If the post ID is of an attachment, then the parent post status will be given
1244 * instead.
1245 *
1246 * @since 2.0.0
1247 *
1248 * @param int|WP_Post $post Optional. Post ID or post object. Defaults to global $post.
1249 * @return string|false Post status on success, false on failure.
1250 */
1251function get_post_status( $post = null ) {
1252 // Normalize the post object if necessary, skip normalization if called from get_sample_permalink().
1253 if ( ! $post instanceof WP_Post || ! isset( $post->filter ) || 'sample' !== $post->filter ) {
1254 $post = get_post( $post );
1255 }
1256
1257 if ( ! is_object( $post ) ) {
1258 return false;
1259 }
1260
1261 $post_status = $post->post_status;
1262
1263 if (
1264 'attachment' === $post->post_type &&
1265 'inherit' === $post_status
1266 ) {
1267 if (
1268 0 === $post->post_parent ||
1269 ! get_post( $post->post_parent ) ||
1270 $post->ID === $post->post_parent
1271 ) {
1272 // Unattached attachments with inherit status are assumed to be published.
1273 $post_status = 'publish';
1274 } elseif ( 'trash' === get_post_status( $post->post_parent ) ) {
1275 // Get parent status prior to trashing.
1276 $post_status = get_post_meta( $post->post_parent, '_wp_trash_meta_status', true );
1277
1278 if ( ! $post_status ) {
1279 // Assume publish as above.
1280 $post_status = 'publish';
1281 }
1282 } else {
1283 $post_status = get_post_status( $post->post_parent );
1284 }
1285 } elseif (
1286 'attachment' === $post->post_type &&
1287 ! in_array( $post_status, array( 'private', 'trash', 'auto-draft' ), true )
1288 ) {
1289 /*
1290 * Ensure uninherited attachments have a permitted status either 'private', 'trash', 'auto-draft'.
1291 * This is to match the logic in wp_insert_post().
1292 *
1293 * Note: 'inherit' is excluded from this check as it is resolved to the parent post's
1294 * status in the logic block above.
1295 */
1296 $post_status = 'publish';
1297 }
1298
1299 /**
1300 * Filters the post status.
1301 *
1302 * @since 4.4.0
1303 * @since 5.7.0 The attachment post type is now passed through this filter.
1304 *
1305 * @param string $post_status The post status.
1306 * @param WP_Post $post The post object.
1307 */
1308 return apply_filters( 'get_post_status', $post_status, $post );
1309}
1310
1311/**
1312 * Retrieves all of the WordPress supported post statuses.
1313 *
1314 * Posts have a limited set of valid status values, this provides the
1315 * post_status values and descriptions.
1316 *
1317 * @since 2.5.0
1318 *
1319 * @return string[] Array of post status labels keyed by their status.
1320 */
1321function get_post_statuses() {
1322 $status = array(
1323 'draft' => __( 'Draft' ),
1324 'pending' => __( 'Pending Review' ),
1325 'private' => __( 'Private' ),
1326 'publish' => __( 'Published' ),
1327 );
1328
1329 return $status;
1330}
1331
1332/**
1333 * Retrieves all of the WordPress support page statuses.
1334 *
1335 * Pages have a limited set of valid status values, this provides the
1336 * post_status values and descriptions.
1337 *
1338 * @since 2.5.0
1339 *
1340 * @return string[] Array of page status labels keyed by their status.
1341 */
1342function get_page_statuses() {
1343 $status = array(
1344 'draft' => __( 'Draft' ),
1345 'private' => __( 'Private' ),
1346 'publish' => __( 'Published' ),
1347 );
1348
1349 return $status;
1350}
1351
1352/**
1353 * Returns statuses for privacy requests.
1354 *
1355 * @since 4.9.6
1356 * @access private
1357 *
1358 * @return string[] Array of privacy request status labels keyed by their status.
1359 */
1360function _wp_privacy_statuses() {
1361 return array(
1362 'request-pending' => _x( 'Pending', 'request status' ), // Pending confirmation from user.
1363 'request-confirmed' => _x( 'Confirmed', 'request status' ), // User has confirmed the action.
1364 'request-failed' => _x( 'Failed', 'request status' ), // User failed to confirm the action.
1365 'request-completed' => _x( 'Completed', 'request status' ), // Admin has handled the request.
1366 );
1367}
1368
1369/**
1370 * Registers a post status. Do not use before init.
1371 *
1372 * A simple function for creating or modifying a post status based on the
1373 * parameters given. The function will accept an array (second optional
1374 * parameter), along with a string for the post status name.
1375 *
1376 * Arguments prefixed with an _underscore shouldn't be used by plugins and themes.
1377 *
1378 * @since 3.0.0
1379 *
1380 * @global stdClass[] $wp_post_statuses Inserts new post status object into the list
1381 *
1382 * @param string $post_status Name of the post status.
1383 * @param array|string $args {
1384 * Optional. Array or string of post status arguments.
1385 *
1386 * @type bool|string $label A descriptive name for the post status marked
1387 * for translation. Defaults to value of $post_status.
1388 * @type array|false $label_count Nooped plural text from _n_noop() to provide the singular
1389 * and plural forms of the label for counts. Default false
1390 * which means the `$label` argument will be used for both
1391 * the singular and plural forms of this label.
1392 * @type bool $exclude_from_search Whether to exclude posts with this post status
1393 * from search results. Default is value of $internal.
1394 * @type bool $_builtin Whether the status is built-in. Core-use only.
1395 * Default false.
1396 * @type bool $public Whether posts of this status should be shown
1397 * in the front end of the site. Default false.
1398 * @type bool $internal Whether the status is for internal use only.
1399 * Default false.
1400 * @type bool $protected Whether posts with this status should be protected.
1401 * Default false.
1402 * @type bool $private Whether posts with this status should be private.
1403 * Default false.
1404 * @type bool $publicly_queryable Whether posts with this status should be publicly-
1405 * queryable. Default is value of $public.
1406 * @type bool $show_in_admin_all_list Whether to include posts in the edit listing for
1407 * their post type. Default is the opposite value
1408 * of $internal.
1409 * @type bool $show_in_admin_status_list Show in the list of statuses with post counts at
1410 * the top of the edit listings,
1411 * e.g. All (12) | Published (9) | My Custom Status (2)
1412 * Default is the opposite value of $internal.
1413 * @type bool $date_floating Whether the post has a floating creation date.
1414 * Default to false.
1415 * }
1416 * @return object
1417 */
1418function register_post_status( $post_status, $args = array() ) {
1419 global $wp_post_statuses;
1420
1421 if ( ! is_array( $wp_post_statuses ) ) {
1422 $wp_post_statuses = array();
1423 }
1424
1425 // Args prefixed with an underscore are reserved for internal use.
1426 $defaults = array(
1427 'label' => false,
1428 'label_count' => false,
1429 'exclude_from_search' => null,
1430 '_builtin' => false,
1431 'public' => null,
1432 'internal' => null,
1433 'protected' => null,
1434 'private' => null,
1435 'publicly_queryable' => null,
1436 'show_in_admin_status_list' => null,
1437 'show_in_admin_all_list' => null,
1438 'date_floating' => null,
1439 );
1440 $args = wp_parse_args( $args, $defaults );
1441 $args = (object) $args;
1442
1443 $post_status = sanitize_key( $post_status );
1444 $args->name = $post_status;
1445
1446 // Set various defaults.
1447 if ( null === $args->public && null === $args->internal && null === $args->protected && null === $args->private ) {
1448 $args->internal = true;
1449 }
1450
1451 if ( null === $args->public ) {
1452 $args->public = false;
1453 }
1454
1455 if ( null === $args->private ) {
1456 $args->private = false;
1457 }
1458
1459 if ( null === $args->protected ) {
1460 $args->protected = false;
1461 }
1462
1463 if ( null === $args->internal ) {
1464 $args->internal = false;
1465 }
1466
1467 if ( null === $args->publicly_queryable ) {
1468 $args->publicly_queryable = $args->public;
1469 }
1470
1471 if ( null === $args->exclude_from_search ) {
1472 $args->exclude_from_search = $args->internal;
1473 }
1474
1475 if ( null === $args->show_in_admin_all_list ) {
1476 $args->show_in_admin_all_list = ! $args->internal;
1477 }
1478
1479 if ( null === $args->show_in_admin_status_list ) {
1480 $args->show_in_admin_status_list = ! $args->internal;
1481 }
1482
1483 if ( null === $args->date_floating ) {
1484 $args->date_floating = false;
1485 }
1486
1487 if ( false === $args->label ) {
1488 $args->label = $post_status;
1489 }
1490
1491 if ( false === $args->label_count ) {
1492 // phpcs:ignore WordPress.WP.I18n.NonSingularStringLiteralSingular,WordPress.WP.I18n.NonSingularStringLiteralPlural
1493 $args->label_count = _n_noop( $args->label, $args->label );
1494 }
1495
1496 $wp_post_statuses[ $post_status ] = $args;
1497
1498 return $args;
1499}
1500
1501/**
1502 * Retrieves a post status object by name.
1503 *
1504 * @since 3.0.0
1505 *
1506 * @see register_post_status()
1507 *
1508 * @global stdClass[] $wp_post_statuses List of post statuses.
1509 *
1510 * @param string $post_status The name of a registered post status.
1511 * @return stdClass|null A post status object.
1512 */
1513function get_post_status_object( $post_status ) {
1514 global $wp_post_statuses;
1515
1516 if ( ! is_string( $post_status ) || empty( $wp_post_statuses[ $post_status ] ) ) {
1517 return null;
1518 }
1519
1520 return $wp_post_statuses[ $post_status ];
1521}
1522
1523/**
1524 * Gets a list of post statuses.
1525 *
1526 * @since 3.0.0
1527 *
1528 * @see register_post_status()
1529 *
1530 * @global stdClass[] $wp_post_statuses List of post statuses.
1531 *
1532 * @param array|string $args Optional. Array or string of post status arguments to compare against
1533 * properties of the global `$wp_post_statuses objects`. Default empty array.
1534 * @param string $output Optional. The type of output to return, either 'names' or 'objects'. Default 'names'.
1535 * @param string $operator Optional. The logical operation to perform. 'or' means only one element
1536 * from the array needs to match; 'and' means all elements must match.
1537 * Default 'and'.
1538 * @return string[]|stdClass[] A list of post status names or objects.
1539 */
1540function get_post_stati( $args = array(), $output = 'names', $operator = 'and' ) {
1541 global $wp_post_statuses;
1542
1543 $field = ( 'names' === $output ) ? 'name' : false;
1544
1545 return wp_filter_object_list( $wp_post_statuses, $args, $operator, $field );
1546}
1547
1548/**
1549 * Determines whether the post type is hierarchical.
1550 *
1551 * A false return value might also mean that the post type does not exist.
1552 *
1553 * @since 3.0.0
1554 *
1555 * @see get_post_type_object()
1556 *
1557 * @param string $post_type Post type name
1558 * @return bool Whether post type is hierarchical.
1559 */
1560function is_post_type_hierarchical( $post_type ) {
1561 if ( ! post_type_exists( $post_type ) ) {
1562 return false;
1563 }
1564
1565 $post_type = get_post_type_object( $post_type );
1566 return $post_type->hierarchical;
1567}
1568
1569/**
1570 * Determines whether a post type is registered.
1571 *
1572 * For more information on this and similar theme functions, check out
1573 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
1574 * Conditional Tags} article in the Theme Developer Handbook.
1575 *
1576 * @since 3.0.0
1577 *
1578 * @see get_post_type_object()
1579 *
1580 * @param string $post_type Post type name.
1581 * @return bool Whether post type is registered.
1582 */
1583function post_type_exists( $post_type ) {
1584 return (bool) get_post_type_object( $post_type );
1585}
1586
1587/**
1588 * Retrieves the post type of the current post or of a given post.
1589 *
1590 * @since 2.1.0
1591 *
1592 * @param int|WP_Post|null $post Optional. Post ID or post object. Default is global $post.
1593 * @return string|false Post type on success, false on failure.
1594 */
1595function get_post_type( $post = null ) {
1596 $post = get_post( $post );
1597 if ( $post ) {
1598 return $post->post_type;
1599 }
1600
1601 return false;
1602}
1603
1604/**
1605 * Retrieves a post type object by name.
1606 *
1607 * @since 3.0.0
1608 * @since 4.6.0 Object returned is now an instance of `WP_Post_Type`.
1609 *
1610 * @see register_post_type()
1611 *
1612 * @global array $wp_post_types List of post types.
1613 *
1614 * @param string $post_type The name of a registered post type.
1615 * @return WP_Post_Type|null WP_Post_Type object if it exists, null otherwise.
1616 */
1617function get_post_type_object( $post_type ) {
1618 global $wp_post_types;
1619
1620 if ( ! is_scalar( $post_type ) || empty( $wp_post_types[ $post_type ] ) ) {
1621 return null;
1622 }
1623
1624 return $wp_post_types[ $post_type ];
1625}
1626
1627/**
1628 * Gets a list of all registered post type objects.
1629 *
1630 * @since 2.9.0
1631 *
1632 * @see register_post_type() for accepted arguments.
1633 *
1634 * @global array $wp_post_types List of post types.
1635 *
1636 * @param array|string $args Optional. An array of key => value arguments to match against
1637 * the post type objects. Default empty array.
1638 * @param string $output Optional. The type of output to return. Either 'names'
1639 * or 'objects'. Default 'names'.
1640 * @param string $operator Optional. The logical operation to perform. 'or' means only one
1641 * element from the array needs to match; 'and' means all elements
1642 * must match; 'not' means no elements may match. Default 'and'.
1643 * @return string[]|WP_Post_Type[] An array of post type names or objects.
1644 */
1645function get_post_types( $args = array(), $output = 'names', $operator = 'and' ) {
1646 global $wp_post_types;
1647
1648 $field = ( 'names' === $output ) ? 'name' : false;
1649
1650 return wp_filter_object_list( $wp_post_types, $args, $operator, $field );
1651}
1652
1653/**
1654 * Registers a post type.
1655 *
1656 * Note: Post type registrations should not be hooked before the
1657 * {@see 'init'} action. Also, any taxonomy connections should be
1658 * registered via the `$taxonomies` argument to ensure consistency
1659 * when hooks such as {@see 'parse_query'} or {@see 'pre_get_posts'}
1660 * are used.
1661 *
1662 * Post types can support any number of built-in core features such
1663 * as meta boxes, custom fields, post thumbnails, post statuses,
1664 * comments, and more. See the `$supports` argument for a complete
1665 * list of supported features.
1666 *
1667 * @since 2.9.0
1668 * @since 3.0.0 The `show_ui` argument is now enforced on the new post screen.
1669 * @since 4.4.0 The `show_ui` argument is now enforced on the post type listing
1670 * screen and post editing screen.
1671 * @since 4.6.0 Post type object returned is now an instance of `WP_Post_Type`.
1672 * @since 4.7.0 Introduced `show_in_rest`, `rest_base` and `rest_controller_class`
1673 * arguments to register the post type in REST API.
1674 * @since 5.0.0 The `template` and `template_lock` arguments were added.
1675 * @since 5.3.0 The `supports` argument will now accept an array of arguments for a feature.
1676 * @since 5.9.0 The `rest_namespace` argument was added.
1677 *
1678 * @global array $wp_post_types List of post types.
1679 *
1680 * @param string $post_type Post type key. Must not exceed 20 characters and may only contain
1681 * lowercase alphanumeric characters, dashes, and underscores. See sanitize_key().
1682 * @param array|string $args {
1683 * Array or string of arguments for registering a post type.
1684 *
1685 * @type string $label Name of the post type shown in the menu. Usually plural.
1686 * Default is value of $labels['name'].
1687 * @type string[] $labels An array of labels for this post type. If not set, post
1688 * labels are inherited for non-hierarchical types and page
1689 * labels for hierarchical ones. See get_post_type_labels() for a full
1690 * list of supported labels.
1691 * @type string $description A short descriptive summary of what the post type is.
1692 * Default empty.
1693 * @type bool $public Whether a post type is intended for use publicly either via
1694 * the admin interface or by front-end users. While the default
1695 * settings of $exclude_from_search, $publicly_queryable, $show_ui,
1696 * and $show_in_nav_menus are inherited from $public, each does not
1697 * rely on this relationship and controls a very specific intention.
1698 * Default false.
1699 * @type bool $hierarchical Whether the post type is hierarchical (e.g. page). Default false.
1700 * @type bool $exclude_from_search Whether to exclude posts with this post type from front end search
1701 * results. Default is the opposite value of $public.
1702 * @type bool $publicly_queryable Whether queries can be performed on the front end for the post type
1703 * as part of parse_request(). Endpoints would include:
1704 * * ?post_type={post_type_key}
1705 * * ?{post_type_key}={single_post_slug}
1706 * * ?{post_type_query_var}={single_post_slug}
1707 * If not set, the default is inherited from $public.
1708 * @type bool $show_ui Whether to generate and allow a UI for managing this post type in the
1709 * admin. Default is value of $public.
1710 * @type bool|string $show_in_menu Where to show the post type in the admin menu. To work, $show_ui
1711 * must be true. If true, the post type is shown in its own top level
1712 * menu. If false, no menu is shown. If a string of an existing top
1713 * level menu ('tools.php' or 'edit.php?post_type=page', for example), the
1714 * post type will be placed as a sub-menu of that.
1715 * Default is value of $show_ui.
1716 * @type bool $show_in_nav_menus Makes this post type available for selection in navigation menus.
1717 * Default is value of $public.
1718 * @type bool $show_in_admin_bar Makes this post type available via the admin bar. Default is value
1719 * of $show_in_menu.
1720 * @type bool $show_in_rest Whether to include the post type in the REST API. Set this to true
1721 * for the post type to be available in the block editor.
1722 * @type string $rest_base To change the base URL of REST API route. Default is $post_type.
1723 * @type string $rest_namespace To change the namespace URL of REST API route. Default is wp/v2.
1724 * @type string $rest_controller_class REST API controller class name. Default is 'WP_REST_Posts_Controller'.
1725 * @type string|bool $autosave_rest_controller_class REST API controller class name. Default is 'WP_REST_Autosaves_Controller'.
1726 * @type string|bool $revisions_rest_controller_class REST API controller class name. Default is 'WP_REST_Revisions_Controller'.
1727 * @type bool $late_route_registration A flag to direct the REST API controllers for autosave / revisions
1728 * should be registered before/after the post type controller.
1729 * @type int $menu_position The position in the menu order the post type should appear. To work,
1730 * $show_in_menu must be true. Default null (at the bottom).
1731 * @type string $menu_icon The URL to the icon to be used for this menu. Pass a base64-encoded
1732 * SVG using a data URI, which will be colored to match the color scheme
1733 * -- this should begin with 'data:image/svg+xml;base64,'. Pass the name
1734 * of a Dashicons helper class to use a font icon, e.g.
1735 * 'dashicons-chart-pie'. Pass 'none' to leave div.wp-menu-image empty
1736 * so an icon can be added via CSS. Defaults to use the posts icon.
1737 * @type string|array $capability_type The string to use to build the read, edit, and delete capabilities.
1738 * May be passed as an array to allow for alternative plurals when using
1739 * this argument as a base to construct the capabilities, e.g.
1740 * array('story', 'stories'). Default 'post'.
1741 * @type string[] $capabilities Array of capabilities for this post type. $capability_type is used
1742 * as a base to construct capabilities by default.
1743 * See get_post_type_capabilities().
1744 * @type bool $map_meta_cap Whether to use the internal default meta capability handling.
1745 * Default false.
1746 * @type array|false $supports Core feature(s) the post type supports. Serves as an alias for calling
1747 * add_post_type_support() directly. Core features include 'title',
1748 * 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt',
1749 * 'page-attributes', 'thumbnail', 'custom-fields', and 'post-formats'.
1750 * Additionally, the 'revisions' feature dictates whether the post type
1751 * will store revisions, the 'autosave' feature dictates whether the post type
1752 * will be autosaved, and the 'comments' feature dictates whether the
1753 * comments count will show on the edit screen. For backward compatibility reasons,
1754 * adding 'editor' support implies 'autosave' support too. A feature can also be
1755 * specified as an array of arguments to provide additional information
1756 * about supporting that feature.
1757 * Example: `array( 'my_feature', array( 'field' => 'value' ) )`.
1758 * If false, no features will be added.
1759 * Default is an array containing 'title' and 'editor'.
1760 * @type callable $register_meta_box_cb Provide a callback function that sets up the meta boxes for the
1761 * edit form. Do remove_meta_box() and add_meta_box() calls in the
1762 * callback. Default null.
1763 * @type string[] $taxonomies An array of taxonomy identifiers that will be registered for the
1764 * post type. Taxonomies can be registered later with register_taxonomy()
1765 * or register_taxonomy_for_object_type().
1766 * Default empty array.
1767 * @type bool|string $has_archive Whether there should be post type archives, or if a string, the
1768 * archive slug to use. Will generate the proper rewrite rules if
1769 * $rewrite is enabled. Default false.
1770 * @type bool|array $rewrite {
1771 * Triggers the handling of rewrites for this post type. To prevent rewrite, set to false.
1772 * Defaults to true, using $post_type as slug. To specify rewrite rules, an array can be
1773 * passed with any of these keys:
1774 *
1775 * @type string $slug Customize the permastruct slug. Defaults to $post_type key.
1776 * @type bool $with_front Whether the permastruct should be prepended with WP_Rewrite::$front.
1777 * Default true.
1778 * @type bool $feeds Whether the feed permastruct should be built for this post type.
1779 * Default is value of $has_archive.
1780 * @type bool $pages Whether the permastruct should provide for pagination. Default true.
1781 * @type int $ep_mask Endpoint mask to assign. If not specified and permalink_epmask is set,
1782 * inherits from $permalink_epmask. If not specified and permalink_epmask
1783 * is not set, defaults to EP_PERMALINK.
1784 * }
1785 * @type string|bool $query_var Sets the query_var key for this post type. Defaults to $post_type
1786 * key. If false, a post type cannot be loaded at
1787 * ?{query_var}={post_slug}. If specified as a string, the query
1788 * ?{query_var_string}={post_slug} will be valid.
1789 * @type bool $can_export Whether to allow this post type to be exported. Default true.
1790 * @type bool $delete_with_user Whether to delete posts of this type when deleting a user.
1791 * * If true, posts of this type belonging to the user will be moved
1792 * to Trash when the user is deleted.
1793 * * If false, posts of this type belonging to the user will *not*
1794 * be trashed or deleted.
1795 * * If not set (the default), posts are trashed if post type supports
1796 * the 'author' feature. Otherwise posts are not trashed or deleted.
1797 * Default null.
1798 * @type array $template Array of blocks to use as the default initial state for an editor
1799 * session. Each item should be an array containing block name and
1800 * optional attributes. Default empty array.
1801 * @type string|false $template_lock Whether the block template should be locked if $template is set.
1802 * * If set to 'all', the user is unable to insert new blocks,
1803 * move existing blocks and delete blocks.
1804 * * If set to 'insert', the user is able to move existing blocks
1805 * but is unable to insert new blocks and delete blocks.
1806 * Default false.
1807 * @type bool $_builtin FOR INTERNAL USE ONLY! True if this post type is a native or
1808 * "built-in" post_type. Default false.
1809 * @type string $_edit_link FOR INTERNAL USE ONLY! URL segment to use for edit link of
1810 * this post type. Default 'post.php?post=%d'.
1811 * }
1812 * @return WP_Post_Type|WP_Error The registered post type object on success,
1813 * WP_Error object on failure.
1814 */
1815function register_post_type( $post_type, $args = array() ) {
1816 global $wp_post_types;
1817
1818 if ( ! is_array( $wp_post_types ) ) {
1819 $wp_post_types = array();
1820 }
1821
1822 // Sanitize post type name.
1823 $post_type = sanitize_key( $post_type );
1824
1825 if ( empty( $post_type ) || strlen( $post_type ) > 20 ) {
1826 _doing_it_wrong( __FUNCTION__, __( 'Post type names must be between 1 and 20 characters in length.' ), '4.2.0' );
1827 return new WP_Error( 'post_type_length_invalid', __( 'Post type names must be between 1 and 20 characters in length.' ) );
1828 }
1829
1830 $post_type_object = new WP_Post_Type( $post_type, $args );
1831 $post_type_object->add_supports();
1832 $post_type_object->add_rewrite_rules();
1833 $post_type_object->register_meta_boxes();
1834
1835 $wp_post_types[ $post_type ] = $post_type_object;
1836
1837 $post_type_object->add_hooks();
1838 $post_type_object->register_taxonomies();
1839
1840 /**
1841 * Fires after a post type is registered.
1842 *
1843 * @since 3.3.0
1844 * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
1845 *
1846 * @param string $post_type Post type.
1847 * @param WP_Post_Type $post_type_object Arguments used to register the post type.
1848 */
1849 do_action( 'registered_post_type', $post_type, $post_type_object );
1850
1851 /**
1852 * Fires after a specific post type is registered.
1853 *
1854 * The dynamic portion of the filter name, `$post_type`, refers to the post type key.
1855 *
1856 * Possible hook names include:
1857 *
1858 * - `registered_post_type_post`
1859 * - `registered_post_type_page`
1860 *
1861 * @since 6.0.0
1862 *
1863 * @param string $post_type Post type.
1864 * @param WP_Post_Type $post_type_object Arguments used to register the post type.
1865 */
1866 do_action( "registered_post_type_{$post_type}", $post_type, $post_type_object );
1867
1868 return $post_type_object;
1869}
1870
1871/**
1872 * Unregisters a post type.
1873 *
1874 * Cannot be used to unregister built-in post types.
1875 *
1876 * @since 4.5.0
1877 *
1878 * @global array $wp_post_types List of post types.
1879 *
1880 * @param string $post_type Post type to unregister.
1881 * @return true|WP_Error True on success, WP_Error on failure or if the post type doesn't exist.
1882 */
1883function unregister_post_type( $post_type ) {
1884 global $wp_post_types;
1885
1886 if ( ! post_type_exists( $post_type ) ) {
1887 return new WP_Error( 'invalid_post_type', __( 'Invalid post type.' ) );
1888 }
1889
1890 $post_type_object = get_post_type_object( $post_type );
1891
1892 // Do not allow unregistering internal post types.
1893 if ( $post_type_object->_builtin ) {
1894 return new WP_Error( 'invalid_post_type', __( 'Unregistering a built-in post type is not allowed' ) );
1895 }
1896
1897 $post_type_object->remove_supports();
1898 $post_type_object->remove_rewrite_rules();
1899 $post_type_object->unregister_meta_boxes();
1900 $post_type_object->remove_hooks();
1901 $post_type_object->unregister_taxonomies();
1902
1903 unset( $wp_post_types[ $post_type ] );
1904
1905 /**
1906 * Fires after a post type was unregistered.
1907 *
1908 * @since 4.5.0
1909 *
1910 * @param string $post_type Post type key.
1911 */
1912 do_action( 'unregistered_post_type', $post_type );
1913
1914 return true;
1915}
1916
1917/**
1918 * Builds an object with all post type capabilities out of a post type object
1919 *
1920 * Post type capabilities use the 'capability_type' argument as a base, if the
1921 * capability is not set in the 'capabilities' argument array or if the
1922 * 'capabilities' argument is not supplied.
1923 *
1924 * The capability_type argument can optionally be registered as an array, with
1925 * the first value being singular and the second plural, e.g. array('story, 'stories')
1926 * Otherwise, an 's' will be added to the value for the plural form. After
1927 * registration, capability_type will always be a string of the singular value.
1928 *
1929 * By default, the following keys are accepted as part of the capabilities array:
1930 *
1931 * - edit_post, read_post, and delete_post are meta capabilities, which are then
1932 * generally mapped to corresponding primitive capabilities depending on the
1933 * context, which would be the post being edited/read/deleted and the user or
1934 * role being checked. Thus these capabilities would generally not be granted
1935 * directly to users or roles.
1936 *
1937 * - edit_posts - Controls whether objects of this post type can be edited.
1938 * - edit_others_posts - Controls whether objects of this type owned by other users
1939 * can be edited. If the post type does not support an author, then this will
1940 * behave like edit_posts.
1941 * - delete_posts - Controls whether objects of this post type can be deleted.
1942 * - publish_posts - Controls publishing objects of this post type.
1943 * - read_private_posts - Controls whether private objects can be read.
1944 * - create_posts - Controls whether objects of this post type can be created.
1945 *
1946 * These primitive capabilities are checked in core in various locations.
1947 * There are also six other primitive capabilities which are not referenced
1948 * directly in core, except in map_meta_cap(), which takes the three aforementioned
1949 * meta capabilities and translates them into one or more primitive capabilities
1950 * that must then be checked against the user or role, depending on the context.
1951 *
1952 * - read - Controls whether objects of this post type can be read.
1953 * - delete_private_posts - Controls whether private objects can be deleted.
1954 * - delete_published_posts - Controls whether published objects can be deleted.
1955 * - delete_others_posts - Controls whether objects owned by other users can be
1956 * can be deleted. If the post type does not support an author, then this will
1957 * behave like delete_posts.
1958 * - edit_private_posts - Controls whether private objects can be edited.
1959 * - edit_published_posts - Controls whether published objects can be edited.
1960 *
1961 * These additional capabilities are only used in map_meta_cap(). Thus, they are
1962 * only assigned by default if the post type is registered with the 'map_meta_cap'
1963 * argument set to true (default is false).
1964 *
1965 * @since 3.0.0
1966 * @since 5.4.0 'delete_posts' is included in default capabilities.
1967 *
1968 * @see register_post_type()
1969 * @see map_meta_cap()
1970 *
1971 * @param object $args Post type registration arguments.
1972 * @return object {
1973 * Object with all the capabilities as member variables.
1974 *
1975 * @type string $edit_post Capability to edit a post.
1976 * @type string $read_post Capability to read a post.
1977 * @type string $delete_post Capability to delete a post.
1978 * @type string $edit_posts Capability to edit posts.
1979 * @type string $edit_others_posts Capability to edit others' posts.
1980 * @type string $delete_posts Capability to delete posts.
1981 * @type string $publish_posts Capability to publish posts.
1982 * @type string $read_private_posts Capability to read private posts.
1983 * @type string $create_posts Capability to create posts.
1984 * @type string $read Optional. Capability to read a post.
1985 * @type string $delete_private_posts Optional. Capability to delete private posts.
1986 * @type string $delete_published_posts Optional. Capability to delete published posts.
1987 * @type string $delete_others_posts Optional. Capability to delete others' posts.
1988 * @type string $edit_private_posts Optional. Capability to edit private posts.
1989 * @type string $edit_published_posts Optional. Capability to edit published posts.
1990 * }
1991 */
1992function get_post_type_capabilities( $args ) {
1993 if ( ! is_array( $args->capability_type ) ) {
1994 $args->capability_type = array( $args->capability_type, $args->capability_type . 's' );
1995 }
1996
1997 // Singular base for meta capabilities, plural base for primitive capabilities.
1998 list( $singular_base, $plural_base ) = $args->capability_type;
1999
2000 $default_capabilities = array(
2001 // Meta capabilities.
2002 'edit_post' => 'edit_' . $singular_base,
2003 'read_post' => 'read_' . $singular_base,
2004 'delete_post' => 'delete_' . $singular_base,
2005 // Primitive capabilities used outside of map_meta_cap():
2006 'edit_posts' => 'edit_' . $plural_base,
2007 'edit_others_posts' => 'edit_others_' . $plural_base,
2008 'delete_posts' => 'delete_' . $plural_base,
2009 'publish_posts' => 'publish_' . $plural_base,
2010 'read_private_posts' => 'read_private_' . $plural_base,
2011 );
2012
2013 // Primitive capabilities used within map_meta_cap():
2014 if ( $args->map_meta_cap ) {
2015 $default_capabilities_for_mapping = array(
2016 'read' => 'read',
2017 'delete_private_posts' => 'delete_private_' . $plural_base,
2018 'delete_published_posts' => 'delete_published_' . $plural_base,
2019 'delete_others_posts' => 'delete_others_' . $plural_base,
2020 'edit_private_posts' => 'edit_private_' . $plural_base,
2021 'edit_published_posts' => 'edit_published_' . $plural_base,
2022 );
2023 $default_capabilities = array_merge( $default_capabilities, $default_capabilities_for_mapping );
2024 }
2025
2026 $capabilities = array_merge( $default_capabilities, $args->capabilities );
2027
2028 // Post creation capability simply maps to edit_posts by default:
2029 if ( ! isset( $capabilities['create_posts'] ) ) {
2030 $capabilities['create_posts'] = $capabilities['edit_posts'];
2031 }
2032
2033 // Remember meta capabilities for future reference.
2034 if ( $args->map_meta_cap ) {
2035 _post_type_meta_capabilities( $capabilities );
2036 }
2037
2038 return (object) $capabilities;
2039}
2040
2041/**
2042 * Stores or returns a list of post type meta caps for map_meta_cap().
2043 *
2044 * @since 3.1.0
2045 * @access private
2046 *
2047 * @global array $post_type_meta_caps Used to store meta capabilities.
2048 *
2049 * @param string[] $capabilities Post type meta capabilities.
2050 */
2051function _post_type_meta_capabilities( $capabilities = null ) {
2052 global $post_type_meta_caps;
2053
2054 foreach ( $capabilities as $core => $custom ) {
2055 if ( in_array( $core, array( 'read_post', 'delete_post', 'edit_post' ), true ) ) {
2056 $post_type_meta_caps[ $custom ] = $core;
2057 }
2058 }
2059}
2060
2061/**
2062 * Builds an object with all post type labels out of a post type object.
2063 *
2064 * Accepted keys of the label array in the post type object:
2065 *
2066 * - `name` - General name for the post type, usually plural. The same and overridden
2067 * by `$post_type_object->label`. Default is 'Posts' / 'Pages'.
2068 * - `singular_name` - Name for one object of this post type. Default is 'Post' / 'Page'.
2069 * - `add_new` - Label for adding a new item. Default is 'Add Post' / 'Add Page'.
2070 * - `add_new_item` - Label for adding a new singular item. Default is 'Add Post' / 'Add Page'.
2071 * - `edit_item` - Label for editing a singular item. Default is 'Edit Post' / 'Edit Page'.
2072 * - `new_item` - Label for the new item page title. Default is 'New Post' / 'New Page'.
2073 * - `view_item` - Label for viewing a singular item. Default is 'View Post' / 'View Page'.
2074 * - `view_items` - Label for viewing post type archives. Default is 'View Posts' / 'View Pages'.
2075 * - `search_items` - Label for searching plural items. Default is 'Search Posts' / 'Search Pages'.
2076 * - `not_found` - Label used when no items are found. Default is 'No posts found' / 'No pages found'.
2077 * - `not_found_in_trash` - Label used when no items are in the Trash. Default is 'No posts found in Trash' /
2078 * 'No pages found in Trash'.
2079 * - `parent_item_colon` - Label used to prefix parents of hierarchical items. Not used on non-hierarchical
2080 * post types. Default is 'Parent Page:'.
2081 * - `all_items` - Label to signify all items in a submenu link. Default is 'All Posts' / 'All Pages'.
2082 * - `archives` - Label for archives in nav menus. Default is 'Post Archives' / 'Page Archives'.
2083 * - `attributes` - Label for the attributes meta box. Default is 'Post Attributes' / 'Page Attributes'.
2084 * - `insert_into_item` - Label for the media frame button. Default is 'Insert into post' / 'Insert into page'.
2085 * - `uploaded_to_this_item` - Label for the media frame filter. Default is 'Uploaded to this post' /
2086 * 'Uploaded to this page'.
2087 * - `featured_image` - Label for the featured image meta box title. Default is 'Featured image'.
2088 * - `set_featured_image` - Label for setting the featured image. Default is 'Set featured image'.
2089 * - `remove_featured_image` - Label for removing the featured image. Default is 'Remove featured image'.
2090 * - `use_featured_image` - Label in the media frame for using a featured image. Default is 'Use as featured image'.
2091 * - `menu_name` - Label for the menu name. Default is the same as `name`.
2092 * - `filter_items_list` - Label for the table views hidden heading. Default is 'Filter posts list' /
2093 * 'Filter pages list'.
2094 * - `filter_by_date` - Label for the date filter in list tables. Default is 'Filter by date'.
2095 * - `items_list_navigation` - Label for the table pagination hidden heading. Default is 'Posts list navigation' /
2096 * 'Pages list navigation'.
2097 * - `items_list` - Label for the table hidden heading. Default is 'Posts list' / 'Pages list'.
2098 * - `item_published` - Label used when an item is published. Default is 'Post published.' / 'Page published.'
2099 * - `item_published_privately` - Label used when an item is published with private visibility.
2100 * Default is 'Post published privately.' / 'Page published privately.'
2101 * - `item_reverted_to_draft` - Label used when an item is switched to a draft.
2102 * Default is 'Post reverted to draft.' / 'Page reverted to draft.'
2103 * - `item_trashed` - Label used when an item is moved to Trash. Default is 'Post trashed.' / 'Page trashed.'
2104 * - `item_scheduled` - Label used when an item is scheduled for publishing. Default is 'Post scheduled.' /
2105 * 'Page scheduled.'
2106 * - `item_updated` - Label used when an item is updated. Default is 'Post updated.' / 'Page updated.'
2107 * - `item_link` - Title for a navigation link block variation. Default is 'Post Link' / 'Page Link'.
2108 * - `item_link_description` - Description for a navigation link block variation. Default is 'A link to a post.' /
2109 * 'A link to a page.'
2110 *
2111 * Above, the first default value is for non-hierarchical post types (like posts)
2112 * and the second one is for hierarchical post types (like pages).
2113 *
2114 * Note: To set labels used in post type admin notices, see the {@see 'post_updated_messages'} filter.
2115 *
2116 * @since 3.0.0
2117 * @since 4.3.0 Added the `featured_image`, `set_featured_image`, `remove_featured_image`,
2118 * and `use_featured_image` labels.
2119 * @since 4.4.0 Added the `archives`, `insert_into_item`, `uploaded_to_this_item`, `filter_items_list`,
2120 * `items_list_navigation`, and `items_list` labels.
2121 * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
2122 * @since 4.7.0 Added the `view_items` and `attributes` labels.
2123 * @since 5.0.0 Added the `item_published`, `item_published_privately`, `item_reverted_to_draft`,
2124 * `item_scheduled`, and `item_updated` labels.
2125 * @since 5.7.0 Added the `filter_by_date` label.
2126 * @since 5.8.0 Added the `item_link` and `item_link_description` labels.
2127 * @since 6.3.0 Added the `item_trashed` label.
2128 * @since 6.4.0 Changed default values for the `add_new` label to include the type of content.
2129 * This matches `add_new_item` and provides more context for better accessibility.
2130 * @since 6.6.0 Added the `template_name` label.
2131 * @since 6.7.0 Restored pre-6.4.0 defaults for the `add_new` label and updated documentation.
2132 * Updated core usage to reference `add_new_item`.
2133 *
2134 * @access private
2135 *
2136 * @param object|WP_Post_Type $post_type_object Post type object.
2137 * @return object Object with all the labels as member variables.
2138 */
2139function get_post_type_labels( $post_type_object ) {
2140 $nohier_vs_hier_defaults = WP_Post_Type::get_default_labels();
2141
2142 $nohier_vs_hier_defaults['menu_name'] = $nohier_vs_hier_defaults['name'];
2143
2144 $labels = _get_custom_object_labels( $post_type_object, $nohier_vs_hier_defaults );
2145
2146 if ( ! isset( $post_type_object->labels->template_name ) && isset( $post_type_object->labels->singular_name ) ) {
2147 /* translators: %s: Post type name. */
2148 $labels->template_name = sprintf( __( 'Single item: %s' ), $post_type_object->labels->singular_name );
2149 }
2150
2151 $post_type = $post_type_object->name;
2152
2153 $default_labels = clone $labels;
2154
2155 /**
2156 * Filters the labels of a specific post type.
2157 *
2158 * The dynamic portion of the hook name, `$post_type`, refers to
2159 * the post type slug.
2160 *
2161 * Possible hook names include:
2162 *
2163 * - `post_type_labels_post`
2164 * - `post_type_labels_page`
2165 * - `post_type_labels_attachment`
2166 *
2167 * @since 3.5.0
2168 *
2169 * @see get_post_type_labels() for the full list of labels.
2170 *
2171 * @param object $labels Object with labels for the post type as member variables.
2172 */
2173 $labels = apply_filters( "post_type_labels_{$post_type}", $labels );
2174
2175 // Ensure that the filtered labels contain all required default values.
2176 $labels = (object) array_merge( (array) $default_labels, (array) $labels );
2177
2178 return $labels;
2179}
2180
2181/**
2182 * Builds an object with custom-something object (post type, taxonomy) labels
2183 * out of a custom-something object
2184 *
2185 * @since 3.0.0
2186 * @access private
2187 *
2188 * @param object $data_object A custom-something object.
2189 * @param array $nohier_vs_hier_defaults Hierarchical vs non-hierarchical default labels.
2190 * @return object Object containing labels for the given custom-something object.
2191 */
2192function _get_custom_object_labels( $data_object, $nohier_vs_hier_defaults ) {
2193 $data_object->labels = (array) $data_object->labels;
2194
2195 if ( isset( $data_object->label ) && empty( $data_object->labels['name'] ) ) {
2196 $data_object->labels['name'] = $data_object->label;
2197 }
2198
2199 if ( ! isset( $data_object->labels['singular_name'] ) && isset( $data_object->labels['name'] ) ) {
2200 $data_object->labels['singular_name'] = $data_object->labels['name'];
2201 }
2202
2203 if ( ! isset( $data_object->labels['name_admin_bar'] ) ) {
2204 $data_object->labels['name_admin_bar'] =
2205 isset( $data_object->labels['singular_name'] )
2206 ? $data_object->labels['singular_name']
2207 : $data_object->name;
2208 }
2209
2210 if ( ! isset( $data_object->labels['menu_name'] ) && isset( $data_object->labels['name'] ) ) {
2211 $data_object->labels['menu_name'] = $data_object->labels['name'];
2212 }
2213
2214 if ( ! isset( $data_object->labels['all_items'] ) && isset( $data_object->labels['menu_name'] ) ) {
2215 $data_object->labels['all_items'] = $data_object->labels['menu_name'];
2216 }
2217
2218 if ( ! isset( $data_object->labels['archives'] ) && isset( $data_object->labels['all_items'] ) ) {
2219 $data_object->labels['archives'] = $data_object->labels['all_items'];
2220 }
2221
2222 $defaults = array();
2223 foreach ( $nohier_vs_hier_defaults as $key => $value ) {
2224 $defaults[ $key ] = $data_object->hierarchical ? $value[1] : $value[0];
2225 }
2226
2227 $labels = array_merge( $defaults, $data_object->labels );
2228 $data_object->labels = (object) $data_object->labels;
2229
2230 return (object) $labels;
2231}
2232
2233/**
2234 * Adds submenus for post types.
2235 *
2236 * @access private
2237 * @since 3.1.0
2238 */
2239function _add_post_type_submenus() {
2240 foreach ( get_post_types( array( 'show_ui' => true ) ) as $ptype ) {
2241 $ptype_obj = get_post_type_object( $ptype );
2242 // Sub-menus only.
2243 if ( ! $ptype_obj->show_in_menu || true === $ptype_obj->show_in_menu ) {
2244 continue;
2245 }
2246 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" );
2247 }
2248}
2249
2250/**
2251 * Registers support of certain features for a post type.
2252 *
2253 * All core features are directly associated with a functional area of the edit
2254 * screen, such as the editor or a meta box. Features include: 'title', 'editor',
2255 * 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes',
2256 * 'thumbnail', 'custom-fields', and 'post-formats'.
2257 *
2258 * Additionally, the 'revisions' feature dictates whether the post type will
2259 * store revisions, the 'autosave' feature dictates whether the post type
2260 * will be autosaved, and the 'comments' feature dictates whether the comments
2261 * count will show on the edit screen.
2262 *
2263 * A third, optional parameter can also be passed along with a feature to provide
2264 * additional information about supporting that feature.
2265 *
2266 * Example usage:
2267 *
2268 * add_post_type_support( 'my_post_type', 'comments' );
2269 * add_post_type_support( 'my_post_type', array(
2270 * 'author', 'excerpt',
2271 * ) );
2272 * add_post_type_support( 'my_post_type', 'my_feature', array(
2273 * 'field' => 'value',
2274 * ) );
2275 *
2276 * @since 3.0.0
2277 * @since 5.3.0 Formalized the existing and already documented `...$args` parameter
2278 * by adding it to the function signature.
2279 *
2280 * @global array $_wp_post_type_features
2281 *
2282 * @param string $post_type The post type for which to add the feature.
2283 * @param string|array $feature The feature being added, accepts an array of
2284 * feature strings or a single string.
2285 * @param mixed ...$args Optional extra arguments to pass along with certain features.
2286 */
2287function add_post_type_support( $post_type, $feature, ...$args ) {
2288 global $_wp_post_type_features;
2289
2290 $features = (array) $feature;
2291 foreach ( $features as $feature ) {
2292 if ( $args ) {
2293 $_wp_post_type_features[ $post_type ][ $feature ] = $args;
2294 } else {
2295 $_wp_post_type_features[ $post_type ][ $feature ] = true;
2296 }
2297 }
2298}
2299
2300/**
2301 * Removes support for a feature from a post type.
2302 *
2303 * @since 3.0.0
2304 *
2305 * @global array $_wp_post_type_features
2306 *
2307 * @param string $post_type The post type for which to remove the feature.
2308 * @param string $feature The feature being removed.
2309 */
2310function remove_post_type_support( $post_type, $feature ) {
2311 global $_wp_post_type_features;
2312
2313 unset( $_wp_post_type_features[ $post_type ][ $feature ] );
2314}
2315
2316/**
2317 * Gets all the post type features
2318 *
2319 * @since 3.4.0
2320 *
2321 * @global array $_wp_post_type_features
2322 *
2323 * @param string $post_type The post type.
2324 * @return array Post type supports list.
2325 */
2326function get_all_post_type_supports( $post_type ) {
2327 global $_wp_post_type_features;
2328
2329 if ( isset( $_wp_post_type_features[ $post_type ] ) ) {
2330 return $_wp_post_type_features[ $post_type ];
2331 }
2332
2333 return array();
2334}
2335
2336/**
2337 * Checks a post type's support for a given feature.
2338 *
2339 * @since 3.0.0
2340 *
2341 * @global array $_wp_post_type_features
2342 *
2343 * @param string $post_type The post type being checked.
2344 * @param string $feature The feature being checked.
2345 * @return bool Whether the post type supports the given feature.
2346 */
2347function post_type_supports( $post_type, $feature ) {
2348 global $_wp_post_type_features;
2349
2350 return ( isset( $_wp_post_type_features[ $post_type ][ $feature ] ) );
2351}
2352/**
2353 * Retrieves a list of post type names that support a specific feature.
2354 *
2355 * @since 4.5.0
2356 *
2357 * @global array $_wp_post_type_features Post type features
2358 *
2359 * @param array|string $feature Single feature or an array of features the post types should support.
2360 * @param string $operator Optional. The logical operation to perform. 'or' means
2361 * only one element from the array needs to match; 'and'
2362 * means all elements must match; 'not' means no elements may
2363 * match. Default 'and'.
2364 * @return string[] A list of post type names.
2365 */
2366function get_post_types_by_support( $feature, $operator = 'and' ) {
2367 global $_wp_post_type_features;
2368
2369 $features = array_fill_keys( (array) $feature, true );
2370
2371 return array_keys( wp_filter_object_list( $_wp_post_type_features, $features, $operator ) );
2372}
2373
2374/**
2375 * Updates the post type for the post ID.
2376 *
2377 * The page or post cache will be cleaned for the post ID.
2378 *
2379 * @since 2.5.0
2380 *
2381 * @global wpdb $wpdb WordPress database abstraction object.
2382 *
2383 * @param int $post_id Optional. Post ID to change post type. Default 0.
2384 * @param string $post_type Optional. Post type. Accepts 'post' or 'page' to
2385 * name a few. Default 'post'.
2386 * @return int|false Amount of rows changed. Should be 1 for success and 0 for failure.
2387 */
2388function set_post_type( $post_id = 0, $post_type = 'post' ) {
2389 global $wpdb;
2390
2391 $post_type = sanitize_post_field( 'post_type', $post_type, $post_id, 'db' );
2392 $return = $wpdb->update( $wpdb->posts, array( 'post_type' => $post_type ), array( 'ID' => $post_id ) );
2393
2394 clean_post_cache( $post_id );
2395
2396 return $return;
2397}
2398
2399/**
2400 * Determines whether a post type is considered "viewable".
2401 *
2402 * For built-in post types such as posts and pages, the 'public' value will be evaluated.
2403 * For all others, the 'publicly_queryable' value will be used.
2404 *
2405 * @since 4.4.0
2406 * @since 4.5.0 Added the ability to pass a post type name in addition to object.
2407 * @since 4.6.0 Converted the `$post_type` parameter to accept a `WP_Post_Type` object.
2408 * @since 5.9.0 Added `is_post_type_viewable` hook to filter the result.
2409 *
2410 * @param string|WP_Post_Type $post_type Post type name or object.
2411 * @return bool Whether the post type should be considered viewable.
2412 */
2413function is_post_type_viewable( $post_type ) {
2414 if ( is_scalar( $post_type ) ) {
2415 $post_type = get_post_type_object( $post_type );
2416
2417 if ( ! $post_type ) {
2418 return false;
2419 }
2420 }
2421
2422 if ( ! is_object( $post_type ) ) {
2423 return false;
2424 }
2425
2426 $is_viewable = $post_type->publicly_queryable || ( $post_type->_builtin && $post_type->public );
2427
2428 /**
2429 * Filters whether a post type is considered "viewable".
2430 *
2431 * The returned filtered value must be a boolean type to ensure
2432 * `is_post_type_viewable()` only returns a boolean. This strictness
2433 * is by design to maintain backwards-compatibility and guard against
2434 * potential type errors in PHP 8.1+. Non-boolean values (even falsey
2435 * and truthy values) will result in the function returning false.
2436 *
2437 * @since 5.9.0
2438 *
2439 * @param bool $is_viewable Whether the post type is "viewable" (strict type).
2440 * @param WP_Post_Type $post_type Post type object.
2441 */
2442 return true === apply_filters( 'is_post_type_viewable', $is_viewable, $post_type );
2443}
2444
2445/**
2446 * Determines whether a post status is considered "viewable".
2447 *
2448 * For built-in post statuses such as publish and private, the 'public' value will be evaluated.
2449 * For all others, the 'publicly_queryable' value will be used.
2450 *
2451 * @since 5.7.0
2452 * @since 5.9.0 Added `is_post_status_viewable` hook to filter the result.
2453 *
2454 * @param string|stdClass $post_status Post status name or object.
2455 * @return bool Whether the post status should be considered viewable.
2456 */
2457function is_post_status_viewable( $post_status ) {
2458 if ( is_scalar( $post_status ) ) {
2459 if ( ! is_string( $post_status ) ) {
2460 return false;
2461 }
2462
2463 $post_status = get_post_status_object( $post_status );
2464
2465 if ( ! $post_status ) {
2466 return false;
2467 }
2468 }
2469
2470 if (
2471 ! is_object( $post_status ) ||
2472 $post_status->internal ||
2473 $post_status->protected
2474 ) {
2475 return false;
2476 }
2477
2478 $is_viewable = $post_status->publicly_queryable || ( $post_status->_builtin && $post_status->public );
2479
2480 /**
2481 * Filters whether a post status is considered "viewable".
2482 *
2483 * The returned filtered value must be a boolean type to ensure
2484 * `is_post_status_viewable()` only returns a boolean. This strictness
2485 * is by design to maintain backwards-compatibility and guard against
2486 * potential type errors in PHP 8.1+. Non-boolean values (even falsey
2487 * and truthy values) will result in the function returning false.
2488 *
2489 * @since 5.9.0
2490 *
2491 * @param bool $is_viewable Whether the post status is "viewable" (strict type).
2492 * @param stdClass $post_status Post status object.
2493 */
2494 return true === apply_filters( 'is_post_status_viewable', $is_viewable, $post_status );
2495}
2496
2497/**
2498 * Determines whether a post is publicly viewable.
2499 *
2500 * Posts are considered publicly viewable if both the post status and post type
2501 * are viewable.
2502 *
2503 * @since 5.7.0
2504 *
2505 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
2506 * @return bool Whether the post is publicly viewable.
2507 */
2508function is_post_publicly_viewable( $post = null ) {
2509 $post = get_post( $post );
2510
2511 if ( ! $post ) {
2512 return false;
2513 }
2514
2515 $post_type = get_post_type( $post );
2516 $post_status = get_post_status( $post );
2517
2518 return is_post_type_viewable( $post_type ) && is_post_status_viewable( $post_status );
2519}
2520
2521/**
2522 * Determines whether a post is embeddable.
2523 *
2524 * @since 6.8.0
2525 *
2526 * @param int|WP_Post|null $post Optional. Post ID or `WP_Post` object. Defaults to global $post.
2527 * @return bool Whether the post should be considered embeddable.
2528 */
2529function is_post_embeddable( $post = null ) {
2530 $post = get_post( $post );
2531
2532 if ( ! $post ) {
2533 return false;
2534 }
2535
2536 $post_type = get_post_type_object( $post->post_type );
2537
2538 if ( ! $post_type ) {
2539 return false;
2540 }
2541
2542 $is_embeddable = $post_type->embeddable;
2543
2544 /**
2545 * Filter whether a post is embeddable.
2546 *
2547 * @since 6.8.0
2548 *
2549 * @param bool $is_embeddable Whether the post is embeddable.
2550 * @param WP_Post $post Post object.
2551 */
2552 return apply_filters( 'is_post_embeddable', $is_embeddable, $post );
2553}
2554
2555/**
2556 * Retrieves an array of the latest posts, or posts matching the given criteria.
2557 *
2558 * For more information on the accepted arguments, see the
2559 * {@link https://developer.wordpress.org/reference/classes/wp_query/
2560 * WP_Query} documentation in the Developer Handbook.
2561 *
2562 * The `$ignore_sticky_posts` and `$no_found_rows` arguments are ignored by
2563 * this function and both are set to `true`.
2564 *
2565 * The defaults are as follows:
2566 *
2567 * @since 1.2.0
2568 *
2569 * @see WP_Query
2570 * @see WP_Query::parse_query()
2571 *
2572 * @param array $args {
2573 * Optional. Arguments to retrieve posts. See WP_Query::parse_query() for all available arguments.
2574 *
2575 * @type int $numberposts Total number of posts to retrieve. Is an alias of `$posts_per_page`
2576 * in WP_Query. Accepts -1 for all. Default 5.
2577 * @type int|string $category Category ID or comma-separated list of IDs (this or any children).
2578 * Is an alias of `$cat` in WP_Query. Default 0.
2579 * @type int[] $include An array of post IDs to retrieve, sticky posts will be included.
2580 * Is an alias of `$post__in` in WP_Query. Default empty array.
2581 * @type int[] $exclude An array of post IDs not to retrieve. Default empty array.
2582 * @type bool $suppress_filters Whether to suppress filters. Default true.
2583 * }
2584 * @return WP_Post[]|int[] Array of post objects or post IDs.
2585 */
2586function get_posts( $args = null ) {
2587 $defaults = array(
2588 'numberposts' => 5,
2589 'category' => 0,
2590 'orderby' => 'date',
2591 'order' => 'DESC',
2592 'include' => array(),
2593 'exclude' => array(),
2594 'meta_key' => '',
2595 'meta_value' => '',
2596 'post_type' => 'post',
2597 'suppress_filters' => true,
2598 );
2599
2600 $parsed_args = wp_parse_args( $args, $defaults );
2601 if ( empty( $parsed_args['post_status'] ) ) {
2602 $parsed_args['post_status'] = ( 'attachment' === $parsed_args['post_type'] ) ? 'inherit' : 'publish';
2603 }
2604 if ( ! empty( $parsed_args['numberposts'] ) && empty( $parsed_args['posts_per_page'] ) ) {
2605 $parsed_args['posts_per_page'] = $parsed_args['numberposts'];
2606 }
2607 if ( ! empty( $parsed_args['category'] ) ) {
2608 $parsed_args['cat'] = $parsed_args['category'];
2609 }
2610 if ( ! empty( $parsed_args['include'] ) ) {
2611 $incposts = wp_parse_id_list( $parsed_args['include'] );
2612 $parsed_args['posts_per_page'] = count( $incposts ); // Only the number of posts included.
2613 $parsed_args['post__in'] = $incposts;
2614 } elseif ( ! empty( $parsed_args['exclude'] ) ) {
2615 $parsed_args['post__not_in'] = wp_parse_id_list( $parsed_args['exclude'] );
2616 }
2617
2618 $parsed_args['ignore_sticky_posts'] = true;
2619 $parsed_args['no_found_rows'] = true;
2620
2621 $get_posts = new WP_Query();
2622 return $get_posts->query( $parsed_args );
2623}
2624
2625//
2626// Post meta functions.
2627//
2628
2629/**
2630 * Adds a meta field to the given post.
2631 *
2632 * Post meta data is called "Custom Fields" on the Administration Screen.
2633 *
2634 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
2635 *
2636 * @since 1.5.0
2637 *
2638 * @param int $post_id Post ID.
2639 * @param string $meta_key Metadata name.
2640 * @param mixed $meta_value Metadata value. Arrays and objects are stored as serialized data and
2641 * will be returned as the same type when retrieved. Other data types will
2642 * be stored as strings in the database:
2643 * - false is stored and retrieved as an empty string ('')
2644 * - true is stored and retrieved as '1'
2645 * - numbers (both integer and float) are stored and retrieved as strings
2646 * Must be serializable if non-scalar.
2647 * @param bool $unique Optional. Whether the same key should not be added.
2648 * Default false.
2649 * @return int|false Meta ID on success, false on failure.
2650 */
2651function add_post_meta( $post_id, $meta_key, $meta_value, $unique = false ) {
2652 // Make sure meta is added to the post, not a revision.
2653 $the_post = wp_is_post_revision( $post_id );
2654 if ( $the_post ) {
2655 $post_id = $the_post;
2656 }
2657
2658 return add_metadata( 'post', $post_id, $meta_key, $meta_value, $unique );
2659}
2660
2661/**
2662 * Deletes a post meta field for the given post ID.
2663 *
2664 * You can match based on the key, or key and value. Removing based on key and
2665 * value, will keep from removing duplicate metadata with the same key. It also
2666 * allows removing all metadata matching the key, if needed.
2667 *
2668 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
2669 *
2670 * @since 1.5.0
2671 *
2672 * @param int $post_id Post ID.
2673 * @param string $meta_key Metadata name.
2674 * @param mixed $meta_value Optional. Metadata value. If provided,
2675 * rows will only be removed that match the value.
2676 * Must be serializable if non-scalar. Default empty.
2677 * @return bool True on success, false on failure.
2678 */
2679function delete_post_meta( $post_id, $meta_key, $meta_value = '' ) {
2680 // Make sure meta is deleted from the post, not from a revision.
2681 $the_post = wp_is_post_revision( $post_id );
2682 if ( $the_post ) {
2683 $post_id = $the_post;
2684 }
2685
2686 return delete_metadata( 'post', $post_id, $meta_key, $meta_value );
2687}
2688
2689/**
2690 * Retrieves a post meta field for the given post ID.
2691 *
2692 * @since 1.5.0
2693 *
2694 * @param int $post_id Post ID.
2695 * @param string $key Optional. The meta key to retrieve. By default,
2696 * returns data for all keys. Default empty.
2697 * @param bool $single Optional. Whether to return a single value.
2698 * This parameter has no effect if `$key` is not specified.
2699 * Default false.
2700 * @return mixed An array of values if `$single` is false.
2701 * The value of the meta field if `$single` is true.
2702 * False for an invalid `$post_id` (non-numeric, zero, or negative value).
2703 * An empty array if a valid but non-existing post ID is passed and `$single` is false.
2704 * An empty string if a valid but non-existing post ID is passed and `$single` is true.
2705 * Note: Non-serialized values are returned as strings:
2706 * - false values are returned as empty strings ('')
2707 * - true values are returned as '1'
2708 * - numbers (both integer and float) are returned as strings
2709 * Arrays and objects retain their original type.
2710 */
2711function get_post_meta( $post_id, $key = '', $single = false ) {
2712 return get_metadata( 'post', $post_id, $key, $single );
2713}
2714
2715/**
2716 * Updates a post meta field based on the given post ID.
2717 *
2718 * Use the `$prev_value` parameter to differentiate between meta fields with the
2719 * same key and post ID.
2720 *
2721 * If the meta field for the post does not exist, it will be added and its ID returned.
2722 *
2723 * Can be used in place of add_post_meta().
2724 *
2725 * For historical reasons both the meta key and the meta value are expected to be "slashed" (slashes escaped) on input.
2726 *
2727 * @since 1.5.0
2728 *
2729 * @param int $post_id Post ID.
2730 * @param string $meta_key Metadata key.
2731 * @param mixed $meta_value Metadata value. Must be serializable if non-scalar.
2732 * @param mixed $prev_value Optional. Previous value to check before updating.
2733 * If specified, only update existing metadata entries with
2734 * this value. Otherwise, update all entries. Default empty.
2735 * @return int|bool Meta ID if the key didn't exist, true on successful update,
2736 * false on failure or if the value passed to the function
2737 * is the same as the one that is already in the database.
2738 */
2739function update_post_meta( $post_id, $meta_key, $meta_value, $prev_value = '' ) {
2740 // Make sure meta is updated for the post, not for a revision.
2741 $the_post = wp_is_post_revision( $post_id );
2742 if ( $the_post ) {
2743 $post_id = $the_post;
2744 }
2745
2746 return update_metadata( 'post', $post_id, $meta_key, $meta_value, $prev_value );
2747}
2748
2749/**
2750 * Deletes everything from post meta matching the given meta key.
2751 *
2752 * @since 2.3.0
2753 *
2754 * @param string $post_meta_key Key to search for when deleting.
2755 * @return bool Whether the post meta key was deleted from the database.
2756 */
2757function delete_post_meta_by_key( $post_meta_key ) {
2758 return delete_metadata( 'post', null, $post_meta_key, '', true );
2759}
2760
2761/**
2762 * Registers a meta key for posts.
2763 *
2764 * @since 4.9.8
2765 *
2766 * @param string $post_type Post type to register a meta key for. Pass an empty string
2767 * to register the meta key across all existing post types.
2768 * @param string $meta_key The meta key to register.
2769 * @param array $args Data used to describe the meta key when registered. See
2770 * {@see register_meta()} for a list of supported arguments.
2771 * @return bool True if the meta key was successfully registered, false if not.
2772 */
2773function register_post_meta( $post_type, $meta_key, array $args ) {
2774 $args['object_subtype'] = $post_type;
2775
2776 return register_meta( 'post', $meta_key, $args );
2777}
2778
2779/**
2780 * Unregisters a meta key for posts.
2781 *
2782 * @since 4.9.8
2783 *
2784 * @param string $post_type Post type the meta key is currently registered for. Pass
2785 * an empty string if the meta key is registered across all
2786 * existing post types.
2787 * @param string $meta_key The meta key to unregister.
2788 * @return bool True on success, false if the meta key was not previously registered.
2789 */
2790function unregister_post_meta( $post_type, $meta_key ) {
2791 return unregister_meta_key( 'post', $meta_key, $post_type );
2792}
2793
2794/**
2795 * Retrieves post meta fields, based on post ID.
2796 *
2797 * The post meta fields are retrieved from the cache where possible,
2798 * so the function is optimized to be called more than once.
2799 *
2800 * @since 1.2.0
2801 *
2802 * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`.
2803 * @return mixed An array of values.
2804 * False for an invalid `$post_id` (non-numeric, zero, or negative value).
2805 * An empty string if a valid but non-existing post ID is passed.
2806 */
2807function get_post_custom( $post_id = 0 ) {
2808 $post_id = absint( $post_id );
2809
2810 if ( ! $post_id ) {
2811 $post_id = get_the_ID();
2812 }
2813
2814 return get_post_meta( $post_id );
2815}
2816
2817/**
2818 * Retrieves meta field names for a post.
2819 *
2820 * If there are no meta fields, then nothing (null) will be returned.
2821 *
2822 * @since 1.2.0
2823 *
2824 * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`.
2825 * @return array|void Array of the keys, if retrieved.
2826 */
2827function get_post_custom_keys( $post_id = 0 ) {
2828 $custom = get_post_custom( $post_id );
2829
2830 if ( ! is_array( $custom ) ) {
2831 return;
2832 }
2833
2834 $keys = array_keys( $custom );
2835 if ( $keys ) {
2836 return $keys;
2837 }
2838}
2839
2840/**
2841 * Retrieves values for a custom post field.
2842 *
2843 * The parameters must not be considered optional. All of the post meta fields
2844 * will be retrieved and only the meta field key values returned.
2845 *
2846 * @since 1.2.0
2847 *
2848 * @param string $key Optional. Meta field key. Default empty.
2849 * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`.
2850 * @return array|null Meta field values.
2851 */
2852function get_post_custom_values( $key = '', $post_id = 0 ) {
2853 if ( ! $key ) {
2854 return null;
2855 }
2856
2857 $custom = get_post_custom( $post_id );
2858
2859 return isset( $custom[ $key ] ) ? $custom[ $key ] : null;
2860}
2861
2862/**
2863 * Determines whether a post is sticky.
2864 *
2865 * Sticky posts should remain at the top of The Loop. If the post ID is not
2866 * given, then The Loop ID for the current post will be used.
2867 *
2868 * For more information on this and similar theme functions, check out
2869 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
2870 * Conditional Tags} article in the Theme Developer Handbook.
2871 *
2872 * @since 2.7.0
2873 *
2874 * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`.
2875 * @return bool Whether post is sticky.
2876 */
2877function is_sticky( $post_id = 0 ) {
2878 $post_id = absint( $post_id );
2879
2880 if ( ! $post_id ) {
2881 $post_id = get_the_ID();
2882 }
2883
2884 $stickies = get_option( 'sticky_posts' );
2885
2886 if ( is_array( $stickies ) ) {
2887 $stickies = array_map( 'intval', $stickies );
2888 $is_sticky = in_array( $post_id, $stickies, true );
2889 } else {
2890 $is_sticky = false;
2891 }
2892
2893 /**
2894 * Filters whether a post is sticky.
2895 *
2896 * @since 5.3.0
2897 *
2898 * @param bool $is_sticky Whether a post is sticky.
2899 * @param int $post_id Post ID.
2900 */
2901 return apply_filters( 'is_sticky', $is_sticky, $post_id );
2902}
2903
2904/**
2905 * Sanitizes every post field.
2906 *
2907 * If the context is 'raw', then the post object or array will get minimal
2908 * sanitization of the integer fields.
2909 *
2910 * @since 2.3.0
2911 *
2912 * @see sanitize_post_field()
2913 *
2914 * @param object|WP_Post|array $post The post object or array
2915 * @param string $context Optional. How to sanitize post fields.
2916 * Accepts 'raw', 'edit', 'db', 'display',
2917 * 'attribute', or 'js'. Default 'display'.
2918 * @return object|WP_Post|array The now sanitized post object or array (will be the
2919 * same type as `$post`).
2920 */
2921function sanitize_post( $post, $context = 'display' ) {
2922 if ( is_object( $post ) ) {
2923 // Check if post already filtered for this context.
2924 if ( isset( $post->filter ) && $context === $post->filter ) {
2925 return $post;
2926 }
2927 if ( ! isset( $post->ID ) ) {
2928 $post->ID = 0;
2929 }
2930 foreach ( array_keys( get_object_vars( $post ) ) as $field ) {
2931 $post->$field = sanitize_post_field( $field, $post->$field, $post->ID, $context );
2932 }
2933 $post->filter = $context;
2934 } elseif ( is_array( $post ) ) {
2935 // Check if post already filtered for this context.
2936 if ( isset( $post['filter'] ) && $context === $post['filter'] ) {
2937 return $post;
2938 }
2939 if ( ! isset( $post['ID'] ) ) {
2940 $post['ID'] = 0;
2941 }
2942 foreach ( array_keys( $post ) as $field ) {
2943 $post[ $field ] = sanitize_post_field( $field, $post[ $field ], $post['ID'], $context );
2944 }
2945 $post['filter'] = $context;
2946 }
2947 return $post;
2948}
2949
2950/**
2951 * Sanitizes a post field based on context.
2952 *
2953 * Possible context values are: 'raw', 'edit', 'db', 'display', 'attribute' and
2954 * 'js'. The 'display' context is used by default. 'attribute' and 'js' contexts
2955 * are treated like 'display' when calling filters.
2956 *
2957 * @since 2.3.0
2958 * @since 4.4.0 Like `sanitize_post()`, `$context` defaults to 'display'.
2959 *
2960 * @param string $field The Post Object field name.
2961 * @param mixed $value The Post Object value.
2962 * @param int $post_id Post ID.
2963 * @param string $context Optional. How to sanitize the field. Possible values are 'raw', 'edit',
2964 * 'db', 'display', 'attribute' and 'js'. Default 'display'.
2965 * @return mixed Sanitized value.
2966 */
2967function sanitize_post_field( $field, $value, $post_id, $context = 'display' ) {
2968 $int_fields = array( 'ID', 'post_parent', 'menu_order' );
2969 if ( in_array( $field, $int_fields, true ) ) {
2970 $value = (int) $value;
2971 }
2972
2973 // Fields which contain arrays of integers.
2974 $array_int_fields = array( 'ancestors' );
2975 if ( in_array( $field, $array_int_fields, true ) ) {
2976 $value = array_map( 'absint', $value );
2977 return $value;
2978 }
2979
2980 if ( 'raw' === $context ) {
2981 return $value;
2982 }
2983
2984 $prefixed = false;
2985 if ( str_contains( $field, 'post_' ) ) {
2986 $prefixed = true;
2987 $field_no_prefix = str_replace( 'post_', '', $field );
2988 }
2989
2990 if ( 'edit' === $context ) {
2991 $format_to_edit = array( 'post_content', 'post_excerpt', 'post_title', 'post_password' );
2992
2993 if ( $prefixed ) {
2994
2995 /**
2996 * Filters the value of a specific post field to edit.
2997 *
2998 * The dynamic portion of the hook name, `$field`, refers to the post
2999 * field name. Possible filter names include:
3000 *
3001 * - `edit_post_author`
3002 * - `edit_post_date`
3003 * - `edit_post_date_gmt`
3004 * - `edit_post_content`
3005 * - `edit_post_title`
3006 * - `edit_post_excerpt`
3007 * - `edit_post_status`
3008 * - `edit_post_password`
3009 * - `edit_post_name`
3010 * - `edit_post_modified`
3011 * - `edit_post_modified_gmt`
3012 * - `edit_post_content_filtered`
3013 * - `edit_post_parent`
3014 * - `edit_post_type`
3015 * - `edit_post_mime_type`
3016 *
3017 * @since 2.3.0
3018 *
3019 * @param mixed $value Value of the post field.
3020 * @param int $post_id Post ID.
3021 */
3022 $value = apply_filters( "edit_{$field}", $value, $post_id );
3023
3024 /**
3025 * Filters the value of a specific post field to edit.
3026 *
3027 * Only applied to post fields with a name which is prefixed with `post_`.
3028 *
3029 * The dynamic portion of the hook name, `$field_no_prefix`, refers to the
3030 * post field name minus the `post_` prefix. Possible filter names include:
3031 *
3032 * - `author_edit_pre`
3033 * - `date_edit_pre`
3034 * - `date_gmt_edit_pre`
3035 * - `content_edit_pre`
3036 * - `title_edit_pre`
3037 * - `excerpt_edit_pre`
3038 * - `status_edit_pre`
3039 * - `password_edit_pre`
3040 * - `name_edit_pre`
3041 * - `modified_edit_pre`
3042 * - `modified_gmt_edit_pre`
3043 * - `content_filtered_edit_pre`
3044 * - `parent_edit_pre`
3045 * - `type_edit_pre`
3046 * - `mime_type_edit_pre`
3047 *
3048 * @since 2.3.0
3049 *
3050 * @param mixed $value Value of the post field.
3051 * @param int $post_id Post ID.
3052 */
3053 $value = apply_filters( "{$field_no_prefix}_edit_pre", $value, $post_id );
3054 } else {
3055 /**
3056 * Filters the value of a specific post field to edit.
3057 *
3058 * Only applied to post fields not prefixed with `post_`.
3059 *
3060 * The dynamic portion of the hook name, `$field`, refers to the
3061 * post field name. Possible filter names include:
3062 *
3063 * - `edit_post_ID`
3064 * - `edit_post_ping_status`
3065 * - `edit_post_pinged`
3066 * - `edit_post_to_ping`
3067 * - `edit_post_comment_count`
3068 * - `edit_post_comment_status`
3069 * - `edit_post_guid`
3070 * - `edit_post_menu_order`
3071 *
3072 * @since 2.3.0
3073 *
3074 * @param mixed $value Value of the post field.
3075 * @param int $post_id Post ID.
3076 */
3077 $value = apply_filters( "edit_post_{$field}", $value, $post_id );
3078 }
3079
3080 if ( in_array( $field, $format_to_edit, true ) ) {
3081 if ( 'post_content' === $field ) {
3082 $value = format_to_edit( $value, user_can_richedit() );
3083 } else {
3084 $value = format_to_edit( $value );
3085 }
3086 } else {
3087 $value = esc_attr( $value );
3088 }
3089 } elseif ( 'db' === $context ) {
3090 if ( $prefixed ) {
3091
3092 /**
3093 * Filters the value of a specific post field before saving.
3094 *
3095 * Only applied to post fields with a name which is prefixed with `post_`.
3096 *
3097 * The dynamic portion of the hook name, `$field`, refers to the post
3098 * field name. Possible filter names include:
3099 *
3100 * - `pre_post_author`
3101 * - `pre_post_date`
3102 * - `pre_post_date_gmt`
3103 * - `pre_post_content`
3104 * - `pre_post_title`
3105 * - `pre_post_excerpt`
3106 * - `pre_post_status`
3107 * - `pre_post_password`
3108 * - `pre_post_name`
3109 * - `pre_post_modified`
3110 * - `pre_post_modified_gmt`
3111 * - `pre_post_content_filtered`
3112 * - `pre_post_parent`
3113 * - `pre_post_type`
3114 * - `pre_post_mime_type`
3115 *
3116 * @since 2.3.0
3117 *
3118 * @param mixed $value Value of the post field.
3119 */
3120 $value = apply_filters( "pre_{$field}", $value );
3121
3122 /**
3123 * Filters the value of a specific field before saving.
3124 *
3125 * Only applied to post fields with a name which is prefixed with `post_`.
3126 *
3127 * The dynamic portion of the hook name, `$field_no_prefix`, refers to the
3128 * post field name minus the `post_` prefix. Possible filter names include:
3129 *
3130 * - `author_save_pre`
3131 * - `date_save_pre`
3132 * - `date_gmt_save_pre`
3133 * - `content_save_pre`
3134 * - `title_save_pre`
3135 * - `excerpt_save_pre`
3136 * - `status_save_pre`
3137 * - `password_save_pre`
3138 * - `name_save_pre`
3139 * - `modified_save_pre`
3140 * - `modified_gmt_save_pre`
3141 * - `content_filtered_save_pre`
3142 * - `parent_save_pre`
3143 * - `type_save_pre`
3144 * - `mime_type_save_pre`
3145 *
3146 * @since 2.3.0
3147 *
3148 * @param mixed $value Value of the post field.
3149 */
3150 $value = apply_filters( "{$field_no_prefix}_save_pre", $value );
3151 } else {
3152 /**
3153 * Filters the value of a specific field before saving.
3154 *
3155 * Only applied to post fields with a name which is prefixed with `post_`.
3156 *
3157 * The dynamic portion of the hook name, `$field_no_prefix`, refers to the
3158 * post field name minus the `post_` prefix. Possible filter names include:
3159 *
3160 * - `pre_post_ID`
3161 * - `pre_post_comment_status`
3162 * - `pre_post_ping_status`
3163 * - `pre_post_to_ping`
3164 * - `pre_post_pinged`
3165 * - `pre_post_guid`
3166 * - `pre_post_menu_order`
3167 * - `pre_post_comment_count`
3168 *
3169 * @since 2.3.0
3170 *
3171 * @param mixed $value Value of the post field.
3172 */
3173 $value = apply_filters( "pre_post_{$field}", $value );
3174
3175 /**
3176 * Filters the value of a specific post field before saving.
3177 *
3178 * Only applied to post fields with a name which is *not* prefixed with `post_`.
3179 *
3180 * The dynamic portion of the hook name, `$field`, refers to the post
3181 * field name. Possible filter names include:
3182 *
3183 * - `ID_pre`
3184 * - `comment_status_pre`
3185 * - `ping_status_pre`
3186 * - `to_ping_pre`
3187 * - `pinged_pre`
3188 * - `guid_pre`
3189 * - `menu_order_pre`
3190 * - `comment_count_pre`
3191 *
3192 * @since 2.3.0
3193 *
3194 * @param mixed $value Value of the post field.
3195 */
3196 $value = apply_filters( "{$field}_pre", $value );
3197 }
3198 } else {
3199
3200 // Use display filters by default.
3201 if ( $prefixed ) {
3202
3203 /**
3204 * Filters the value of a specific post field for display.
3205 *
3206 * Only applied to post fields with a name which is prefixed with `post_`.
3207 *
3208 * The dynamic portion of the hook name, `$field`, refers to the post
3209 * field name. Possible filter names include:
3210 *
3211 * - `post_author`
3212 * - `post_date`
3213 * - `post_date_gmt`
3214 * - `post_content`
3215 * - `post_title`
3216 * - `post_excerpt`
3217 * - `post_status`
3218 * - `post_password`
3219 * - `post_name`
3220 * - `post_modified`
3221 * - `post_modified_gmt`
3222 * - `post_content_filtered`
3223 * - `post_parent`
3224 * - `post_type`
3225 * - `post_mime_type`
3226 *
3227 * @since 2.3.0
3228 *
3229 * @param mixed $value Value of the prefixed post field.
3230 * @param int $post_id Post ID.
3231 * @param string $context Context for how to sanitize the field.
3232 * Accepts 'raw', 'edit', 'db', 'display',
3233 * 'attribute', or 'js'. Default 'display'.
3234 */
3235 $value = apply_filters( "{$field}", $value, $post_id, $context );
3236 } else {
3237 /**
3238 * Filters the value of a specific post field for display.
3239 *
3240 * Only applied to post fields name which is *not* prefixed with `post_`.
3241 *
3242 * The dynamic portion of the hook name, `$field`, refers to the post
3243 * field name. Possible filter names include:
3244 *
3245 * - `post_ID`
3246 * - `post_comment_status`
3247 * - `post_ping_status`
3248 * - `post_to_ping`
3249 * - `post_pinged`
3250 * - `post_guid`
3251 * - `post_menu_order`
3252 * - `post_comment_count`
3253 *
3254 * @since 2.3.0
3255 *
3256 * @param mixed $value Value of the unprefixed post field.
3257 * @param int $post_id Post ID
3258 * @param string $context Context for how to sanitize the field.
3259 * Accepts 'raw', 'edit', 'db', 'display',
3260 * 'attribute', or 'js'. Default 'display'.
3261 */
3262 $value = apply_filters( "post_{$field}", $value, $post_id, $context );
3263 }
3264
3265 if ( 'attribute' === $context ) {
3266 $value = esc_attr( $value );
3267 } elseif ( 'js' === $context ) {
3268 $value = esc_js( $value );
3269 }
3270 }
3271
3272 // Restore the type for integer fields after esc_attr().
3273 if ( in_array( $field, $int_fields, true ) ) {
3274 $value = (int) $value;
3275 }
3276 return $value;
3277}
3278
3279/**
3280 * Makes a post sticky.
3281 *
3282 * Sticky posts should be displayed at the top of the front page.
3283 *
3284 * @since 2.7.0
3285 *
3286 * @param int $post_id Post ID.
3287 */
3288function stick_post( $post_id ) {
3289 $post_id = (int) $post_id;
3290 $stickies = get_option( 'sticky_posts' );
3291 $updated = false;
3292
3293 if ( ! is_array( $stickies ) ) {
3294 $stickies = array();
3295 } else {
3296 $stickies = array_unique( array_map( 'intval', $stickies ) );
3297 }
3298
3299 if ( ! in_array( $post_id, $stickies, true ) ) {
3300 $stickies[] = $post_id;
3301 $updated = update_option( 'sticky_posts', array_values( $stickies ) );
3302 }
3303
3304 if ( $updated ) {
3305 /**
3306 * Fires once a post has been added to the sticky list.
3307 *
3308 * @since 4.6.0
3309 *
3310 * @param int $post_id ID of the post that was stuck.
3311 */
3312 do_action( 'post_stuck', $post_id );
3313 }
3314}
3315
3316/**
3317 * Un-sticks a post.
3318 *
3319 * Sticky posts should be displayed at the top of the front page.
3320 *
3321 * @since 2.7.0
3322 *
3323 * @param int $post_id Post ID.
3324 */
3325function unstick_post( $post_id ) {
3326 $post_id = (int) $post_id;
3327 $stickies = get_option( 'sticky_posts' );
3328
3329 if ( ! is_array( $stickies ) ) {
3330 return;
3331 }
3332
3333 $stickies = array_values( array_unique( array_map( 'intval', $stickies ) ) );
3334
3335 if ( ! in_array( $post_id, $stickies, true ) ) {
3336 return;
3337 }
3338
3339 $offset = array_search( $post_id, $stickies, true );
3340 if ( false === $offset ) {
3341 return;
3342 }
3343
3344 array_splice( $stickies, $offset, 1 );
3345
3346 $updated = update_option( 'sticky_posts', $stickies );
3347
3348 if ( $updated ) {
3349 /**
3350 * Fires once a post has been removed from the sticky list.
3351 *
3352 * @since 4.6.0
3353 *
3354 * @param int $post_id ID of the post that was unstuck.
3355 */
3356 do_action( 'post_unstuck', $post_id );
3357 }
3358}
3359
3360/**
3361 * Returns the cache key for wp_count_posts() based on the passed arguments.
3362 *
3363 * @since 3.9.0
3364 * @access private
3365 *
3366 * @param string $type Optional. Post type to retrieve count Default 'post'.
3367 * @param string $perm Optional. 'readable' or empty. Default empty.
3368 * @return string The cache key.
3369 */
3370function _count_posts_cache_key( $type = 'post', $perm = '' ) {
3371 $cache_key = 'posts-' . $type;
3372
3373 if ( 'readable' === $perm && is_user_logged_in() ) {
3374 $post_type_object = get_post_type_object( $type );
3375
3376 if ( $post_type_object && ! current_user_can( $post_type_object->cap->read_private_posts ) ) {
3377 $cache_key .= '_' . $perm . '_' . get_current_user_id();
3378 }
3379 }
3380
3381 return $cache_key;
3382}
3383
3384/**
3385 * Counts number of posts of a post type and if user has permissions to view.
3386 *
3387 * This function provides an efficient method of finding the amount of post's
3388 * type a blog has. Another method is to count the amount of items in
3389 * get_posts(), but that method has a lot of overhead with doing so. Therefore,
3390 * when developing for 2.5+, use this function instead.
3391 *
3392 * The $perm parameter checks for 'readable' value and if the user can read
3393 * private posts, it will display that for the user that is signed in.
3394 *
3395 * @since 2.5.0
3396 *
3397 * @global wpdb $wpdb WordPress database abstraction object.
3398 *
3399 * @param string $type Optional. Post type to retrieve count. Default 'post'.
3400 * @param string $perm Optional. 'readable' or empty. Default empty.
3401 * @return stdClass An object containing the number of posts for each status,
3402 * or an empty object if the post type does not exist.
3403 */
3404function wp_count_posts( $type = 'post', $perm = '' ) {
3405 global $wpdb;
3406
3407 if ( ! post_type_exists( $type ) ) {
3408 return new stdClass();
3409 }
3410
3411 $cache_key = _count_posts_cache_key( $type, $perm );
3412
3413 $counts = wp_cache_get( $cache_key, 'counts' );
3414 if ( false !== $counts ) {
3415 // We may have cached this before every status was registered.
3416 foreach ( get_post_stati() as $status ) {
3417 if ( ! isset( $counts->{$status} ) ) {
3418 $counts->{$status} = 0;
3419 }
3420 }
3421
3422 /** This filter is documented in wp-includes/post.php */
3423 return apply_filters( 'wp_count_posts', $counts, $type, $perm );
3424 }
3425
3426 if (
3427 'readable' === $perm &&
3428 is_user_logged_in() &&
3429 ! current_user_can( get_post_type_object( $type )->cap->read_private_posts )
3430 ) {
3431 // Optimized query uses subqueries which can leverage DB indexes for better performance. See #61097.
3432 $query = "
3433 SELECT post_status, COUNT(*) AS num_posts
3434 FROM (
3435 SELECT post_status
3436 FROM {$wpdb->posts}
3437 WHERE post_type = %s AND post_status != 'private'
3438 UNION ALL
3439 SELECT post_status
3440 FROM {$wpdb->posts}
3441 WHERE post_type = %s AND post_status = 'private' AND post_author = %d
3442 ) AS filtered_posts
3443 ";
3444 $args = array( $type, $type, get_current_user_id() );
3445 } else {
3446 $query = "
3447 SELECT post_status, COUNT(*) AS num_posts
3448 FROM {$wpdb->posts}
3449 WHERE post_type = %s
3450 ";
3451 $args = array( $type );
3452 }
3453
3454 $query .= ' GROUP BY post_status';
3455
3456 $results = (array) $wpdb->get_results(
3457 $wpdb->prepare( $query, ...$args ), // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared -- Placeholders are used in the string contained in the variable.
3458 ARRAY_A
3459 );
3460 $counts = array_fill_keys( get_post_stati(), 0 );
3461
3462 foreach ( $results as $row ) {
3463 $counts[ $row['post_status'] ] = $row['num_posts'];
3464 }
3465
3466 $counts = (object) $counts;
3467 wp_cache_set( $cache_key, $counts, 'counts' );
3468
3469 /**
3470 * Filters the post counts by status for the current post type.
3471 *
3472 * @since 3.7.0
3473 *
3474 * @param stdClass $counts An object containing the current post_type's post
3475 * counts by status.
3476 * @param string $type Post type.
3477 * @param string $perm The permission to determine if the posts are 'readable'
3478 * by the current user.
3479 */
3480 return apply_filters( 'wp_count_posts', $counts, $type, $perm );
3481}
3482
3483/**
3484 * Counts number of attachments for the mime type(s).
3485 *
3486 * If you set the optional mime_type parameter, then an array will still be
3487 * returned, but will only have the item you are looking for. It does not give
3488 * you the number of attachments that are children of a post. You can get that
3489 * by counting the number of children that post has.
3490 *
3491 * @since 2.5.0
3492 *
3493 * @global wpdb $wpdb WordPress database abstraction object.
3494 *
3495 * @param string|string[] $mime_type Optional. Array or comma-separated list of
3496 * MIME patterns. Default empty.
3497 * @return stdClass An object containing the attachment counts by mime type.
3498 */
3499function wp_count_attachments( $mime_type = '' ) {
3500 global $wpdb;
3501
3502 $cache_key = sprintf(
3503 'attachments%s',
3504 ! empty( $mime_type ) ? ':' . str_replace( '/', '_', implode( '-', (array) $mime_type ) ) : ''
3505 );
3506
3507 $counts = wp_cache_get( $cache_key, 'counts' );
3508
3509 if ( false === $counts ) {
3510 $and = wp_post_mime_type_where( $mime_type );
3511 $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 );
3512
3513 $counts = array();
3514 foreach ( (array) $count as $row ) {
3515 $counts[ $row['post_mime_type'] ] = $row['num_posts'];
3516 }
3517 $counts['trash'] = $wpdb->get_var( "SELECT COUNT( * ) FROM $wpdb->posts WHERE post_type = 'attachment' AND post_status = 'trash' $and" );
3518
3519 wp_cache_set( $cache_key, (object) $counts, 'counts' );
3520 }
3521
3522 /**
3523 * Filters the attachment counts by mime type.
3524 *
3525 * @since 3.7.0
3526 *
3527 * @param stdClass $counts An object containing the attachment counts by
3528 * mime type.
3529 * @param string|string[] $mime_type Array or comma-separated list of MIME patterns.
3530 */
3531 return apply_filters( 'wp_count_attachments', (object) $counts, $mime_type );
3532}
3533
3534/**
3535 * Gets default post mime types.
3536 *
3537 * @since 2.9.0
3538 * @since 5.3.0 Added the 'Documents', 'Spreadsheets', and 'Archives' mime type groups.
3539 *
3540 * @return array List of post mime types.
3541 */
3542function get_post_mime_types() {
3543 $post_mime_types = array( // array( adj, noun )
3544 'image' => array(
3545 __( 'Images' ),
3546 __( 'Manage Images' ),
3547 /* translators: %s: Number of images. */
3548 _n_noop(
3549 'Image <span class="count">(%s)</span>',
3550 'Images <span class="count">(%s)</span>'
3551 ),
3552 ),
3553 'audio' => array(
3554 _x( 'Audio', 'file type group' ),
3555 __( 'Manage Audio' ),
3556 /* translators: %s: Number of audio files. */
3557 _n_noop(
3558 'Audio <span class="count">(%s)</span>',
3559 'Audio <span class="count">(%s)</span>'
3560 ),
3561 ),
3562 'video' => array(
3563 _x( 'Video', 'file type group' ),
3564 __( 'Manage Video' ),
3565 /* translators: %s: Number of video files. */
3566 _n_noop(
3567 'Video <span class="count">(%s)</span>',
3568 'Video <span class="count">(%s)</span>'
3569 ),
3570 ),
3571 'document' => array(
3572 __( 'Documents' ),
3573 __( 'Manage Documents' ),
3574 /* translators: %s: Number of documents. */
3575 _n_noop(
3576 'Document <span class="count">(%s)</span>',
3577 'Documents <span class="count">(%s)</span>'
3578 ),
3579 ),
3580 'spreadsheet' => array(
3581 __( 'Spreadsheets' ),
3582 __( 'Manage Spreadsheets' ),
3583 /* translators: %s: Number of spreadsheets. */
3584 _n_noop(
3585 'Spreadsheet <span class="count">(%s)</span>',
3586 'Spreadsheets <span class="count">(%s)</span>'
3587 ),
3588 ),
3589 'archive' => array(
3590 _x( 'Archives', 'file type group' ),
3591 __( 'Manage Archives' ),
3592 /* translators: %s: Number of archives. */
3593 _n_noop(
3594 'Archive <span class="count">(%s)</span>',
3595 'Archives <span class="count">(%s)</span>'
3596 ),
3597 ),
3598 );
3599
3600 $ext_types = wp_get_ext_types();
3601 $mime_types = wp_get_mime_types();
3602
3603 foreach ( $post_mime_types as $group => $labels ) {
3604 if ( in_array( $group, array( 'image', 'audio', 'video' ), true ) ) {
3605 continue;
3606 }
3607
3608 if ( ! isset( $ext_types[ $group ] ) ) {
3609 unset( $post_mime_types[ $group ] );
3610 continue;
3611 }
3612
3613 $group_mime_types = array();
3614 foreach ( $ext_types[ $group ] as $extension ) {
3615 foreach ( $mime_types as $exts => $mime ) {
3616 if ( preg_match( '!^(' . $exts . ')$!i', $extension ) ) {
3617 $group_mime_types[] = $mime;
3618 break;
3619 }
3620 }
3621 }
3622 $group_mime_types = implode( ',', array_unique( $group_mime_types ) );
3623
3624 $post_mime_types[ $group_mime_types ] = $labels;
3625 unset( $post_mime_types[ $group ] );
3626 }
3627
3628 /**
3629 * Filters the default list of post mime types.
3630 *
3631 * @since 2.5.0
3632 *
3633 * @param array $post_mime_types Default list of post mime types.
3634 */
3635 return apply_filters( 'post_mime_types', $post_mime_types );
3636}
3637
3638/**
3639 * Checks a MIME-Type against a list.
3640 *
3641 * If the `$wildcard_mime_types` parameter is a string, it must be comma separated
3642 * list. If the `$real_mime_types` is a string, it is also comma separated to
3643 * create the list.
3644 *
3645 * @since 2.5.0
3646 *
3647 * @param string|string[] $wildcard_mime_types Mime types, e.g. `audio/mpeg`, `image` (same as `image/*`),
3648 * or `flash` (same as `*flash*`).
3649 * @param string|string[] $real_mime_types Real post mime type values.
3650 * @return array array(wildcard=>array(real types)).
3651 */
3652function wp_match_mime_types( $wildcard_mime_types, $real_mime_types ) {
3653 $matches = array();
3654 if ( is_string( $wildcard_mime_types ) ) {
3655 $wildcard_mime_types = array_map( 'trim', explode( ',', $wildcard_mime_types ) );
3656 }
3657 if ( is_string( $real_mime_types ) ) {
3658 $real_mime_types = array_map( 'trim', explode( ',', $real_mime_types ) );
3659 }
3660
3661 $patternses = array();
3662 $wild = '[-._a-z0-9]*';
3663
3664 foreach ( (array) $wildcard_mime_types as $type ) {
3665 $mimes = array_map( 'trim', explode( ',', $type ) );
3666 foreach ( $mimes as $mime ) {
3667 $regex = str_replace( '__wildcard__', $wild, preg_quote( str_replace( '*', '__wildcard__', $mime ) ) );
3668
3669 $patternses[][ $type ] = "^$regex$";
3670
3671 if ( ! str_contains( $mime, '/' ) ) {
3672 $patternses[][ $type ] = "^$regex/";
3673 $patternses[][ $type ] = $regex;
3674 }
3675 }
3676 }
3677 asort( $patternses );
3678
3679 foreach ( $patternses as $patterns ) {
3680 foreach ( $patterns as $type => $pattern ) {
3681 foreach ( (array) $real_mime_types as $real ) {
3682 if ( preg_match( "#$pattern#", $real )
3683 && ( empty( $matches[ $type ] ) || false === array_search( $real, $matches[ $type ], true ) )
3684 ) {
3685 $matches[ $type ][] = $real;
3686 }
3687 }
3688 }
3689 }
3690
3691 return $matches;
3692}
3693
3694/**
3695 * Converts MIME types into SQL.
3696 *
3697 * @since 2.5.0
3698 *
3699 * @param string|string[] $post_mime_types List of mime types or comma separated string
3700 * of mime types.
3701 * @param string $table_alias Optional. Specify a table alias, if needed.
3702 * Default empty.
3703 * @return string The SQL AND clause for mime searching.
3704 */
3705function wp_post_mime_type_where( $post_mime_types, $table_alias = '' ) {
3706 $where = '';
3707 $wildcards = array( '', '%', '%/%' );
3708 if ( is_string( $post_mime_types ) ) {
3709 $post_mime_types = array_map( 'trim', explode( ',', $post_mime_types ) );
3710 }
3711
3712 $where_clauses = array();
3713
3714 foreach ( (array) $post_mime_types as $mime_type ) {
3715 $mime_type = preg_replace( '/\s/', '', $mime_type );
3716 $slashpos = strpos( $mime_type, '/' );
3717 if ( false !== $slashpos ) {
3718 $mime_group = preg_replace( '/[^-*.a-zA-Z0-9]/', '', substr( $mime_type, 0, $slashpos ) );
3719 $mime_subgroup = preg_replace( '/[^-*.+a-zA-Z0-9]/', '', substr( $mime_type, $slashpos + 1 ) );
3720 if ( empty( $mime_subgroup ) ) {
3721 $mime_subgroup = '*';
3722 } else {
3723 $mime_subgroup = str_replace( '/', '', $mime_subgroup );
3724 }
3725 $mime_pattern = "$mime_group/$mime_subgroup";
3726 } else {
3727 $mime_pattern = preg_replace( '/[^-*.a-zA-Z0-9]/', '', $mime_type );
3728 if ( ! str_contains( $mime_pattern, '*' ) ) {
3729 $mime_pattern .= '/*';
3730 }
3731 }
3732
3733 $mime_pattern = preg_replace( '/\*+/', '%', $mime_pattern );
3734
3735 if ( in_array( $mime_type, $wildcards, true ) ) {
3736 return '';
3737 }
3738
3739 if ( str_contains( $mime_pattern, '%' ) ) {
3740 $where_clauses[] = empty( $table_alias ) ? "post_mime_type LIKE '$mime_pattern'" : "$table_alias.post_mime_type LIKE '$mime_pattern'";
3741 } else {
3742 $where_clauses[] = empty( $table_alias ) ? "post_mime_type = '$mime_pattern'" : "$table_alias.post_mime_type = '$mime_pattern'";
3743 }
3744 }
3745
3746 if ( ! empty( $where_clauses ) ) {
3747 $where = ' AND (' . implode( ' OR ', $where_clauses ) . ') ';
3748 }
3749
3750 return $where;
3751}
3752
3753/**
3754 * Trashes or deletes a post or page.
3755 *
3756 * When the post and page is permanently deleted, everything that is tied to
3757 * it is deleted also. This includes comments, post meta fields, and terms
3758 * associated with the post.
3759 *
3760 * The post or page is moved to Trash instead of permanently deleted unless
3761 * Trash is disabled, item is already in the Trash, or $force_delete is true.
3762 *
3763 * @since 1.0.0
3764 *
3765 * @global wpdb $wpdb WordPress database abstraction object.
3766 * @see wp_delete_attachment()
3767 * @see wp_trash_post()
3768 *
3769 * @param int $post_id Post ID. (The default of 0 is for historical reasons; providing it is incorrect.)
3770 * @param bool $force_delete Optional. Whether to bypass Trash and force deletion.
3771 * Default false.
3772 * @return WP_Post|false|null Post data on success, false or null on failure.
3773 */
3774function wp_delete_post( $post_id = 0, $force_delete = false ) {
3775 global $wpdb;
3776
3777 $post_id = (int) $post_id;
3778 if ( $post_id <= 0 ) {
3779 _doing_it_wrong( __FUNCTION__, __( 'The post ID must be greater than 0.' ), '6.9.0' );
3780 return false;
3781 }
3782
3783 $post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id ) );
3784
3785 if ( ! $post ) {
3786 return $post;
3787 }
3788
3789 $post = get_post( $post );
3790
3791 if ( ! $force_delete
3792 && ( 'post' === $post->post_type || 'page' === $post->post_type )
3793 && 'trash' !== get_post_status( $post_id ) && EMPTY_TRASH_DAYS
3794 ) {
3795 return wp_trash_post( $post_id );
3796 }
3797
3798 if ( 'attachment' === $post->post_type ) {
3799 return wp_delete_attachment( $post_id, $force_delete );
3800 }
3801
3802 /**
3803 * Filters whether a post deletion should take place.
3804 *
3805 * @since 4.4.0
3806 *
3807 * @param WP_Post|false|null $check Whether to go forward with deletion. Anything other than null will short-circuit deletion.
3808 * @param WP_Post $post Post object.
3809 * @param bool $force_delete Whether to bypass the Trash.
3810 */
3811 $check = apply_filters( 'pre_delete_post', null, $post, $force_delete );
3812 if ( null !== $check ) {
3813 return $check;
3814 }
3815
3816 /**
3817 * Fires before a post is deleted, at the start of wp_delete_post().
3818 *
3819 * @since 3.2.0
3820 * @since 5.5.0 Added the `$post` parameter.
3821 *
3822 * @see wp_delete_post()
3823 *
3824 * @param int $post_id Post ID.
3825 * @param WP_Post $post Post object.
3826 */
3827 do_action( 'before_delete_post', $post_id, $post );
3828
3829 delete_post_meta( $post_id, '_wp_trash_meta_status' );
3830 delete_post_meta( $post_id, '_wp_trash_meta_time' );
3831
3832 wp_delete_object_term_relationships( $post_id, get_object_taxonomies( $post->post_type ) );
3833
3834 $parent_data = array( 'post_parent' => $post->post_parent );
3835 $parent_where = array( 'post_parent' => $post_id );
3836
3837 if ( is_post_type_hierarchical( $post->post_type ) ) {
3838 // Point children of this page to its parent, also clean the cache of affected children.
3839 $children_query = $wpdb->prepare(
3840 "SELECT * FROM $wpdb->posts WHERE post_parent = %d AND post_type = %s",
3841 $post_id,
3842 $post->post_type
3843 );
3844
3845 $children = $wpdb->get_results( $children_query );
3846
3847 if ( $children ) {
3848 $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => $post->post_type ) );
3849 }
3850 }
3851
3852 // Do raw query. wp_get_post_revisions() is filtered.
3853 $revision_ids = $wpdb->get_col(
3854 $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE post_parent = %d AND post_type = 'revision'", $post_id )
3855 );
3856
3857 // Use wp_delete_post (via wp_delete_post_revision) again. Ensures any meta/misplaced data gets cleaned up.
3858 foreach ( $revision_ids as $revision_id ) {
3859 wp_delete_post_revision( $revision_id );
3860 }
3861
3862 // Point all attachments to this post up one level.
3863 $wpdb->update( $wpdb->posts, $parent_data, $parent_where + array( 'post_type' => 'attachment' ) );
3864
3865 wp_defer_comment_counting( true );
3866
3867 $comment_ids = $wpdb->get_col(
3868 $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d ORDER BY comment_ID DESC", $post_id )
3869 );
3870
3871 foreach ( $comment_ids as $comment_id ) {
3872 wp_delete_comment( $comment_id, true );
3873 }
3874
3875 wp_defer_comment_counting( false );
3876
3877 $post_meta_ids = $wpdb->get_col(
3878 $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id )
3879 );
3880
3881 foreach ( $post_meta_ids as $mid ) {
3882 delete_metadata_by_mid( 'post', $mid );
3883 }
3884
3885 /**
3886 * Fires immediately before a post is deleted from the database.
3887 *
3888 * The dynamic portion of the hook name, `$post->post_type`, refers to
3889 * the post type slug.
3890 *
3891 * @since 6.6.0
3892 *
3893 * @param int $post_id Post ID.
3894 * @param WP_Post $post Post object.
3895 */
3896 do_action( "delete_post_{$post->post_type}", $post_id, $post );
3897
3898 /**
3899 * Fires immediately before a post is deleted from the database.
3900 *
3901 * @since 1.2.0
3902 * @since 5.5.0 Added the `$post` parameter.
3903 *
3904 * @param int $post_id Post ID.
3905 * @param WP_Post $post Post object.
3906 */
3907 do_action( 'delete_post', $post_id, $post );
3908
3909 $result = $wpdb->delete( $wpdb->posts, array( 'ID' => $post_id ) );
3910 if ( ! $result ) {
3911 return false;
3912 }
3913
3914 /**
3915 * Fires immediately after a post is deleted from the database.
3916 *
3917 * The dynamic portion of the hook name, `$post->post_type`, refers to
3918 * the post type slug.
3919 *
3920 * @since 6.6.0
3921 *
3922 * @param int $post_id Post ID.
3923 * @param WP_Post $post Post object.
3924 */
3925 do_action( "deleted_post_{$post->post_type}", $post_id, $post );
3926
3927 /**
3928 * Fires immediately after a post is deleted from the database.
3929 *
3930 * @since 2.2.0
3931 * @since 5.5.0 Added the `$post` parameter.
3932 *
3933 * @param int $post_id Post ID.
3934 * @param WP_Post $post Post object.
3935 */
3936 do_action( 'deleted_post', $post_id, $post );
3937
3938 clean_post_cache( $post );
3939
3940 if ( is_post_type_hierarchical( $post->post_type ) && $children ) {
3941 foreach ( $children as $child ) {
3942 clean_post_cache( $child );
3943 }
3944 }
3945
3946 wp_clear_scheduled_hook( 'publish_future_post', array( $post_id ) );
3947
3948 /**
3949 * Fires after a post is deleted, at the conclusion of wp_delete_post().
3950 *
3951 * @since 3.2.0
3952 * @since 5.5.0 Added the `$post` parameter.
3953 *
3954 * @see wp_delete_post()
3955 *
3956 * @param int $post_id Post ID.
3957 * @param WP_Post $post Post object.
3958 */
3959 do_action( 'after_delete_post', $post_id, $post );
3960
3961 return $post;
3962}
3963
3964/**
3965 * Resets the page_on_front, show_on_front, and page_for_post settings when
3966 * a linked page is deleted or trashed.
3967 *
3968 * Also ensures the post is no longer sticky.
3969 *
3970 * @since 3.7.0
3971 * @access private
3972 *
3973 * @param int $post_id Post ID.
3974 */
3975function _reset_front_page_settings_for_post( $post_id ) {
3976 $post = get_post( $post_id );
3977
3978 if ( 'page' === $post->post_type ) {
3979 /*
3980 * If the page is defined in option page_on_front or post_for_posts,
3981 * adjust the corresponding options.
3982 */
3983 if ( (int) get_option( 'page_on_front' ) === $post->ID ) {
3984 update_option( 'show_on_front', 'posts' );
3985 update_option( 'page_on_front', 0 );
3986 }
3987 if ( (int) get_option( 'page_for_posts' ) === $post->ID ) {
3988 update_option( 'page_for_posts', 0 );
3989 }
3990 }
3991
3992 unstick_post( $post->ID );
3993}
3994
3995/**
3996 * Moves a post or page to the Trash
3997 *
3998 * If Trash is disabled, the post or page is permanently deleted.
3999 *
4000 * @since 2.9.0
4001 *
4002 * @see wp_delete_post()
4003 *
4004 * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`
4005 * if `EMPTY_TRASH_DAYS` equals true.
4006 * @return WP_Post|false|null Post data on success, false or null on failure.
4007 */
4008function wp_trash_post( $post_id = 0 ) {
4009 if ( ! EMPTY_TRASH_DAYS ) {
4010 return wp_delete_post( $post_id, true );
4011 }
4012
4013 $post = get_post( $post_id );
4014
4015 if ( ! $post ) {
4016 return $post;
4017 }
4018
4019 if ( 'trash' === $post->post_status ) {
4020 return false;
4021 }
4022
4023 $previous_status = $post->post_status;
4024
4025 /**
4026 * Filters whether a post trashing should take place.
4027 *
4028 * @since 4.9.0
4029 * @since 6.3.0 Added the `$previous_status` parameter.
4030 *
4031 * @param bool|null $trash Whether to go forward with trashing.
4032 * @param WP_Post $post Post object.
4033 * @param string $previous_status The status of the post about to be trashed.
4034 */
4035 $check = apply_filters( 'pre_trash_post', null, $post, $previous_status );
4036
4037 if ( null !== $check ) {
4038 return $check;
4039 }
4040
4041 /**
4042 * Fires before a post is sent to the Trash.
4043 *
4044 * @since 3.3.0
4045 * @since 6.3.0 Added the `$previous_status` parameter.
4046 *
4047 * @param int $post_id Post ID.
4048 * @param string $previous_status The status of the post about to be trashed.
4049 */
4050 do_action( 'wp_trash_post', $post_id, $previous_status );
4051
4052 add_post_meta( $post_id, '_wp_trash_meta_status', $previous_status );
4053 add_post_meta( $post_id, '_wp_trash_meta_time', time() );
4054
4055 $post_updated = wp_update_post(
4056 array(
4057 'ID' => $post_id,
4058 'post_status' => 'trash',
4059 )
4060 );
4061
4062 if ( ! $post_updated ) {
4063 return false;
4064 }
4065
4066 wp_trash_post_comments( $post_id );
4067
4068 /**
4069 * Fires after a post is sent to the Trash.
4070 *
4071 * @since 2.9.0
4072 * @since 6.3.0 Added the `$previous_status` parameter.
4073 *
4074 * @param int $post_id Post ID.
4075 * @param string $previous_status The status of the post at the point where it was trashed.
4076 */
4077 do_action( 'trashed_post', $post_id, $previous_status );
4078
4079 return $post;
4080}
4081
4082/**
4083 * Restores a post from the Trash.
4084 *
4085 * @since 2.9.0
4086 * @since 5.6.0 An untrashed post is now returned to 'draft' status by default, except for
4087 * attachments which are returned to their original 'inherit' status.
4088 *
4089 * @param int $post_id Optional. Post ID. Default is the ID of the global `$post`.
4090 * @return WP_Post|false|null Post data on success, false or null on failure.
4091 */
4092function wp_untrash_post( $post_id = 0 ) {
4093 $post = get_post( $post_id );
4094
4095 if ( ! $post ) {
4096 return $post;
4097 }
4098
4099 $post_id = $post->ID;
4100
4101 if ( 'trash' !== $post->post_status ) {
4102 return false;
4103 }
4104
4105 $previous_status = get_post_meta( $post_id, '_wp_trash_meta_status', true );
4106
4107 /**
4108 * Filters whether a post untrashing should take place.
4109 *
4110 * @since 4.9.0
4111 * @since 5.6.0 Added the `$previous_status` parameter.
4112 *
4113 * @param bool|null $untrash Whether to go forward with untrashing.
4114 * @param WP_Post $post Post object.
4115 * @param string $previous_status The status of the post at the point where it was trashed.
4116 */
4117 $check = apply_filters( 'pre_untrash_post', null, $post, $previous_status );
4118 if ( null !== $check ) {
4119 return $check;
4120 }
4121
4122 /**
4123 * Fires before a post is restored from the Trash.
4124 *
4125 * @since 2.9.0
4126 * @since 5.6.0 Added the `$previous_status` parameter.
4127 *
4128 * @param int $post_id Post ID.
4129 * @param string $previous_status The status of the post at the point where it was trashed.
4130 */
4131 do_action( 'untrash_post', $post_id, $previous_status );
4132
4133 $new_status = ( 'attachment' === $post->post_type ) ? 'inherit' : 'draft';
4134
4135 /**
4136 * Filters the status that a post gets assigned when it is restored from the trash (untrashed).
4137 *
4138 * By default posts that are restored will be assigned a status of 'draft'. Return the value of `$previous_status`
4139 * in order to assign the status that the post had before it was trashed. The `wp_untrash_post_set_previous_status()`
4140 * function is available for this.
4141 *
4142 * Prior to WordPress 5.6.0, restored posts were always assigned their original status.
4143 *
4144 * @since 5.6.0
4145 *
4146 * @param string $new_status The new status of the post being restored.
4147 * @param int $post_id The ID of the post being restored.
4148 * @param string $previous_status The status of the post at the point where it was trashed.
4149 */
4150 $post_status = apply_filters( 'wp_untrash_post_status', $new_status, $post_id, $previous_status );
4151
4152 delete_post_meta( $post_id, '_wp_trash_meta_status' );
4153 delete_post_meta( $post_id, '_wp_trash_meta_time' );
4154
4155 $post_updated = wp_update_post(
4156 array(
4157 'ID' => $post_id,
4158 'post_status' => $post_status,
4159 )
4160 );
4161
4162 if ( ! $post_updated ) {
4163 return false;
4164 }
4165
4166 wp_untrash_post_comments( $post_id );
4167
4168 /**
4169 * Fires after a post is restored from the Trash.
4170 *
4171 * @since 2.9.0
4172 * @since 5.6.0 Added the `$previous_status` parameter.
4173 *
4174 * @param int $post_id Post ID.
4175 * @param string $previous_status The status of the post at the point where it was trashed.
4176 */
4177 do_action( 'untrashed_post', $post_id, $previous_status );
4178
4179 return $post;
4180}
4181
4182/**
4183 * Moves comments for a post to the Trash.
4184 *
4185 * @since 2.9.0
4186 *
4187 * @global wpdb $wpdb WordPress database abstraction object.
4188 *
4189 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
4190 * @return mixed|void False on failure.
4191 */
4192function wp_trash_post_comments( $post = null ) {
4193 global $wpdb;
4194
4195 $post = get_post( $post );
4196
4197 if ( ! $post ) {
4198 return;
4199 }
4200
4201 $post_id = $post->ID;
4202
4203 /**
4204 * Fires before comments are sent to the Trash.
4205 *
4206 * @since 2.9.0
4207 *
4208 * @param int $post_id Post ID.
4209 */
4210 do_action( 'trash_post_comments', $post_id );
4211
4212 $comments = $wpdb->get_results( $wpdb->prepare( "SELECT comment_ID, comment_approved FROM $wpdb->comments WHERE comment_post_ID = %d", $post_id ) );
4213
4214 if ( ! $comments ) {
4215 return;
4216 }
4217
4218 // Cache current status for each comment.
4219 $statuses = array();
4220 foreach ( $comments as $comment ) {
4221 $statuses[ $comment->comment_ID ] = $comment->comment_approved;
4222 }
4223 add_post_meta( $post_id, '_wp_trash_meta_comments_status', $statuses );
4224
4225 // Set status for all comments to post-trashed.
4226 $result = $wpdb->update( $wpdb->comments, array( 'comment_approved' => 'post-trashed' ), array( 'comment_post_ID' => $post_id ) );
4227
4228 clean_comment_cache( array_keys( $statuses ) );
4229
4230 /**
4231 * Fires after comments are sent to the Trash.
4232 *
4233 * @since 2.9.0
4234 *
4235 * @param int $post_id Post ID.
4236 * @param array $statuses Array of comment statuses.
4237 */
4238 do_action( 'trashed_post_comments', $post_id, $statuses );
4239
4240 return $result;
4241}
4242
4243/**
4244 * Restores comments for a post from the Trash.
4245 *
4246 * @since 2.9.0
4247 *
4248 * @global wpdb $wpdb WordPress database abstraction object.
4249 *
4250 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
4251 * @return true|void
4252 */
4253function wp_untrash_post_comments( $post = null ) {
4254 global $wpdb;
4255
4256 $post = get_post( $post );
4257
4258 if ( ! $post ) {
4259 return;
4260 }
4261
4262 $post_id = $post->ID;
4263
4264 $statuses = get_post_meta( $post_id, '_wp_trash_meta_comments_status', true );
4265
4266 if ( ! $statuses ) {
4267 return true;
4268 }
4269
4270 /**
4271 * Fires before comments are restored for a post from the Trash.
4272 *
4273 * @since 2.9.0
4274 *
4275 * @param int $post_id Post ID.
4276 */
4277 do_action( 'untrash_post_comments', $post_id );
4278
4279 // Restore each comment to its original status.
4280 $group_by_status = array();
4281 foreach ( $statuses as $comment_id => $comment_status ) {
4282 $group_by_status[ $comment_status ][] = $comment_id;
4283 }
4284
4285 foreach ( $group_by_status as $status => $comments ) {
4286 // Confidence check. This shouldn't happen.
4287 if ( 'post-trashed' === $status ) {
4288 $status = '0';
4289 }
4290 $comments_in = implode( ', ', array_map( 'intval', $comments ) );
4291 $wpdb->query( $wpdb->prepare( "UPDATE $wpdb->comments SET comment_approved = %s WHERE comment_ID IN ($comments_in)", $status ) );
4292 }
4293
4294 clean_comment_cache( array_keys( $statuses ) );
4295
4296 delete_post_meta( $post_id, '_wp_trash_meta_comments_status' );
4297
4298 /**
4299 * Fires after comments are restored for a post from the Trash.
4300 *
4301 * @since 2.9.0
4302 *
4303 * @param int $post_id Post ID.
4304 */
4305 do_action( 'untrashed_post_comments', $post_id );
4306}
4307
4308/**
4309 * Retrieves the list of categories for a post.
4310 *
4311 * Compatibility layer for themes and plugins. Also an easy layer of abstraction
4312 * away from the complexity of the taxonomy layer.
4313 *
4314 * @since 2.1.0
4315 *
4316 * @see wp_get_object_terms()
4317 *
4318 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
4319 * global $post. Default 0.
4320 * @param array $args Optional. Category query parameters. Default empty array.
4321 * See WP_Term_Query::__construct() for supported arguments.
4322 * @return array|WP_Error List of categories. If the `$fields` argument passed via `$args` is 'all' or
4323 * 'all_with_object_id', an array of WP_Term objects will be returned. If `$fields`
4324 * is 'ids', an array of category IDs. If `$fields` is 'names', an array of category names.
4325 * WP_Error object if 'category' taxonomy doesn't exist.
4326 */
4327function wp_get_post_categories( $post_id = 0, $args = array() ) {
4328 $post_id = (int) $post_id;
4329
4330 $defaults = array( 'fields' => 'ids' );
4331 $args = wp_parse_args( $args, $defaults );
4332
4333 $cats = wp_get_object_terms( $post_id, 'category', $args );
4334 return $cats;
4335}
4336
4337/**
4338 * Retrieves the tags for a post.
4339 *
4340 * There is only one default for this function, called 'fields' and by default
4341 * is set to 'all'. There are other defaults that can be overridden in
4342 * wp_get_object_terms().
4343 *
4344 * @since 2.3.0
4345 *
4346 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
4347 * global $post. Default 0.
4348 * @param array $args Optional. Tag query parameters. Default empty array.
4349 * See WP_Term_Query::__construct() for supported arguments.
4350 * @return array|WP_Error Array of WP_Term objects on success or empty array if no tags were found.
4351 * WP_Error object if 'post_tag' taxonomy doesn't exist.
4352 */
4353function wp_get_post_tags( $post_id = 0, $args = array() ) {
4354 return wp_get_post_terms( $post_id, 'post_tag', $args );
4355}
4356
4357/**
4358 * Retrieves the terms for a post.
4359 *
4360 * @since 2.8.0
4361 *
4362 * @param int $post_id Optional. The Post ID. Does not default to the ID of the
4363 * global $post. Default 0.
4364 * @param string|string[] $taxonomy Optional. The taxonomy slug or array of slugs for which
4365 * to retrieve terms. Default 'post_tag'.
4366 * @param array $args {
4367 * Optional. Term query parameters. See WP_Term_Query::__construct() for supported arguments.
4368 *
4369 * @type string $fields Term fields to retrieve. Default 'all'.
4370 * }
4371 * @return array|WP_Error Array of WP_Term objects on success or empty array if no terms were found.
4372 * WP_Error object if `$taxonomy` doesn't exist.
4373 */
4374function wp_get_post_terms( $post_id = 0, $taxonomy = 'post_tag', $args = array() ) {
4375 $post_id = (int) $post_id;
4376
4377 $defaults = array( 'fields' => 'all' );
4378 $args = wp_parse_args( $args, $defaults );
4379
4380 $tags = wp_get_object_terms( $post_id, $taxonomy, $args );
4381
4382 return $tags;
4383}
4384
4385/**
4386 * Retrieves a number of recent posts.
4387 *
4388 * @since 1.0.0
4389 *
4390 * @see get_posts()
4391 *
4392 * @param array $args Optional. Arguments to retrieve posts. Default empty array.
4393 * @param string $output Optional. The required return type. One of OBJECT or ARRAY_A, which
4394 * correspond to a WP_Post object or an associative array, respectively.
4395 * Default ARRAY_A.
4396 * @return array|false Array of recent posts, where the type of each element is determined
4397 * by the `$output` parameter. Empty array on failure.
4398 */
4399function wp_get_recent_posts( $args = array(), $output = ARRAY_A ) {
4400
4401 if ( is_numeric( $args ) ) {
4402 _deprecated_argument( __FUNCTION__, '3.1.0', __( 'Passing an integer number of posts is deprecated. Pass an array of arguments instead.' ) );
4403 $args = array( 'numberposts' => absint( $args ) );
4404 }
4405
4406 // Set default arguments.
4407 $defaults = array(
4408 'numberposts' => 10,
4409 'offset' => 0,
4410 'category' => 0,
4411 'orderby' => 'post_date',
4412 'order' => 'DESC',
4413 'include' => '',
4414 'exclude' => '',
4415 'meta_key' => '',
4416 'meta_value' => '',
4417 'post_type' => 'post',
4418 'post_status' => 'draft, publish, future, pending, private',
4419 'suppress_filters' => true,
4420 );
4421
4422 $parsed_args = wp_parse_args( $args, $defaults );
4423
4424 $results = get_posts( $parsed_args );
4425
4426 // Backward compatibility. Prior to 3.1 expected posts to be returned in array.
4427 if ( ARRAY_A === $output ) {
4428 foreach ( $results as $key => $result ) {
4429 $results[ $key ] = get_object_vars( $result );
4430 }
4431 return $results ? $results : array();
4432 }
4433
4434 return $results ? $results : false;
4435}
4436
4437/**
4438 * Inserts or update a post.
4439 *
4440 * If the $postarr parameter has 'ID' set to a value, then post will be updated.
4441 *
4442 * You can set the post date manually, by setting the values for 'post_date'
4443 * and 'post_date_gmt' keys. You can close the comments or open the comments by
4444 * setting the value for 'comment_status' key.
4445 *
4446 * @since 1.0.0
4447 * @since 2.6.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
4448 * @since 4.2.0 Support was added for encoding emoji in the post title, content, and excerpt.
4449 * @since 4.4.0 A 'meta_input' array can now be passed to `$postarr` to add post meta data.
4450 * @since 5.6.0 Added the `$fire_after_hooks` parameter.
4451 *
4452 * @see sanitize_post()
4453 * @global wpdb $wpdb WordPress database abstraction object.
4454 *
4455 * @param array $postarr {
4456 * An array of elements that make up a post to update or insert.
4457 *
4458 * @type int $ID The post ID. If equal to something other than 0,
4459 * the post with that ID will be updated. Default 0.
4460 * @type int $post_author The ID of the user who added the post. Default is
4461 * the current user ID.
4462 * @type string $post_date The date of the post. Default is the current time.
4463 * @type string $post_date_gmt The date of the post in the GMT timezone. Default is
4464 * the value of `$post_date`.
4465 * @type string $post_content The post content. Default empty.
4466 * @type string $post_content_filtered The filtered post content. Default empty.
4467 * @type string $post_title The post title. Default empty.
4468 * @type string $post_excerpt The post excerpt. Default empty.
4469 * @type string $post_status The post status. Default 'draft'.
4470 * @type string $post_type The post type. Default 'post'.
4471 * @type string $comment_status Whether the post can accept comments. Accepts 'open' or 'closed'.
4472 * Default is the value of 'default_comment_status' option.
4473 * @type string $ping_status Whether the post can accept pings. Accepts 'open' or 'closed'.
4474 * Default is the value of 'default_ping_status' option.
4475 * @type string $post_password The password to access the post. Default empty.
4476 * @type string $post_name The post name. Default is the sanitized post title
4477 * when creating a new post.
4478 * @type string $to_ping Space or carriage return-separated list of URLs to ping.
4479 * Default empty.
4480 * @type string $pinged Space or carriage return-separated list of URLs that have
4481 * been pinged. Default empty.
4482 * @type int $post_parent Set this for the post it belongs to, if any. Default 0.
4483 * @type int $menu_order The order the post should be displayed in. Default 0.
4484 * @type string $post_mime_type The mime type of the post. Default empty.
4485 * @type string $guid Global Unique ID for referencing the post. Default empty.
4486 * @type int $import_id The post ID to be used when inserting a new post.
4487 * If specified, must not match any existing post ID. Default 0.
4488 * @type int[] $post_category Array of category IDs.
4489 * Defaults to value of the 'default_category' option.
4490 * @type array $tags_input Array of tag names, slugs, or IDs. Default empty.
4491 * @type array $tax_input An array of taxonomy terms keyed by their taxonomy name.
4492 * If the taxonomy is hierarchical, the term list needs to be
4493 * either an array of term IDs or a comma-separated string of IDs.
4494 * If the taxonomy is non-hierarchical, the term list can be an array
4495 * that contains term names or slugs, or a comma-separated string
4496 * of names or slugs. This is because, in hierarchical taxonomy,
4497 * child terms can have the same names with different parent terms,
4498 * so the only way to connect them is using ID. Default empty.
4499 * @type array $meta_input Array of post meta values keyed by their post meta key. Default empty.
4500 * @type string $page_template Page template to use.
4501 * }
4502 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
4503 * @param bool $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
4504 * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
4505 */
4506function wp_insert_post( $postarr, $wp_error = false, $fire_after_hooks = true ) {
4507 global $wpdb;
4508
4509 // Capture original pre-sanitized array for passing into filters.
4510 $unsanitized_postarr = $postarr;
4511
4512 $user_id = get_current_user_id();
4513
4514 $defaults = array(
4515 'post_author' => $user_id,
4516 'post_content' => '',
4517 'post_content_filtered' => '',
4518 'post_title' => '',
4519 'post_excerpt' => '',
4520 'post_status' => 'draft',
4521 'post_type' => 'post',
4522 'comment_status' => '',
4523 'ping_status' => '',
4524 'post_password' => '',
4525 'to_ping' => '',
4526 'pinged' => '',
4527 'post_parent' => 0,
4528 'menu_order' => 0,
4529 'guid' => '',
4530 'import_id' => 0,
4531 'context' => '',
4532 'post_date' => '',
4533 'post_date_gmt' => '',
4534 );
4535
4536 $postarr = wp_parse_args( $postarr, $defaults );
4537
4538 unset( $postarr['filter'] );
4539
4540 $postarr = sanitize_post( $postarr, 'db' );
4541
4542 // Are we updating or creating?
4543 $post_id = 0;
4544 $update = false;
4545 $guid = $postarr['guid'];
4546
4547 if ( ! empty( $postarr['ID'] ) ) {
4548 $update = true;
4549
4550 // Get the post ID and GUID.
4551 $post_id = $postarr['ID'];
4552 $post_before = get_post( $post_id );
4553
4554 if ( is_null( $post_before ) ) {
4555 if ( $wp_error ) {
4556 return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
4557 }
4558 return 0;
4559 }
4560
4561 $guid = get_post_field( 'guid', $post_id );
4562 $previous_status = get_post_field( 'post_status', $post_id );
4563 } else {
4564 $previous_status = 'new';
4565 $post_before = null;
4566 }
4567
4568 $post_type = empty( $postarr['post_type'] ) ? 'post' : $postarr['post_type'];
4569
4570 $post_title = $postarr['post_title'];
4571 $post_content = $postarr['post_content'];
4572 $post_excerpt = $postarr['post_excerpt'];
4573
4574 if ( isset( $postarr['post_name'] ) ) {
4575 $post_name = $postarr['post_name'];
4576 } elseif ( $update ) {
4577 // For an update, don't modify the post_name if it wasn't supplied as an argument.
4578 $post_name = $post_before->post_name;
4579 }
4580
4581 $maybe_empty = 'attachment' !== $post_type
4582 && ! $post_content && ! $post_title && ! $post_excerpt
4583 && post_type_supports( $post_type, 'editor' )
4584 && post_type_supports( $post_type, 'title' )
4585 && post_type_supports( $post_type, 'excerpt' );
4586
4587 /**
4588 * Filters whether the post should be considered "empty".
4589 *
4590 * The post is considered "empty" if both:
4591 * 1. The post type supports the title, editor, and excerpt fields
4592 * 2. The title, editor, and excerpt fields are all empty
4593 *
4594 * Returning a truthy value from the filter will effectively short-circuit
4595 * the new post being inserted and return 0. If $wp_error is true, a WP_Error
4596 * will be returned instead.
4597 *
4598 * @since 3.3.0
4599 *
4600 * @param bool $maybe_empty Whether the post should be considered "empty".
4601 * @param array $postarr Array of post data.
4602 */
4603 if ( apply_filters( 'wp_insert_post_empty_content', $maybe_empty, $postarr ) ) {
4604 if ( $wp_error ) {
4605 return new WP_Error( 'empty_content', __( 'Content, title, and excerpt are empty.' ) );
4606 } else {
4607 return 0;
4608 }
4609 }
4610
4611 $post_status = empty( $postarr['post_status'] ) ? 'draft' : $postarr['post_status'];
4612
4613 if ( 'attachment' === $post_type && ! in_array( $post_status, array( 'inherit', 'private', 'trash', 'auto-draft' ), true ) ) {
4614 $post_status = 'inherit';
4615 }
4616
4617 if ( ! empty( $postarr['post_category'] ) ) {
4618 // Filter out empty terms.
4619 $post_category = array_filter( $postarr['post_category'] );
4620 } elseif ( $update && ! isset( $postarr['post_category'] ) ) {
4621 $post_category = $post_before->post_category;
4622 }
4623
4624 // Make sure we set a valid category.
4625 if ( empty( $post_category ) || 0 === count( $post_category ) || ! is_array( $post_category ) ) {
4626 // 'post' requires at least one category.
4627 if ( 'post' === $post_type && 'auto-draft' !== $post_status ) {
4628 $post_category = array( get_option( 'default_category' ) );
4629 } else {
4630 $post_category = array();
4631 }
4632 }
4633
4634 /*
4635 * Don't allow contributors to set the post slug for pending review posts.
4636 *
4637 * For new posts check the primitive capability, for updates check the meta capability.
4638 */
4639 if ( 'pending' === $post_status ) {
4640 $post_type_object = get_post_type_object( $post_type );
4641
4642 if ( ! $update && $post_type_object && ! current_user_can( $post_type_object->cap->publish_posts ) ) {
4643 $post_name = '';
4644 } elseif ( $update && ! current_user_can( 'publish_post', $post_id ) ) {
4645 $post_name = '';
4646 }
4647 }
4648
4649 /*
4650 * Create a valid post name. Drafts and pending posts are allowed to have
4651 * an empty post name.
4652 */
4653 if ( empty( $post_name ) ) {
4654 if ( ! in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true ) ) {
4655 $post_name = sanitize_title( $post_title );
4656 } else {
4657 $post_name = '';
4658 }
4659 } else {
4660 // On updates, we need to check to see if it's using the old, fixed sanitization context.
4661 $check_name = sanitize_title( $post_name, '', 'old-save' );
4662
4663 if ( $update
4664 && strtolower( urlencode( $post_name ) ) === $check_name
4665 && get_post_field( 'post_name', $post_id ) === $check_name
4666 ) {
4667 $post_name = $check_name;
4668 } else { // New post, or slug has changed.
4669 $post_name = sanitize_title( $post_name );
4670 }
4671 }
4672
4673 /*
4674 * Resolve the post date from any provided post date or post date GMT strings;
4675 * if none are provided, the date will be set to now.
4676 */
4677 $post_date = wp_resolve_post_date( $postarr['post_date'], $postarr['post_date_gmt'] );
4678
4679 if ( ! $post_date ) {
4680 if ( $wp_error ) {
4681 return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
4682 } else {
4683 return 0;
4684 }
4685 }
4686
4687 if ( empty( $postarr['post_date_gmt'] ) || '0000-00-00 00:00:00' === $postarr['post_date_gmt'] ) {
4688 if ( ! in_array( $post_status, get_post_stati( array( 'date_floating' => true ) ), true ) ) {
4689 $post_date_gmt = get_gmt_from_date( $post_date );
4690 } else {
4691 $post_date_gmt = '0000-00-00 00:00:00';
4692 }
4693 } else {
4694 $post_date_gmt = $postarr['post_date_gmt'];
4695 }
4696
4697 if ( $update || '0000-00-00 00:00:00' === $post_date ) {
4698 $post_modified = current_time( 'mysql' );
4699 $post_modified_gmt = current_time( 'mysql', true );
4700 } else {
4701 $post_modified = $post_date;
4702 $post_modified_gmt = $post_date_gmt;
4703 }
4704
4705 if ( 'attachment' !== $post_type ) {
4706 $now = gmdate( 'Y-m-d H:i:s' );
4707
4708 if ( 'publish' === $post_status ) {
4709 if ( strtotime( $post_date_gmt ) - strtotime( $now ) >= MINUTE_IN_SECONDS ) {
4710 $post_status = 'future';
4711 }
4712 } elseif ( 'future' === $post_status ) {
4713 if ( strtotime( $post_date_gmt ) - strtotime( $now ) < MINUTE_IN_SECONDS ) {
4714 $post_status = 'publish';
4715 }
4716 }
4717 }
4718
4719 // Comment status.
4720 if ( empty( $postarr['comment_status'] ) ) {
4721 if ( $update ) {
4722 $comment_status = 'closed';
4723 } else {
4724 $comment_status = get_default_comment_status( $post_type );
4725 }
4726 } else {
4727 $comment_status = $postarr['comment_status'];
4728 }
4729
4730 // These variables are needed by compact() later.
4731 $post_content_filtered = $postarr['post_content_filtered'];
4732 $post_author = isset( $postarr['post_author'] ) ? $postarr['post_author'] : $user_id;
4733 $ping_status = empty( $postarr['ping_status'] ) ? get_default_comment_status( $post_type, 'pingback' ) : $postarr['ping_status'];
4734 $to_ping = isset( $postarr['to_ping'] ) ? sanitize_trackback_urls( $postarr['to_ping'] ) : '';
4735 $pinged = isset( $postarr['pinged'] ) ? $postarr['pinged'] : '';
4736 $import_id = isset( $postarr['import_id'] ) ? $postarr['import_id'] : 0;
4737
4738 /*
4739 * The 'wp_insert_post_parent' filter expects all variables to be present.
4740 * Previously, these variables would have already been extracted
4741 */
4742 if ( isset( $postarr['menu_order'] ) ) {
4743 $menu_order = (int) $postarr['menu_order'];
4744 } else {
4745 $menu_order = 0;
4746 }
4747
4748 $post_password = isset( $postarr['post_password'] ) ? $postarr['post_password'] : '';
4749 if ( 'private' === $post_status ) {
4750 $post_password = '';
4751 }
4752
4753 if ( isset( $postarr['post_parent'] ) ) {
4754 $post_parent = (int) $postarr['post_parent'];
4755 } else {
4756 $post_parent = 0;
4757 }
4758
4759 $new_postarr = array_merge(
4760 array(
4761 'ID' => $post_id,
4762 ),
4763 compact( array_diff( array_keys( $defaults ), array( 'context', 'filter' ) ) )
4764 );
4765
4766 /**
4767 * Filters the post parent -- used to check for and prevent hierarchy loops.
4768 *
4769 * @since 3.1.0
4770 *
4771 * @param int $post_parent Post parent ID.
4772 * @param int $post_id Post ID.
4773 * @param array $new_postarr Array of parsed post data.
4774 * @param array $postarr Array of sanitized, but otherwise unmodified post data.
4775 */
4776 $post_parent = apply_filters( 'wp_insert_post_parent', $post_parent, $post_id, $new_postarr, $postarr );
4777
4778 /*
4779 * If the post is being untrashed and it has a desired slug stored in post meta,
4780 * reassign it.
4781 */
4782 if ( 'trash' === $previous_status && 'trash' !== $post_status ) {
4783 $desired_post_slug = get_post_meta( $post_id, '_wp_desired_post_slug', true );
4784
4785 if ( $desired_post_slug ) {
4786 delete_post_meta( $post_id, '_wp_desired_post_slug' );
4787 $post_name = $desired_post_slug;
4788 }
4789 }
4790
4791 // If a trashed post has the desired slug, change it and let this post have it.
4792 if ( 'trash' !== $post_status && $post_name ) {
4793 /**
4794 * Filters whether or not to add a `__trashed` suffix to trashed posts that match the name of the updated post.
4795 *
4796 * @since 5.4.0
4797 *
4798 * @param bool $add_trashed_suffix Whether to attempt to add the suffix.
4799 * @param string $post_name The name of the post being updated.
4800 * @param int $post_id Post ID.
4801 */
4802 $add_trashed_suffix = apply_filters( 'add_trashed_suffix_to_trashed_posts', true, $post_name, $post_id );
4803
4804 if ( $add_trashed_suffix ) {
4805 wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_id );
4806 }
4807 }
4808
4809 // When trashing an existing post, change its slug to allow non-trashed posts to use it.
4810 if ( 'trash' === $post_status && 'trash' !== $previous_status && 'new' !== $previous_status ) {
4811 $post_name = wp_add_trashed_suffix_to_post_name_for_post( $post_id );
4812 }
4813
4814 $post_name = wp_unique_post_slug( $post_name, $post_id, $post_status, $post_type, $post_parent );
4815
4816 // Don't unslash.
4817 $post_mime_type = isset( $postarr['post_mime_type'] ) ? $postarr['post_mime_type'] : '';
4818
4819 // Expected_slashed (everything!).
4820 $data = compact(
4821 'post_author',
4822 'post_date',
4823 'post_date_gmt',
4824 'post_content',
4825 'post_content_filtered',
4826 'post_title',
4827 'post_excerpt',
4828 'post_status',
4829 'post_type',
4830 'comment_status',
4831 'ping_status',
4832 'post_password',
4833 'post_name',
4834 'to_ping',
4835 'pinged',
4836 'post_modified',
4837 'post_modified_gmt',
4838 'post_parent',
4839 'menu_order',
4840 'post_mime_type',
4841 'guid'
4842 );
4843
4844 $emoji_fields = array( 'post_title', 'post_content', 'post_excerpt' );
4845
4846 foreach ( $emoji_fields as $emoji_field ) {
4847 if ( isset( $data[ $emoji_field ] ) ) {
4848 $charset = $wpdb->get_col_charset( $wpdb->posts, $emoji_field );
4849
4850 // The 'utf8' character set is a deprecated alias of 'utf8mb3'. See <https://dev.mysql.com/doc/refman/8.4/en/charset-unicode-utf8.html>.
4851 if ( 'utf8' === $charset || 'utf8mb3' === $charset ) {
4852 $data[ $emoji_field ] = wp_encode_emoji( $data[ $emoji_field ] );
4853 }
4854 }
4855 }
4856
4857 if ( 'attachment' === $post_type ) {
4858 /**
4859 * Filters attachment post data before it is updated in or added to the database.
4860 *
4861 * @since 3.9.0
4862 * @since 5.4.1 The `$unsanitized_postarr` parameter was added.
4863 * @since 6.0.0 The `$update` parameter was added.
4864 *
4865 * @param array $data An array of slashed, sanitized, and processed attachment post data.
4866 * @param array $postarr An array of slashed and sanitized attachment post data, but not processed.
4867 * @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed attachment post data
4868 * as originally passed to wp_insert_post().
4869 * @param bool $update Whether this is an existing attachment post being updated.
4870 */
4871 $data = apply_filters( 'wp_insert_attachment_data', $data, $postarr, $unsanitized_postarr, $update );
4872 } else {
4873 /**
4874 * Filters slashed post data just before it is inserted into the database.
4875 *
4876 * @since 2.7.0
4877 * @since 5.4.1 The `$unsanitized_postarr` parameter was added.
4878 * @since 6.0.0 The `$update` parameter was added.
4879 *
4880 * @param array $data An array of slashed, sanitized, and processed post data.
4881 * @param array $postarr An array of sanitized (and slashed) but otherwise unmodified post data.
4882 * @param array $unsanitized_postarr An array of slashed yet *unsanitized* and unprocessed post data as
4883 * originally passed to wp_insert_post().
4884 * @param bool $update Whether this is an existing post being updated.
4885 */
4886 $data = apply_filters( 'wp_insert_post_data', $data, $postarr, $unsanitized_postarr, $update );
4887 }
4888
4889 $data = wp_unslash( $data );
4890 $where = array( 'ID' => $post_id );
4891
4892 if ( $update ) {
4893 /**
4894 * Fires immediately before an existing post is updated in the database.
4895 *
4896 * @since 2.5.0
4897 *
4898 * @param int $post_id Post ID.
4899 * @param array $data Array of unslashed post data.
4900 */
4901 do_action( 'pre_post_update', $post_id, $data );
4902
4903 if ( false === $wpdb->update( $wpdb->posts, $data, $where ) ) {
4904 if ( $wp_error ) {
4905 if ( 'attachment' === $post_type ) {
4906 $message = __( 'Could not update attachment in the database.' );
4907 } else {
4908 $message = __( 'Could not update post in the database.' );
4909 }
4910
4911 return new WP_Error( 'db_update_error', $message, $wpdb->last_error );
4912 } else {
4913 return 0;
4914 }
4915 }
4916 } else {
4917 // If there is a suggested ID, use it if not already present.
4918 if ( ! empty( $import_id ) ) {
4919 $import_id = (int) $import_id;
4920
4921 if ( ! $wpdb->get_var( $wpdb->prepare( "SELECT ID FROM $wpdb->posts WHERE ID = %d", $import_id ) ) ) {
4922 $data['ID'] = $import_id;
4923 }
4924 }
4925
4926 /**
4927 * Fires immediately before a new post is inserted in the database.
4928 *
4929 * @since 6.9.0
4930 *
4931 * @param array $data Array of unslashed post data.
4932 */
4933 do_action( 'pre_post_insert', $data );
4934
4935 if ( false === $wpdb->insert( $wpdb->posts, $data ) ) {
4936 if ( $wp_error ) {
4937 if ( 'attachment' === $post_type ) {
4938 $message = __( 'Could not insert attachment into the database.' );
4939 } else {
4940 $message = __( 'Could not insert post into the database.' );
4941 }
4942
4943 return new WP_Error( 'db_insert_error', $message, $wpdb->last_error );
4944 } else {
4945 return 0;
4946 }
4947 }
4948
4949 $post_id = (int) $wpdb->insert_id;
4950
4951 // Use the newly generated $post_id.
4952 $where = array( 'ID' => $post_id );
4953 }
4954
4955 if ( empty( $data['post_name'] ) && ! in_array( $data['post_status'], array( 'draft', 'pending', 'auto-draft' ), true ) ) {
4956 $data['post_name'] = wp_unique_post_slug( sanitize_title( $data['post_title'], $post_id ), $post_id, $data['post_status'], $post_type, $post_parent );
4957
4958 $wpdb->update( $wpdb->posts, array( 'post_name' => $data['post_name'] ), $where );
4959 clean_post_cache( $post_id );
4960 }
4961
4962 if ( is_object_in_taxonomy( $post_type, 'category' ) ) {
4963 wp_set_post_categories( $post_id, $post_category );
4964 }
4965
4966 if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $post_type, 'post_tag' ) ) {
4967 wp_set_post_tags( $post_id, $postarr['tags_input'] );
4968 }
4969
4970 // Add default term for all associated custom taxonomies.
4971 if ( 'auto-draft' !== $post_status ) {
4972 foreach ( get_object_taxonomies( $post_type, 'object' ) as $taxonomy => $tax_object ) {
4973
4974 if ( ! empty( $tax_object->default_term ) ) {
4975
4976 // Filter out empty terms.
4977 if ( isset( $postarr['tax_input'][ $taxonomy ] ) && is_array( $postarr['tax_input'][ $taxonomy ] ) ) {
4978 $postarr['tax_input'][ $taxonomy ] = array_filter( $postarr['tax_input'][ $taxonomy ] );
4979 }
4980
4981 // Passed custom taxonomy list overwrites the existing list if not empty.
4982 $terms = wp_get_object_terms( $post_id, $taxonomy, array( 'fields' => 'ids' ) );
4983 if ( ! empty( $terms ) && empty( $postarr['tax_input'][ $taxonomy ] ) ) {
4984 $postarr['tax_input'][ $taxonomy ] = $terms;
4985 }
4986
4987 if ( empty( $postarr['tax_input'][ $taxonomy ] ) ) {
4988 $default_term_id = get_option( 'default_term_' . $taxonomy );
4989 if ( ! empty( $default_term_id ) ) {
4990 $postarr['tax_input'][ $taxonomy ] = array( (int) $default_term_id );
4991 }
4992 }
4993 }
4994 }
4995 }
4996
4997 // New-style support for all custom taxonomies.
4998 if ( ! empty( $postarr['tax_input'] ) ) {
4999 foreach ( $postarr['tax_input'] as $taxonomy => $tags ) {
5000 $taxonomy_obj = get_taxonomy( $taxonomy );
5001
5002 if ( ! $taxonomy_obj ) {
5003 /* translators: %s: Taxonomy name. */
5004 _doing_it_wrong( __FUNCTION__, sprintf( __( 'Invalid taxonomy: %s.' ), $taxonomy ), '4.4.0' );
5005 continue;
5006 }
5007
5008 // array = hierarchical, string = non-hierarchical.
5009 if ( is_array( $tags ) ) {
5010 $tags = array_filter( $tags );
5011 }
5012
5013 if ( current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
5014 wp_set_post_terms( $post_id, $tags, $taxonomy );
5015 }
5016 }
5017 }
5018
5019 if ( ! empty( $postarr['meta_input'] ) ) {
5020 foreach ( $postarr['meta_input'] as $field => $value ) {
5021 update_post_meta( $post_id, $field, $value );
5022 }
5023 }
5024
5025 $current_guid = get_post_field( 'guid', $post_id );
5026
5027 // Set GUID.
5028 if ( ! $update && '' === $current_guid ) {
5029 $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post_id ) ), $where );
5030 }
5031
5032 if ( 'attachment' === $postarr['post_type'] ) {
5033 if ( ! empty( $postarr['file'] ) ) {
5034 update_attached_file( $post_id, $postarr['file'] );
5035 }
5036
5037 if ( ! empty( $postarr['context'] ) ) {
5038 add_post_meta( $post_id, '_wp_attachment_context', $postarr['context'], true );
5039 }
5040 }
5041
5042 // Set or remove featured image.
5043 if ( isset( $postarr['_thumbnail_id'] ) ) {
5044 $thumbnail_support = current_theme_supports( 'post-thumbnails', $post_type ) && post_type_supports( $post_type, 'thumbnail' ) || 'revision' === $post_type;
5045
5046 if ( ! $thumbnail_support && 'attachment' === $post_type && $post_mime_type ) {
5047 if ( wp_attachment_is( 'audio', $post_id ) ) {
5048 $thumbnail_support = post_type_supports( 'attachment:audio', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:audio' );
5049 } elseif ( wp_attachment_is( 'video', $post_id ) ) {
5050 $thumbnail_support = post_type_supports( 'attachment:video', 'thumbnail' ) || current_theme_supports( 'post-thumbnails', 'attachment:video' );
5051 }
5052 }
5053
5054 if ( $thumbnail_support ) {
5055 $thumbnail_id = (int) $postarr['_thumbnail_id'];
5056 if ( -1 === $thumbnail_id ) {
5057 delete_post_thumbnail( $post_id );
5058 } else {
5059 set_post_thumbnail( $post_id, $thumbnail_id );
5060 }
5061 }
5062 }
5063
5064 clean_post_cache( $post_id );
5065
5066 do_action('godaddy/wp_insert_post/before_get_post_instance');
5067 $post = get_post( $post_id );
5068 do_action('godaddy/wp_insert_post/after_get_post_instance');
5069
5070 if ( ! empty( $postarr['page_template'] ) ) {
5071 $post->page_template = $postarr['page_template'];
5072 $page_templates = wp_get_theme()->get_page_templates( $post );
5073
5074 if ( 'default' !== $postarr['page_template'] && ! isset( $page_templates[ $postarr['page_template'] ] ) ) {
5075 if ( $wp_error ) {
5076 return new WP_Error( 'invalid_page_template', __( 'Invalid page template.' ) );
5077 }
5078
5079 update_post_meta( $post_id, '_wp_page_template', 'default' );
5080 } else {
5081 update_post_meta( $post_id, '_wp_page_template', $postarr['page_template'] );
5082 }
5083 }
5084
5085 if ( 'attachment' !== $postarr['post_type'] ) {
5086 wp_transition_post_status( $data['post_status'], $previous_status, $post );
5087 } else {
5088 if ( $update ) {
5089 /**
5090 * Fires once an existing attachment has been updated.
5091 *
5092 * @since 2.0.0
5093 *
5094 * @param int $post_id Attachment ID.
5095 */
5096 do_action( 'edit_attachment', $post_id );
5097
5098 $post_after = get_post( $post_id );
5099
5100 /**
5101 * Fires once an existing attachment has been updated.
5102 *
5103 * @since 4.4.0
5104 *
5105 * @param int $post_id Post ID.
5106 * @param WP_Post $post_after Post object following the update.
5107 * @param WP_Post $post_before Post object before the update.
5108 */
5109 do_action( 'attachment_updated', $post_id, $post_after, $post_before );
5110 } else {
5111
5112 /**
5113 * Fires once an attachment has been added.
5114 *
5115 * @since 2.0.0
5116 *
5117 * @param int $post_id Attachment ID.
5118 */
5119 do_action( 'add_attachment', $post_id );
5120 }
5121
5122 return $post_id;
5123 }
5124
5125 if ( $update ) {
5126 /**
5127 * Fires once an existing post has been updated.
5128 *
5129 * The dynamic portion of the hook name, `$post->post_type`, refers to
5130 * the post type slug.
5131 *
5132 * Possible hook names include:
5133 *
5134 * - `edit_post_post`
5135 * - `edit_post_page`
5136 *
5137 * @since 5.1.0
5138 *
5139 * @param int $post_id Post ID.
5140 * @param WP_Post $post Post object.
5141 */
5142 do_action( "edit_post_{$post->post_type}", $post_id, $post );
5143
5144 /**
5145 * Fires once an existing post has been updated.
5146 *
5147 * @since 1.2.0
5148 *
5149 * @param int $post_id Post ID.
5150 * @param WP_Post $post Post object.
5151 */
5152 do_action( 'edit_post', $post_id, $post );
5153
5154 $post_after = get_post( $post_id );
5155
5156 /**
5157 * Fires once an existing post has been updated.
5158 *
5159 * @since 3.0.0
5160 *
5161 * @param int $post_id Post ID.
5162 * @param WP_Post $post_after Post object following the update.
5163 * @param WP_Post $post_before Post object before the update.
5164 */
5165 do_action( 'post_updated', $post_id, $post_after, $post_before );
5166 }
5167
5168 /**
5169 * Fires once a post has been saved.
5170 *
5171 * The dynamic portion of the hook name, `$post->post_type`, refers to
5172 * the post type slug.
5173 *
5174 * Possible hook names include:
5175 *
5176 * - `save_post_post`
5177 * - `save_post_page`
5178 *
5179 * @since 3.7.0
5180 *
5181 * @param int $post_id Post ID.
5182 * @param WP_Post $post Post object.
5183 * @param bool $update Whether this is an existing post being updated.
5184 */
5185 do_action( "save_post_{$post->post_type}", $post_id, $post, $update );
5186
5187 /**
5188 * Fires once a post has been saved.
5189 *
5190 * @since 1.5.0
5191 *
5192 * @param int $post_id Post ID.
5193 * @param WP_Post $post Post object.
5194 * @param bool $update Whether this is an existing post being updated.
5195 */
5196 do_action( 'save_post', $post_id, $post, $update );
5197
5198 /**
5199 * Fires once a post has been saved.
5200 *
5201 * @since 2.0.0
5202 *
5203 * @param int $post_id Post ID.
5204 * @param WP_Post $post Post object.
5205 * @param bool $update Whether this is an existing post being updated.
5206 */
5207 do_action( 'wp_insert_post', $post_id, $post, $update );
5208
5209 if ( $fire_after_hooks ) {
5210 wp_after_insert_post( $post, $update, $post_before );
5211 }
5212
5213 return $post_id;
5214}
5215
5216/**
5217 * Updates a post with new post data.
5218 *
5219 * The date does not have to be set for drafts. You can set the date and it will
5220 * not be overridden.
5221 *
5222 * @since 1.0.0
5223 * @since 3.5.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
5224 * @since 5.6.0 Added the `$fire_after_hooks` parameter.
5225 *
5226 * @param array|object $postarr Optional. Post data. Arrays are expected to be escaped,
5227 * objects are not. See wp_insert_post() for accepted arguments.
5228 * Default array.
5229 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
5230 * @param bool $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
5231 * @return int|WP_Error The post ID on success. The value 0 or WP_Error on failure.
5232 */
5233function wp_update_post( $postarr = array(), $wp_error = false, $fire_after_hooks = true ) {
5234 if ( is_object( $postarr ) ) {
5235 // Non-escaped post was passed.
5236 $postarr = get_object_vars( $postarr );
5237 $postarr = wp_slash( $postarr );
5238 }
5239
5240 // First, get all of the original fields.
5241 $post = get_post( $postarr['ID'], ARRAY_A );
5242
5243 if ( is_null( $post ) ) {
5244 if ( $wp_error ) {
5245 return new WP_Error( 'invalid_post', __( 'Invalid post ID.' ) );
5246 }
5247 return 0;
5248 }
5249
5250 // Escape data pulled from DB.
5251 $post = wp_slash( $post );
5252
5253 // Passed post category list overwrites existing category list if not empty.
5254 if ( isset( $postarr['post_category'] ) && is_array( $postarr['post_category'] )
5255 && count( $postarr['post_category'] ) > 0
5256 ) {
5257 $post_cats = $postarr['post_category'];
5258 } else {
5259 $post_cats = $post['post_category'];
5260 }
5261
5262 // Drafts shouldn't be assigned a date unless explicitly done so by the user.
5263 if ( isset( $post['post_status'] )
5264 && in_array( $post['post_status'], array( 'draft', 'pending', 'auto-draft' ), true )
5265 && empty( $postarr['edit_date'] ) && ( '0000-00-00 00:00:00' === $post['post_date_gmt'] )
5266 ) {
5267 $clear_date = true;
5268 } else {
5269 $clear_date = false;
5270 }
5271
5272 // Merge old and new fields with new fields overwriting old ones.
5273 $postarr = array_merge( $post, $postarr );
5274 $postarr['post_category'] = $post_cats;
5275 if ( $clear_date ) {
5276 $postarr['post_date'] = current_time( 'mysql' );
5277 $postarr['post_date_gmt'] = '';
5278 }
5279
5280 if ( 'attachment' === $postarr['post_type'] ) {
5281 return wp_insert_attachment( $postarr, false, 0, $wp_error );
5282 }
5283
5284 // Discard 'tags_input' parameter if it's the same as existing post tags.
5285 if ( isset( $postarr['tags_input'] ) && is_object_in_taxonomy( $postarr['post_type'], 'post_tag' ) ) {
5286 $tags = get_the_terms( $postarr['ID'], 'post_tag' );
5287 $tag_names = array();
5288
5289 if ( $tags && ! is_wp_error( $tags ) ) {
5290 $tag_names = wp_list_pluck( $tags, 'name' );
5291 }
5292
5293 if ( $postarr['tags_input'] === $tag_names ) {
5294 unset( $postarr['tags_input'] );
5295 }
5296 }
5297
5298 return wp_insert_post( $postarr, $wp_error, $fire_after_hooks );
5299}
5300
5301/**
5302 * Publishes a post by transitioning the post status.
5303 *
5304 * @since 2.1.0
5305 *
5306 * @global wpdb $wpdb WordPress database abstraction object.
5307 *
5308 * @param int|WP_Post $post Post ID or post object.
5309 */
5310function wp_publish_post( $post ) {
5311 global $wpdb;
5312
5313 $post = get_post( $post );
5314
5315 if ( ! $post ) {
5316 return;
5317 }
5318
5319 if ( 'publish' === $post->post_status ) {
5320 return;
5321 }
5322
5323 $post_before = get_post( $post->ID );
5324
5325 // Ensure at least one term is applied for taxonomies with a default term.
5326 foreach ( get_object_taxonomies( $post->post_type, 'object' ) as $taxonomy => $tax_object ) {
5327 // Skip taxonomy if no default term is set.
5328 if (
5329 'category' !== $taxonomy &&
5330 empty( $tax_object->default_term )
5331 ) {
5332 continue;
5333 }
5334
5335 // Do not modify previously set terms.
5336 if ( ! empty( get_the_terms( $post, $taxonomy ) ) ) {
5337 continue;
5338 }
5339
5340 if ( 'category' === $taxonomy ) {
5341 $default_term_id = (int) get_option( 'default_category', 0 );
5342 } else {
5343 $default_term_id = (int) get_option( 'default_term_' . $taxonomy, 0 );
5344 }
5345
5346 if ( ! $default_term_id ) {
5347 continue;
5348 }
5349 wp_set_post_terms( $post->ID, array( $default_term_id ), $taxonomy );
5350 }
5351
5352 $wpdb->update( $wpdb->posts, array( 'post_status' => 'publish' ), array( 'ID' => $post->ID ) );
5353
5354 clean_post_cache( $post->ID );
5355
5356 $old_status = $post->post_status;
5357 $post->post_status = 'publish';
5358 wp_transition_post_status( 'publish', $old_status, $post );
5359
5360 /** This action is documented in wp-includes/post.php */
5361 do_action( "edit_post_{$post->post_type}", $post->ID, $post );
5362
5363 /** This action is documented in wp-includes/post.php */
5364 do_action( 'edit_post', $post->ID, $post );
5365
5366 /** This action is documented in wp-includes/post.php */
5367 do_action( "save_post_{$post->post_type}", $post->ID, $post, true );
5368
5369 /** This action is documented in wp-includes/post.php */
5370 do_action( 'save_post', $post->ID, $post, true );
5371
5372 /** This action is documented in wp-includes/post.php */
5373 do_action( 'wp_insert_post', $post->ID, $post, true );
5374
5375 wp_after_insert_post( $post, true, $post_before );
5376}
5377
5378/**
5379 * Publishes future post and make sure post ID has future post status.
5380 *
5381 * Invoked by cron 'publish_future_post' event. This safeguard prevents cron
5382 * from publishing drafts, etc.
5383 *
5384 * @since 2.5.0
5385 *
5386 * @param int|WP_Post $post Post ID or post object.
5387 */
5388function check_and_publish_future_post( $post ) {
5389 $post = get_post( $post );
5390
5391 if ( ! $post ) {
5392 return;
5393 }
5394
5395 if ( 'future' !== $post->post_status ) {
5396 return;
5397 }
5398
5399 $time = strtotime( $post->post_date_gmt . ' GMT' );
5400
5401 // Uh oh, someone jumped the gun!
5402 if ( $time > time() ) {
5403 wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) ); // Clear anything else in the system.
5404 wp_schedule_single_event( $time, 'publish_future_post', array( $post->ID ) );
5405 return;
5406 }
5407
5408 // wp_publish_post() returns no meaningful value.
5409 wp_publish_post( $post->ID );
5410}
5411
5412/**
5413 * Uses wp_checkdate to return a valid Gregorian-calendar value for post_date.
5414 * If post_date is not provided, this first checks post_date_gmt if provided,
5415 * then falls back to use the current time.
5416 *
5417 * For back-compat purposes in wp_insert_post, an empty post_date and an invalid
5418 * post_date_gmt will continue to return '1970-01-01 00:00:00' rather than false.
5419 *
5420 * @since 5.7.0
5421 *
5422 * @param string $post_date The date in mysql format (`Y-m-d H:i:s`).
5423 * @param string $post_date_gmt The GMT date in mysql format (`Y-m-d H:i:s`).
5424 * @return string|false A valid Gregorian-calendar date string, or false on failure.
5425 */
5426function wp_resolve_post_date( $post_date = '', $post_date_gmt = '' ) {
5427 // If the date is empty, set the date to now.
5428 if ( empty( $post_date ) || '0000-00-00 00:00:00' === $post_date ) {
5429 if ( empty( $post_date_gmt ) || '0000-00-00 00:00:00' === $post_date_gmt ) {
5430 $post_date = current_time( 'mysql' );
5431 } else {
5432 $post_date = get_date_from_gmt( $post_date_gmt );
5433 }
5434 }
5435
5436 // Validate the date.
5437 preg_match( '/^(\d{4})-(\d{1,2})-(\d{1,2})/', $post_date, $matches );
5438
5439 if ( empty( $matches ) || ! is_array( $matches ) || count( $matches ) < 4 ) {
5440 return false;
5441 }
5442
5443 $valid_date = wp_checkdate( $matches[2], $matches[3], $matches[1], $post_date );
5444
5445 if ( ! $valid_date ) {
5446 return false;
5447 }
5448 return $post_date;
5449}
5450
5451/**
5452 * Computes a unique slug for the post, when given the desired slug and some post details.
5453 *
5454 * @since 2.8.0
5455 *
5456 * @global wpdb $wpdb WordPress database abstraction object.
5457 * @global WP_Rewrite $wp_rewrite WordPress rewrite component.
5458 *
5459 * @param string $slug The desired slug (post_name).
5460 * @param int $post_id Post ID.
5461 * @param string $post_status No uniqueness checks are made if the post is still draft or pending.
5462 * @param string $post_type Post type.
5463 * @param int $post_parent Post parent ID.
5464 * @return string Unique slug for the post, based on $post_name (with a -1, -2, etc. suffix)
5465 */
5466function wp_unique_post_slug( $slug, $post_id, $post_status, $post_type, $post_parent ) {
5467 if ( in_array( $post_status, array( 'draft', 'pending', 'auto-draft' ), true )
5468 || ( 'inherit' === $post_status && 'revision' === $post_type ) || 'user_request' === $post_type
5469 ) {
5470 return $slug;
5471 }
5472
5473 /**
5474 * Filters the post slug before it is generated to be unique.
5475 *
5476 * Returning a non-null value will short-circuit the
5477 * unique slug generation, returning the passed value instead.
5478 *
5479 * @since 5.1.0
5480 *
5481 * @param string|null $override_slug Short-circuit return value.
5482 * @param string $slug The desired slug (post_name).
5483 * @param int $post_id Post ID.
5484 * @param string $post_status The post status.
5485 * @param string $post_type Post type.
5486 * @param int $post_parent Post parent ID.
5487 */
5488 $override_slug = apply_filters( 'pre_wp_unique_post_slug', null, $slug, $post_id, $post_status, $post_type, $post_parent );
5489 if ( null !== $override_slug ) {
5490 return $override_slug;
5491 }
5492
5493 global $wpdb, $wp_rewrite;
5494
5495 $original_slug = $slug;
5496
5497 $feeds = $wp_rewrite->feeds;
5498 if ( ! is_array( $feeds ) ) {
5499 $feeds = array();
5500 }
5501
5502 if ( 'attachment' === $post_type ) {
5503 // Attachment slugs must be unique across all types.
5504 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND ID != %d LIMIT 1";
5505 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_id ) );
5506
5507 /**
5508 * Filters whether the post slug would make a bad attachment slug.
5509 *
5510 * @since 3.1.0
5511 *
5512 * @param bool $bad_slug Whether the slug would be bad as an attachment slug.
5513 * @param string $slug The post slug.
5514 */
5515 $is_bad_attachment_slug = apply_filters( 'wp_unique_post_slug_is_bad_attachment_slug', false, $slug );
5516
5517 if ( $post_name_check
5518 || in_array( $slug, $feeds, true ) || 'embed' === $slug
5519 || $is_bad_attachment_slug
5520 ) {
5521 $suffix = 2;
5522 do {
5523 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
5524 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_id ) );
5525 ++$suffix;
5526 } while ( $post_name_check );
5527 $slug = $alt_post_name;
5528 }
5529 } elseif ( is_post_type_hierarchical( $post_type ) ) {
5530 if ( 'nav_menu_item' === $post_type ) {
5531 return $slug;
5532 }
5533
5534 /*
5535 * Page slugs must be unique within their own trees. Pages are in a separate
5536 * namespace than posts so page slugs are allowed to overlap post slugs.
5537 */
5538 $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";
5539 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_id, $post_parent ) );
5540
5541 /**
5542 * Filters whether the post slug would make a bad hierarchical post slug.
5543 *
5544 * @since 3.1.0
5545 *
5546 * @param bool $bad_slug Whether the post slug would be bad in a hierarchical post context.
5547 * @param string $slug The post slug.
5548 * @param string $post_type Post type.
5549 * @param int $post_parent Post parent ID.
5550 */
5551 $is_bad_hierarchical_slug = apply_filters( 'wp_unique_post_slug_is_bad_hierarchical_slug', false, $slug, $post_type, $post_parent );
5552
5553 if ( $post_name_check
5554 || in_array( $slug, $feeds, true ) || 'embed' === $slug
5555 || preg_match( "@^($wp_rewrite->pagination_base)?\d+$@", $slug )
5556 || $is_bad_hierarchical_slug
5557 ) {
5558 $suffix = 2;
5559 do {
5560 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
5561 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_id, $post_parent ) );
5562 ++$suffix;
5563 } while ( $post_name_check );
5564 $slug = $alt_post_name;
5565 }
5566 } else {
5567 // Post slugs must be unique across all posts.
5568 $check_sql = "SELECT post_name FROM $wpdb->posts WHERE post_name = %s AND post_type = %s AND ID != %d LIMIT 1";
5569 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $slug, $post_type, $post_id ) );
5570
5571 $post = get_post( $post_id );
5572
5573 // Prevent new post slugs that could result in URLs that conflict with date archives.
5574 $conflicts_with_date_archive = false;
5575 if ( 'post' === $post_type && ( ! $post || $post->post_name !== $slug ) && preg_match( '/^[0-9]+$/', $slug ) ) {
5576 $slug_num = (int) $slug;
5577
5578 if ( $slug_num ) {
5579 $permastructs = array_values( array_filter( explode( '/', get_option( 'permalink_structure' ) ) ) );
5580 $postname_index = array_search( '%postname%', $permastructs, true );
5581
5582 /*
5583 * Potential date clashes are as follows:
5584 *
5585 * - Any integer in the first permastruct position could be a year.
5586 * - An integer between 1 and 12 that follows 'year' conflicts with 'monthnum'.
5587 * - An integer between 1 and 31 that follows 'monthnum' conflicts with 'day'.
5588 */
5589 if ( 0 === $postname_index ||
5590 ( $postname_index && '%year%' === $permastructs[ $postname_index - 1 ] && 13 > $slug_num ) ||
5591 ( $postname_index && '%monthnum%' === $permastructs[ $postname_index - 1 ] && 32 > $slug_num )
5592 ) {
5593 $conflicts_with_date_archive = true;
5594 }
5595 }
5596 }
5597
5598 /**
5599 * Filters whether the post slug would be bad as a flat slug.
5600 *
5601 * @since 3.1.0
5602 *
5603 * @param bool $bad_slug Whether the post slug would be bad as a flat slug.
5604 * @param string $slug The post slug.
5605 * @param string $post_type Post type.
5606 */
5607 $is_bad_flat_slug = apply_filters( 'wp_unique_post_slug_is_bad_flat_slug', false, $slug, $post_type );
5608
5609 if ( $post_name_check
5610 || in_array( $slug, $feeds, true ) || 'embed' === $slug
5611 || $conflicts_with_date_archive
5612 || $is_bad_flat_slug
5613 ) {
5614 $suffix = 2;
5615 do {
5616 $alt_post_name = _truncate_post_slug( $slug, 200 - ( strlen( $suffix ) + 1 ) ) . "-$suffix";
5617 $post_name_check = $wpdb->get_var( $wpdb->prepare( $check_sql, $alt_post_name, $post_type, $post_id ) );
5618 ++$suffix;
5619 } while ( $post_name_check );
5620 $slug = $alt_post_name;
5621 }
5622 }
5623
5624 /**
5625 * Filters the unique post slug.
5626 *
5627 * @since 3.3.0
5628 *
5629 * @param string $slug The post slug.
5630 * @param int $post_id Post ID.
5631 * @param string $post_status The post status.
5632 * @param string $post_type Post type.
5633 * @param int $post_parent Post parent ID
5634 * @param string $original_slug The original post slug.
5635 */
5636 return apply_filters( 'wp_unique_post_slug', $slug, $post_id, $post_status, $post_type, $post_parent, $original_slug );
5637}
5638
5639/**
5640 * Truncates a post slug.
5641 *
5642 * @since 3.6.0
5643 * @access private
5644 *
5645 * @see utf8_uri_encode()
5646 *
5647 * @param string $slug The slug to truncate.
5648 * @param int $length Optional. Max length of the slug. Default 200 (characters).
5649 * @return string The truncated slug.
5650 */
5651function _truncate_post_slug( $slug, $length = 200 ) {
5652 if ( strlen( $slug ) > $length ) {
5653 $decoded_slug = urldecode( $slug );
5654 if ( $decoded_slug === $slug ) {
5655 $slug = substr( $slug, 0, $length );
5656 } else {
5657 $slug = utf8_uri_encode( $decoded_slug, $length, true );
5658 }
5659 }
5660
5661 return rtrim( $slug, '-' );
5662}
5663
5664/**
5665 * Adds tags to a post.
5666 *
5667 * @see wp_set_post_tags()
5668 *
5669 * @since 2.3.0
5670 *
5671 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
5672 * @param string|array $tags Optional. An array of tags to set for the post, or a string of tags
5673 * separated by commas. Default empty.
5674 * @return array|false|WP_Error Array of affected term IDs. WP_Error or false on failure.
5675 */
5676function wp_add_post_tags( $post_id = 0, $tags = '' ) {
5677 return wp_set_post_tags( $post_id, $tags, true );
5678}
5679
5680/**
5681 * Sets the tags for a post.
5682 *
5683 * @since 2.3.0
5684 *
5685 * @see wp_set_object_terms()
5686 *
5687 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
5688 * @param string|array $tags Optional. An array of tags to set for the post, or a string of tags
5689 * separated by commas. Default empty.
5690 * @param bool $append Optional. If true, don't delete existing tags, just add on. If false,
5691 * replace the tags with the new tags. Default false.
5692 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
5693 */
5694function wp_set_post_tags( $post_id = 0, $tags = '', $append = false ) {
5695 return wp_set_post_terms( $post_id, $tags, 'post_tag', $append );
5696}
5697
5698/**
5699 * Sets the terms for a post.
5700 *
5701 * @since 2.8.0
5702 *
5703 * @see wp_set_object_terms()
5704 *
5705 * @param int $post_id Optional. The Post ID. Does not default to the ID of the global $post.
5706 * @param string|array $terms Optional. An array of terms to set for the post, or a string of terms
5707 * separated by commas. Hierarchical taxonomies must always pass IDs rather
5708 * than names so that children with the same names but different parents
5709 * aren't confused. Default empty.
5710 * @param string $taxonomy Optional. Taxonomy name. Default 'post_tag'.
5711 * @param bool $append Optional. If true, don't delete existing terms, just add on. If false,
5712 * replace the terms with the new terms. Default false.
5713 * @return array|false|WP_Error Array of term taxonomy IDs of affected terms. WP_Error or false on failure.
5714 */
5715function wp_set_post_terms( $post_id = 0, $terms = '', $taxonomy = 'post_tag', $append = false ) {
5716 $post_id = (int) $post_id;
5717
5718 if ( ! $post_id ) {
5719 return false;
5720 }
5721
5722 if ( empty( $terms ) ) {
5723 $terms = array();
5724 }
5725
5726 if ( ! is_array( $terms ) ) {
5727 $comma = _x( ',', 'tag delimiter' );
5728 if ( ',' !== $comma ) {
5729 $terms = str_replace( $comma, ',', $terms );
5730 }
5731 $terms = explode( ',', trim( $terms, " \n\t\r\0\x0B," ) );
5732 }
5733
5734 /*
5735 * Hierarchical taxonomies must always pass IDs rather than names so that
5736 * children with the same names but different parents aren't confused.
5737 */
5738 if ( is_taxonomy_hierarchical( $taxonomy ) ) {
5739 $terms = array_unique( array_map( 'intval', $terms ) );
5740 }
5741
5742 return wp_set_object_terms( $post_id, $terms, $taxonomy, $append );
5743}
5744
5745/**
5746 * Sets categories for a post.
5747 *
5748 * If no categories are provided, the default category is used.
5749 *
5750 * @since 2.1.0
5751 *
5752 * @param int $post_id Optional. The Post ID. Does not default to the ID
5753 * of the global $post. Default 0.
5754 * @param int[]|int $post_categories Optional. List of category IDs, or the ID of a single category.
5755 * Default empty array.
5756 * @param bool $append If true, don't delete existing categories, just add on.
5757 * If false, replace the categories with the new categories.
5758 * @return array|false|WP_Error Array of term taxonomy IDs of affected categories. WP_Error or false on failure.
5759 */
5760function wp_set_post_categories( $post_id = 0, $post_categories = array(), $append = false ) {
5761 $post_id = (int) $post_id;
5762 $post_type = get_post_type( $post_id );
5763 $post_status = get_post_status( $post_id );
5764
5765 // If $post_categories isn't already an array, make it one.
5766 $post_categories = (array) $post_categories;
5767
5768 if ( empty( $post_categories ) ) {
5769 /**
5770 * Filters post types (in addition to 'post') that require a default category.
5771 *
5772 * @since 5.5.0
5773 *
5774 * @param string[] $post_types An array of post type names. Default empty array.
5775 */
5776 $default_category_post_types = apply_filters( 'default_category_post_types', array() );
5777
5778 // Regular posts always require a default category.
5779 $default_category_post_types = array_merge( $default_category_post_types, array( 'post' ) );
5780
5781 if ( in_array( $post_type, $default_category_post_types, true )
5782 && is_object_in_taxonomy( $post_type, 'category' )
5783 && 'auto-draft' !== $post_status
5784 ) {
5785 $post_categories = array( get_option( 'default_category' ) );
5786 $append = false;
5787 } else {
5788 $post_categories = array();
5789 }
5790 } elseif ( 1 === count( $post_categories ) && '' === reset( $post_categories ) ) {
5791 return true;
5792 }
5793
5794 return wp_set_post_terms( $post_id, $post_categories, 'category', $append );
5795}
5796
5797/**
5798 * Fires actions related to the transitioning of a post's status.
5799 *
5800 * When a post is saved, the post status is "transitioned" from one status to another,
5801 * though this does not always mean the status has actually changed before and after
5802 * the save. This function fires a number of action hooks related to that transition:
5803 * the generic {@see 'transition_post_status'} action, as well as the dynamic hooks
5804 * {@see '$old_status_to_$new_status'} and {@see '$new_status_$post->post_type'}. Note
5805 * that the function does not transition the post object in the database.
5806 *
5807 * For instance: When publishing a post for the first time, the post status may transition
5808 * from 'draft' – or some other status – to 'publish'. However, if a post is already
5809 * published and is simply being updated, the "old" and "new" statuses may both be 'publish'
5810 * before and after the transition.
5811 *
5812 * @since 2.3.0
5813 *
5814 * @param string $new_status Transition to this post status.
5815 * @param string $old_status Previous post status.
5816 * @param WP_Post $post Post data.
5817 */
5818function wp_transition_post_status( $new_status, $old_status, $post ) {
5819 /**
5820 * Fires when a post is transitioned from one status to another.
5821 *
5822 * @since 2.3.0
5823 *
5824 * @param string $new_status New post status.
5825 * @param string $old_status Old post status.
5826 * @param WP_Post $post Post object.
5827 */
5828 do_action( 'transition_post_status', $new_status, $old_status, $post );
5829
5830 /**
5831 * Fires when a post is transitioned from one status to another.
5832 *
5833 * The dynamic portions of the hook name, `$new_status` and `$old_status`,
5834 * refer to the old and new post statuses, respectively.
5835 *
5836 * Possible hook names include:
5837 *
5838 * - `draft_to_publish`
5839 * - `publish_to_trash`
5840 * - `pending_to_draft`
5841 *
5842 * @since 2.3.0
5843 *
5844 * @param WP_Post $post Post object.
5845 */
5846 do_action( "{$old_status}_to_{$new_status}", $post );
5847
5848 /**
5849 * Fires when a post is transitioned from one status to another.
5850 *
5851 * The dynamic portions of the hook name, `$new_status` and `$post->post_type`,
5852 * refer to the new post status and post type, respectively.
5853 *
5854 * Possible hook names include:
5855 *
5856 * - `draft_post`
5857 * - `future_post`
5858 * - `pending_post`
5859 * - `private_post`
5860 * - `publish_post`
5861 * - `trash_post`
5862 * - `draft_page`
5863 * - `future_page`
5864 * - `pending_page`
5865 * - `private_page`
5866 * - `publish_page`
5867 * - `trash_page`
5868 * - `publish_attachment`
5869 * - `trash_attachment`
5870 *
5871 * Please note: When this action is hooked using a particular post status (like
5872 * 'publish', as `publish_{$post->post_type}`), it will fire both when a post is
5873 * first transitioned to that status from something else, as well as upon
5874 * subsequent post updates (old and new status are both the same).
5875 *
5876 * Therefore, if you are looking to only fire a callback when a post is first
5877 * transitioned to a status, use the {@see 'transition_post_status'} hook instead.
5878 *
5879 * @since 2.3.0
5880 * @since 5.9.0 Added `$old_status` parameter.
5881 *
5882 * @param int $post_id Post ID.
5883 * @param WP_Post $post Post object.
5884 * @param string $old_status Old post status.
5885 */
5886 do_action( "{$new_status}_{$post->post_type}", $post->ID, $post, $old_status );
5887}
5888
5889/**
5890 * Fires actions after a post, its terms and meta data has been saved.
5891 *
5892 * @since 5.6.0
5893 *
5894 * @param int|WP_Post $post The post ID or object that has been saved.
5895 * @param bool $update Whether this is an existing post being updated.
5896 * @param null|WP_Post $post_before Null for new posts, the WP_Post object prior
5897 * to the update for updated posts.
5898 */
5899function wp_after_insert_post( $post, $update, $post_before ) {
5900 $post = get_post( $post );
5901
5902 if ( ! $post ) {
5903 return;
5904 }
5905
5906 $post_id = $post->ID;
5907
5908 /**
5909 * Fires once a post, its terms and meta data has been saved.
5910 *
5911 * @since 5.6.0
5912 *
5913 * @param int $post_id Post ID.
5914 * @param WP_Post $post Post object.
5915 * @param bool $update Whether this is an existing post being updated.
5916 * @param null|WP_Post $post_before Null for new posts, the WP_Post object prior
5917 * to the update for updated posts.
5918 */
5919 do_action( 'wp_after_insert_post', $post_id, $post, $update, $post_before );
5920}
5921
5922//
5923// Comment, trackback, and pingback functions.
5924//
5925
5926/**
5927 * Adds a URL to those already pinged.
5928 *
5929 * @since 1.5.0
5930 * @since 4.7.0 `$post` can be a WP_Post object.
5931 * @since 4.7.0 `$uri` can be an array of URIs.
5932 *
5933 * @global wpdb $wpdb WordPress database abstraction object.
5934 *
5935 * @param int|WP_Post $post Post ID or post object.
5936 * @param string|array $uri Ping URI or array of URIs.
5937 * @return int|false How many rows were updated.
5938 */
5939function add_ping( $post, $uri ) {
5940 global $wpdb;
5941
5942 $post = get_post( $post );
5943
5944 if ( ! $post ) {
5945 return false;
5946 }
5947
5948 $pung = trim( $post->pinged );
5949 $pung = preg_split( '/\s/', $pung );
5950
5951 if ( is_array( $uri ) ) {
5952 $pung = array_merge( $pung, $uri );
5953 } else {
5954 $pung[] = $uri;
5955 }
5956 $new = implode( "\n", $pung );
5957
5958 /**
5959 * Filters the new ping URL to add for the given post.
5960 *
5961 * @since 2.0.0
5962 *
5963 * @param string $new New ping URL to add.
5964 */
5965 $new = apply_filters( 'add_ping', $new );
5966
5967 $return = $wpdb->update( $wpdb->posts, array( 'pinged' => $new ), array( 'ID' => $post->ID ) );
5968 clean_post_cache( $post->ID );
5969 return $return;
5970}
5971
5972/**
5973 * Retrieves enclosures already enclosed for a post.
5974 *
5975 * @since 1.5.0
5976 *
5977 * @param int $post_id Post ID.
5978 * @return string[] Array of enclosures for the given post.
5979 */
5980function get_enclosed( $post_id ) {
5981 $custom_fields = get_post_custom( $post_id );
5982 $pung = array();
5983 if ( ! is_array( $custom_fields ) ) {
5984 return $pung;
5985 }
5986
5987 foreach ( $custom_fields as $key => $val ) {
5988 if ( 'enclosure' !== $key || ! is_array( $val ) ) {
5989 continue;
5990 }
5991 foreach ( $val as $enc ) {
5992 $enclosure = explode( "\n", $enc );
5993 $pung[] = trim( $enclosure[0] );
5994 }
5995 }
5996
5997 /**
5998 * Filters the list of enclosures already enclosed for the given post.
5999 *
6000 * @since 2.0.0
6001 *
6002 * @param string[] $pung Array of enclosures for the given post.
6003 * @param int $post_id Post ID.
6004 */
6005 return apply_filters( 'get_enclosed', $pung, $post_id );
6006}
6007
6008/**
6009 * Retrieves URLs already pinged for a post.
6010 *
6011 * @since 1.5.0
6012 *
6013 * @since 4.7.0 `$post` can be a WP_Post object.
6014 *
6015 * @param int|WP_Post $post Post ID or object.
6016 * @return string[]|false Array of URLs already pinged for the given post, false if the post is not found.
6017 */
6018function get_pung( $post ) {
6019 $post = get_post( $post );
6020
6021 if ( ! $post ) {
6022 return false;
6023 }
6024
6025 $pung = trim( $post->pinged );
6026 $pung = preg_split( '/\s/', $pung );
6027
6028 /**
6029 * Filters the list of already-pinged URLs for the given post.
6030 *
6031 * @since 2.0.0
6032 *
6033 * @param string[] $pung Array of URLs already pinged for the given post.
6034 */
6035 return apply_filters( 'get_pung', $pung );
6036}
6037
6038/**
6039 * Retrieves URLs that need to be pinged.
6040 *
6041 * @since 1.5.0
6042 * @since 4.7.0 `$post` can be a WP_Post object.
6043 *
6044 * @param int|WP_Post $post Post ID or post object.
6045 * @return string[]|false List of URLs yet to ping.
6046 */
6047function get_to_ping( $post ) {
6048 $post = get_post( $post );
6049
6050 if ( ! $post ) {
6051 return false;
6052 }
6053
6054 $to_ping = sanitize_trackback_urls( $post->to_ping );
6055 $to_ping = preg_split( '/\s/', $to_ping, -1, PREG_SPLIT_NO_EMPTY );
6056
6057 /**
6058 * Filters the list of URLs yet to ping for the given post.
6059 *
6060 * @since 2.0.0
6061 *
6062 * @param string[] $to_ping List of URLs yet to ping.
6063 */
6064 return apply_filters( 'get_to_ping', $to_ping );
6065}
6066
6067/**
6068 * Does trackbacks for a list of URLs.
6069 *
6070 * @since 1.0.0
6071 *
6072 * @param string $tb_list Comma separated list of URLs.
6073 * @param int $post_id Post ID.
6074 */
6075function trackback_url_list( $tb_list, $post_id ) {
6076 if ( ! empty( $tb_list ) ) {
6077 // Get post data.
6078 $postdata = get_post( $post_id, ARRAY_A );
6079
6080 // Form an excerpt.
6081 $excerpt = strip_tags( $postdata['post_excerpt'] ? $postdata['post_excerpt'] : $postdata['post_content'] );
6082
6083 if ( strlen( $excerpt ) > 255 ) {
6084 $excerpt = substr( $excerpt, 0, 252 ) . '&hellip;';
6085 }
6086
6087 $trackback_urls = explode( ',', $tb_list );
6088 foreach ( (array) $trackback_urls as $tb_url ) {
6089 $tb_url = trim( $tb_url );
6090 trackback( $tb_url, wp_unslash( $postdata['post_title'] ), $excerpt, $post_id );
6091 }
6092 }
6093}
6094
6095//
6096// Page functions.
6097//
6098
6099/**
6100 * Gets a list of page IDs.
6101 *
6102 * @since 2.0.0
6103 *
6104 * @global wpdb $wpdb WordPress database abstraction object.
6105 *
6106 * @return string[] List of page IDs as strings.
6107 */
6108function get_all_page_ids() {
6109 global $wpdb;
6110
6111 $page_ids = wp_cache_get( 'all_page_ids', 'posts' );
6112 if ( ! is_array( $page_ids ) ) {
6113 $page_ids = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_type = 'page'" );
6114 wp_cache_add( 'all_page_ids', $page_ids, 'posts' );
6115 }
6116
6117 return $page_ids;
6118}
6119
6120/**
6121 * Retrieves page data given a page ID or page object.
6122 *
6123 * Use get_post() instead of get_page().
6124 *
6125 * @since 1.5.1
6126 * @deprecated 3.5.0 Use get_post()
6127 *
6128 * @param int|WP_Post $page Page object or page ID. Passed by reference.
6129 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
6130 * correspond to a WP_Post object, an associative array, or a numeric array,
6131 * respectively. Default OBJECT.
6132 * @param string $filter Optional. How the return value should be filtered. Accepts 'raw',
6133 * 'edit', 'db', 'display'. Default 'raw'.
6134 * @return WP_Post|array|null WP_Post or array on success, null on failure.
6135 */
6136function get_page( $page, $output = OBJECT, $filter = 'raw' ) {
6137 return get_post( $page, $output, $filter );
6138}
6139
6140/**
6141 * Retrieves a page given its path.
6142 *
6143 * @since 2.1.0
6144 *
6145 * @global wpdb $wpdb WordPress database abstraction object.
6146 *
6147 * @param string $page_path Page path.
6148 * @param string $output Optional. The required return type. One of OBJECT, ARRAY_A, or ARRAY_N, which
6149 * correspond to a WP_Post object, an associative array, or a numeric array,
6150 * respectively. Default OBJECT.
6151 * @param string|array $post_type Optional. Post type or array of post types. Default 'page'.
6152 * @return WP_Post|array|null WP_Post (or array) on success, or null on failure.
6153 */
6154function get_page_by_path( $page_path, $output = OBJECT, $post_type = 'page' ) {
6155 global $wpdb;
6156
6157 $last_changed = wp_cache_get_last_changed( 'posts' );
6158
6159 $hash = md5( $page_path . serialize( $post_type ) );
6160 $cache_key = "get_page_by_path:$hash";
6161 $cached = wp_cache_get_salted( $cache_key, 'post-queries', $last_changed );
6162 if ( false !== $cached ) {
6163 // Special case: '0' is a bad `$page_path`.
6164 if ( '0' === $cached || 0 === $cached ) {
6165 return null;
6166 } else {
6167 return get_post( $cached, $output );
6168 }
6169 }
6170
6171 $page_path = rawurlencode( urldecode( $page_path ) );
6172 $page_path = str_replace( '%2F', '/', $page_path );
6173 $page_path = str_replace( '%20', ' ', $page_path );
6174 $parts = explode( '/', trim( $page_path, '/' ) );
6175 $parts = array_map( 'sanitize_title_for_query', $parts );
6176 $escaped_parts = esc_sql( $parts );
6177
6178 $in_string = "'" . implode( "','", $escaped_parts ) . "'";
6179
6180 if ( is_array( $post_type ) ) {
6181 $post_types = $post_type;
6182 } else {
6183 $post_types = array( $post_type, 'attachment' );
6184 }
6185
6186 $post_types = esc_sql( $post_types );
6187 $post_type_in_string = "'" . implode( "','", $post_types ) . "'";
6188 $sql = "
6189 SELECT ID, post_name, post_parent, post_type
6190 FROM $wpdb->posts
6191 WHERE post_name IN ($in_string)
6192 AND post_type IN ($post_type_in_string)
6193 ";
6194
6195 $pages = $wpdb->get_results( $sql, OBJECT_K );
6196
6197 $revparts = array_reverse( $parts );
6198
6199 $found_id = 0;
6200 foreach ( (array) $pages as $page ) {
6201 if ( $page->post_name === $revparts[0] ) {
6202 $count = 0;
6203 $p = $page;
6204
6205 /*
6206 * Loop through the given path parts from right to left,
6207 * ensuring each matches the post ancestry.
6208 */
6209 while ( 0 !== (int) $p->post_parent && isset( $pages[ $p->post_parent ] ) ) {
6210 ++$count;
6211 $parent = $pages[ $p->post_parent ];
6212 if ( ! isset( $revparts[ $count ] ) || $parent->post_name !== $revparts[ $count ] ) {
6213 break;
6214 }
6215 $p = $parent;
6216 }
6217
6218 if ( 0 === (int) $p->post_parent
6219 && count( $revparts ) === $count + 1
6220 && $p->post_name === $revparts[ $count ]
6221 ) {
6222 $found_id = $page->ID;
6223 if ( $page->post_type === $post_type ) {
6224 break;
6225 }
6226 }
6227 }
6228 }
6229
6230 // We cache misses as well as hits.
6231 wp_cache_set_salted( $cache_key, $found_id, 'post-queries', $last_changed );
6232
6233 if ( $found_id ) {
6234 return get_post( $found_id, $output );
6235 }
6236
6237 return null;
6238}
6239
6240/**
6241 * Identifies descendants of a given page ID in a list of page objects.
6242 *
6243 * Descendants are identified from the `$pages` array passed to the function. No database queries are performed.
6244 *
6245 * @since 1.5.1
6246 *
6247 * @param int $page_id Page ID.
6248 * @param WP_Post[] $pages List of page objects from which descendants should be identified.
6249 * @return WP_Post[] List of page children.
6250 */
6251function get_page_children( $page_id, $pages ) {
6252 // Build a hash of ID -> children.
6253 $children = array();
6254 foreach ( (array) $pages as $page ) {
6255 $children[ (int) $page->post_parent ][] = $page;
6256 }
6257
6258 $page_list = array();
6259
6260 // Start the search by looking at immediate children.
6261 if ( isset( $children[ $page_id ] ) ) {
6262 // Always start at the end of the stack in order to preserve original `$pages` order.
6263 $to_look = array_reverse( $children[ $page_id ] );
6264
6265 while ( $to_look ) {
6266 $p = array_pop( $to_look );
6267 $page_list[] = $p;
6268 if ( isset( $children[ $p->ID ] ) ) {
6269 foreach ( array_reverse( $children[ $p->ID ] ) as $child ) {
6270 // Append to the `$to_look` stack to descend the tree.
6271 $to_look[] = $child;
6272 }
6273 }
6274 }
6275 }
6276
6277 return $page_list;
6278}
6279
6280/**
6281 * Orders the pages with children under parents in a flat list.
6282 *
6283 * It uses auxiliary structure to hold parent-children relationships and
6284 * runs in O(N) complexity
6285 *
6286 * @since 2.0.0
6287 *
6288 * @param WP_Post[] $pages Posts array (passed by reference).
6289 * @param int $page_id Optional. Parent page ID. Default 0.
6290 * @return string[] Array of post names keyed by ID and arranged by hierarchy. Children immediately follow their parents.
6291 */
6292function get_page_hierarchy( &$pages, $page_id = 0 ) {
6293 if ( empty( $pages ) ) {
6294 return array();
6295 }
6296
6297 $children = array();
6298 foreach ( (array) $pages as $p ) {
6299 $parent_id = (int) $p->post_parent;
6300 $children[ $parent_id ][] = $p;
6301 }
6302
6303 $result = array();
6304 _page_traverse_name( $page_id, $children, $result );
6305
6306 return $result;
6307}
6308
6309/**
6310 * Traverses and return all the nested children post names of a root page.
6311 *
6312 * $children contains parent-children relations
6313 *
6314 * @since 2.9.0
6315 * @access private
6316 *
6317 * @see _page_traverse_name()
6318 *
6319 * @param int $page_id Page ID.
6320 * @param array $children Parent-children relations (passed by reference).
6321 * @param string[] $result Array of page names keyed by ID (passed by reference).
6322 */
6323function _page_traverse_name( $page_id, &$children, &$result ) {
6324 if ( isset( $children[ $page_id ] ) ) {
6325 foreach ( (array) $children[ $page_id ] as $child ) {
6326 $result[ $child->ID ] = $child->post_name;
6327 _page_traverse_name( $child->ID, $children, $result );
6328 }
6329 }
6330}
6331
6332/**
6333 * Builds the URI path for a page.
6334 *
6335 * Sub pages will be in the "directory" under the parent page post name.
6336 *
6337 * @since 1.5.0
6338 * @since 4.6.0 The `$page` parameter was made optional.
6339 *
6340 * @param WP_Post|object|int $page Optional. Page ID or WP_Post object. Default is global $post.
6341 * @return string|false Page URI, false on error.
6342 */
6343function get_page_uri( $page = 0 ) {
6344 if ( ! $page instanceof WP_Post ) {
6345 $page = get_post( $page );
6346 }
6347
6348 if ( ! $page ) {
6349 return false;
6350 }
6351
6352 $uri = $page->post_name;
6353
6354 foreach ( $page->ancestors as $parent ) {
6355 $parent = get_post( $parent );
6356 if ( $parent && $parent->post_name ) {
6357 $uri = $parent->post_name . '/' . $uri;
6358 }
6359 }
6360
6361 /**
6362 * Filters the URI for a page.
6363 *
6364 * @since 4.4.0
6365 *
6366 * @param string $uri Page URI.
6367 * @param WP_Post $page Page object.
6368 */
6369 return apply_filters( 'get_page_uri', $uri, $page );
6370}
6371
6372/**
6373 * Retrieves an array of pages (or hierarchical post type items).
6374 *
6375 * @since 1.5.0
6376 * @since 6.3.0 Use WP_Query internally.
6377 *
6378 * @param array|string $args {
6379 * Optional. Array or string of arguments to retrieve pages.
6380 *
6381 * @type int $child_of Page ID to return child and grandchild pages of. Note: The value
6382 * of `$hierarchical` has no bearing on whether `$child_of` returns
6383 * hierarchical results. Default 0, or no restriction.
6384 * @type string $sort_order How to sort retrieved pages. Accepts 'ASC', 'DESC'. Default 'ASC'.
6385 * @type string $sort_column What columns to sort pages by, comma-separated. Accepts 'post_author',
6386 * 'post_date', 'post_title', 'post_name', 'post_modified', 'menu_order',
6387 * 'post_modified_gmt', 'post_parent', 'ID', 'rand', 'comment_count'.
6388 * 'post_' can be omitted for any values that start with it.
6389 * Default 'post_title'.
6390 * @type bool $hierarchical Whether to return pages hierarchically. If false in conjunction with
6391 * `$child_of` also being false, both arguments will be disregarded.
6392 * Default true.
6393 * @type int[] $exclude Array of page IDs to exclude. Default empty array.
6394 * @type int[] $include Array of page IDs to include. Cannot be used with `$child_of`,
6395 * `$parent`, `$exclude`, `$meta_key`, `$meta_value`, or `$hierarchical`.
6396 * Default empty array.
6397 * @type string $meta_key Only include pages with this meta key. Default empty.
6398 * @type string $meta_value Only include pages with this meta value. Requires `$meta_key`.
6399 * Default empty.
6400 * @type string $authors A comma-separated list of author IDs. Default empty.
6401 * @type int $parent Page ID to return direct children of. Default -1, or no restriction.
6402 * @type string|int[] $exclude_tree Comma-separated string or array of page IDs to exclude.
6403 * Default empty array.
6404 * @type int $number The number of pages to return. Default 0, or all pages.
6405 * @type int $offset The number of pages to skip before returning. Requires `$number`.
6406 * Default 0.
6407 * @type string $post_type The post type to query. Default 'page'.
6408 * @type string|array $post_status A comma-separated list or array of post statuses to include.
6409 * Default 'publish'.
6410 * }
6411 * @return WP_Post[]|false Array of pages (or hierarchical post type items). Boolean false if the
6412 * specified post type is not hierarchical or the specified status is not
6413 * supported by the post type.
6414 */
6415function get_pages( $args = array() ) {
6416 $defaults = array(
6417 'child_of' => 0,
6418 'sort_order' => 'ASC',
6419 'sort_column' => 'post_title',
6420 'hierarchical' => 1,
6421 'exclude' => array(),
6422 'include' => array(),
6423 'meta_key' => '',
6424 'meta_value' => '',
6425 'authors' => '',
6426 'parent' => -1,
6427 'exclude_tree' => array(),
6428 'number' => '',
6429 'offset' => 0,
6430 'post_type' => 'page',
6431 'post_status' => 'publish',
6432 );
6433
6434 $parsed_args = wp_parse_args( $args, $defaults );
6435
6436 $number = (int) $parsed_args['number'];
6437 $offset = (int) $parsed_args['offset'];
6438 $child_of = (int) $parsed_args['child_of'];
6439 $hierarchical = $parsed_args['hierarchical'];
6440 $exclude = $parsed_args['exclude'];
6441 $meta_key = $parsed_args['meta_key'];
6442 $meta_value = $parsed_args['meta_value'];
6443 $parent = $parsed_args['parent'];
6444 $post_status = $parsed_args['post_status'];
6445
6446 // Make sure the post type is hierarchical.
6447 $hierarchical_post_types = get_post_types( array( 'hierarchical' => true ) );
6448 if ( ! in_array( $parsed_args['post_type'], $hierarchical_post_types, true ) ) {
6449 return false;
6450 }
6451
6452 if ( $parent > 0 && ! $child_of ) {
6453 $hierarchical = false;
6454 }
6455
6456 // Make sure we have a valid post status.
6457 if ( ! is_array( $post_status ) ) {
6458 $post_status = explode( ',', $post_status );
6459 }
6460 if ( array_diff( $post_status, get_post_stati() ) ) {
6461 return false;
6462 }
6463
6464 $query_args = array(
6465 'orderby' => 'post_title',
6466 'order' => 'ASC',
6467 'post__not_in' => wp_parse_id_list( $exclude ),
6468 'meta_key' => $meta_key,
6469 'meta_value' => $meta_value,
6470 'posts_per_page' => -1,
6471 'offset' => $offset,
6472 'post_type' => $parsed_args['post_type'],
6473 'post_status' => $post_status,
6474 'update_post_term_cache' => false,
6475 'update_post_meta_cache' => false,
6476 'ignore_sticky_posts' => true,
6477 'no_found_rows' => true,
6478 );
6479
6480 if ( ! empty( $parsed_args['include'] ) ) {
6481 $child_of = 0; // Ignore child_of, parent, exclude, meta_key, and meta_value params if using include.
6482 $parent = -1;
6483 unset( $query_args['post__not_in'], $query_args['meta_key'], $query_args['meta_value'] );
6484 $hierarchical = false;
6485 $query_args['post__in'] = wp_parse_id_list( $parsed_args['include'] );
6486 }
6487
6488 if ( ! empty( $parsed_args['authors'] ) ) {
6489 $post_authors = wp_parse_list( $parsed_args['authors'] );
6490
6491 if ( ! empty( $post_authors ) ) {
6492 $query_args['author__in'] = array();
6493 foreach ( $post_authors as $post_author ) {
6494 // Do we have an author id or an author login?
6495 if ( 0 === (int) $post_author ) {
6496 $post_author = get_user_by( 'login', $post_author );
6497 if ( empty( $post_author ) ) {
6498 continue;
6499 }
6500 if ( empty( $post_author->ID ) ) {
6501 continue;
6502 }
6503 $post_author = $post_author->ID;
6504 }
6505 $query_args['author__in'][] = (int) $post_author;
6506 }
6507 }
6508 }
6509
6510 if ( is_array( $parent ) ) {
6511 $post_parent__in = array_map( 'absint', (array) $parent );
6512 if ( ! empty( $post_parent__in ) ) {
6513 $query_args['post_parent__in'] = $post_parent__in;
6514 }
6515 } elseif ( $parent >= 0 ) {
6516 $query_args['post_parent'] = $parent;
6517 }
6518
6519 /*
6520 * Maintain backward compatibility for `sort_column` key.
6521 * Additionally to `WP_Query`, it has been supporting the `post_modified_gmt` field, so this logic will translate
6522 * it to `post_modified` which should result in the same order given the two dates in the fields match.
6523 */
6524 $orderby = wp_parse_list( $parsed_args['sort_column'] );
6525 $orderby = array_map(
6526 static function ( $orderby_field ) {
6527 $orderby_field = trim( $orderby_field );
6528 if ( 'post_modified_gmt' === $orderby_field || 'modified_gmt' === $orderby_field ) {
6529 $orderby_field = str_replace( '_gmt', '', $orderby_field );
6530 }
6531 return $orderby_field;
6532 },
6533 $orderby
6534 );
6535 if ( $orderby ) {
6536 $query_args['orderby'] = array_fill_keys( $orderby, $parsed_args['sort_order'] );
6537 }
6538
6539 $order = $parsed_args['sort_order'];
6540 if ( $order ) {
6541 $query_args['order'] = $order;
6542 }
6543
6544 if ( ! empty( $number ) ) {
6545 $query_args['posts_per_page'] = $number;
6546 }
6547
6548 /**
6549 * Filters query arguments passed to WP_Query in get_pages.
6550 *
6551 * @since 6.3.0
6552 *
6553 * @param array $query_args Array of arguments passed to WP_Query.
6554 * @param array $parsed_args Array of get_pages() arguments.
6555 */
6556 $query_args = apply_filters( 'get_pages_query_args', $query_args, $parsed_args );
6557
6558 $pages = new WP_Query();
6559 $pages = $pages->query( $query_args );
6560
6561 if ( $child_of || $hierarchical ) {
6562 $pages = get_page_children( $child_of, $pages );
6563 }
6564
6565 if ( ! empty( $parsed_args['exclude_tree'] ) ) {
6566 $exclude = wp_parse_id_list( $parsed_args['exclude_tree'] );
6567 foreach ( $exclude as $id ) {
6568 $children = get_page_children( $id, $pages );
6569 foreach ( $children as $child ) {
6570 $exclude[] = $child->ID;
6571 }
6572 }
6573
6574 $num_pages = count( $pages );
6575 for ( $i = 0; $i < $num_pages; $i++ ) {
6576 if ( in_array( $pages[ $i ]->ID, $exclude, true ) ) {
6577 unset( $pages[ $i ] );
6578 }
6579 }
6580 }
6581
6582 /**
6583 * Filters the retrieved list of pages.
6584 *
6585 * @since 2.1.0
6586 *
6587 * @param WP_Post[] $pages Array of page objects.
6588 * @param array $parsed_args Array of get_pages() arguments.
6589 */
6590 return apply_filters( 'get_pages', $pages, $parsed_args );
6591}
6592
6593//
6594// Attachment functions.
6595//
6596
6597/**
6598 * Determines whether an attachment URI is local and really an attachment.
6599 *
6600 * For more information on this and similar theme functions, check out
6601 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
6602 * Conditional Tags} article in the Theme Developer Handbook.
6603 *
6604 * @since 2.0.0
6605 *
6606 * @param string $url URL to check
6607 * @return bool True on success, false on failure.
6608 */
6609function is_local_attachment( $url ) {
6610 if ( ! str_contains( $url, home_url() ) ) {
6611 return false;
6612 }
6613 if ( str_contains( $url, home_url( '/?attachment_id=' ) ) ) {
6614 return true;
6615 }
6616
6617 $id = url_to_postid( $url );
6618 if ( $id ) {
6619 $post = get_post( $id );
6620 if ( 'attachment' === $post->post_type ) {
6621 return true;
6622 }
6623 }
6624 return false;
6625}
6626
6627/**
6628 * Inserts an attachment.
6629 *
6630 * If you set the 'ID' in the $args parameter, it will mean that you are
6631 * updating and attempt to update the attachment. You can also set the
6632 * attachment name or title by setting the key 'post_name' or 'post_title'.
6633 *
6634 * You can set the dates for the attachment manually by setting the 'post_date'
6635 * and 'post_date_gmt' keys' values.
6636 *
6637 * By default, the comments will use the default settings for whether the
6638 * comments are allowed. You can close them manually or keep them open by
6639 * setting the value for the 'comment_status' key.
6640 *
6641 * @since 2.0.0
6642 * @since 4.7.0 Added the `$wp_error` parameter to allow a WP_Error to be returned on failure.
6643 * @since 5.6.0 Added the `$fire_after_hooks` parameter.
6644 *
6645 * @see wp_insert_post()
6646 *
6647 * @param string|array $args Arguments for inserting an attachment.
6648 * @param string|false $file Optional. Filename. Default false.
6649 * @param int $parent_post_id Optional. Parent post ID or 0 for no parent. Default 0.
6650 * @param bool $wp_error Optional. Whether to return a WP_Error on failure. Default false.
6651 * @param bool $fire_after_hooks Optional. Whether to fire the after insert hooks. Default true.
6652 * @return int|WP_Error The attachment ID on success. The value 0 or WP_Error on failure.
6653 */
6654function wp_insert_attachment( $args, $file = false, $parent_post_id = 0, $wp_error = false, $fire_after_hooks = true ) {
6655 $defaults = array(
6656 'file' => $file,
6657 'post_parent' => 0,
6658 );
6659
6660 $data = wp_parse_args( $args, $defaults );
6661
6662 if ( ! empty( $parent_post_id ) ) {
6663 $data['post_parent'] = $parent_post_id;
6664 }
6665
6666 $data['post_type'] = 'attachment';
6667
6668 return wp_insert_post( $data, $wp_error, $fire_after_hooks );
6669}
6670
6671/**
6672 * Trashes or deletes an attachment.
6673 *
6674 * When an attachment is permanently deleted, the file will also be removed.
6675 * Deletion removes all post meta fields, taxonomy, comments, etc. associated
6676 * with the attachment (except the main post).
6677 *
6678 * The attachment is moved to the Trash instead of permanently deleted unless Trash
6679 * for media is disabled, item is already in the Trash, or $force_delete is true.
6680 *
6681 * @since 2.0.0
6682 *
6683 * @global wpdb $wpdb WordPress database abstraction object.
6684 *
6685 * @param int $post_id Attachment ID.
6686 * @param bool $force_delete Optional. Whether to bypass Trash and force deletion.
6687 * Default false.
6688 * @return WP_Post|false|null Post data on success, false or null on failure.
6689 */
6690function wp_delete_attachment( $post_id, $force_delete = false ) {
6691 global $wpdb;
6692
6693 $post = $wpdb->get_row( $wpdb->prepare( "SELECT * FROM $wpdb->posts WHERE ID = %d", $post_id ) );
6694
6695 if ( ! $post ) {
6696 return $post;
6697 }
6698
6699 $post = get_post( $post );
6700
6701 if ( 'attachment' !== $post->post_type ) {
6702 return false;
6703 }
6704
6705 if ( ! $force_delete && EMPTY_TRASH_DAYS && MEDIA_TRASH && 'trash' !== $post->post_status ) {
6706 return wp_trash_post( $post_id );
6707 }
6708
6709 /**
6710 * Filters whether an attachment deletion should take place.
6711 *
6712 * @since 5.5.0
6713 *
6714 * @param WP_Post|false|null $delete Whether to go forward with deletion.
6715 * @param WP_Post $post Post object.
6716 * @param bool $force_delete Whether to bypass the Trash.
6717 */
6718 $check = apply_filters( 'pre_delete_attachment', null, $post, $force_delete );
6719 if ( null !== $check ) {
6720 return $check;
6721 }
6722
6723 delete_post_meta( $post_id, '_wp_trash_meta_status' );
6724 delete_post_meta( $post_id, '_wp_trash_meta_time' );
6725
6726 $meta = wp_get_attachment_metadata( $post_id );
6727 $backup_sizes = get_post_meta( $post->ID, '_wp_attachment_backup_sizes', true );
6728 $file = get_attached_file( $post_id );
6729
6730 if ( is_multisite() && is_string( $file ) && ! empty( $file ) ) {
6731 clean_dirsize_cache( $file );
6732 }
6733
6734 /**
6735 * Fires before an attachment is deleted, at the start of wp_delete_attachment().
6736 *
6737 * @since 2.0.0
6738 * @since 5.5.0 Added the `$post` parameter.
6739 *
6740 * @param int $post_id Attachment ID.
6741 * @param WP_Post $post Post object.
6742 */
6743 do_action( 'delete_attachment', $post_id, $post );
6744
6745 wp_delete_object_term_relationships( $post_id, array( 'category', 'post_tag' ) );
6746 wp_delete_object_term_relationships( $post_id, get_object_taxonomies( $post->post_type ) );
6747
6748 // Delete all for any posts.
6749 delete_metadata( 'post', null, '_thumbnail_id', $post_id, true );
6750
6751 wp_defer_comment_counting( true );
6752
6753 $comment_ids = $wpdb->get_col( $wpdb->prepare( "SELECT comment_ID FROM $wpdb->comments WHERE comment_post_ID = %d ORDER BY comment_ID DESC", $post_id ) );
6754 foreach ( $comment_ids as $comment_id ) {
6755 wp_delete_comment( $comment_id, true );
6756 }
6757
6758 wp_defer_comment_counting( false );
6759
6760 $post_meta_ids = $wpdb->get_col( $wpdb->prepare( "SELECT meta_id FROM $wpdb->postmeta WHERE post_id = %d ", $post_id ) );
6761 foreach ( $post_meta_ids as $mid ) {
6762 delete_metadata_by_mid( 'post', $mid );
6763 }
6764
6765 /** This action is documented in wp-includes/post.php */
6766 do_action( 'delete_post', $post_id, $post );
6767 $result = $wpdb->delete( $wpdb->posts, array( 'ID' => $post_id ) );
6768 if ( ! $result ) {
6769 return false;
6770 }
6771 /** This action is documented in wp-includes/post.php */
6772 do_action( 'deleted_post', $post_id, $post );
6773
6774 wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file );
6775
6776 clean_post_cache( $post );
6777
6778 return $post;
6779}
6780
6781/**
6782 * Deletes all files that belong to the given attachment.
6783 *
6784 * @since 4.9.7
6785 *
6786 * @global wpdb $wpdb WordPress database abstraction object.
6787 *
6788 * @param int $post_id Attachment ID.
6789 * @param array $meta The attachment's meta data.
6790 * @param array $backup_sizes The meta data for the attachment's backup images.
6791 * @param string $file Absolute path to the attachment's file.
6792 * @return bool True on success, false on failure.
6793 */
6794function wp_delete_attachment_files( $post_id, $meta, $backup_sizes, $file ) {
6795 global $wpdb;
6796
6797 $uploadpath = wp_get_upload_dir();
6798 $deleted = true;
6799
6800 if ( ! empty( $meta['thumb'] ) ) {
6801 // Don't delete the thumb if another attachment uses it.
6802 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 ) ) ) {
6803 $thumbfile = str_replace( wp_basename( $file ), $meta['thumb'], $file );
6804
6805 if ( ! empty( $thumbfile ) ) {
6806 $thumbfile = path_join( $uploadpath['basedir'], $thumbfile );
6807 $thumbdir = path_join( $uploadpath['basedir'], dirname( $file ) );
6808
6809 if ( ! wp_delete_file_from_directory( $thumbfile, $thumbdir ) ) {
6810 $deleted = false;
6811 }
6812 }
6813 }
6814 }
6815
6816 // Remove intermediate and backup images if there are any.
6817 if ( isset( $meta['sizes'] ) && is_array( $meta['sizes'] ) ) {
6818 $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
6819
6820 foreach ( $meta['sizes'] as $size => $sizeinfo ) {
6821 $intermediate_file = str_replace( wp_basename( $file ), $sizeinfo['file'], $file );
6822
6823 if ( ! empty( $intermediate_file ) ) {
6824 $intermediate_file = path_join( $uploadpath['basedir'], $intermediate_file );
6825
6826 if ( ! wp_delete_file_from_directory( $intermediate_file, $intermediate_dir ) ) {
6827 $deleted = false;
6828 }
6829 }
6830 }
6831 }
6832
6833 if ( ! empty( $meta['original_image'] ) ) {
6834 if ( empty( $intermediate_dir ) ) {
6835 $intermediate_dir = path_join( $uploadpath['basedir'], dirname( $file ) );
6836 }
6837
6838 $original_image = str_replace( wp_basename( $file ), $meta['original_image'], $file );
6839
6840 if ( ! empty( $original_image ) ) {
6841 $original_image = path_join( $uploadpath['basedir'], $original_image );
6842
6843 if ( ! wp_delete_file_from_directory( $original_image, $intermediate_dir ) ) {
6844 $deleted = false;
6845 }
6846 }
6847 }
6848
6849 if ( is_array( $backup_sizes ) ) {
6850 $del_dir = path_join( $uploadpath['basedir'], dirname( $meta['file'] ) );
6851
6852 foreach ( $backup_sizes as $size ) {
6853 $del_file = path_join( dirname( $meta['file'] ), $size['file'] );
6854
6855 if ( ! empty( $del_file ) ) {
6856 $del_file = path_join( $uploadpath['basedir'], $del_file );
6857
6858 if ( ! wp_delete_file_from_directory( $del_file, $del_dir ) ) {
6859 $deleted = false;
6860 }
6861 }
6862 }
6863 }
6864
6865 if ( ! wp_delete_file_from_directory( $file, $uploadpath['basedir'] ) ) {
6866 $deleted = false;
6867 }
6868
6869 return $deleted;
6870}
6871
6872/**
6873 * Retrieves attachment metadata for attachment ID.
6874 *
6875 * @since 2.1.0
6876 * @since 6.0.0 The `$filesize` value was added to the returned array.
6877 *
6878 * @param int $attachment_id Attachment post ID. Defaults to global $post.
6879 * @param bool $unfiltered Optional. If true, filters are not run. Default false.
6880 * @return array|false {
6881 * Attachment metadata. False on failure.
6882 *
6883 * @type int $width The width of the attachment.
6884 * @type int $height The height of the attachment.
6885 * @type string $file The file path relative to `wp-content/uploads`.
6886 * @type array $sizes Keys are size slugs, each value is an array containing
6887 * 'file', 'width', 'height', and 'mime-type'.
6888 * @type array $image_meta Image metadata.
6889 * @type int $filesize File size of the attachment.
6890 * }
6891 */
6892function wp_get_attachment_metadata( $attachment_id = 0, $unfiltered = false ) {
6893 $attachment_id = (int) $attachment_id;
6894
6895 if ( ! $attachment_id ) {
6896 $post = get_post();
6897
6898 if ( ! $post ) {
6899 return false;
6900 }
6901
6902 $attachment_id = $post->ID;
6903 }
6904
6905 $data = get_post_meta( $attachment_id, '_wp_attachment_metadata', true );
6906
6907 if ( ! $data ) {
6908 return false;
6909 }
6910
6911 if ( $unfiltered ) {
6912 return $data;
6913 }
6914
6915 /**
6916 * Filters the attachment meta data.
6917 *
6918 * @since 2.1.0
6919 *
6920 * @param array $data Array of meta data for the given attachment.
6921 * @param int $attachment_id Attachment post ID.
6922 */
6923 return apply_filters( 'wp_get_attachment_metadata', $data, $attachment_id );
6924}
6925
6926/**
6927 * Updates metadata for an attachment.
6928 *
6929 * @since 2.1.0
6930 *
6931 * @param int $attachment_id Attachment post ID.
6932 * @param array $data Attachment meta data.
6933 * @return int|bool Whether the metadata was successfully updated.
6934 * True on success, the Meta ID if the key didn't exist.
6935 * False if $post is invalid, on failure, or if $data is the same as the existing metadata.
6936 */
6937function wp_update_attachment_metadata( $attachment_id, $data ) {
6938 $attachment_id = (int) $attachment_id;
6939
6940 $post = get_post( $attachment_id );
6941
6942 if ( ! $post ) {
6943 return false;
6944 }
6945
6946 /**
6947 * Filters the updated attachment meta data.
6948 *
6949 * @since 2.1.0
6950 *
6951 * @param array $data Array of updated attachment meta data.
6952 * @param int $attachment_id Attachment post ID.
6953 */
6954 $data = apply_filters( 'wp_update_attachment_metadata', $data, $post->ID );
6955 if ( $data ) {
6956 return update_post_meta( $post->ID, '_wp_attachment_metadata', $data );
6957 } else {
6958 return delete_post_meta( $post->ID, '_wp_attachment_metadata' );
6959 }
6960}
6961
6962/**
6963 * Retrieves the URL for an attachment.
6964 *
6965 * @since 2.1.0
6966 *
6967 * @global string $pagenow The filename of the current screen.
6968 *
6969 * @param int $attachment_id Optional. Attachment post ID. Defaults to global $post.
6970 * @return string|false Attachment URL, otherwise false.
6971 */
6972function wp_get_attachment_url( $attachment_id = 0 ) {
6973 global $pagenow;
6974
6975 $attachment_id = (int) $attachment_id;
6976
6977 $post = get_post( $attachment_id );
6978
6979 if ( ! $post ) {
6980 return false;
6981 }
6982
6983 if ( 'attachment' !== $post->post_type ) {
6984 return false;
6985 }
6986
6987 $url = '';
6988 // Get attached file.
6989 $file = get_post_meta( $post->ID, '_wp_attached_file', true );
6990 if ( $file ) {
6991 // Get upload directory.
6992 $uploads = wp_get_upload_dir();
6993 if ( $uploads && false === $uploads['error'] ) {
6994 // Check that the upload base exists in the file location.
6995 if ( str_starts_with( $file, $uploads['basedir'] ) ) {
6996 // Replace file location with url location.
6997 $url = str_replace( $uploads['basedir'], $uploads['baseurl'], $file );
6998 } elseif ( str_contains( $file, 'wp-content/uploads' ) ) {
6999 // Get the directory name relative to the basedir (back compat for pre-2.7 uploads).
7000 $url = trailingslashit( $uploads['baseurl'] . '/' . _wp_get_attachment_relative_path( $file ) ) . wp_basename( $file );
7001 } else {
7002 // It's a newly-uploaded file, therefore $file is relative to the basedir.
7003 $url = $uploads['baseurl'] . "/$file";
7004 }
7005 }
7006 }
7007
7008 /*
7009 * If any of the above options failed, Fallback on the GUID as used pre-2.7,
7010 * not recommended to rely upon this.
7011 */
7012 if ( ! $url ) {
7013 $url = get_the_guid( $post->ID );
7014 }
7015
7016 // On SSL front end, URLs should be HTTPS.
7017 if ( is_ssl() && ! is_admin() && 'wp-login.php' !== $pagenow ) {
7018 $url = set_url_scheme( $url );
7019 }
7020
7021 /**
7022 * Filters the attachment URL.
7023 *
7024 * @since 2.1.0
7025 *
7026 * @param string $url URL for the given attachment.
7027 * @param int $attachment_id Attachment post ID.
7028 */
7029 $url = apply_filters( 'wp_get_attachment_url', $url, $post->ID );
7030
7031 if ( ! $url ) {
7032 return false;
7033 }
7034
7035 return $url;
7036}
7037
7038/**
7039 * Retrieves the caption for an attachment.
7040 *
7041 * @since 4.6.0
7042 *
7043 * @param int $post_id Optional. Attachment ID. Default is the ID of the global `$post`.
7044 * @return string|false Attachment caption on success, false on failure.
7045 */
7046function wp_get_attachment_caption( $post_id = 0 ) {
7047 $post_id = (int) $post_id;
7048 $post = get_post( $post_id );
7049
7050 if ( ! $post ) {
7051 return false;
7052 }
7053
7054 if ( 'attachment' !== $post->post_type ) {
7055 return false;
7056 }
7057
7058 $caption = $post->post_excerpt;
7059
7060 /**
7061 * Filters the attachment caption.
7062 *
7063 * @since 4.6.0
7064 *
7065 * @param string $caption Caption for the given attachment.
7066 * @param int $post_id Attachment ID.
7067 */
7068 return apply_filters( 'wp_get_attachment_caption', $caption, $post->ID );
7069}
7070
7071/**
7072 * Retrieves URL for an attachment thumbnail.
7073 *
7074 * @since 2.1.0
7075 * @since 6.1.0 Changed to use wp_get_attachment_image_url().
7076 *
7077 * @param int $post_id Optional. Attachment ID. Default is the ID of the global `$post`.
7078 * @return string|false Thumbnail URL on success, false on failure.
7079 */
7080function wp_get_attachment_thumb_url( $post_id = 0 ) {
7081 $post_id = (int) $post_id;
7082
7083 /*
7084 * This uses image_downsize() which also looks for the (very) old format $image_meta['thumb']
7085 * when the newer format $image_meta['sizes']['thumbnail'] doesn't exist.
7086 */
7087 $thumbnail_url = wp_get_attachment_image_url( $post_id, 'thumbnail' );
7088
7089 if ( empty( $thumbnail_url ) ) {
7090 return false;
7091 }
7092
7093 /**
7094 * Filters the attachment thumbnail URL.
7095 *
7096 * @since 2.1.0
7097 *
7098 * @param string $thumbnail_url URL for the attachment thumbnail.
7099 * @param int $post_id Attachment ID.
7100 */
7101 return apply_filters( 'wp_get_attachment_thumb_url', $thumbnail_url, $post_id );
7102}
7103
7104/**
7105 * Verifies an attachment is of a given type.
7106 *
7107 * @since 4.2.0
7108 *
7109 * @param string $type Attachment type. Accepts `image`, `audio`, `video`, or a file extension.
7110 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
7111 * @return bool True if an accepted type or a matching file extension, false otherwise.
7112 */
7113function wp_attachment_is( $type, $post = null ) {
7114 $post = get_post( $post );
7115
7116 if ( ! $post ) {
7117 return false;
7118 }
7119
7120 $file = get_attached_file( $post->ID );
7121
7122 if ( ! $file ) {
7123 return false;
7124 }
7125
7126 if ( str_starts_with( $post->post_mime_type, $type . '/' ) ) {
7127 return true;
7128 }
7129
7130 $check = wp_check_filetype( $file );
7131
7132 if ( empty( $check['ext'] ) ) {
7133 return false;
7134 }
7135
7136 $ext = $check['ext'];
7137
7138 if ( 'import' !== $post->post_mime_type ) {
7139 return $type === $ext;
7140 }
7141
7142 switch ( $type ) {
7143 case 'image':
7144 $image_exts = array( 'jpg', 'jpeg', 'jpe', 'gif', 'png', 'webp', 'avif', 'heic' );
7145 return in_array( $ext, $image_exts, true );
7146
7147 case 'audio':
7148 return in_array( $ext, wp_get_audio_extensions(), true );
7149
7150 case 'video':
7151 return in_array( $ext, wp_get_video_extensions(), true );
7152
7153 default:
7154 return $type === $ext;
7155 }
7156}
7157
7158/**
7159 * Determines whether an attachment is an image.
7160 *
7161 * For more information on this and similar theme functions, check out
7162 * the {@link https://developer.wordpress.org/themes/basics/conditional-tags/
7163 * Conditional Tags} article in the Theme Developer Handbook.
7164 *
7165 * @since 2.1.0
7166 * @since 4.2.0 Modified into wrapper for wp_attachment_is() and
7167 * allowed WP_Post object to be passed.
7168 *
7169 * @param int|WP_Post $post Optional. Attachment ID or object. Default is global $post.
7170 * @return bool Whether the attachment is an image.
7171 */
7172function wp_attachment_is_image( $post = null ) {
7173 return wp_attachment_is( 'image', $post );
7174}
7175
7176/**
7177 * Retrieves the icon for a MIME type or attachment.
7178 *
7179 * @since 2.1.0
7180 * @since 6.5.0 Added the `$preferred_ext` parameter.
7181 *
7182 * @param string|int $mime MIME type or attachment ID.
7183 * @param string $preferred_ext File format to prefer in return. Default '.png'.
7184 * @return string|false Icon, false otherwise.
7185 */
7186function wp_mime_type_icon( $mime = 0, $preferred_ext = '.png' ) {
7187 if ( ! is_numeric( $mime ) ) {
7188 $icon = wp_cache_get( "mime_type_icon_$mime" );
7189 }
7190
7191 // Check if preferred file format variable is present and is a validly formatted file extension.
7192 if ( ! empty( $preferred_ext ) && is_string( $preferred_ext ) && ! str_starts_with( $preferred_ext, '.' ) ) {
7193 $preferred_ext = '.' . strtolower( $preferred_ext );
7194 }
7195
7196 $post_id = 0;
7197 if ( empty( $icon ) ) {
7198 $post_mimes = array();
7199 if ( is_numeric( $mime ) ) {
7200 $mime = (int) $mime;
7201 $post = get_post( $mime );
7202 if ( $post ) {
7203 $post_id = (int) $post->ID;
7204 $file = get_attached_file( $post_id );
7205 $ext = preg_replace( '/^.+?\.([^.]+)$/', '$1', $file );
7206 if ( ! empty( $ext ) ) {
7207 $post_mimes[] = $ext;
7208 $ext_type = wp_ext2type( $ext );
7209 if ( $ext_type ) {
7210 $post_mimes[] = $ext_type;
7211 }
7212 }
7213 $mime = $post->post_mime_type;
7214 } else {
7215 $mime = 0;
7216 }
7217 } else {
7218 $post_mimes[] = $mime;
7219 }
7220
7221 $icon_files = wp_cache_get( 'icon_files' );
7222
7223 if ( ! is_array( $icon_files ) ) {
7224 /**
7225 * Filters the icon directory path.
7226 *
7227 * @since 2.0.0
7228 *
7229 * @param string $path Icon directory absolute path.
7230 */
7231 $icon_dir = apply_filters( 'icon_dir', ABSPATH . WPINC . '/images/media' );
7232
7233 /**
7234 * Filters the icon directory URI.
7235 *
7236 * @since 2.0.0
7237 *
7238 * @param string $uri Icon directory URI.
7239 */
7240 $icon_dir_uri = apply_filters( 'icon_dir_uri', includes_url( 'images/media' ) );
7241
7242 /**
7243 * Filters the array of icon directory URIs.
7244 *
7245 * @since 2.5.0
7246 *
7247 * @param string[] $uris Array of icon directory URIs keyed by directory absolute path.
7248 */
7249 $dirs = apply_filters( 'icon_dirs', array( $icon_dir => $icon_dir_uri ) );
7250 $icon_files = array();
7251 $all_icons = array();
7252 while ( $dirs ) {
7253 $keys = array_keys( $dirs );
7254 $dir = array_shift( $keys );
7255 $uri = array_shift( $dirs );
7256 $dh = opendir( $dir );
7257 if ( $dh ) {
7258 while ( false !== $file = readdir( $dh ) ) {
7259 $file = wp_basename( $file );
7260 if ( str_starts_with( $file, '.' ) ) {
7261 continue;
7262 }
7263
7264 $ext = strtolower( substr( $file, -4 ) );
7265 if ( ! in_array( $ext, array( '.svg', '.png', '.gif', '.jpg' ), true ) ) {
7266 if ( is_dir( "$dir/$file" ) ) {
7267 $dirs[ "$dir/$file" ] = "$uri/$file";
7268 }
7269 continue;
7270 }
7271 $all_icons[ "$dir/$file" ] = "$uri/$file";
7272 if ( $ext === $preferred_ext ) {
7273 $icon_files[ "$dir/$file" ] = "$uri/$file";
7274 }
7275 }
7276 closedir( $dh );
7277 }
7278 }
7279 // If directory only contained icons of a non-preferred format, return those.
7280 if ( empty( $icon_files ) ) {
7281 $icon_files = $all_icons;
7282 }
7283 wp_cache_add( 'icon_files', $icon_files, 'default', 600 );
7284 }
7285
7286 $types = array();
7287 // Icon wp_basename - extension = MIME wildcard.
7288 foreach ( $icon_files as $file => $uri ) {
7289 $types[ preg_replace( '/^([^.]*).*$/', '$1', wp_basename( $file ) ) ] =& $icon_files[ $file ];
7290 }
7291
7292 if ( ! empty( $mime ) ) {
7293 $post_mimes[] = substr( $mime, 0, strpos( $mime, '/' ) );
7294 $post_mimes[] = substr( $mime, strpos( $mime, '/' ) + 1 );
7295 $post_mimes[] = str_replace( '/', '_', $mime );
7296 }
7297
7298 $matches = wp_match_mime_types( array_keys( $types ), $post_mimes );
7299 $matches['default'] = array( 'default' );
7300
7301 foreach ( $matches as $match => $wilds ) {
7302 foreach ( $wilds as $wild ) {
7303 if ( ! isset( $types[ $wild ] ) ) {
7304 continue;
7305 }
7306
7307 $icon = $types[ $wild ];
7308 if ( ! is_numeric( $mime ) ) {
7309 wp_cache_add( "mime_type_icon_$mime", $icon );
7310 }
7311 break 2;
7312 }
7313 }
7314 }
7315
7316 /**
7317 * Filters the mime type icon.
7318 *
7319 * @since 2.1.0
7320 *
7321 * @param string $icon Path to the mime type icon.
7322 * @param string $mime Mime type.
7323 * @param int $post_id Attachment ID. Will equal 0 if the function passed
7324 * the mime type.
7325 */
7326 return apply_filters( 'wp_mime_type_icon', $icon, $mime, $post_id );
7327}
7328
7329/**
7330 * Checks for changed slugs for published post objects and save the old slug.
7331 *
7332 * The function is used when a post object of any type is updated,
7333 * by comparing the current and previous post objects.
7334 *
7335 * If the slug was changed and not already part of the old slugs then it will be
7336 * added to the post meta field ('_wp_old_slug') for storing old slugs for that
7337 * post.
7338 *
7339 * The most logically usage of this function is redirecting changed post objects, so
7340 * that those that linked to an changed post will be redirected to the new post.
7341 *
7342 * @since 2.1.0
7343 *
7344 * @param int $post_id Post ID.
7345 * @param WP_Post $post The post object.
7346 * @param WP_Post $post_before The previous post object.
7347 */
7348function wp_check_for_changed_slugs( $post_id, $post, $post_before ) {
7349 // Don't bother if it hasn't changed.
7350 if ( $post->post_name === $post_before->post_name ) {
7351 return;
7352 }
7353
7354 // We're only concerned with published, non-hierarchical objects.
7355 if ( ! ( 'publish' === $post->post_status || ( 'attachment' === $post->post_type && 'inherit' === $post->post_status ) )
7356 || is_post_type_hierarchical( $post->post_type )
7357 ) {
7358 return;
7359 }
7360
7361 $old_slugs = (array) get_post_meta( $post_id, '_wp_old_slug' );
7362
7363 // If we haven't added this old slug before, add it now.
7364 if ( ! empty( $post_before->post_name ) && ! in_array( $post_before->post_name, $old_slugs, true ) ) {
7365 add_post_meta( $post_id, '_wp_old_slug', $post_before->post_name );
7366 }
7367
7368 // If the new slug was used previously, delete it from the list.
7369 if ( in_array( $post->post_name, $old_slugs, true ) ) {
7370 delete_post_meta( $post_id, '_wp_old_slug', $post->post_name );
7371 }
7372}
7373
7374/**
7375 * Checks for changed dates for published post objects and save the old date.
7376 *
7377 * The function is used when a post object of any type is updated,
7378 * by comparing the current and previous post objects.
7379 *
7380 * If the date was changed and not already part of the old dates then it will be
7381 * added to the post meta field ('_wp_old_date') for storing old dates for that
7382 * post.
7383 *
7384 * The most logically usage of this function is redirecting changed post objects, so
7385 * that those that linked to an changed post will be redirected to the new post.
7386 *
7387 * @since 4.9.3
7388 *
7389 * @param int $post_id Post ID.
7390 * @param WP_Post $post The post object.
7391 * @param WP_Post $post_before The previous post object.
7392 */
7393function wp_check_for_changed_dates( $post_id, $post, $post_before ) {
7394 $previous_date = gmdate( 'Y-m-d', strtotime( $post_before->post_date ) );
7395 $new_date = gmdate( 'Y-m-d', strtotime( $post->post_date ) );
7396
7397 // Don't bother if it hasn't changed.
7398 if ( $new_date === $previous_date ) {
7399 return;
7400 }
7401
7402 // We're only concerned with published, non-hierarchical objects.
7403 if ( ! ( 'publish' === $post->post_status || ( 'attachment' === $post->post_type && 'inherit' === $post->post_status ) )
7404 || is_post_type_hierarchical( $post->post_type )
7405 ) {
7406 return;
7407 }
7408
7409 $old_dates = (array) get_post_meta( $post_id, '_wp_old_date' );
7410
7411 // If we haven't added this old date before, add it now.
7412 if ( ! empty( $previous_date ) && ! in_array( $previous_date, $old_dates, true ) ) {
7413 add_post_meta( $post_id, '_wp_old_date', $previous_date );
7414 }
7415
7416 // If the new slug was used previously, delete it from the list.
7417 if ( in_array( $new_date, $old_dates, true ) ) {
7418 delete_post_meta( $post_id, '_wp_old_date', $new_date );
7419 }
7420}
7421
7422/**
7423 * Retrieves the private post SQL based on capability.
7424 *
7425 * This function provides a standardized way to appropriately select on the
7426 * post_status of a post type. The function will return a piece of SQL code
7427 * that can be added to a WHERE clause; this SQL is constructed to allow all
7428 * published posts, and all private posts to which the user has access.
7429 *
7430 * @since 2.2.0
7431 * @since 4.3.0 Added the ability to pass an array to `$post_type`.
7432 *
7433 * @param string|array $post_type Single post type or an array of post types. Currently only supports 'post' or 'page'.
7434 * @return string SQL code that can be added to a where clause.
7435 */
7436function get_private_posts_cap_sql( $post_type ) {
7437 return get_posts_by_author_sql( $post_type, false );
7438}
7439
7440/**
7441 * Retrieves the post SQL based on capability, author, and type.
7442 *
7443 * @since 3.0.0
7444 * @since 4.3.0 Introduced the ability to pass an array of post types to `$post_type`.
7445 *
7446 * @see get_private_posts_cap_sql()
7447 * @global wpdb $wpdb WordPress database abstraction object.
7448 *
7449 * @param string|string[] $post_type Single post type or an array of post types.
7450 * @param bool $full Optional. Returns a full WHERE statement instead of just
7451 * an 'andalso' term. Default true.
7452 * @param int $post_author Optional. Query posts having a single author ID. Default null.
7453 * @param bool $public_only Optional. Only return public posts. Skips cap checks for
7454 * $current_user. Default false.
7455 * @return string SQL WHERE code that can be added to a query.
7456 */
7457function get_posts_by_author_sql( $post_type, $full = true, $post_author = null, $public_only = false ) {
7458 global $wpdb;
7459
7460 if ( is_array( $post_type ) ) {
7461 $post_types = $post_type;
7462 } else {
7463 $post_types = array( $post_type );
7464 }
7465
7466 $post_type_clauses = array();
7467 foreach ( $post_types as $post_type ) {
7468 $post_type_obj = get_post_type_object( $post_type );
7469
7470 if ( ! $post_type_obj ) {
7471 continue;
7472 }
7473
7474 /**
7475 * Filters the capability to read private posts for a custom post type
7476 * when generating SQL for getting posts by author.
7477 *
7478 * @since 2.2.0
7479 * @deprecated 3.2.0 The hook transitioned from "somewhat useless" to "totally useless".
7480 *
7481 * @param string $cap Capability.
7482 */
7483 $cap = apply_filters_deprecated( 'pub_priv_sql_capability', array( '' ), '3.2.0' );
7484
7485 if ( ! $cap ) {
7486 $cap = current_user_can( $post_type_obj->cap->read_private_posts );
7487 }
7488
7489 // Only need to check the cap if $public_only is false.
7490 $post_status_sql = "post_status = 'publish'";
7491
7492 if ( false === $public_only ) {
7493 if ( $cap ) {
7494 // Does the user have the capability to view private posts? Guess so.
7495 $post_status_sql .= " OR post_status = 'private'";
7496 } elseif ( is_user_logged_in() ) {
7497 // Users can view their own private posts.
7498 $id = get_current_user_id();
7499 if ( null === $post_author || ! $full ) {
7500 $post_status_sql .= " OR post_status = 'private' AND post_author = $id";
7501 } elseif ( $id === (int) $post_author ) {
7502 $post_status_sql .= " OR post_status = 'private'";
7503 } // Else none.
7504 } // Else none.
7505 }
7506
7507 $post_type_clauses[] = "( post_type = '" . $post_type . "' AND ( $post_status_sql ) )";
7508 }
7509
7510 if ( empty( $post_type_clauses ) ) {
7511 return $full ? 'WHERE 1 = 0' : '1 = 0';
7512 }
7513
7514 $sql = '( ' . implode( ' OR ', $post_type_clauses ) . ' )';
7515
7516 if ( null !== $post_author ) {
7517 $sql .= $wpdb->prepare( ' AND post_author = %d', $post_author );
7518 }
7519
7520 if ( $full ) {
7521 $sql = 'WHERE ' . $sql;
7522 }
7523
7524 return $sql;
7525}
7526
7527/**
7528 * Retrieves the most recent time that a post on the site was published.
7529 *
7530 * The server timezone is the default and is the difference between GMT and
7531 * server time. The 'blog' value is the date when the last post was posted.
7532 * The 'gmt' is when the last post was posted in GMT formatted date.
7533 *
7534 * @since 0.71
7535 * @since 4.4.0 The `$post_type` argument was added.
7536 *
7537 * @param string $timezone Optional. The timezone for the timestamp. Accepts 'server', 'blog', or 'gmt'.
7538 * 'server' uses the server's internal timezone.
7539 * 'blog' uses the `post_date` field, which proxies to the timezone set for the site.
7540 * 'gmt' uses the `post_date_gmt` field.
7541 * Default 'server'.
7542 * @param string $post_type Optional. The post type to check. Default 'any'.
7543 * @return string The date of the last post, or false on failure.
7544 */
7545function get_lastpostdate( $timezone = 'server', $post_type = 'any' ) {
7546 $lastpostdate = _get_last_post_time( $timezone, 'date', $post_type );
7547
7548 /**
7549 * Filters the most recent time that a post on the site was published.
7550 *
7551 * @since 2.3.0
7552 * @since 5.5.0 Added the `$post_type` parameter.
7553 *
7554 * @param string|false $lastpostdate The most recent time that a post was published,
7555 * in 'Y-m-d H:i:s' format. False on failure.
7556 * @param string $timezone Location to use for getting the post published date.
7557 * See get_lastpostdate() for accepted `$timezone` values.
7558 * @param string $post_type The post type to check.
7559 */
7560 return apply_filters( 'get_lastpostdate', $lastpostdate, $timezone, $post_type );
7561}
7562
7563/**
7564 * Gets the most recent time that a post on the site was modified.
7565 *
7566 * The server timezone is the default and is the difference between GMT and
7567 * server time. The 'blog' value is just when the last post was modified.
7568 * The 'gmt' is when the last post was modified in GMT time.
7569 *
7570 * @since 1.2.0
7571 * @since 4.4.0 The `$post_type` argument was added.
7572 *
7573 * @param string $timezone Optional. The timezone for the timestamp. See get_lastpostdate()
7574 * for information on accepted values.
7575 * Default 'server'.
7576 * @param string $post_type Optional. The post type to check. Default 'any'.
7577 * @return string The timestamp in 'Y-m-d H:i:s' format, or false on failure.
7578 */
7579function get_lastpostmodified( $timezone = 'server', $post_type = 'any' ) {
7580 /**
7581 * Pre-filter the return value of get_lastpostmodified() before the query is run.
7582 *
7583 * @since 4.4.0
7584 *
7585 * @param string|false $lastpostmodified The most recent time that a post was modified,
7586 * in 'Y-m-d H:i:s' format, or false. Returning anything
7587 * other than false will short-circuit the function.
7588 * @param string $timezone Location to use for getting the post modified date.
7589 * See get_lastpostdate() for accepted `$timezone` values.
7590 * @param string $post_type The post type to check.
7591 */
7592 $lastpostmodified = apply_filters( 'pre_get_lastpostmodified', false, $timezone, $post_type );
7593
7594 if ( false !== $lastpostmodified ) {
7595 return $lastpostmodified;
7596 }
7597
7598 $lastpostmodified = _get_last_post_time( $timezone, 'modified', $post_type );
7599 $lastpostdate = get_lastpostdate( $timezone, $post_type );
7600
7601 if ( $lastpostdate > $lastpostmodified ) {
7602 $lastpostmodified = $lastpostdate;
7603 }
7604
7605 /**
7606 * Filters the most recent time that a post on the site was modified.
7607 *
7608 * @since 2.3.0
7609 * @since 5.5.0 Added the `$post_type` parameter.
7610 *
7611 * @param string|false $lastpostmodified The most recent time that a post was modified,
7612 * in 'Y-m-d H:i:s' format. False on failure.
7613 * @param string $timezone Location to use for getting the post modified date.
7614 * See get_lastpostdate() for accepted `$timezone` values.
7615 * @param string $post_type The post type to check.
7616 */
7617 return apply_filters( 'get_lastpostmodified', $lastpostmodified, $timezone, $post_type );
7618}
7619
7620/**
7621 * Gets the timestamp of the last time any post was modified or published.
7622 *
7623 * @since 3.1.0
7624 * @since 4.4.0 The `$post_type` argument was added.
7625 * @access private
7626 *
7627 * @global wpdb $wpdb WordPress database abstraction object.
7628 *
7629 * @param string $timezone The timezone for the timestamp. See get_lastpostdate().
7630 * for information on accepted values.
7631 * @param string $field Post field to check. Accepts 'date' or 'modified'.
7632 * @param string $post_type Optional. The post type to check. Default 'any'.
7633 * @return string|false The timestamp in 'Y-m-d H:i:s' format, or false on failure.
7634 */
7635function _get_last_post_time( $timezone, $field, $post_type = 'any' ) {
7636 global $wpdb;
7637
7638 if ( ! in_array( $field, array( 'date', 'modified' ), true ) ) {
7639 return false;
7640 }
7641
7642 $timezone = strtolower( $timezone );
7643
7644 $key = "lastpost{$field}:$timezone";
7645 if ( 'any' !== $post_type ) {
7646 $key .= ':' . sanitize_key( $post_type );
7647 }
7648
7649 $date = wp_cache_get( $key, 'timeinfo' );
7650 if ( false !== $date ) {
7651 return $date;
7652 }
7653
7654 if ( 'any' === $post_type ) {
7655 $post_types = get_post_types( array( 'public' => true ) );
7656 array_walk( $post_types, array( $wpdb, 'escape_by_ref' ) );
7657 $post_types = "'" . implode( "', '", $post_types ) . "'";
7658 } else {
7659 $post_types = "'" . sanitize_key( $post_type ) . "'";
7660 }
7661
7662 switch ( $timezone ) {
7663 case 'gmt':
7664 $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" );
7665 break;
7666 case 'blog':
7667 $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" );
7668 break;
7669 case 'server':
7670 $add_seconds_server = gmdate( 'Z' );
7671 $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" );
7672 break;
7673 }
7674
7675 if ( $date ) {
7676 wp_cache_set( $key, $date, 'timeinfo' );
7677
7678 return $date;
7679 }
7680
7681 return false;
7682}
7683
7684/**
7685 * Updates posts in cache.
7686 *
7687 * @since 1.5.1
7688 *
7689 * @param WP_Post[] $posts Array of post objects (passed by reference).
7690 */
7691function update_post_cache( &$posts ) {
7692 if ( ! $posts ) {
7693 return;
7694 }
7695
7696 $data = array();
7697 foreach ( $posts as $post ) {
7698 if ( empty( $post->filter ) || 'raw' !== $post->filter ) {
7699 $post = sanitize_post( $post, 'raw' );
7700 }
7701 $data[ $post->ID ] = $post;
7702 }
7703 wp_cache_add_multiple( $data, 'posts' );
7704}
7705
7706/**
7707 * Will clean the post in the cache.
7708 *
7709 * Cleaning means delete from the cache of the post. Will call to clean the term
7710 * object cache associated with the post ID.
7711 *
7712 * This function not run if $_wp_suspend_cache_invalidation is not empty. See
7713 * wp_suspend_cache_invalidation().
7714 *
7715 * @since 2.0.0
7716 *
7717 * @global bool $_wp_suspend_cache_invalidation
7718 *
7719 * @param int|WP_Post $post Post ID or post object to remove from the cache.
7720 */
7721function clean_post_cache( $post ) {
7722 global $_wp_suspend_cache_invalidation;
7723
7724 if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
7725 return;
7726 }
7727
7728 $post = get_post( $post );
7729
7730 if ( ! $post ) {
7731 return;
7732 }
7733
7734 wp_cache_delete( $post->ID, 'posts' );
7735 wp_cache_delete( 'post_parent:' . (string) $post->ID, 'posts' );
7736 wp_cache_delete( $post->ID, 'post_meta' );
7737
7738 clean_object_term_cache( $post->ID, $post->post_type );
7739
7740 wp_cache_delete( 'wp_get_archives', 'general' );
7741
7742 /**
7743 * Fires immediately after the given post's cache is cleaned.
7744 *
7745 * @since 2.5.0
7746 *
7747 * @param int $post_id Post ID.
7748 * @param WP_Post $post Post object.
7749 */
7750 do_action( 'clean_post_cache', $post->ID, $post );
7751
7752 if ( 'page' === $post->post_type ) {
7753 wp_cache_delete( 'all_page_ids', 'posts' );
7754
7755 /**
7756 * Fires immediately after the given page's cache is cleaned.
7757 *
7758 * @since 2.5.0
7759 *
7760 * @param int $post_id Post ID.
7761 */
7762 do_action( 'clean_page_cache', $post->ID );
7763 }
7764
7765 wp_cache_set_posts_last_changed();
7766}
7767
7768/**
7769 * Updates post, term, and metadata caches for a list of post objects.
7770 *
7771 * @since 1.5.0
7772 *
7773 * @param WP_Post[] $posts Array of post objects (passed by reference).
7774 * @param string $post_type Optional. Post type. Default 'post'.
7775 * @param bool $update_term_cache Optional. Whether to update the term cache. Default true.
7776 * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
7777 */
7778function update_post_caches( &$posts, $post_type = 'post', $update_term_cache = true, $update_meta_cache = true ) {
7779 // No point in doing all this work if we didn't match any posts.
7780 if ( ! $posts ) {
7781 return;
7782 }
7783
7784 update_post_cache( $posts );
7785
7786 $post_ids = array();
7787 foreach ( $posts as $post ) {
7788 $post_ids[] = $post->ID;
7789 }
7790
7791 if ( ! $post_type ) {
7792 $post_type = 'any';
7793 }
7794
7795 if ( $update_term_cache ) {
7796 if ( is_array( $post_type ) ) {
7797 $ptypes = $post_type;
7798 } elseif ( 'any' === $post_type ) {
7799 $ptypes = array();
7800 // Just use the post_types in the supplied posts.
7801 foreach ( $posts as $post ) {
7802 $ptypes[] = $post->post_type;
7803 }
7804 $ptypes = array_unique( $ptypes );
7805 } else {
7806 $ptypes = array( $post_type );
7807 }
7808
7809 if ( ! empty( $ptypes ) ) {
7810 update_object_term_cache( $post_ids, $ptypes );
7811 }
7812 }
7813
7814 if ( $update_meta_cache ) {
7815 update_postmeta_cache( $post_ids );
7816 }
7817}
7818
7819/**
7820 * Updates post author user caches for a list of post objects.
7821 *
7822 * @since 6.1.0
7823 *
7824 * @param WP_Post[] $posts Array of post objects.
7825 */
7826function update_post_author_caches( $posts ) {
7827 /*
7828 * cache_users() is a pluggable function so is not available prior
7829 * to the `plugins_loaded` hook firing. This is to ensure against
7830 * fatal errors when the function is not available.
7831 */
7832 if ( ! function_exists( 'cache_users' ) ) {
7833 return;
7834 }
7835
7836 $author_ids = wp_list_pluck( $posts, 'post_author' );
7837 $author_ids = array_map( 'absint', $author_ids );
7838 $author_ids = array_unique( array_filter( $author_ids ) );
7839
7840 cache_users( $author_ids );
7841}
7842
7843/**
7844 * Updates parent post caches for a list of post objects.
7845 *
7846 * @since 6.1.0
7847 *
7848 * @param WP_Post[] $posts Array of post objects.
7849 */
7850function update_post_parent_caches( $posts ) {
7851 $parent_ids = wp_list_pluck( $posts, 'post_parent' );
7852 $parent_ids = array_map( 'absint', $parent_ids );
7853 $parent_ids = array_unique( array_filter( $parent_ids ) );
7854
7855 if ( ! empty( $parent_ids ) ) {
7856 _prime_post_caches( $parent_ids, false );
7857 }
7858}
7859
7860/**
7861 * Updates metadata cache for a list of post IDs.
7862 *
7863 * Performs SQL query to retrieve the metadata for the post IDs and updates the
7864 * metadata cache for the posts. Therefore, the functions, which call this
7865 * function, do not need to perform SQL queries on their own.
7866 *
7867 * @since 2.1.0
7868 *
7869 * @param int[] $post_ids Array of post IDs.
7870 * @return array|false An array of metadata on success, false if there is nothing to update.
7871 */
7872function update_postmeta_cache( $post_ids ) {
7873 return update_meta_cache( 'post', $post_ids );
7874}
7875
7876/**
7877 * Will clean the attachment in the cache.
7878 *
7879 * Cleaning means delete from the cache. Optionally will clean the term
7880 * object cache associated with the attachment ID.
7881 *
7882 * This function will not run if $_wp_suspend_cache_invalidation is not empty.
7883 *
7884 * @since 3.0.0
7885 *
7886 * @global bool $_wp_suspend_cache_invalidation
7887 *
7888 * @param int $id The attachment ID in the cache to clean.
7889 * @param bool $clean_terms Optional. Whether to clean terms cache. Default false.
7890 */
7891function clean_attachment_cache( $id, $clean_terms = false ) {
7892 global $_wp_suspend_cache_invalidation;
7893
7894 if ( ! empty( $_wp_suspend_cache_invalidation ) ) {
7895 return;
7896 }
7897
7898 $id = (int) $id;
7899
7900 wp_cache_delete( $id, 'posts' );
7901 wp_cache_delete( $id, 'post_meta' );
7902
7903 if ( $clean_terms ) {
7904 clean_object_term_cache( $id, 'attachment' );
7905 }
7906
7907 /**
7908 * Fires after the given attachment's cache is cleaned.
7909 *
7910 * @since 3.0.0
7911 *
7912 * @param int $id Attachment ID.
7913 */
7914 do_action( 'clean_attachment_cache', $id );
7915}
7916
7917//
7918// Hooks.
7919//
7920
7921/**
7922 * Hook for managing future post transitions to published.
7923 *
7924 * @since 2.3.0
7925 * @access private
7926 *
7927 * @see wp_clear_scheduled_hook()
7928 * @global wpdb $wpdb WordPress database abstraction object.
7929 *
7930 * @param string $new_status New post status.
7931 * @param string $old_status Previous post status.
7932 * @param WP_Post $post Post object.
7933 */
7934function _transition_post_status( $new_status, $old_status, $post ) {
7935 global $wpdb;
7936
7937 if ( 'publish' !== $old_status && 'publish' === $new_status ) {
7938 // Reset GUID if transitioning to publish and it is empty.
7939 if ( '' === get_the_guid( $post->ID ) ) {
7940 $wpdb->update( $wpdb->posts, array( 'guid' => get_permalink( $post->ID ) ), array( 'ID' => $post->ID ) );
7941 }
7942
7943 /**
7944 * Fires when a post's status is transitioned from private to published.
7945 *
7946 * @since 1.5.0
7947 * @deprecated 2.3.0 Use {@see 'private_to_publish'} instead.
7948 *
7949 * @param int $post_id Post ID.
7950 */
7951 do_action_deprecated( 'private_to_published', array( $post->ID ), '2.3.0', 'private_to_publish' );
7952 }
7953
7954 // If published posts changed clear the lastpostmodified cache.
7955 if ( 'publish' === $new_status || 'publish' === $old_status ) {
7956 foreach ( array( 'server', 'gmt', 'blog' ) as $timezone ) {
7957 wp_cache_delete( "lastpostmodified:$timezone", 'timeinfo' );
7958 wp_cache_delete( "lastpostdate:$timezone", 'timeinfo' );
7959 wp_cache_delete( "lastpostdate:$timezone:{$post->post_type}", 'timeinfo' );
7960 }
7961 }
7962
7963 if ( $new_status !== $old_status ) {
7964 wp_cache_delete( _count_posts_cache_key( $post->post_type ), 'counts' );
7965 wp_cache_delete( _count_posts_cache_key( $post->post_type, 'readable' ), 'counts' );
7966 }
7967
7968 // Always clears the hook in case the post status bounced from future to draft.
7969 wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
7970}
7971
7972/**
7973 * Hook used to schedule publication for a post marked for the future.
7974 *
7975 * The $post properties used and must exist are 'ID' and 'post_date_gmt'.
7976 *
7977 * @since 2.3.0
7978 * @access private
7979 *
7980 * @param int $deprecated Not used. Can be set to null. Never implemented. Not marked
7981 * as deprecated with _deprecated_argument() as it conflicts with
7982 * wp_transition_post_status() and the default filter for _future_post_hook().
7983 * @param WP_Post $post Post object.
7984 */
7985function _future_post_hook( $deprecated, $post ) {
7986 wp_clear_scheduled_hook( 'publish_future_post', array( $post->ID ) );
7987 wp_schedule_single_event( strtotime( get_gmt_from_date( $post->post_date ) . ' GMT' ), 'publish_future_post', array( $post->ID ) );
7988}
7989
7990/**
7991 * Hook to schedule pings and enclosures when a post is published.
7992 *
7993 * Uses XMLRPC_REQUEST and WP_IMPORTING constants.
7994 *
7995 * @since 2.3.0
7996 * @access private
7997 *
7998 * @param int $post_id The ID of the post being published.
7999 */
8000function _publish_post_hook( $post_id ) {
8001 if ( defined( 'XMLRPC_REQUEST' ) ) {
8002 /**
8003 * Fires when _publish_post_hook() is called during an XML-RPC request.
8004 *
8005 * @since 2.1.0
8006 *
8007 * @param int $post_id Post ID.
8008 */
8009 do_action( 'xmlrpc_publish_post', $post_id );
8010 }
8011
8012 if ( defined( 'WP_IMPORTING' ) ) {
8013 return;
8014 }
8015
8016 if ( get_option( 'default_pingback_flag' ) ) {
8017 add_post_meta( $post_id, '_pingme', '1', true );
8018 }
8019 add_post_meta( $post_id, '_encloseme', '1', true );
8020
8021 $to_ping = get_to_ping( $post_id );
8022 if ( ! empty( $to_ping ) ) {
8023 add_post_meta( $post_id, '_trackbackme', '1' );
8024 }
8025
8026 if ( ! wp_next_scheduled( 'do_pings' ) ) {
8027 wp_schedule_single_event( time(), 'do_pings' );
8028 }
8029}
8030
8031/**
8032 * Returns the ID of the post's parent.
8033 *
8034 * @since 3.1.0
8035 * @since 5.9.0 The `$post` parameter was made optional.
8036 *
8037 * @param int|WP_Post|null $post Optional. Post ID or post object. Defaults to global $post.
8038 * @return int|false Post parent ID (which can be 0 if there is no parent),
8039 * or false if the post does not exist.
8040 */
8041function wp_get_post_parent_id( $post = null ) {
8042 $post = get_post( $post );
8043
8044 if ( ! $post || is_wp_error( $post ) ) {
8045 return false;
8046 }
8047
8048 return (int) $post->post_parent;
8049}
8050
8051/**
8052 * Checks the given subset of the post hierarchy for hierarchy loops.
8053 *
8054 * Prevents loops from forming and breaks those that it finds. Attached
8055 * to the {@see 'wp_insert_post_parent'} filter.
8056 *
8057 * @since 3.1.0
8058 *
8059 * @see wp_find_hierarchy_loop()
8060 *
8061 * @param int $post_parent ID of the parent for the post we're checking.
8062 * @param int $post_id ID of the post we're checking.
8063 * @return int The new post_parent for the post, 0 otherwise.
8064 */
8065function wp_check_post_hierarchy_for_loops( $post_parent, $post_id ) {
8066 // Nothing fancy here - bail.
8067 if ( ! $post_parent ) {
8068 return 0;
8069 }
8070
8071 // New post can't cause a loop.
8072 if ( ! $post_id ) {
8073 return $post_parent;
8074 }
8075
8076 // Can't be its own parent.
8077 if ( $post_parent === $post_id ) {
8078 return 0;
8079 }
8080
8081 // Now look for larger loops.
8082 $loop = wp_find_hierarchy_loop( 'wp_get_post_parent_id', $post_id, $post_parent );
8083 if ( ! $loop ) {
8084 return $post_parent; // No loop.
8085 }
8086
8087 // Setting $post_parent to the given value causes a loop.
8088 if ( isset( $loop[ $post_id ] ) ) {
8089 return 0;
8090 }
8091
8092 // There's a loop, but it doesn't contain $post_id. Break the loop.
8093 foreach ( array_keys( $loop ) as $loop_member ) {
8094 wp_update_post(
8095 array(
8096 'ID' => $loop_member,
8097 'post_parent' => 0,
8098 )
8099 );
8100 }
8101
8102 return $post_parent;
8103}
8104
8105/**
8106 * Sets the post thumbnail (featured image) for the given post.
8107 *
8108 * @since 3.1.0
8109 *
8110 * @param int|WP_Post $post Post ID or post object where thumbnail should be attached.
8111 * @param int $thumbnail_id Thumbnail to attach.
8112 * @return int|bool Post meta ID if the key didn't exist (ie. this is the first time that
8113 * a thumbnail has been saved for the post), true on successful update,
8114 * false on failure or if the value passed is the same as the one that
8115 * is already in the database.
8116 */
8117function set_post_thumbnail( $post, $thumbnail_id ) {
8118 $post = get_post( $post );
8119 $thumbnail_id = absint( $thumbnail_id );
8120 if ( $post && $thumbnail_id && get_post( $thumbnail_id ) ) {
8121 if ( wp_get_attachment_image( $thumbnail_id, 'thumbnail' ) ) {
8122 return update_post_meta( $post->ID, '_thumbnail_id', $thumbnail_id );
8123 } else {
8124 return delete_post_meta( $post->ID, '_thumbnail_id' );
8125 }
8126 }
8127 return false;
8128}
8129
8130/**
8131 * Removes the thumbnail (featured image) from the given post.
8132 *
8133 * @since 3.3.0
8134 *
8135 * @param int|WP_Post $post Post ID or post object from which the thumbnail should be removed.
8136 * @return bool True on success, false on failure.
8137 */
8138function delete_post_thumbnail( $post ) {
8139 $post = get_post( $post );
8140 if ( $post ) {
8141 return delete_post_meta( $post->ID, '_thumbnail_id' );
8142 }
8143 return false;
8144}
8145
8146/**
8147 * Deletes auto-drafts for new posts that are > 7 days old.
8148 *
8149 * @since 3.4.0
8150 *
8151 * @global wpdb $wpdb WordPress database abstraction object.
8152 */
8153function wp_delete_auto_drafts() {
8154 global $wpdb;
8155
8156 // Cleanup old auto-drafts more than 7 days old.
8157 $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND DATE_SUB( NOW(), INTERVAL 7 DAY ) > post_date" );
8158 foreach ( (array) $old_posts as $delete ) {
8159 // Force delete.
8160 wp_delete_post( $delete, true );
8161 }
8162}
8163
8164/**
8165 * Queues posts for lazy-loading of term meta.
8166 *
8167 * @since 4.5.0
8168 *
8169 * @param WP_Post[] $posts Array of WP_Post objects.
8170 */
8171function wp_queue_posts_for_term_meta_lazyload( $posts ) {
8172 $post_type_taxonomies = array();
8173 $prime_post_terms = array();
8174 foreach ( $posts as $post ) {
8175 if ( ! ( $post instanceof WP_Post ) ) {
8176 continue;
8177 }
8178
8179 if ( ! isset( $post_type_taxonomies[ $post->post_type ] ) ) {
8180 $post_type_taxonomies[ $post->post_type ] = get_object_taxonomies( $post->post_type );
8181 }
8182
8183 foreach ( $post_type_taxonomies[ $post->post_type ] as $taxonomy ) {
8184 $prime_post_terms[ $taxonomy ][] = $post->ID;
8185 }
8186 }
8187
8188 $term_ids = array();
8189 if ( $prime_post_terms ) {
8190 foreach ( $prime_post_terms as $taxonomy => $post_ids ) {
8191 $cached_term_ids = wp_cache_get_multiple( $post_ids, "{$taxonomy}_relationships" );
8192 if ( is_array( $cached_term_ids ) ) {
8193 $cached_term_ids = array_filter( $cached_term_ids );
8194 foreach ( $cached_term_ids as $_term_ids ) {
8195 // Backward compatibility for if a plugin is putting objects into the cache, rather than IDs.
8196 foreach ( $_term_ids as $term_id ) {
8197 if ( is_numeric( $term_id ) ) {
8198 $term_ids[] = (int) $term_id;
8199 } elseif ( isset( $term_id->term_id ) ) {
8200 $term_ids[] = (int) $term_id->term_id;
8201 }
8202 }
8203 }
8204 }
8205 }
8206 $term_ids = array_unique( $term_ids );
8207 }
8208
8209 wp_lazyload_term_meta( $term_ids );
8210}
8211
8212/**
8213 * Updates the custom taxonomies' term counts when a post's status is changed.
8214 *
8215 * For example, default posts term counts (for custom taxonomies) don't include
8216 * private / draft posts.
8217 *
8218 * @since 3.3.0
8219 * @access private
8220 *
8221 * @param string $new_status New post status.
8222 * @param string $old_status Old post status.
8223 * @param WP_Post $post Post object.
8224 */
8225function _update_term_count_on_transition_post_status( $new_status, $old_status, $post ) {
8226 if ( $new_status === $old_status ) {
8227 return;
8228 }
8229
8230 // Update counts for the post's terms.
8231 foreach ( (array) get_object_taxonomies( $post->post_type, 'objects' ) as $taxonomy ) {
8232 /** This filter is documented in wp-includes/taxonomy.php */
8233 $counted_statuses = apply_filters( 'update_post_term_count_statuses', array( 'publish' ), $taxonomy );
8234
8235 /*
8236 * Do not recalculate term count if both the old and new status are not included in term counts.
8237 * This accounts for a transition such as draft -> pending.
8238 */
8239 if ( ! in_array( $old_status, $counted_statuses, true ) && ! in_array( $new_status, $counted_statuses, true ) ) {
8240 continue;
8241 }
8242
8243 /*
8244 * Do not recalculate term count if both the old and new status are included in term counts.
8245 *
8246 * This accounts for transitioning between statuses which are both included in term counts. This can only occur
8247 * if the `update_post_term_count_statuses` filter is in use to count more than just the 'publish' status.
8248 */
8249 if ( in_array( $old_status, $counted_statuses, true ) && in_array( $new_status, $counted_statuses, true ) ) {
8250 continue;
8251 }
8252
8253 $tt_ids = wp_get_object_terms( $post->ID, $taxonomy->name, array( 'fields' => 'tt_ids' ) );
8254 wp_update_term_count( $tt_ids, $taxonomy->name );
8255 }
8256}
8257
8258/**
8259 * Adds any posts from the given IDs to the cache that do not already exist in cache.
8260 *
8261 * @since 3.4.0
8262 * @since 6.1.0 This function is no longer marked as "private".
8263 *
8264 * @see update_post_cache()
8265 * @see update_postmeta_cache()
8266 * @see update_object_term_cache()
8267 *
8268 * @global wpdb $wpdb WordPress database abstraction object.
8269 *
8270 * @param int[] $ids ID list.
8271 * @param bool $update_term_cache Optional. Whether to update the term cache. Default true.
8272 * @param bool $update_meta_cache Optional. Whether to update the meta cache. Default true.
8273 */
8274function _prime_post_caches( $ids, $update_term_cache = true, $update_meta_cache = true ) {
8275 global $wpdb;
8276
8277 $non_cached_ids = _get_non_cached_ids( $ids, 'posts' );
8278 if ( ! empty( $non_cached_ids ) ) {
8279 $fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.* FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $non_cached_ids ) ) );
8280 $fresh_posts = apply_filters('godaddy/prime_post_caches/posts', $fresh_posts, $non_cached_ids);
8281
8282 if ( $fresh_posts ) {
8283 // Despite the name, update_post_cache() expects an array rather than a single post.
8284 update_post_cache( $fresh_posts );
8285 }
8286 }
8287
8288 if ( $update_meta_cache ) {
8289 update_postmeta_cache( $ids );
8290 }
8291
8292 if ( $update_term_cache ) {
8293 $post_types = array_map( 'get_post_type', $ids );
8294 $post_types = array_unique( $post_types );
8295 update_object_term_cache( $ids, $post_types );
8296 }
8297}
8298
8299/**
8300 * Prime the cache containing the parent ID of various post objects.
8301 *
8302 * @since 6.4.0
8303 *
8304 * @global wpdb $wpdb WordPress database abstraction object.
8305 *
8306 * @param int[] $ids ID list.
8307 */
8308function _prime_post_parent_id_caches( array $ids ) {
8309 global $wpdb;
8310
8311 $ids = array_filter( $ids, '_validate_cache_id' );
8312 $ids = array_unique( array_map( 'intval', $ids ), SORT_NUMERIC );
8313
8314 if ( empty( $ids ) ) {
8315 return;
8316 }
8317
8318 $cache_keys = array();
8319 foreach ( $ids as $id ) {
8320 $cache_keys[ $id ] = 'post_parent:' . (string) $id;
8321 }
8322
8323 $cached_data = wp_cache_get_multiple( array_values( $cache_keys ), 'posts' );
8324
8325 $non_cached_ids = array();
8326 foreach ( $cache_keys as $id => $cache_key ) {
8327 if ( false === $cached_data[ $cache_key ] ) {
8328 $non_cached_ids[] = $id;
8329 }
8330 }
8331
8332 if ( ! empty( $non_cached_ids ) ) {
8333 $fresh_posts = $wpdb->get_results( sprintf( "SELECT $wpdb->posts.ID, $wpdb->posts.post_parent FROM $wpdb->posts WHERE ID IN (%s)", implode( ',', $non_cached_ids ) ) );
8334
8335 if ( $fresh_posts ) {
8336 $post_parent_data = array();
8337 foreach ( $fresh_posts as $fresh_post ) {
8338 $post_parent_data[ 'post_parent:' . (string) $fresh_post->ID ] = (int) $fresh_post->post_parent;
8339 }
8340
8341 wp_cache_add_multiple( $post_parent_data, 'posts' );
8342 }
8343 }
8344}
8345
8346/**
8347 * Adds a suffix if any trashed posts have a given slug.
8348 *
8349 * Store its desired (i.e. current) slug so it can try to reclaim it
8350 * if the post is untrashed.
8351 *
8352 * For internal use.
8353 *
8354 * @since 4.5.0
8355 * @access private
8356 *
8357 * @param string $post_name Post slug.
8358 * @param int $post_id Optional. Post ID that should be ignored. Default 0.
8359 */
8360function wp_add_trashed_suffix_to_post_name_for_trashed_posts( $post_name, $post_id = 0 ) {
8361 $trashed_posts_with_desired_slug = get_posts(
8362 array(
8363 'name' => $post_name,
8364 'post_status' => 'trash',
8365 'post_type' => 'any',
8366 'nopaging' => true,
8367 'post__not_in' => array( $post_id ),
8368 )
8369 );
8370
8371 if ( ! empty( $trashed_posts_with_desired_slug ) ) {
8372 foreach ( $trashed_posts_with_desired_slug as $_post ) {
8373 wp_add_trashed_suffix_to_post_name_for_post( $_post );
8374 }
8375 }
8376}
8377
8378/**
8379 * Adds a trashed suffix for a given post.
8380 *
8381 * Store its desired (i.e. current) slug so it can try to reclaim it
8382 * if the post is untrashed.
8383 *
8384 * For internal use.
8385 *
8386 * @since 4.5.0
8387 * @access private
8388 *
8389 * @global wpdb $wpdb WordPress database abstraction object.
8390 *
8391 * @param WP_Post $post The post.
8392 * @return string New slug for the post.
8393 */
8394function wp_add_trashed_suffix_to_post_name_for_post( $post ) {
8395 global $wpdb;
8396
8397 $post = get_post( $post );
8398
8399 if ( str_ends_with( $post->post_name, '__trashed' ) ) {
8400 return $post->post_name;
8401 }
8402 add_post_meta( $post->ID, '_wp_desired_post_slug', $post->post_name );
8403 $post_name = _truncate_post_slug( $post->post_name, 191 ) . '__trashed';
8404 $wpdb->update( $wpdb->posts, array( 'post_name' => $post_name ), array( 'ID' => $post->ID ) );
8405 clean_post_cache( $post->ID );
8406 return $post_name;
8407}
8408
8409/**
8410 * Sets the last changed time for the 'posts' cache group.
8411 *
8412 * @since 5.0.0
8413 */
8414function wp_cache_set_posts_last_changed() {
8415 wp_cache_set_last_changed( 'posts' );
8416}
8417
8418/**
8419 * Gets all available post MIME types for a given post type.
8420 *
8421 * @since 2.5.0
8422 *
8423 * @global wpdb $wpdb WordPress database abstraction object.
8424 *
8425 * @param string $type
8426 * @return string[] An array of MIME types.
8427 */
8428function get_available_post_mime_types( $type = 'attachment' ) {
8429 global $wpdb;
8430
8431 /**
8432 * Filters the list of available post MIME types for the given post type.
8433 *
8434 * @since 6.4.0
8435 *
8436 * @param string[]|null $mime_types An array of MIME types. Default null.
8437 * @param string $type The post type name. Usually 'attachment' but can be any post type.
8438 */
8439 $mime_types = apply_filters( 'pre_get_available_post_mime_types', null, $type );
8440
8441 if ( ! is_array( $mime_types ) ) {
8442 $mime_types = $wpdb->get_col( $wpdb->prepare( "SELECT DISTINCT post_mime_type FROM $wpdb->posts WHERE post_type = %s AND post_mime_type != ''", $type ) );
8443 }
8444
8445 // Remove nulls from returned $mime_types.
8446 return array_values( array_filter( $mime_types ) );
8447}
8448
8449/**
8450 * Retrieves the path to an uploaded image file.
8451 *
8452 * Similar to `get_attached_file()` however some images may have been processed after uploading
8453 * to make them suitable for web use. In this case the attached "full" size file is usually replaced
8454 * with a scaled down version of the original image. This function always returns the path
8455 * to the originally uploaded image file.
8456 *
8457 * @since 5.3.0
8458 * @since 5.4.0 Added the `$unfiltered` parameter.
8459 *
8460 * @param int $attachment_id Attachment ID.
8461 * @param bool $unfiltered Optional. Passed through to `get_attached_file()`. Default false.
8462 * @return string|false Path to the original image file or false if the attachment is not an image.
8463 */
8464function wp_get_original_image_path( $attachment_id, $unfiltered = false ) {
8465 if ( ! wp_attachment_is_image( $attachment_id ) ) {
8466 return false;
8467 }
8468
8469 $image_meta = wp_get_attachment_metadata( $attachment_id );
8470 $image_file = get_attached_file( $attachment_id, $unfiltered );
8471
8472 if ( empty( $image_meta['original_image'] ) ) {
8473 $original_image = $image_file;
8474 } else {
8475 $original_image = path_join( dirname( $image_file ), $image_meta['original_image'] );
8476 }
8477
8478 /**
8479 * Filters the path to the original image.
8480 *
8481 * @since 5.3.0
8482 *
8483 * @param string $original_image Path to original image file.
8484 * @param int $attachment_id Attachment ID.
8485 */
8486 return apply_filters( 'wp_get_original_image_path', $original_image, $attachment_id );
8487}
8488
8489/**
8490 * Retrieves the URL to an original attachment image.
8491 *
8492 * Similar to `wp_get_attachment_url()` however some images may have been
8493 * processed after uploading. In this case this function returns the URL
8494 * to the originally uploaded image file.
8495 *
8496 * @since 5.3.0
8497 *
8498 * @param int $attachment_id Attachment post ID.
8499 * @return string|false Attachment image URL, false on error or if the attachment is not an image.
8500 */
8501function wp_get_original_image_url( $attachment_id ) {
8502 if ( ! wp_attachment_is_image( $attachment_id ) ) {
8503 return false;
8504 }
8505
8506 $image_url = wp_get_attachment_url( $attachment_id );
8507
8508 if ( ! $image_url ) {
8509 return false;
8510 }
8511
8512 $image_meta = wp_get_attachment_metadata( $attachment_id );
8513
8514 if ( empty( $image_meta['original_image'] ) ) {
8515 $original_image_url = $image_url;
8516 } else {
8517 $original_image_url = path_join( dirname( $image_url ), $image_meta['original_image'] );
8518 }
8519
8520 /**
8521 * Filters the URL to the original attachment image.
8522 *
8523 * @since 5.3.0
8524 *
8525 * @param string $original_image_url URL to original image.
8526 * @param int $attachment_id Attachment ID.
8527 */
8528 return apply_filters( 'wp_get_original_image_url', $original_image_url, $attachment_id );
8529}
8530
8531/**
8532 * Filters callback which sets the status of an untrashed post to its previous status.
8533 *
8534 * This can be used as a callback on the `wp_untrash_post_status` filter.
8535 *
8536 * @since 5.6.0
8537 *
8538 * @param string $new_status The new status of the post being restored.
8539 * @param int $post_id The ID of the post being restored.
8540 * @param string $previous_status The status of the post at the point where it was trashed.
8541 * @return string The new status of the post.
8542 */
8543function wp_untrash_post_set_previous_status( $new_status, $post_id, $previous_status ) {
8544 return $previous_status;
8545}
8546
8547/**
8548 * Returns whether the post can be edited in the block editor.
8549 *
8550 * @since 5.0.0
8551 * @since 6.1.0 Moved to wp-includes from wp-admin.
8552 *
8553 * @param int|WP_Post $post Post ID or WP_Post object.
8554 * @return bool Whether the post can be edited in the block editor.
8555 */
8556function use_block_editor_for_post( $post ) {
8557 $post = get_post( $post );
8558
8559 if ( ! $post ) {
8560 return false;
8561 }
8562
8563 // We're in the meta box loader, so don't use the block editor.
8564 if ( is_admin() && isset( $_GET['meta-box-loader'] ) ) {
8565 check_admin_referer( 'meta-box-loader', 'meta-box-loader-nonce' );
8566 return false;
8567 }
8568
8569 $use_block_editor = use_block_editor_for_post_type( $post->post_type );
8570
8571 /**
8572 * Filters whether a post is able to be edited in the block editor.
8573 *
8574 * @since 5.0.0
8575 *
8576 * @param bool $use_block_editor Whether the post can be edited or not.
8577 * @param WP_Post $post The post being checked.
8578 */
8579 return apply_filters( 'use_block_editor_for_post', $use_block_editor, $post );
8580}
8581
8582/**
8583 * Returns whether a post type is compatible with the block editor.
8584 *
8585 * The block editor depends on the REST API, and if the post type is not shown in the
8586 * REST API, then it won't work with the block editor.
8587 *
8588 * @since 5.0.0
8589 * @since 6.1.0 Moved to wp-includes from wp-admin.
8590 *
8591 * @param string $post_type The post type.
8592 * @return bool Whether the post type can be edited with the block editor.
8593 */
8594function use_block_editor_for_post_type( $post_type ) {
8595 if ( ! post_type_exists( $post_type ) ) {
8596 return false;
8597 }
8598
8599 if ( ! post_type_supports( $post_type, 'editor' ) ) {
8600 return false;
8601 }
8602
8603 $post_type_object = get_post_type_object( $post_type );
8604 if ( $post_type_object && ! $post_type_object->show_in_rest ) {
8605 return false;
8606 }
8607
8608 /**
8609 * Filters whether a post is able to be edited in the block editor.
8610 *
8611 * @since 5.0.0
8612 *
8613 * @param bool $use_block_editor Whether the post type can be edited or not. Default true.
8614 * @param string $post_type The post type being checked.
8615 */
8616 return apply_filters( 'use_block_editor_for_post_type', true, $post_type );
8617}
8618
8619/**
8620 * Registers any additional post meta fields.
8621 *
8622 * @since 6.3.0 Adds `wp_pattern_sync_status` meta field to the wp_block post type so an unsynced option can be added.
8623 *
8624 * @link https://github.com/WordPress/gutenberg/pull/51144
8625 */
8626function wp_create_initial_post_meta() {
8627 register_post_meta(
8628 'wp_block',
8629 'wp_pattern_sync_status',
8630 array(
8631 'sanitize_callback' => 'sanitize_text_field',
8632 'single' => true,
8633 'type' => 'string',
8634 'show_in_rest' => array(
8635 'schema' => array(
8636 'type' => 'string',
8637 'enum' => array( 'partial', 'unsynced' ),
8638 ),
8639 ),
8640 )
8641 );
8642}
8643
Ui Ux Design – Teachers Night Out https://cardgames4educators.com Wed, 16 Oct 2024 22:24:18 +0000 en-US hourly 1 https://wordpress.org/?v=6.9.4 https://cardgames4educators.com/wp-content/uploads/2024/06/cropped-Card-4-Educators-logo-32x32.png Ui Ux Design – Teachers Night Out https://cardgames4educators.com 32 32 Masters In English How English Speaker https://cardgames4educators.com/masters-in-english-how-english-speaker/ https://cardgames4educators.com/masters-in-english-how-english-speaker/#comments Mon, 27 May 2024 08:54:45 +0000 https://themexriver.com/wp/kadu/?p=1

Erat himenaeos neque id sagittis massa. Hac suscipit pulvinar dignissim platea magnis eu. Don tellus a pharetra inceptos efficitur dui pulvinar. Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent pulvinar odio volutpat parturient. Quisque risus finibus suspendisse mus purus magnis facilisi condimentum consectetur dui. Curae elit suspendisse cursus vehicula.

Turpis taciti class non vel pretium quis pulvinar tempor lobortis nunc. Libero phasellus parturient sapien volutpat malesuada ornare. Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae. Porta est tempor ex eget feugiat vulputate ipsum. Justo nec iaculis habitant diam arcu fermentum.

We offer comprehen sive emplo ment services such as assistance wit employer compliance.Our company is your strategic HR partner as instead of HR. john smithson

Cubilia dignissim sollicitudin rhoncus lacinia maximus. Cras lorem fermentum bibendum pellentesque nisl etiam ligula enim cubilia. Vulputate pede sapien torquent montes tempus malesuada in mattis dis turpis vitae.

Exploring Learning Landscapes in Academic

Feugiat facilisis penatibus pulvinar nunc dictumst donec odio platea habitasse. Lacus porta dolor purus elit ante bibendum tortor netus taciti nullam cubilia. Erat per suspendisse placerat morbi egestas pulvinar bibendum sollicitudin nec. Euismod cubilia eleifend velit himenaeos sodales lectus. Leo maximus cras ac porttitor aliquam torquent.

]]>
https://cardgames4educators.com/masters-in-english-how-english-speaker/feed/ 1