1<?php
2/**
3 * WordPress Post Administration API.
4 *
5 * @package WordPress
6 * @subpackage Administration
7 */
8
9/**
10 * Renames `$_POST` data from form names to DB post columns.
11 *
12 * Manipulates `$_POST` directly.
13 *
14 * @since 2.6.0
15 *
16 * @param bool $update Whether the post already exists.
17 * @param array|null $post_data Optional. The array of post data to process.
18 * Defaults to the `$_POST` superglobal.
19 * @return array|WP_Error Array of post data on success, WP_Error on failure.
20 */
21function _wp_translate_postdata( $update = false, $post_data = null ) {
22
23 if ( empty( $post_data ) ) {
24 $post_data = &$_POST;
25 }
26
27 if ( $update ) {
28 $post_data['ID'] = (int) $post_data['post_ID'];
29 }
30
31 $ptype = get_post_type_object( $post_data['post_type'] );
32
33 if ( $update && ! current_user_can( 'edit_post', $post_data['ID'] ) ) {
34 if ( 'page' === $post_data['post_type'] ) {
35 return new WP_Error( 'edit_others_pages', __( 'Sorry, you are not allowed to edit pages as this user.' ) );
36 } else {
37 return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to edit posts as this user.' ) );
38 }
39 } elseif ( ! $update && ! current_user_can( $ptype->cap->create_posts ) ) {
40 if ( 'page' === $post_data['post_type'] ) {
41 return new WP_Error( 'edit_others_pages', __( 'Sorry, you are not allowed to create pages as this user.' ) );
42 } else {
43 return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to create posts as this user.' ) );
44 }
45 }
46
47 if ( isset( $post_data['content'] ) ) {
48 $post_data['post_content'] = $post_data['content'];
49 }
50
51 if ( isset( $post_data['excerpt'] ) ) {
52 $post_data['post_excerpt'] = $post_data['excerpt'];
53 }
54
55 if ( isset( $post_data['parent_id'] ) ) {
56 $post_data['post_parent'] = (int) $post_data['parent_id'];
57 }
58
59 if ( isset( $post_data['trackback_url'] ) ) {
60 $post_data['to_ping'] = $post_data['trackback_url'];
61 }
62
63 $post_data['user_ID'] = get_current_user_id();
64
65 if ( ! empty( $post_data['post_author_override'] ) ) {
66 $post_data['post_author'] = (int) $post_data['post_author_override'];
67 } else {
68 if ( ! empty( $post_data['post_author'] ) ) {
69 $post_data['post_author'] = (int) $post_data['post_author'];
70 } else {
71 $post_data['post_author'] = (int) $post_data['user_ID'];
72 }
73 }
74
75 if ( isset( $post_data['user_ID'] ) && ( $post_data['post_author'] !== $post_data['user_ID'] )
76 && ! current_user_can( $ptype->cap->edit_others_posts ) ) {
77
78 if ( $update ) {
79 if ( 'page' === $post_data['post_type'] ) {
80 return new WP_Error( 'edit_others_pages', __( 'Sorry, you are not allowed to edit pages as this user.' ) );
81 } else {
82 return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to edit posts as this user.' ) );
83 }
84 } else {
85 if ( 'page' === $post_data['post_type'] ) {
86 return new WP_Error( 'edit_others_pages', __( 'Sorry, you are not allowed to create pages as this user.' ) );
87 } else {
88 return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to create posts as this user.' ) );
89 }
90 }
91 }
92
93 if ( ! empty( $post_data['post_status'] ) ) {
94 $post_data['post_status'] = sanitize_key( $post_data['post_status'] );
95
96 // No longer an auto-draft.
97 if ( 'auto-draft' === $post_data['post_status'] ) {
98 $post_data['post_status'] = 'draft';
99 }
100
101 if ( ! get_post_status_object( $post_data['post_status'] ) ) {
102 unset( $post_data['post_status'] );
103 }
104 }
105
106 // What to do based on which button they pressed.
107 if ( isset( $post_data['saveasdraft'] ) && '' !== $post_data['saveasdraft'] ) {
108 $post_data['post_status'] = 'draft';
109 }
110 if ( isset( $post_data['saveasprivate'] ) && '' !== $post_data['saveasprivate'] ) {
111 $post_data['post_status'] = 'private';
112 }
113 if ( isset( $post_data['publish'] ) && ( '' !== $post_data['publish'] )
114 && ( ! isset( $post_data['post_status'] ) || 'private' !== $post_data['post_status'] )
115 ) {
116 $post_data['post_status'] = 'publish';
117 }
118 if ( isset( $post_data['advanced'] ) && '' !== $post_data['advanced'] ) {
119 $post_data['post_status'] = 'draft';
120 }
121 if ( isset( $post_data['pending'] ) && '' !== $post_data['pending'] ) {
122 $post_data['post_status'] = 'pending';
123 }
124
125 if ( isset( $post_data['ID'] ) ) {
126 $post_id = $post_data['ID'];
127 } else {
128 $post_id = false;
129 }
130 $previous_status = $post_id ? get_post_field( 'post_status', $post_id ) : false;
131
132 if ( isset( $post_data['post_status'] ) && 'private' === $post_data['post_status'] && ! current_user_can( $ptype->cap->publish_posts ) ) {
133 $post_data['post_status'] = $previous_status ? $previous_status : 'pending';
134 }
135
136 $published_statuses = array( 'publish', 'future' );
137
138 /*
139 * Posts 'submitted for approval' are submitted to $_POST the same as if they were being published.
140 * Change status from 'publish' to 'pending' if user lacks permissions to publish or to resave published posts.
141 */
142 if ( isset( $post_data['post_status'] )
143 && ( in_array( $post_data['post_status'], $published_statuses, true )
144 && ! current_user_can( $ptype->cap->publish_posts ) )
145 ) {
146 if ( ! in_array( $previous_status, $published_statuses, true ) || ! current_user_can( 'edit_post', $post_id ) ) {
147 $post_data['post_status'] = 'pending';
148 }
149 }
150
151 if ( ! isset( $post_data['post_status'] ) ) {
152 $post_data['post_status'] = 'auto-draft' === $previous_status ? 'draft' : $previous_status;
153 }
154
155 if ( isset( $post_data['post_password'] ) && ! current_user_can( $ptype->cap->publish_posts ) ) {
156 unset( $post_data['post_password'] );
157 }
158
159 if ( ! isset( $post_data['comment_status'] ) ) {
160 $post_data['comment_status'] = 'closed';
161 }
162
163 if ( ! isset( $post_data['ping_status'] ) ) {
164 $post_data['ping_status'] = 'closed';
165 }
166
167 foreach ( array( 'aa', 'mm', 'jj', 'hh', 'mn' ) as $timeunit ) {
168 if ( ! empty( $post_data[ 'hidden_' . $timeunit ] ) && $post_data[ 'hidden_' . $timeunit ] !== $post_data[ $timeunit ] ) {
169 $post_data['edit_date'] = '1';
170 break;
171 }
172 }
173
174 if ( ! empty( $post_data['edit_date'] ) ) {
175 $aa = $post_data['aa'];
176 $mm = $post_data['mm'];
177 $jj = $post_data['jj'];
178 $hh = $post_data['hh'];
179 $mn = $post_data['mn'];
180 $ss = $post_data['ss'];
181 $aa = ( $aa <= 0 ) ? gmdate( 'Y' ) : $aa;
182 $mm = ( $mm <= 0 ) ? gmdate( 'n' ) : $mm;
183 $jj = ( $jj > 31 ) ? 31 : $jj;
184 $jj = ( $jj <= 0 ) ? gmdate( 'j' ) : $jj;
185 $hh = ( $hh > 23 ) ? $hh - 24 : $hh;
186 $mn = ( $mn > 59 ) ? $mn - 60 : $mn;
187 $ss = ( $ss > 59 ) ? $ss - 60 : $ss;
188
189 $post_data['post_date'] = sprintf( '%04d-%02d-%02d %02d:%02d:%02d', $aa, $mm, $jj, $hh, $mn, $ss );
190
191 $valid_date = wp_checkdate( $mm, $jj, $aa, $post_data['post_date'] );
192 if ( ! $valid_date ) {
193 return new WP_Error( 'invalid_date', __( 'Invalid date.' ) );
194 }
195
196 /*
197 * Only assign a post date if the user has explicitly set a new value.
198 * See #59125 and #19907.
199 */
200 $previous_date = $post_id ? get_post_field( 'post_date', $post_id ) : false;
201 if ( $previous_date && $previous_date !== $post_data['post_date'] ) {
202 $post_data['edit_date'] = true;
203 $post_data['post_date_gmt'] = get_gmt_from_date( $post_data['post_date'] );
204 } else {
205 $post_data['edit_date'] = false;
206 unset( $post_data['post_date'] );
207 unset( $post_data['post_date_gmt'] );
208 }
209 }
210
211 if ( isset( $post_data['post_category'] ) ) {
212 $category_object = get_taxonomy( 'category' );
213 if ( ! current_user_can( $category_object->cap->assign_terms ) ) {
214 unset( $post_data['post_category'] );
215 }
216 }
217
218 return $post_data;
219}
220
221/**
222 * Returns only allowed post data fields.
223 *
224 * @since 5.0.1
225 *
226 * @param array|WP_Error|null $post_data The array of post data to process, or an error object.
227 * Defaults to the `$_POST` superglobal.
228 * @return array|WP_Error Array of post data on success, WP_Error on failure.
229 */
230function _wp_get_allowed_postdata( $post_data = null ) {
231 if ( empty( $post_data ) ) {
232 $post_data = $_POST;
233 }
234
235 // Pass through errors.
236 if ( is_wp_error( $post_data ) ) {
237 return $post_data;
238 }
239
240 return array_diff_key( $post_data, array_flip( array( 'meta_input', 'file', 'guid' ) ) );
241}
242
243/**
244 * Updates an existing post with values provided in `$_POST`.
245 *
246 * If post data is passed as an argument, it is treated as an array of data
247 * keyed appropriately for turning into a post object.
248 *
249 * If post data is not passed, the `$_POST` global variable is used instead.
250 *
251 * @since 1.5.0
252 *
253 * @global wpdb $wpdb WordPress database abstraction object.
254 *
255 * @param array|null $post_data Optional. The array of post data to process.
256 * Defaults to the `$_POST` superglobal.
257 * @return int Post ID.
258 */
259function edit_post( $post_data = null ) {
260 global $wpdb;
261
262 if ( empty( $post_data ) ) {
263 $post_data = &$_POST;
264 }
265
266 // Clear out any data in internal vars.
267 unset( $post_data['filter'] );
268
269 $post_id = (int) $post_data['post_ID'];
270 $post = get_post( $post_id );
271
272 $post_data['post_type'] = $post->post_type;
273 $post_data['post_mime_type'] = $post->post_mime_type;
274
275 if ( ! empty( $post_data['post_status'] ) ) {
276 $post_data['post_status'] = sanitize_key( $post_data['post_status'] );
277
278 if ( 'inherit' === $post_data['post_status'] ) {
279 unset( $post_data['post_status'] );
280 }
281 }
282
283 $ptype = get_post_type_object( $post_data['post_type'] );
284 if ( ! current_user_can( 'edit_post', $post_id ) ) {
285 if ( 'page' === $post_data['post_type'] ) {
286 wp_die( __( 'Sorry, you are not allowed to edit this page.' ) );
287 } else {
288 wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
289 }
290 }
291
292 if ( post_type_supports( $ptype->name, 'revisions' ) ) {
293 $revisions = wp_get_post_revisions(
294 $post_id,
295 array(
296 'order' => 'ASC',
297 'posts_per_page' => 1,
298 )
299 );
300 $revision = current( $revisions );
301
302 // Check if the revisions have been upgraded.
303 if ( $revisions && _wp_get_post_revision_version( $revision ) < 1 ) {
304 _wp_upgrade_revisions_of_post( $post, wp_get_post_revisions( $post_id ) );
305 }
306 }
307
308 if ( isset( $post_data['visibility'] ) ) {
309 switch ( $post_data['visibility'] ) {
310 case 'public':
311 $post_data['post_password'] = '';
312 break;
313 case 'password':
314 unset( $post_data['sticky'] );
315 break;
316 case 'private':
317 $post_data['post_status'] = 'private';
318 $post_data['post_password'] = '';
319 unset( $post_data['sticky'] );
320 break;
321 }
322 }
323
324 $post_data = _wp_translate_postdata( true, $post_data );
325 if ( is_wp_error( $post_data ) ) {
326 wp_die( $post_data->get_error_message() );
327 }
328 $translated = _wp_get_allowed_postdata( $post_data );
329
330 // Post formats.
331 if ( isset( $post_data['post_format'] ) ) {
332 set_post_format( $post_id, $post_data['post_format'] );
333 }
334
335 $format_meta_urls = array( 'url', 'link_url', 'quote_source_url' );
336 foreach ( $format_meta_urls as $format_meta_url ) {
337 $keyed = '_format_' . $format_meta_url;
338 if ( isset( $post_data[ $keyed ] ) ) {
339 update_post_meta( $post_id, $keyed, wp_slash( sanitize_url( wp_unslash( $post_data[ $keyed ] ) ) ) );
340 }
341 }
342
343 $format_keys = array( 'quote', 'quote_source_name', 'image', 'gallery', 'audio_embed', 'video_embed' );
344
345 foreach ( $format_keys as $key ) {
346 $keyed = '_format_' . $key;
347 if ( isset( $post_data[ $keyed ] ) ) {
348 if ( current_user_can( 'unfiltered_html' ) ) {
349 update_post_meta( $post_id, $keyed, $post_data[ $keyed ] );
350 } else {
351 update_post_meta( $post_id, $keyed, wp_filter_post_kses( $post_data[ $keyed ] ) );
352 }
353 }
354 }
355
356 if ( 'attachment' === $post_data['post_type'] && preg_match( '#^(audio|video)/#', $post_data['post_mime_type'] ) ) {
357 $id3data = wp_get_attachment_metadata( $post_id );
358 if ( ! is_array( $id3data ) ) {
359 $id3data = array();
360 }
361
362 foreach ( wp_get_attachment_id3_keys( $post, 'edit' ) as $key => $label ) {
363 if ( isset( $post_data[ 'id3_' . $key ] ) ) {
364 $id3data[ $key ] = sanitize_text_field( wp_unslash( $post_data[ 'id3_' . $key ] ) );
365 }
366 }
367 wp_update_attachment_metadata( $post_id, $id3data );
368 }
369
370 // Meta stuff.
371 if ( isset( $post_data['meta'] ) && $post_data['meta'] ) {
372 foreach ( $post_data['meta'] as $key => $value ) {
373 $meta = get_post_meta_by_id( $key );
374 if ( ! $meta ) {
375 continue;
376 }
377
378 if ( (int) $meta->post_id !== $post_id ) {
379 continue;
380 }
381
382 if ( is_protected_meta( $meta->meta_key, 'post' )
383 || ! current_user_can( 'edit_post_meta', $post_id, $meta->meta_key )
384 ) {
385 continue;
386 }
387
388 if ( is_protected_meta( $value['key'], 'post' )
389 || ! current_user_can( 'edit_post_meta', $post_id, $value['key'] )
390 ) {
391 continue;
392 }
393
394 update_meta( $key, $value['key'], $value['value'] );
395 }
396 }
397
398 if ( isset( $post_data['deletemeta'] ) && $post_data['deletemeta'] ) {
399 foreach ( $post_data['deletemeta'] as $key => $value ) {
400 $meta = get_post_meta_by_id( $key );
401 if ( ! $meta ) {
402 continue;
403 }
404
405 if ( (int) $meta->post_id !== $post_id ) {
406 continue;
407 }
408
409 if ( is_protected_meta( $meta->meta_key, 'post' )
410 || ! current_user_can( 'delete_post_meta', $post_id, $meta->meta_key )
411 ) {
412 continue;
413 }
414
415 delete_meta( $key );
416 }
417 }
418
419 // Attachment stuff.
420 if ( 'attachment' === $post_data['post_type'] ) {
421 if ( isset( $post_data['_wp_attachment_image_alt'] ) ) {
422 $image_alt = wp_unslash( $post_data['_wp_attachment_image_alt'] );
423
424 if ( get_post_meta( $post_id, '_wp_attachment_image_alt', true ) !== $image_alt ) {
425 $image_alt = wp_strip_all_tags( $image_alt, true );
426
427 // update_post_meta() expects slashed.
428 update_post_meta( $post_id, '_wp_attachment_image_alt', wp_slash( $image_alt ) );
429 }
430 }
431
432 $attachment_data = isset( $post_data['attachments'][ $post_id ] ) ? $post_data['attachments'][ $post_id ] : array();
433
434 /** This filter is documented in wp-admin/includes/media.php */
435 $translated = apply_filters( 'attachment_fields_to_save', $translated, $attachment_data );
436 }
437
438 // Convert taxonomy input to term IDs, to avoid ambiguity.
439 if ( isset( $post_data['tax_input'] ) ) {
440 foreach ( (array) $post_data['tax_input'] as $taxonomy => $terms ) {
441 $tax_object = get_taxonomy( $taxonomy );
442
443 if ( $tax_object && isset( $tax_object->meta_box_sanitize_cb ) ) {
444 $translated['tax_input'][ $taxonomy ] = call_user_func_array( $tax_object->meta_box_sanitize_cb, array( $taxonomy, $terms ) );
445 }
446 }
447 }
448
449 add_meta( $post_id );
450
451 update_post_meta( $post_id, '_edit_last', get_current_user_id() );
452
453 $success = wp_update_post( $translated );
454
455 // If the save failed, see if we can confidence check the main fields and try again.
456 if ( ! $success && is_callable( array( $wpdb, 'strip_invalid_text_for_column' ) ) ) {
457 $fields = array( 'post_title', 'post_content', 'post_excerpt' );
458
459 foreach ( $fields as $field ) {
460 if ( isset( $translated[ $field ] ) ) {
461 $translated[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->posts, $field, $translated[ $field ] );
462 }
463 }
464
465 wp_update_post( $translated );
466 }
467
468 // Now that we have an ID we can fix any attachment anchor hrefs.
469 _fix_attachment_links( $post_id );
470
471 wp_set_post_lock( $post_id );
472
473 if ( current_user_can( $ptype->cap->edit_others_posts ) && current_user_can( $ptype->cap->publish_posts ) ) {
474 if ( ! empty( $post_data['sticky'] ) ) {
475 stick_post( $post_id );
476 } else {
477 unstick_post( $post_id );
478 }
479 }
480
481 return $post_id;
482}
483
484/**
485 * Processes the post data for the bulk editing of posts.
486 *
487 * Updates all bulk edited posts/pages, adding (but not removing) tags and
488 * categories. Skips pages when they would be their own parent or child.
489 *
490 * @since 2.7.0
491 *
492 * @global wpdb $wpdb WordPress database abstraction object.
493 *
494 * @param array|null $post_data Optional. The array of post data to process.
495 * Defaults to the `$_POST` superglobal.
496 * @return array {
497 * An array of updated, skipped, and locked post IDs.
498 *
499 * @type int[] $updated An array of updated post IDs.
500 * @type int[] $skipped An array of skipped post IDs.
501 * @type int[] $locked An array of locked post IDs.
502 * }
503 */
504function bulk_edit_posts( $post_data = null ) {
505 global $wpdb;
506
507 if ( empty( $post_data ) ) {
508 $post_data = &$_POST;
509 }
510
511 if ( isset( $post_data['post_type'] ) ) {
512 $ptype = get_post_type_object( $post_data['post_type'] );
513 } else {
514 $ptype = get_post_type_object( 'post' );
515 }
516
517 if ( ! current_user_can( $ptype->cap->edit_posts ) ) {
518 if ( 'page' === $ptype->name ) {
519 wp_die( __( 'Sorry, you are not allowed to edit pages.' ) );
520 } else {
521 wp_die( __( 'Sorry, you are not allowed to edit posts.' ) );
522 }
523 }
524
525 if ( '-1' === $post_data['_status'] ) {
526 $post_data['post_status'] = null;
527 unset( $post_data['post_status'] );
528 } else {
529 $post_data['post_status'] = $post_data['_status'];
530 }
531 unset( $post_data['_status'] );
532
533 if ( ! empty( $post_data['post_status'] ) ) {
534 $post_data['post_status'] = sanitize_key( $post_data['post_status'] );
535
536 if ( 'inherit' === $post_data['post_status'] ) {
537 unset( $post_data['post_status'] );
538 }
539 }
540
541 $post_ids = array_map( 'intval', (array) $post_data['post'] );
542
543 $reset = array(
544 'post_author',
545 'post_status',
546 'post_password',
547 'post_parent',
548 'page_template',
549 'comment_status',
550 'ping_status',
551 'keep_private',
552 'tax_input',
553 'post_category',
554 'sticky',
555 'post_format',
556 );
557
558 foreach ( $reset as $field ) {
559 if ( isset( $post_data[ $field ] ) && ( '' === $post_data[ $field ] || '-1' === $post_data[ $field ] ) ) {
560 unset( $post_data[ $field ] );
561 }
562 }
563
564 if ( isset( $post_data['post_category'] ) ) {
565 if ( is_array( $post_data['post_category'] ) && ! empty( $post_data['post_category'] ) ) {
566 $new_cats = array_map( 'absint', $post_data['post_category'] );
567 } else {
568 unset( $post_data['post_category'] );
569 }
570 }
571
572 $tax_input = array();
573 if ( isset( $post_data['tax_input'] ) ) {
574 foreach ( $post_data['tax_input'] as $tax_name => $terms ) {
575 if ( empty( $terms ) ) {
576 continue;
577 }
578
579 if ( is_taxonomy_hierarchical( $tax_name ) ) {
580 $tax_input[ $tax_name ] = array_map( 'absint', $terms );
581 } else {
582 $comma = _x( ',', 'tag delimiter' );
583 if ( ',' !== $comma ) {
584 $terms = str_replace( $comma, ',', $terms );
585 }
586 $tax_input[ $tax_name ] = explode( ',', trim( $terms, " \n\t\r\0\x0B," ) );
587 }
588 }
589 }
590
591 if ( isset( $post_data['post_parent'] ) && (int) $post_data['post_parent'] ) {
592 $parent = (int) $post_data['post_parent'];
593 $pages = $wpdb->get_results( "SELECT ID, post_parent FROM $wpdb->posts WHERE post_type = 'page'" );
594 $children = array();
595
596 for ( $i = 0; $i < 50 && $parent > 0; $i++ ) {
597 $children[] = $parent;
598
599 foreach ( $pages as $page ) {
600 if ( (int) $page->ID === $parent ) {
601 $parent = (int) $page->post_parent;
602 break;
603 }
604 }
605 }
606 }
607
608 $updated = array();
609 $skipped = array();
610 $locked = array();
611 $shared_post_data = $post_data;
612
613 foreach ( $post_ids as $post_id ) {
614 // Start with fresh post data with each iteration.
615 $post_data = $shared_post_data;
616
617 $post_type_object = get_post_type_object( get_post_type( $post_id ) );
618
619 if ( ! isset( $post_type_object )
620 || ( isset( $children ) && in_array( $post_id, $children, true ) )
621 || ! current_user_can( 'edit_post', $post_id )
622 ) {
623 $skipped[] = $post_id;
624 continue;
625 }
626
627 if ( wp_check_post_lock( $post_id ) ) {
628 $locked[] = $post_id;
629 continue;
630 }
631
632 $post = get_post( $post_id );
633 $tax_names = get_object_taxonomies( $post );
634
635 foreach ( $tax_names as $tax_name ) {
636 $taxonomy_obj = get_taxonomy( $tax_name );
637
638 if ( ! $taxonomy_obj->show_in_quick_edit ) {
639 continue;
640 }
641
642 if ( isset( $tax_input[ $tax_name ] ) && current_user_can( $taxonomy_obj->cap->assign_terms ) ) {
643 $new_terms = $tax_input[ $tax_name ];
644 } else {
645 $new_terms = array();
646 }
647
648 if ( $taxonomy_obj->hierarchical ) {
649 $current_terms = (array) wp_get_object_terms( $post_id, $tax_name, array( 'fields' => 'ids' ) );
650 } else {
651 $current_terms = (array) wp_get_object_terms( $post_id, $tax_name, array( 'fields' => 'names' ) );
652 }
653
654 $post_data['tax_input'][ $tax_name ] = array_merge( $current_terms, $new_terms );
655 }
656
657 if ( isset( $new_cats ) && in_array( 'category', $tax_names, true ) ) {
658 $cats = (array) wp_get_post_categories( $post_id );
659
660 if (
661 isset( $post_data['indeterminate_post_category'] )
662 && is_array( $post_data['indeterminate_post_category'] )
663 ) {
664 $indeterminate_post_category = $post_data['indeterminate_post_category'];
665 } else {
666 $indeterminate_post_category = array();
667 }
668
669 $indeterminate_cats = array_intersect( $cats, $indeterminate_post_category );
670 $determinate_cats = array_diff( $new_cats, $indeterminate_post_category );
671 $post_data['post_category'] = array_unique( array_merge( $indeterminate_cats, $determinate_cats ) );
672
673 unset( $post_data['tax_input']['category'] );
674 }
675
676 $post_data['post_ID'] = $post_id;
677 $post_data['post_type'] = $post->post_type;
678 $post_data['post_mime_type'] = $post->post_mime_type;
679
680 foreach ( array( 'comment_status', 'ping_status', 'post_author' ) as $field ) {
681 if ( ! isset( $post_data[ $field ] ) ) {
682 $post_data[ $field ] = $post->$field;
683 }
684 }
685
686 $post_data = _wp_translate_postdata( true, $post_data );
687 if ( is_wp_error( $post_data ) ) {
688 $skipped[] = $post_id;
689 continue;
690 }
691 $post_data = _wp_get_allowed_postdata( $post_data );
692
693 if ( isset( $shared_post_data['post_format'] ) ) {
694 set_post_format( $post_id, $shared_post_data['post_format'] );
695 }
696
697 // Prevent wp_insert_post() from overwriting post format with the old data.
698 unset( $post_data['tax_input']['post_format'] );
699
700 // Reset post date of scheduled post to be published.
701 if (
702 in_array( $post->post_status, array( 'future', 'draft' ), true ) &&
703 'publish' === $post_data['post_status']
704 ) {
705 $post_data['post_date'] = current_time( 'mysql' );
706 $post_data['post_date_gmt'] = '';
707 }
708
709 $post_id = wp_update_post( $post_data );
710 update_post_meta( $post_id, '_edit_last', get_current_user_id() );
711 $updated[] = $post_id;
712
713 if ( isset( $post_data['sticky'] ) && current_user_can( $ptype->cap->edit_others_posts ) ) {
714 if ( 'sticky' === $post_data['sticky'] ) {
715 stick_post( $post_id );
716 } else {
717 unstick_post( $post_id );
718 }
719 }
720 }
721
722 /**
723 * Fires after processing the post data for bulk edit.
724 *
725 * @since 6.3.0
726 *
727 * @param int[] $updated An array of updated post IDs.
728 * @param array $shared_post_data Associative array containing the post data.
729 */
730 do_action( 'bulk_edit_posts', $updated, $shared_post_data );
731
732 return array(
733 'updated' => $updated,
734 'skipped' => $skipped,
735 'locked' => $locked,
736 );
737}
738
739/**
740 * Returns default post information to use when populating the "Write Post" form.
741 *
742 * @since 2.0.0
743 *
744 * @param string $post_type Optional. A post type string. Default 'post'.
745 * @param bool $create_in_db Optional. Whether to insert the post into database. Default false.
746 * @return WP_Post Post object containing all the default post data as attributes
747 */
748function get_default_post_to_edit( $post_type = 'post', $create_in_db = false ) {
749 $post_title = '';
750 if ( ! empty( $_REQUEST['post_title'] ) ) {
751 $post_title = esc_html( wp_unslash( $_REQUEST['post_title'] ) );
752 }
753
754 $post_content = '';
755 if ( ! empty( $_REQUEST['content'] ) ) {
756 $post_content = esc_html( wp_unslash( $_REQUEST['content'] ) );
757 }
758
759 $post_excerpt = '';
760 if ( ! empty( $_REQUEST['excerpt'] ) ) {
761 $post_excerpt = esc_html( wp_unslash( $_REQUEST['excerpt'] ) );
762 }
763
764 if ( $create_in_db ) {
765 $post_id = wp_insert_post(
766 array(
767 'post_title' => __( 'Auto Draft' ),
768 'post_type' => $post_type,
769 'post_status' => 'auto-draft',
770 ),
771 true,
772 false
773 );
774
775 if ( is_wp_error( $post_id ) ) {
776 wp_die( $post_id->get_error_message() );
777 }
778
779 $post = get_post( $post_id );
780
781 if ( current_theme_supports( 'post-formats' ) && post_type_supports( $post->post_type, 'post-formats' ) && get_option( 'default_post_format' ) ) {
782 set_post_format( $post, get_option( 'default_post_format' ) );
783 }
784
785 wp_after_insert_post( $post, false, null );
786
787 // Schedule auto-draft cleanup.
788 if ( ! wp_next_scheduled( 'wp_scheduled_auto_draft_delete' ) ) {
789 wp_schedule_event( time(), 'daily', 'wp_scheduled_auto_draft_delete' );
790 }
791 } else {
792 $post = new stdClass();
793 $post->ID = 0;
794 $post->post_author = '';
795 $post->post_date = '';
796 $post->post_date_gmt = '';
797 $post->post_password = '';
798 $post->post_name = '';
799 $post->post_type = $post_type;
800 $post->post_status = 'draft';
801 $post->to_ping = '';
802 $post->pinged = '';
803 $post->comment_status = get_default_comment_status( $post_type );
804 $post->ping_status = get_default_comment_status( $post_type, 'pingback' );
805 $post->post_pingback = get_option( 'default_pingback_flag' );
806 $post->post_category = get_option( 'default_category' );
807 $post->page_template = 'default';
808 $post->post_parent = 0;
809 $post->menu_order = 0;
810 $post = new WP_Post( $post );
811 }
812
813 /**
814 * Filters the default post content initially used in the "Write Post" form.
815 *
816 * @since 1.5.0
817 *
818 * @param string $post_content Default post content.
819 * @param WP_Post $post Post object.
820 */
821 $post->post_content = (string) apply_filters( 'default_content', $post_content, $post );
822
823 /**
824 * Filters the default post title initially used in the "Write Post" form.
825 *
826 * @since 1.5.0
827 *
828 * @param string $post_title Default post title.
829 * @param WP_Post $post Post object.
830 */
831 $post->post_title = (string) apply_filters( 'default_title', $post_title, $post );
832
833 /**
834 * Filters the default post excerpt initially used in the "Write Post" form.
835 *
836 * @since 1.5.0
837 *
838 * @param string $post_excerpt Default post excerpt.
839 * @param WP_Post $post Post object.
840 */
841 $post->post_excerpt = (string) apply_filters( 'default_excerpt', $post_excerpt, $post );
842
843 return $post;
844}
845
846/**
847 * Determines if a post exists based on title, content, date and type.
848 *
849 * @since 2.0.0
850 * @since 5.2.0 Added the `$type` parameter.
851 * @since 5.8.0 Added the `$status` parameter.
852 *
853 * @global wpdb $wpdb WordPress database abstraction object.
854 *
855 * @param string $title Post title.
856 * @param string $content Optional. Post content.
857 * @param string $date Optional. Post date.
858 * @param string $type Optional. Post type.
859 * @param string $status Optional. Post status.
860 * @return int Post ID if post exists, 0 otherwise.
861 */
862function post_exists( $title, $content = '', $date = '', $type = '', $status = '' ) {
863 global $wpdb;
864
865 $post_title = wp_unslash( sanitize_post_field( 'post_title', $title, 0, 'db' ) );
866 $post_content = wp_unslash( sanitize_post_field( 'post_content', $content, 0, 'db' ) );
867 $post_date = wp_unslash( sanitize_post_field( 'post_date', $date, 0, 'db' ) );
868 $post_type = wp_unslash( sanitize_post_field( 'post_type', $type, 0, 'db' ) );
869 $post_status = wp_unslash( sanitize_post_field( 'post_status', $status, 0, 'db' ) );
870
871 $query = "SELECT ID FROM $wpdb->posts WHERE 1=1";
872 $args = array();
873
874 if ( ! empty( $date ) ) {
875 $query .= ' AND post_date = %s';
876 $args[] = $post_date;
877 }
878
879 if ( ! empty( $title ) ) {
880 $query .= ' AND post_title = %s';
881 $args[] = $post_title;
882 }
883
884 if ( ! empty( $content ) ) {
885 $query .= ' AND post_content = %s';
886 $args[] = $post_content;
887 }
888
889 if ( ! empty( $type ) ) {
890 $query .= ' AND post_type = %s';
891 $args[] = $post_type;
892 }
893
894 if ( ! empty( $status ) ) {
895 $query .= ' AND post_status = %s';
896 $args[] = $post_status;
897 }
898
899 if ( ! empty( $args ) ) {
900 return (int) $wpdb->get_var( $wpdb->prepare( $query, $args ) );
901 }
902
903 return 0;
904}
905
906/**
907 * Creates a new post from the "Write Post" form using `$_POST` information.
908 *
909 * @since 2.1.0
910 *
911 * @global WP_User $current_user
912 *
913 * @return int|WP_Error Post ID on success, WP_Error on failure.
914 */
915function wp_write_post() {
916 if ( isset( $_POST['post_type'] ) ) {
917 $ptype = get_post_type_object( $_POST['post_type'] );
918 } else {
919 $ptype = get_post_type_object( 'post' );
920 }
921
922 if ( ! current_user_can( $ptype->cap->edit_posts ) ) {
923 if ( 'page' === $ptype->name ) {
924 return new WP_Error( 'edit_pages', __( 'Sorry, you are not allowed to create pages on this site.' ) );
925 } else {
926 return new WP_Error( 'edit_posts', __( 'Sorry, you are not allowed to create posts or drafts on this site.' ) );
927 }
928 }
929
930 $_POST['post_mime_type'] = '';
931
932 // Clear out any data in internal vars.
933 unset( $_POST['filter'] );
934
935 // Edit, don't write, if we have a post ID.
936 if ( isset( $_POST['post_ID'] ) ) {
937 return edit_post();
938 }
939
940 if ( isset( $_POST['visibility'] ) ) {
941 switch ( $_POST['visibility'] ) {
942 case 'public':
943 $_POST['post_password'] = '';
944 break;
945 case 'password':
946 unset( $_POST['sticky'] );
947 break;
948 case 'private':
949 $_POST['post_status'] = 'private';
950 $_POST['post_password'] = '';
951 unset( $_POST['sticky'] );
952 break;
953 }
954 }
955
956 $translated = _wp_translate_postdata( false );
957 if ( is_wp_error( $translated ) ) {
958 return $translated;
959 }
960 $translated = _wp_get_allowed_postdata( $translated );
961
962 // Create the post.
963 $post_id = wp_insert_post( $translated );
964 if ( is_wp_error( $post_id ) ) {
965 return $post_id;
966 }
967
968 if ( empty( $post_id ) ) {
969 return 0;
970 }
971
972 add_meta( $post_id );
973
974 add_post_meta( $post_id, '_edit_last', $GLOBALS['current_user']->ID );
975
976 // Now that we have an ID we can fix any attachment anchor hrefs.
977 _fix_attachment_links( $post_id );
978
979 wp_set_post_lock( $post_id );
980
981 return $post_id;
982}
983
984/**
985 * Calls wp_write_post() and handles the errors.
986 *
987 * @since 2.0.0
988 *
989 * @return int|void Post ID on success, void on failure.
990 */
991function write_post() {
992 $result = wp_write_post();
993 if ( is_wp_error( $result ) ) {
994 wp_die( $result->get_error_message() );
995 } else {
996 return $result;
997 }
998}
999
1000//
1001// Post Meta.
1002//
1003
1004/**
1005 * Adds post meta data defined in the `$_POST` superglobal for a post with given ID.
1006 *
1007 * @since 1.2.0
1008 *
1009 * @param int $post_id
1010 * @return int|bool
1011 */
1012function add_meta( $post_id ) {
1013 $post_id = (int) $post_id;
1014
1015 $metakeyselect = isset( $_POST['metakeyselect'] ) ? wp_unslash( trim( $_POST['metakeyselect'] ) ) : '';
1016 $metakeyinput = isset( $_POST['metakeyinput'] ) ? wp_unslash( trim( $_POST['metakeyinput'] ) ) : '';
1017 $metavalue = isset( $_POST['metavalue'] ) ? $_POST['metavalue'] : '';
1018 if ( is_string( $metavalue ) ) {
1019 $metavalue = trim( $metavalue );
1020 }
1021
1022 if ( ( ( '#NONE#' !== $metakeyselect ) && ! empty( $metakeyselect ) ) || ! empty( $metakeyinput ) ) {
1023 /*
1024 * We have a key/value pair. If both the select and the input
1025 * for the key have data, the input takes precedence.
1026 */
1027 if ( '#NONE#' !== $metakeyselect ) {
1028 $metakey = $metakeyselect;
1029 }
1030
1031 if ( $metakeyinput ) {
1032 $metakey = $metakeyinput; // Default.
1033 }
1034
1035 if ( is_protected_meta( $metakey, 'post' ) || ! current_user_can( 'add_post_meta', $post_id, $metakey ) ) {
1036 return false;
1037 }
1038
1039 $metakey = wp_slash( $metakey );
1040
1041 return add_post_meta( $post_id, $metakey, $metavalue );
1042 }
1043
1044 return false;
1045}
1046
1047/**
1048 * Deletes post meta data by meta ID.
1049 *
1050 * @since 1.2.0
1051 *
1052 * @param int $mid
1053 * @return bool
1054 */
1055function delete_meta( $mid ) {
1056 return delete_metadata_by_mid( 'post', $mid );
1057}
1058
1059/**
1060 * Returns a list of previously defined keys.
1061 *
1062 * @since 1.2.0
1063 *
1064 * @global wpdb $wpdb WordPress database abstraction object.
1065 *
1066 * @return string[] Array of meta key names.
1067 */
1068function get_meta_keys() {
1069 global $wpdb;
1070
1071 $keys = $wpdb->get_col(
1072 "SELECT meta_key
1073 FROM $wpdb->postmeta
1074 GROUP BY meta_key
1075 ORDER BY meta_key"
1076 );
1077
1078 return $keys;
1079}
1080
1081/**
1082 * Returns post meta data by meta ID.
1083 *
1084 * @since 2.1.0
1085 *
1086 * @param int $mid
1087 * @return object|bool
1088 */
1089function get_post_meta_by_id( $mid ) {
1090 return get_metadata_by_mid( 'post', $mid );
1091}
1092
1093/**
1094 * Returns meta data for the given post ID.
1095 *
1096 * @since 1.2.0
1097 *
1098 * @global wpdb $wpdb WordPress database abstraction object.
1099 *
1100 * @param int $post_id A post ID.
1101 * @return array[] {
1102 * Array of meta data arrays for the given post ID.
1103 *
1104 * @type array ...$0 {
1105 * Associative array of meta data.
1106 *
1107 * @type string $meta_key Meta key.
1108 * @type mixed $meta_value Meta value.
1109 * @type string $meta_id Meta ID as a numeric string.
1110 * @type string $post_id Post ID as a numeric string.
1111 * }
1112 * }
1113 */
1114function has_meta( $post_id ) {
1115 global $wpdb;
1116
1117 return $wpdb->get_results(
1118 $wpdb->prepare(
1119 "SELECT meta_key, meta_value, meta_id, post_id
1120 FROM $wpdb->postmeta WHERE post_id = %d
1121 ORDER BY meta_key,meta_id",
1122 $post_id
1123 ),
1124 ARRAY_A
1125 );
1126}
1127
1128/**
1129 * Updates post meta data by meta ID.
1130 *
1131 * @since 1.2.0
1132 *
1133 * @param int $meta_id Meta ID.
1134 * @param string $meta_key Meta key. Expect slashed.
1135 * @param string $meta_value Meta value. Expect slashed.
1136 * @return bool
1137 */
1138function update_meta( $meta_id, $meta_key, $meta_value ) {
1139 $meta_key = wp_unslash( $meta_key );
1140 $meta_value = wp_unslash( $meta_value );
1141
1142 return update_metadata_by_mid( 'post', $meta_id, $meta_value, $meta_key );
1143}
1144
1145//
1146// Private.
1147//
1148
1149/**
1150 * Replaces hrefs of attachment anchors with up-to-date permalinks.
1151 *
1152 * @since 2.3.0
1153 * @access private
1154 *
1155 * @param int|WP_Post $post Post ID or post object.
1156 * @return void|int|WP_Error Void if nothing fixed. 0 or WP_Error on update failure. The post ID on update success.
1157 */
1158function _fix_attachment_links( $post ) {
1159 $post = get_post( $post, ARRAY_A );
1160 $content = $post['post_content'];
1161
1162 // Don't run if no pretty permalinks or post is not published, scheduled, or privately published.
1163 if ( ! get_option( 'permalink_structure' ) || ! in_array( $post['post_status'], array( 'publish', 'future', 'private' ), true ) ) {
1164 return;
1165 }
1166
1167 // Short if there aren't any links or no '?attachment_id=' strings (strpos cannot be zero).
1168 if ( ! strpos( $content, '?attachment_id=' ) || ! preg_match_all( '/<a ([^>]+)>[\s\S]+?<\/a>/', $content, $link_matches ) ) {
1169 return;
1170 }
1171
1172 $site_url = get_bloginfo( 'url' );
1173 $site_url = substr( $site_url, (int) strpos( $site_url, '://' ) ); // Remove the http(s).
1174 $replace = '';
1175
1176 foreach ( $link_matches[1] as $key => $value ) {
1177 if ( ! strpos( $value, '?attachment_id=' ) || ! strpos( $value, 'wp-att-' )
1178 || ! preg_match( '/href=(["\'])[^"\']*\?attachment_id=(\d+)[^"\']*\\1/', $value, $url_match )
1179 || ! preg_match( '/rel=["\'][^"\']*wp-att-(\d+)/', $value, $rel_match ) ) {
1180 continue;
1181 }
1182
1183 $quote = $url_match[1]; // The quote (single or double).
1184 $url_id = (int) $url_match[2];
1185 $rel_id = (int) $rel_match[1];
1186
1187 if ( ! $url_id || ! $rel_id || $url_id !== $rel_id || ! str_contains( $url_match[0], $site_url ) ) {
1188 continue;
1189 }
1190
1191 $link = $link_matches[0][ $key ];
1192 $replace = str_replace( $url_match[0], 'href=' . $quote . get_attachment_link( $url_id ) . $quote, $link );
1193
1194 $content = str_replace( $link, $replace, $content );
1195 }
1196
1197 if ( $replace ) {
1198 $post['post_content'] = $content;
1199 // Escape data pulled from DB.
1200 $post = add_magic_quotes( $post );
1201
1202 return wp_update_post( $post );
1203 }
1204}
1205
1206/**
1207 * Returns all the possible statuses for a post type.
1208 *
1209 * @since 2.5.0
1210 *
1211 * @param string $type The post_type you want the statuses for. Default 'post'.
1212 * @return string[] An array of all the statuses for the supplied post type.
1213 */
1214function get_available_post_statuses( $type = 'post' ) {
1215 $statuses = wp_count_posts( $type );
1216
1217 return array_keys( get_object_vars( $statuses ) );
1218}
1219
1220/**
1221 * Runs the query to fetch the posts for listing on the edit posts page.
1222 *
1223 * @since 2.5.0
1224 *
1225 * @param array|false $q Optional. Array of query variables to use to build the query.
1226 * Defaults to the `$_GET` superglobal.
1227 * @return string[] An array of all the statuses for the queried post type.
1228 */
1229function wp_edit_posts_query( $q = false ) {
1230 if ( false === $q ) {
1231 $q = $_GET;
1232 }
1233
1234 $q['m'] = isset( $q['m'] ) ? (int) $q['m'] : 0;
1235 $q['cat'] = isset( $q['cat'] ) ? (int) $q['cat'] : 0;
1236
1237 $post_statuses = get_post_stati();
1238
1239 if ( isset( $q['post_type'] ) && in_array( $q['post_type'], get_post_types(), true ) ) {
1240 $post_type = $q['post_type'];
1241 } else {
1242 $post_type = 'post';
1243 }
1244
1245 $avail_post_stati = get_available_post_statuses( $post_type );
1246 $post_status = '';
1247 $perm = '';
1248
1249 if ( isset( $q['post_status'] ) && in_array( $q['post_status'], $post_statuses, true ) ) {
1250 $post_status = $q['post_status'];
1251 $perm = 'readable';
1252 }
1253
1254 $orderby = '';
1255
1256 if ( isset( $q['orderby'] ) ) {
1257 $orderby = $q['orderby'];
1258 } elseif ( isset( $q['post_status'] ) && in_array( $q['post_status'], array( 'pending', 'draft' ), true ) ) {
1259 $orderby = 'modified';
1260 }
1261
1262 $order = '';
1263
1264 if ( isset( $q['order'] ) ) {
1265 $order = $q['order'];
1266 } elseif ( isset( $q['post_status'] ) && 'pending' === $q['post_status'] ) {
1267 $order = 'ASC';
1268 }
1269
1270 $per_page = "edit_{$post_type}_per_page";
1271 $posts_per_page = (int) get_user_option( $per_page );
1272 if ( empty( $posts_per_page ) || $posts_per_page < 1 ) {
1273 $posts_per_page = 20;
1274 }
1275
1276 /**
1277 * Filters the number of items per page to show for a specific 'per_page' type.
1278 *
1279 * The dynamic portion of the hook name, `$post_type`, refers to the post type.
1280 *
1281 * Possible hook names include:
1282 *
1283 * - `edit_post_per_page`
1284 * - `edit_page_per_page`
1285 * - `edit_attachment_per_page`
1286 *
1287 * @since 3.0.0
1288 *
1289 * @param int $posts_per_page Number of posts to display per page for the given post
1290 * type. Default 20.
1291 */
1292 $posts_per_page = apply_filters( "edit_{$post_type}_per_page", $posts_per_page );
1293
1294 /**
1295 * Filters the number of posts displayed per page when specifically listing "posts".
1296 *
1297 * @since 2.8.0
1298 *
1299 * @param int $posts_per_page Number of posts to be displayed. Default 20.
1300 * @param string $post_type The post type.
1301 */
1302 $posts_per_page = apply_filters( 'edit_posts_per_page', $posts_per_page, $post_type );
1303
1304 $query = compact( 'post_type', 'post_status', 'perm', 'order', 'orderby', 'posts_per_page' );
1305
1306 // Hierarchical types require special args.
1307 if ( is_post_type_hierarchical( $post_type ) && empty( $orderby ) ) {
1308 $query['orderby'] = 'menu_order title';
1309 $query['order'] = 'asc';
1310 $query['posts_per_page'] = -1;
1311 $query['posts_per_archive_page'] = -1;
1312 $query['fields'] = 'id=>parent';
1313 }
1314
1315 if ( ! empty( $q['show_sticky'] ) ) {
1316 $query['post__in'] = (array) get_option( 'sticky_posts' );
1317 }
1318
1319 wp( $query );
1320
1321 return $avail_post_stati;
1322}
1323
1324/**
1325 * Returns the query variables for the current attachments request.
1326 *
1327 * @since 4.2.0
1328 *
1329 * @param array|false $q Optional. Array of query variables to use to build the query.
1330 * Defaults to the `$_GET` superglobal.
1331 * @return array The parsed query vars.
1332 */
1333function wp_edit_attachments_query_vars( $q = false ) {
1334 if ( false === $q ) {
1335 $q = $_GET;
1336 }
1337 $q['m'] = isset( $q['m'] ) ? (int) $q['m'] : 0;
1338 $q['cat'] = isset( $q['cat'] ) ? (int) $q['cat'] : 0;
1339 $q['post_type'] = 'attachment';
1340 $post_type = get_post_type_object( 'attachment' );
1341 $states = 'inherit';
1342 if ( current_user_can( $post_type->cap->read_private_posts ) ) {
1343 $states .= ',private';
1344 }
1345
1346 $q['post_status'] = isset( $q['status'] ) && 'trash' === $q['status'] ? 'trash' : $states;
1347 $q['post_status'] = isset( $q['attachment-filter'] ) && 'trash' === $q['attachment-filter'] ? 'trash' : $states;
1348
1349 $media_per_page = (int) get_user_option( 'upload_per_page' );
1350 if ( empty( $media_per_page ) || $media_per_page < 1 ) {
1351 $media_per_page = 20;
1352 }
1353
1354 /**
1355 * Filters the number of items to list per page when listing media items.
1356 *
1357 * @since 2.9.0
1358 *
1359 * @param int $media_per_page Number of media to list. Default 20.
1360 */
1361 $q['posts_per_page'] = apply_filters( 'upload_per_page', $media_per_page );
1362
1363 $post_mime_types = get_post_mime_types();
1364 if ( isset( $q['post_mime_type'] ) && ! array_intersect( (array) $q['post_mime_type'], array_keys( $post_mime_types ) ) ) {
1365 unset( $q['post_mime_type'] );
1366 }
1367
1368 foreach ( array_keys( $post_mime_types ) as $type ) {
1369 if ( isset( $q['attachment-filter'] ) && "post_mime_type:$type" === $q['attachment-filter'] ) {
1370 $q['post_mime_type'] = $type;
1371 break;
1372 }
1373 }
1374
1375 if ( isset( $q['detached'] ) || ( isset( $q['attachment-filter'] ) && 'detached' === $q['attachment-filter'] ) ) {
1376 $q['post_parent'] = 0;
1377 }
1378
1379 if ( isset( $q['mine'] ) || ( isset( $q['attachment-filter'] ) && 'mine' === $q['attachment-filter'] ) ) {
1380 $q['author'] = get_current_user_id();
1381 }
1382
1383 // Filter query clauses to include filenames.
1384 if ( isset( $q['s'] ) ) {
1385 add_filter( 'wp_allow_query_attachment_by_filename', '__return_true' );
1386 }
1387
1388 return $q;
1389}
1390
1391/**
1392 * Executes a query for attachments. An array of WP_Query arguments
1393 * can be passed in, which will override the arguments set by this function.
1394 *
1395 * @since 2.5.0
1396 *
1397 * @param array|false $q Optional. Array of query variables to use to build the query.
1398 * Defaults to the `$_GET` superglobal.
1399 * @return array {
1400 * Array containing the post mime types and available post mime types.
1401 *
1402 * @type array[] $post_mime_types Post mime types.
1403 * @type string[] $avail_post_mime_types Available post mime types.
1404 * }
1405 */
1406function wp_edit_attachments_query( $q = false ) {
1407 wp( wp_edit_attachments_query_vars( $q ) );
1408
1409 $post_mime_types = get_post_mime_types();
1410 $avail_post_mime_types = get_available_post_mime_types( 'attachment' );
1411
1412 return array( $post_mime_types, $avail_post_mime_types );
1413}
1414
1415/**
1416 * Returns the list of classes to be used by a meta box.
1417 *
1418 * @since 2.5.0
1419 *
1420 * @param string $box_id Meta box ID (used in the 'id' attribute for the meta box).
1421 * @param string $screen_id The screen on which the meta box is shown.
1422 * @return string Space-separated string of class names.
1423 */
1424function postbox_classes( $box_id, $screen_id ) {
1425 if ( isset( $_GET['edit'] ) && $_GET['edit'] === $box_id ) {
1426 $classes = array( '' );
1427 } elseif ( get_user_option( 'closedpostboxes_' . $screen_id ) ) {
1428 $closed = get_user_option( 'closedpostboxes_' . $screen_id );
1429 if ( ! is_array( $closed ) ) {
1430 $classes = array( '' );
1431 } else {
1432 $classes = in_array( $box_id, $closed, true ) ? array( 'closed' ) : array( '' );
1433 }
1434 } else {
1435 $classes = array( '' );
1436 }
1437
1438 /**
1439 * Filters the postbox classes for a specific screen and box ID combo.
1440 *
1441 * The dynamic portions of the hook name, `$screen_id` and `$box_id`, refer to
1442 * the screen ID and meta box ID, respectively.
1443 *
1444 * @since 3.2.0
1445 *
1446 * @param string[] $classes An array of postbox classes.
1447 */
1448 $classes = apply_filters( "postbox_classes_{$screen_id}_{$box_id}", $classes );
1449
1450 return implode( ' ', $classes );
1451}
1452
1453/**
1454 * Returns a sample permalink based on the post name.
1455 *
1456 * @since 2.5.0
1457 *
1458 * @param int|WP_Post $post Post ID or post object.
1459 * @param string|null $title Optional. Title to override the post's current title
1460 * when generating the post name. Default null.
1461 * @param string|null $name Optional. Name to override the post name. Default null.
1462 * @return array {
1463 * Array containing the sample permalink with placeholder for the post name, and the post name.
1464 *
1465 * @type string $0 The permalink with placeholder for the post name.
1466 * @type string $1 The post name.
1467 * }
1468 */
1469function get_sample_permalink( $post, $title = null, $name = null ) {
1470 $post = get_post( $post );
1471
1472 if ( ! $post ) {
1473 return array( '', '' );
1474 }
1475
1476 $ptype = get_post_type_object( $post->post_type );
1477
1478 $original_status = $post->post_status;
1479 $original_date = $post->post_date;
1480 $original_name = $post->post_name;
1481 $original_filter = $post->filter;
1482
1483 // Hack: get_permalink() would return plain permalink for drafts, so we will fake that our post is published.
1484 if ( in_array( $post->post_status, array( 'auto-draft', 'draft', 'pending', 'future' ), true ) ) {
1485 $post->post_status = 'publish';
1486 $post->post_name = sanitize_title( $post->post_name ? $post->post_name : $post->post_title, $post->ID );
1487 }
1488
1489 /*
1490 * If the user wants to set a new name -- override the current one.
1491 * Note: if empty name is supplied -- use the title instead, see #6072.
1492 */
1493 if ( ! is_null( $name ) ) {
1494 $post->post_name = sanitize_title( $name ? $name : $title, $post->ID );
1495 }
1496
1497 $post->post_name = wp_unique_post_slug( $post->post_name, $post->ID, $post->post_status, $post->post_type, $post->post_parent );
1498
1499 $post->filter = 'sample';
1500
1501 $permalink = get_permalink( $post, true );
1502
1503 // Replace custom post_type token with generic pagename token for ease of use.
1504 $permalink = str_replace( "%$post->post_type%", '%pagename%', $permalink );
1505
1506 // Handle page hierarchy.
1507 if ( $ptype->hierarchical ) {
1508 $uri = get_page_uri( $post );
1509 if ( $uri ) {
1510 $uri = untrailingslashit( $uri );
1511 $uri = strrev( stristr( strrev( $uri ), '/' ) );
1512 $uri = untrailingslashit( $uri );
1513 }
1514
1515 /** This filter is documented in wp-admin/edit-tag-form.php */
1516 $uri = apply_filters( 'editable_slug', $uri, $post );
1517 if ( ! empty( $uri ) ) {
1518 $uri .= '/';
1519 }
1520 $permalink = str_replace( '%pagename%', "{$uri}%pagename%", $permalink );
1521 }
1522
1523 /** This filter is documented in wp-admin/edit-tag-form.php */
1524 $permalink = array( $permalink, apply_filters( 'editable_slug', $post->post_name, $post ) );
1525 $post->post_status = $original_status;
1526 $post->post_date = $original_date;
1527 $post->post_name = $original_name;
1528 $post->filter = $original_filter;
1529
1530 /**
1531 * Filters the sample permalink.
1532 *
1533 * @since 4.4.0
1534 *
1535 * @param array $permalink {
1536 * Array containing the sample permalink with placeholder for the post name, and the post name.
1537 *
1538 * @type string $0 The permalink with placeholder for the post name.
1539 * @type string $1 The post name.
1540 * }
1541 * @param int $post_id Post ID.
1542 * @param string $title Post title.
1543 * @param string $name Post name (slug).
1544 * @param WP_Post $post Post object.
1545 */
1546 return apply_filters( 'get_sample_permalink', $permalink, $post->ID, $title, $name, $post );
1547}
1548
1549/**
1550 * Returns the HTML of the sample permalink slug editor.
1551 *
1552 * @since 2.5.0
1553 *
1554 * @param int|WP_Post $post Post ID or post object.
1555 * @param string|null $new_title Optional. New title. Default null.
1556 * @param string|null $new_slug Optional. New slug. Default null.
1557 * @return string The HTML of the sample permalink slug editor.
1558 */
1559function get_sample_permalink_html( $post, $new_title = null, $new_slug = null ) {
1560 $post = get_post( $post );
1561
1562 if ( ! $post ) {
1563 return '';
1564 }
1565
1566 list($permalink, $post_name) = get_sample_permalink( $post->ID, $new_title, $new_slug );
1567
1568 $view_link = false;
1569 $preview_target = '';
1570
1571 if ( current_user_can( 'read_post', $post->ID ) ) {
1572 if ( 'draft' === $post->post_status || empty( $post->post_name ) ) {
1573 $view_link = get_preview_post_link( $post );
1574 $preview_target = " target='wp-preview-{$post->ID}'";
1575 } else {
1576 if ( 'publish' === $post->post_status || 'attachment' === $post->post_type ) {
1577 $view_link = get_permalink( $post );
1578 } else {
1579 // Allow non-published (private, future) to be viewed at a pretty permalink, in case $post->post_name is set.
1580 $view_link = str_replace( array( '%pagename%', '%postname%' ), $post->post_name, $permalink );
1581 }
1582 }
1583 }
1584
1585 // Permalinks without a post/page name placeholder don't have anything to edit.
1586 if ( ! str_contains( $permalink, '%postname%' ) && ! str_contains( $permalink, '%pagename%' ) ) {
1587 $return = '<strong>' . __( 'Permalink:' ) . "</strong>\n";
1588
1589 if ( false !== $view_link ) {
1590 $display_link = urldecode( $view_link );
1591 $return .= '<a id="sample-permalink" href="' . esc_url( $view_link ) . '"' . $preview_target . '>' . esc_html( $display_link ) . "</a>\n";
1592 } else {
1593 $return .= '<span id="sample-permalink">' . $permalink . "</span>\n";
1594 }
1595
1596 // Encourage a pretty permalink setting.
1597 if ( ! get_option( 'permalink_structure' ) && current_user_can( 'manage_options' )
1598 && ! ( 'page' === get_option( 'show_on_front' ) && (int) get_option( 'page_on_front' ) === $post->ID )
1599 ) {
1600 $return .= '<span id="change-permalinks"><a href="options-permalink.php" class="button button-small">' . __( 'Change Permalink Structure' ) . "</a></span>\n";
1601 }
1602 } else {
1603 if ( mb_strlen( $post_name ) > 34 ) {
1604 $post_name_abridged = mb_substr( $post_name, 0, 16 ) . '…' . mb_substr( $post_name, -16 );
1605 } else {
1606 $post_name_abridged = $post_name;
1607 }
1608
1609 $post_name_html = '<span id="editable-post-name">' . esc_html( $post_name_abridged ) . '</span>';
1610 $display_link = str_replace( array( '%pagename%', '%postname%' ), $post_name_html, esc_html( urldecode( $permalink ) ) );
1611
1612 $return = '<strong>' . __( 'Permalink:' ) . "</strong>\n";
1613 $return .= '<span id="sample-permalink"><a href="' . esc_url( $view_link ) . '"' . $preview_target . '>' . $display_link . "</a></span>\n";
1614 $return .= '‎'; // Fix bi-directional text display defect in RTL languages.
1615 $return .= '<span id="edit-slug-buttons"><button type="button" class="edit-slug button button-small hide-if-no-js" aria-label="' . __( 'Edit permalink' ) . '">' . __( 'Edit' ) . "</button></span>\n";
1616 $return .= '<span id="editable-post-name-full">' . esc_html( $post_name ) . "</span>\n";
1617 }
1618
1619 /**
1620 * Filters the sample permalink HTML markup.
1621 *
1622 * @since 2.9.0
1623 * @since 4.4.0 Added `$post` parameter.
1624 *
1625 * @param string $return Sample permalink HTML markup.
1626 * @param int $post_id Post ID.
1627 * @param string|null $new_title New sample permalink title.
1628 * @param string|null $new_slug New sample permalink slug.
1629 * @param WP_Post $post Post object.
1630 */
1631 $return = apply_filters( 'get_sample_permalink_html', $return, $post->ID, $new_title, $new_slug, $post );
1632
1633 return $return;
1634}
1635
1636/**
1637 * Returns HTML for the post thumbnail meta box.
1638 *
1639 * @since 2.9.0
1640 *
1641 * @param int|null $thumbnail_id Optional. Thumbnail attachment ID. Default null.
1642 * @param int|WP_Post|null $post Optional. The post ID or object associated
1643 * with the thumbnail. Defaults to global $post.
1644 * @return string The post thumbnail HTML.
1645 */
1646function _wp_post_thumbnail_html( $thumbnail_id = null, $post = null ) {
1647 $_wp_additional_image_sizes = wp_get_additional_image_sizes();
1648
1649 $post = get_post( $post );
1650 $post_type_object = get_post_type_object( $post->post_type );
1651 $set_thumbnail_link = '<p class="hide-if-no-js"><a href="%s" id="set-post-thumbnail"%s class="thickbox">%s</a></p>';
1652 $upload_iframe_src = get_upload_iframe_src( 'image', $post->ID );
1653
1654 $content = sprintf(
1655 $set_thumbnail_link,
1656 esc_url( $upload_iframe_src ),
1657 '', // Empty when there's no featured image set, `aria-describedby` attribute otherwise.
1658 esc_html( $post_type_object->labels->set_featured_image )
1659 );
1660
1661 if ( $thumbnail_id && get_post( $thumbnail_id ) ) {
1662 $size = isset( $_wp_additional_image_sizes['post-thumbnail'] ) ? 'post-thumbnail' : array( 266, 266 );
1663
1664 /**
1665 * Filters the size used to display the post thumbnail image in the 'Featured image' meta box.
1666 *
1667 * Note: When a theme adds 'post-thumbnail' support, a special 'post-thumbnail'
1668 * image size is registered, which differs from the 'thumbnail' image size
1669 * managed via the Settings > Media screen.
1670 *
1671 * @since 4.4.0
1672 *
1673 * @param string|int[] $size Requested image size. Can be any registered image size name, or
1674 * an array of width and height values in pixels (in that order).
1675 * @param int $thumbnail_id Post thumbnail attachment ID.
1676 * @param WP_Post $post The post object associated with the thumbnail.
1677 */
1678 $size = apply_filters( 'admin_post_thumbnail_size', $size, $thumbnail_id, $post );
1679
1680 $thumbnail_html = wp_get_attachment_image( $thumbnail_id, $size );
1681
1682 if ( ! empty( $thumbnail_html ) ) {
1683 $content = sprintf(
1684 $set_thumbnail_link,
1685 esc_url( $upload_iframe_src ),
1686 ' aria-describedby="set-post-thumbnail-desc"',
1687 $thumbnail_html
1688 );
1689 $content .= '<p class="hide-if-no-js howto" id="set-post-thumbnail-desc">' . __( 'Click the image to edit or update' ) . '</p>';
1690 $content .= '<p class="hide-if-no-js"><a href="#" id="remove-post-thumbnail">' . esc_html( $post_type_object->labels->remove_featured_image ) . '</a></p>';
1691 }
1692 }
1693
1694 $content .= '<input type="hidden" id="_thumbnail_id" name="_thumbnail_id" value="' . esc_attr( $thumbnail_id ? $thumbnail_id : '-1' ) . '" />';
1695
1696 /**
1697 * Filters the admin post thumbnail HTML markup to return.
1698 *
1699 * @since 2.9.0
1700 * @since 3.5.0 Added the `$post_id` parameter.
1701 * @since 4.6.0 Added the `$thumbnail_id` parameter.
1702 *
1703 * @param string $content Admin post thumbnail HTML markup.
1704 * @param int $post_id Post ID.
1705 * @param int|null $thumbnail_id Thumbnail attachment ID, or null if there isn't one.
1706 */
1707 return apply_filters( 'admin_post_thumbnail_html', $content, $post->ID, $thumbnail_id );
1708}
1709
1710/**
1711 * Determines whether the post is currently being edited by another user.
1712 *
1713 * @since 2.5.0
1714 *
1715 * @param int|WP_Post $post ID or object of the post to check for editing.
1716 * @return int|false ID of the user with lock. False if the post does not exist, post is not locked,
1717 * the user with lock does not exist, or the post is locked by current user.
1718 */
1719function wp_check_post_lock( $post ) {
1720 $post = get_post( $post );
1721
1722 if ( ! $post ) {
1723 return false;
1724 }
1725
1726 $lock = get_post_meta( $post->ID, '_edit_lock', true );
1727
1728 if ( ! $lock ) {
1729 return false;
1730 }
1731
1732 $lock = explode( ':', $lock );
1733 $time = $lock[0];
1734 $user = isset( $lock[1] ) ? (int) $lock[1] : (int) get_post_meta( $post->ID, '_edit_last', true );
1735
1736 if ( ! get_userdata( $user ) ) {
1737 return false;
1738 }
1739
1740 /** This filter is documented in wp-admin/includes/ajax-actions.php */
1741 $time_window = apply_filters( 'wp_check_post_lock_window', 150 );
1742
1743 if ( $time && $time > time() - $time_window && get_current_user_id() !== $user ) {
1744 return $user;
1745 }
1746
1747 return false;
1748}
1749
1750/**
1751 * Marks the post as currently being edited by the current user.
1752 *
1753 * @since 2.5.0
1754 *
1755 * @param int|WP_Post $post ID or object of the post being edited.
1756 * @return array|false {
1757 * Array of the lock time and user ID. False if the post does not exist, or there
1758 * is no current user.
1759 *
1760 * @type int $0 The current time as a Unix timestamp.
1761 * @type int $1 The ID of the current user.
1762 * }
1763 */
1764function wp_set_post_lock( $post ) {
1765 $post = get_post( $post );
1766
1767 if ( ! $post ) {
1768 return false;
1769 }
1770
1771 $user_id = get_current_user_id();
1772
1773 if ( 0 === $user_id ) {
1774 return false;
1775 }
1776
1777 $now = time();
1778 $lock = "$now:$user_id";
1779
1780 update_post_meta( $post->ID, '_edit_lock', $lock );
1781
1782 return array( $now, $user_id );
1783}
1784
1785/**
1786 * Outputs the HTML for the notice to say that someone else is editing or has taken over editing of this post.
1787 *
1788 * @since 2.8.5
1789 */
1790function _admin_notice_post_locked() {
1791 $post = get_post();
1792
1793 if ( ! $post ) {
1794 return;
1795 }
1796
1797 $user = null;
1798 $user_id = wp_check_post_lock( $post->ID );
1799
1800 if ( $user_id ) {
1801 $user = get_userdata( $user_id );
1802 }
1803
1804 if ( $user ) {
1805 /**
1806 * Filters whether to show the post locked dialog.
1807 *
1808 * Returning false from the filter will prevent the dialog from being displayed.
1809 *
1810 * @since 3.6.0
1811 *
1812 * @param bool $display Whether to display the dialog. Default true.
1813 * @param WP_Post $post Post object.
1814 * @param WP_User $user The user with the lock for the post.
1815 */
1816 if ( ! apply_filters( 'show_post_locked_dialog', true, $post, $user ) ) {
1817 return;
1818 }
1819
1820 $locked = true;
1821 } else {
1822 $locked = false;
1823 }
1824
1825 $sendback = wp_get_referer();
1826 $sendback_text = __( 'Go back' );
1827
1828 if ( ! $locked || ! $sendback || str_contains( $sendback, 'post.php' ) || str_contains( $sendback, 'post-new.php' ) ) {
1829 $sendback = admin_url( 'edit.php' );
1830
1831 if ( 'post' !== $post->post_type ) {
1832 $sendback = add_query_arg( 'post_type', $post->post_type, $sendback );
1833 }
1834
1835 $post_type_object = get_post_type_object( $post->post_type );
1836
1837 if ( $post_type_object ) {
1838 $sendback_text = $post_type_object->labels->all_items;
1839 }
1840 }
1841
1842 $hidden = $locked ? '' : ' hidden';
1843
1844 ?>
1845 <div id="post-lock-dialog" class="notification-dialog-wrap<?php echo $hidden; ?>">
1846 <div class="notification-dialog-background"></div>
1847 <div class="notification-dialog">
1848 <?php
1849
1850 if ( $locked ) {
1851 $query_args = array();
1852 if ( get_post_type_object( $post->post_type )->public ) {
1853 if ( 'publish' === $post->post_status || $user->ID !== (int) $post->post_author ) {
1854 // Latest content is in autosave.
1855 $nonce = wp_create_nonce( 'post_preview_' . $post->ID );
1856 $query_args['preview_id'] = $post->ID;
1857 $query_args['preview_nonce'] = $nonce;
1858 }
1859 }
1860
1861 $preview_link = get_preview_post_link( $post->ID, $query_args );
1862
1863 /**
1864 * Filters whether to allow the post lock to be overridden.
1865 *
1866 * Returning false from the filter will disable the ability
1867 * to override the post lock.
1868 *
1869 * @since 3.6.0
1870 *
1871 * @param bool $override Whether to allow the post lock to be overridden. Default true.
1872 * @param WP_Post $post Post object.
1873 * @param WP_User $user The user with the lock for the post.
1874 */
1875 $override = apply_filters( 'override_post_lock', true, $post, $user );
1876 $tab_last = $override ? '' : ' wp-tab-last';
1877
1878 ?>
1879 <div class="post-locked-message">
1880 <div class="post-locked-avatar"><?php echo get_avatar( $user->ID, 64 ); ?></div>
1881 <p class="currently-editing wp-tab-first" tabindex="0">
1882 <?php
1883 if ( $override ) {
1884 /* translators: %s: User's display name. */
1885 printf( __( '%s is currently editing this post. Do you want to take over?' ), esc_html( $user->display_name ) );
1886 } else {
1887 /* translators: %s: User's display name. */
1888 printf( __( '%s is currently editing this post.' ), esc_html( $user->display_name ) );
1889 }
1890 ?>
1891 </p>
1892 <?php
1893 /**
1894 * Fires inside the post locked dialog before the buttons are displayed.
1895 *
1896 * @since 3.6.0
1897 * @since 5.4.0 The $user parameter was added.
1898 *
1899 * @param WP_Post $post Post object.
1900 * @param WP_User $user The user with the lock for the post.
1901 */
1902 do_action( 'post_locked_dialog', $post, $user );
1903 ?>
1904 <p>
1905 <a class="button" href="<?php echo esc_url( $sendback ); ?>"><?php echo $sendback_text; ?></a>
1906 <?php if ( $preview_link ) { ?>
1907 <a class="button<?php echo $tab_last; ?>" href="<?php echo esc_url( $preview_link ); ?>"><?php _e( 'Preview' ); ?></a>
1908 <?php
1909 }
1910
1911 // Allow plugins to prevent some users overriding the post lock.
1912 if ( $override ) {
1913 ?>
1914 <a class="button button-primary wp-tab-last" href="<?php echo esc_url( add_query_arg( 'get-post-lock', '1', wp_nonce_url( get_edit_post_link( $post->ID, 'url' ), 'lock-post_' . $post->ID ) ) ); ?>"><?php _e( 'Take over' ); ?></a>
1915 <?php
1916 }
1917
1918 ?>
1919 </p>
1920 </div>
1921 <?php
1922 } else {
1923 ?>
1924 <div class="post-taken-over">
1925 <div class="post-locked-avatar"></div>
1926 <p class="wp-tab-first" tabindex="0">
1927 <span class="currently-editing"></span><br />
1928 <span class="locked-saving hidden"><img src="<?php echo esc_url( admin_url( 'images/spinner-2x.gif' ) ); ?>" width="16" height="16" alt="" /> <?php _e( 'Saving revision…' ); ?></span>
1929 <span class="locked-saved hidden"><?php _e( 'Your latest changes were saved as a revision.' ); ?></span>
1930 </p>
1931 <?php
1932 /**
1933 * Fires inside the dialog displayed when a user has lost the post lock.
1934 *
1935 * @since 3.6.0
1936 *
1937 * @param WP_Post $post Post object.
1938 */
1939 do_action( 'post_lock_lost_dialog', $post );
1940 ?>
1941 <p><a class="button button-primary wp-tab-last" href="<?php echo esc_url( $sendback ); ?>"><?php echo $sendback_text; ?></a></p>
1942 </div>
1943 <?php
1944 }
1945
1946 ?>
1947 </div>
1948 </div>
1949 <?php
1950}
1951
1952/**
1953 * Creates autosave data for the specified post from `$_POST` data.
1954 *
1955 * @since 2.6.0
1956 *
1957 * @param array|int $post_data Associative array containing the post data, or integer post ID.
1958 * If a numeric post ID is provided, will use the `$_POST` superglobal.
1959 * @return int|WP_Error The autosave revision ID. WP_Error or 0 on error.
1960 */
1961function wp_create_post_autosave( $post_data ) {
1962 if ( is_numeric( $post_data ) ) {
1963 $post_id = $post_data;
1964 $post_data = $_POST;
1965 } else {
1966 $post_id = (int) $post_data['post_ID'];
1967 }
1968
1969 $post_data = _wp_translate_postdata( true, $post_data );
1970 if ( is_wp_error( $post_data ) ) {
1971 return $post_data;
1972 }
1973 $post_data = _wp_get_allowed_postdata( $post_data );
1974
1975 $post_author = get_current_user_id();
1976
1977 // Store one autosave per author. If there is already an autosave, overwrite it.
1978 $old_autosave = wp_get_post_autosave( $post_id, $post_author );
1979 if ( $old_autosave ) {
1980 $new_autosave = _wp_post_revision_data( $post_data, true );
1981 $new_autosave['ID'] = $old_autosave->ID;
1982 $new_autosave['post_author'] = $post_author;
1983
1984 $post = get_post( $post_id );
1985
1986 // If the new autosave has the same content as the post, delete the autosave.
1987 $autosave_is_different = false;
1988 foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
1989 if ( normalize_whitespace( $new_autosave[ $field ] ) !== normalize_whitespace( $post->$field ) ) {
1990 $autosave_is_different = true;
1991 break;
1992 }
1993 }
1994
1995 if ( ! $autosave_is_different ) {
1996 wp_delete_post_revision( $old_autosave->ID );
1997 return 0;
1998 }
1999
2000 /**
2001 * Fires before an autosave is stored.
2002 *
2003 * @since 4.1.0
2004 * @since 6.4.0 The `$is_update` parameter was added to indicate if the autosave is being updated or was newly created.
2005 *
2006 * @param array $new_autosave Post array - the autosave that is about to be saved.
2007 * @param bool $is_update Whether this is an existing autosave.
2008 */
2009 do_action( 'wp_creating_autosave', $new_autosave, true );
2010 return wp_update_post( $new_autosave );
2011 }
2012
2013 // _wp_put_post_revision() expects unescaped.
2014 $post_data = wp_unslash( $post_data );
2015
2016 // Otherwise create the new autosave as a special post revision.
2017 $revision = _wp_put_post_revision( $post_data, true );
2018
2019 if ( ! is_wp_error( $revision ) && 0 !== $revision ) {
2020
2021 /** This action is documented in wp-admin/includes/post.php */
2022 do_action( 'wp_creating_autosave', get_post( $revision, ARRAY_A ), false );
2023 }
2024
2025 return $revision;
2026}
2027
2028/**
2029 * Autosaves the revisioned meta fields.
2030 *
2031 * Iterates through the revisioned meta fields and checks each to see if they are set,
2032 * and have a changed value. If so, the meta value is saved and attached to the autosave.
2033 *
2034 * @since 6.4.0
2035 *
2036 * @param array $new_autosave The new post data being autosaved.
2037 */
2038function wp_autosave_post_revisioned_meta_fields( $new_autosave ) {
2039 /*
2040 * The post data arrives as either $_POST['data']['wp_autosave'] or the $_POST
2041 * itself. This sets $posted_data to the correct variable.
2042 *
2043 * Ignoring sanitization to avoid altering meta. Ignoring the nonce check because
2044 * this is hooked on inner core hooks where a valid nonce was already checked.
2045 */
2046 $posted_data = isset( $_POST['data']['wp_autosave'] ) ? $_POST['data']['wp_autosave'] : $_POST;
2047
2048 $post_type = get_post_type( $new_autosave['post_parent'] );
2049
2050 /*
2051 * Go through the revisioned meta keys and save them as part of the autosave,
2052 * if the meta key is part of the posted data, the meta value is not blank,
2053 * and the meta value has changes from the last autosaved value.
2054 */
2055 foreach ( wp_post_revision_meta_keys( $post_type ) as $meta_key ) {
2056
2057 if ( isset( $posted_data[ $meta_key ] )
2058 && get_post_meta( $new_autosave['ID'], $meta_key, true ) !== wp_unslash( $posted_data[ $meta_key ] )
2059 ) {
2060 /*
2061 * Use the underlying delete_metadata() and add_metadata() functions
2062 * vs delete_post_meta() and add_post_meta() to make sure we're working
2063 * with the actual revision meta.
2064 */
2065 delete_metadata( 'post', $new_autosave['ID'], $meta_key );
2066
2067 // One last check to ensure meta value is not empty.
2068 if ( ! empty( $posted_data[ $meta_key ] ) ) {
2069 // Add the revisions meta data to the autosave.
2070 add_metadata( 'post', $new_autosave['ID'], $meta_key, $posted_data[ $meta_key ] );
2071 }
2072 }
2073 }
2074}
2075
2076/**
2077 * Saves a draft or manually autosaves for the purpose of showing a post preview.
2078 *
2079 * @since 2.7.0
2080 *
2081 * @return string URL to redirect to show the preview.
2082 */
2083function post_preview() {
2084
2085 $post_id = (int) $_POST['post_ID'];
2086 $_POST['ID'] = $post_id;
2087
2088 $post = get_post( $post_id );
2089
2090 if ( ! $post ) {
2091 wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
2092 }
2093
2094 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
2095 wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
2096 }
2097
2098 $is_autosave = false;
2099
2100 if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() === (int) $post->post_author
2101 && ( 'draft' === $post->post_status || 'auto-draft' === $post->post_status )
2102 ) {
2103 $saved_post_id = edit_post();
2104 } else {
2105 $is_autosave = true;
2106
2107 if ( isset( $_POST['post_status'] ) && 'auto-draft' === $_POST['post_status'] ) {
2108 $_POST['post_status'] = 'draft';
2109 }
2110
2111 $saved_post_id = wp_create_post_autosave( $post->ID );
2112 }
2113
2114 if ( is_wp_error( $saved_post_id ) ) {
2115 wp_die( $saved_post_id->get_error_message() );
2116 }
2117
2118 $query_args = array();
2119
2120 if ( $is_autosave && $saved_post_id ) {
2121 $query_args['preview_id'] = $post->ID;
2122 $query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $post->ID );
2123
2124 if ( isset( $_POST['post_format'] ) ) {
2125 $query_args['post_format'] = empty( $_POST['post_format'] ) ? 'standard' : sanitize_key( $_POST['post_format'] );
2126 }
2127
2128 if ( isset( $_POST['_thumbnail_id'] ) ) {
2129 $query_args['_thumbnail_id'] = ( (int) $_POST['_thumbnail_id'] <= 0 ) ? '-1' : (int) $_POST['_thumbnail_id'];
2130 }
2131 }
2132
2133 return get_preview_post_link( $post, $query_args );
2134}
2135
2136/**
2137 * Saves a post submitted with XHR.
2138 *
2139 * Intended for use with heartbeat and autosave.js
2140 *
2141 * @since 3.9.0
2142 *
2143 * @param array $post_data Associative array of the submitted post data.
2144 * @return mixed The value 0 or WP_Error on failure. The saved post ID on success.
2145 * The ID can be the draft post_id or the autosave revision post_id.
2146 */
2147function wp_autosave( $post_data ) {
2148 // Back-compat.
2149 if ( ! defined( 'DOING_AUTOSAVE' ) ) {
2150 define( 'DOING_AUTOSAVE', true );
2151 }
2152
2153 $post_id = (int) $post_data['post_id'];
2154 $post_data['ID'] = $post_id;
2155 $post_data['post_ID'] = $post_id;
2156
2157 if ( false === wp_verify_nonce( $post_data['_wpnonce'], 'update-post_' . $post_id ) ) {
2158 return new WP_Error( 'invalid_nonce', __( 'Error while saving.' ) );
2159 }
2160
2161 $post = get_post( $post_id );
2162
2163 if ( ! current_user_can( 'edit_post', $post->ID ) ) {
2164 return new WP_Error( 'edit_posts', __( 'Sorry, you are not allowed to edit this item.' ) );
2165 }
2166
2167 if ( 'auto-draft' === $post->post_status ) {
2168 $post_data['post_status'] = 'draft';
2169 }
2170
2171 if ( 'page' !== $post_data['post_type'] && ! empty( $post_data['catslist'] ) ) {
2172 $post_data['post_category'] = explode( ',', $post_data['catslist'] );
2173 }
2174
2175 if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() === (int) $post->post_author
2176 && ( 'auto-draft' === $post->post_status || 'draft' === $post->post_status )
2177 ) {
2178 // Drafts and auto-drafts are just overwritten by autosave for the same user if the post is not locked.
2179 return edit_post( wp_slash( $post_data ) );
2180 } else {
2181 /*
2182 * Non-drafts or other users' drafts are not overwritten.
2183 * The autosave is stored in a special post revision for each user.
2184 */
2185 return wp_create_post_autosave( wp_slash( $post_data ) );
2186 }
2187}
2188
2189/**
2190 * Redirects to previous page.
2191 *
2192 * @since 2.7.0
2193 *
2194 * @param int $post_id Optional. Post ID.
2195 */
2196function redirect_post( $post_id = 0 ) {
2197 if ( isset( $_POST['save'] ) || isset( $_POST['publish'] ) ) {
2198 $status = get_post_status( $post_id );
2199
2200 switch ( $status ) {
2201 case 'pending':
2202 $message = 8;
2203 break;
2204 case 'future':
2205 $message = 9;
2206 break;
2207 case 'draft':
2208 $message = 10;
2209 break;
2210 default:
2211 $message = isset( $_POST['publish'] ) ? 6 : 1;
2212 break;
2213 }
2214
2215 $location = add_query_arg( 'message', $message, get_edit_post_link( $post_id, 'url' ) );
2216 } elseif ( isset( $_POST['addmeta'] ) && $_POST['addmeta'] ) {
2217 $location = add_query_arg( 'message', 2, wp_get_referer() );
2218 $location = explode( '#', $location );
2219 $location = $location[0] . '#postcustom';
2220 } elseif ( isset( $_POST['deletemeta'] ) && $_POST['deletemeta'] ) {
2221 $location = add_query_arg( 'message', 3, wp_get_referer() );
2222 $location = explode( '#', $location );
2223 $location = $location[0] . '#postcustom';
2224 } else {
2225 $location = add_query_arg( 'message', 4, get_edit_post_link( $post_id, 'url' ) );
2226 }
2227
2228 /**
2229 * Filters the post redirect destination URL.
2230 *
2231 * @since 2.9.0
2232 *
2233 * @param string $location The destination URL.
2234 * @param int $post_id The post ID.
2235 */
2236 wp_redirect( apply_filters( 'redirect_post_location', $location, $post_id ) );
2237 exit;
2238}
2239
2240/**
2241 * Sanitizes POST values from a checkbox taxonomy metabox.
2242 *
2243 * @since 5.1.0
2244 *
2245 * @param string $taxonomy The taxonomy name.
2246 * @param array $terms Raw term data from the 'tax_input' field.
2247 * @return int[] Array of sanitized term IDs.
2248 */
2249function taxonomy_meta_box_sanitize_cb_checkboxes( $taxonomy, $terms ) {
2250 return array_map( 'intval', $terms );
2251}
2252
2253/**
2254 * Sanitizes POST values from an input taxonomy metabox.
2255 *
2256 * @since 5.1.0
2257 *
2258 * @param string $taxonomy The taxonomy name.
2259 * @param array|string $terms Raw term data from the 'tax_input' field.
2260 * @return array
2261 */
2262function taxonomy_meta_box_sanitize_cb_input( $taxonomy, $terms ) {
2263 /*
2264 * Assume that a 'tax_input' string is a comma-separated list of term names.
2265 * Some languages may use a character other than a comma as a delimiter, so we standardize on
2266 * commas before parsing the list.
2267 */
2268 if ( ! is_array( $terms ) ) {
2269 $comma = _x( ',', 'tag delimiter' );
2270 if ( ',' !== $comma ) {
2271 $terms = str_replace( $comma, ',', $terms );
2272 }
2273 $terms = explode( ',', trim( $terms, " \n\t\r\0\x0B," ) );
2274 }
2275
2276 $clean_terms = array();
2277 foreach ( $terms as $term ) {
2278 // Empty terms are invalid input.
2279 if ( empty( $term ) ) {
2280 continue;
2281 }
2282
2283 $_term = get_terms(
2284 array(
2285 'taxonomy' => $taxonomy,
2286 'name' => $term,
2287 'fields' => 'ids',
2288 'hide_empty' => false,
2289 )
2290 );
2291
2292 if ( ! empty( $_term ) ) {
2293 $clean_terms[] = (int) $_term[0];
2294 } else {
2295 // No existing term was found, so pass the string. A new term will be created.
2296 $clean_terms[] = $term;
2297 }
2298 }
2299
2300 return $clean_terms;
2301}
2302
2303/**
2304 * Prepares server-registered blocks for the block editor.
2305 *
2306 * Returns an associative array of registered block data keyed by block name. Data includes properties
2307 * of a block relevant for client registration.
2308 *
2309 * @since 5.0.0
2310 * @since 6.3.0 Added `selectors` field.
2311 * @since 6.4.0 Added `block_hooks` field.
2312 *
2313 * @return array An associative array of registered block data.
2314 */
2315function get_block_editor_server_block_settings() {
2316 $block_registry = WP_Block_Type_Registry::get_instance();
2317 $blocks = array();
2318 $fields_to_pick = array(
2319 'api_version' => 'apiVersion',
2320 'title' => 'title',
2321 'description' => 'description',
2322 'icon' => 'icon',
2323 'attributes' => 'attributes',
2324 'provides_context' => 'providesContext',
2325 'uses_context' => 'usesContext',
2326 'block_hooks' => 'blockHooks',
2327 'selectors' => 'selectors',
2328 'supports' => 'supports',
2329 'category' => 'category',
2330 'styles' => 'styles',
2331 'textdomain' => 'textdomain',
2332 'parent' => 'parent',
2333 'ancestor' => 'ancestor',
2334 'keywords' => 'keywords',
2335 'example' => 'example',
2336 'variations' => 'variations',
2337 'allowed_blocks' => 'allowedBlocks',
2338 );
2339
2340 foreach ( $block_registry->get_all_registered() as $block_name => $block_type ) {
2341 foreach ( $fields_to_pick as $field => $key ) {
2342 if ( ! isset( $block_type->{ $field } ) ) {
2343 continue;
2344 }
2345
2346 if ( ! isset( $blocks[ $block_name ] ) ) {
2347 $blocks[ $block_name ] = array();
2348 }
2349
2350 $blocks[ $block_name ][ $key ] = $block_type->{ $field };
2351 }
2352 }
2353
2354 return $blocks;
2355}
2356
2357/**
2358 * Renders the meta boxes forms.
2359 *
2360 * @since 5.0.0
2361 *
2362 * @global WP_Post $post Global post object.
2363 * @global WP_Screen $current_screen WordPress current screen object.
2364 * @global array $wp_meta_boxes Global meta box state.
2365 */
2366function the_block_editor_meta_boxes() {
2367 global $post, $current_screen, $wp_meta_boxes;
2368
2369 // Handle meta box state.
2370 $_original_meta_boxes = $wp_meta_boxes;
2371
2372 /**
2373 * Fires right before the meta boxes are rendered.
2374 *
2375 * This allows for the filtering of meta box data, that should already be
2376 * present by this point. Do not use as a means of adding meta box data.
2377 *
2378 * @since 5.0.0
2379 *
2380 * @param array $wp_meta_boxes Global meta box state.
2381 */
2382 $wp_meta_boxes = apply_filters( 'filter_block_editor_meta_boxes', $wp_meta_boxes );
2383 $locations = array( 'side', 'normal', 'advanced' );
2384 $priorities = array( 'high', 'sorted', 'core', 'default', 'low' );
2385
2386 // Render meta boxes.
2387 ?>
2388 <form class="metabox-base-form">
2389 <?php the_block_editor_meta_box_post_form_hidden_fields( $post ); ?>
2390 </form>
2391 <form id="toggle-custom-fields-form" method="post" action="<?php echo esc_url( admin_url( 'post.php' ) ); ?>">
2392 <?php wp_nonce_field( 'toggle-custom-fields', 'toggle-custom-fields-nonce' ); ?>
2393 <input type="hidden" name="action" value="toggle-custom-fields" />
2394 </form>
2395 <?php foreach ( $locations as $location ) : ?>
2396 <form class="metabox-location-<?php echo esc_attr( $location ); ?>" onsubmit="return false;">
2397 <div id="poststuff" class="sidebar-open">
2398 <div id="postbox-container-2" class="postbox-container">
2399 <?php
2400 do_meta_boxes(
2401 $current_screen,
2402 $location,
2403 $post
2404 );
2405 ?>
2406 </div>
2407 </div>
2408 </form>
2409 <?php endforeach; ?>
2410 <?php
2411
2412 $meta_boxes_per_location = array();
2413 foreach ( $locations as $location ) {
2414 $meta_boxes_per_location[ $location ] = array();
2415
2416 if ( ! isset( $wp_meta_boxes[ $current_screen->id ][ $location ] ) ) {
2417 continue;
2418 }
2419
2420 foreach ( $priorities as $priority ) {
2421 if ( ! isset( $wp_meta_boxes[ $current_screen->id ][ $location ][ $priority ] ) ) {
2422 continue;
2423 }
2424
2425 $meta_boxes = (array) $wp_meta_boxes[ $current_screen->id ][ $location ][ $priority ];
2426 foreach ( $meta_boxes as $meta_box ) {
2427 if ( false === $meta_box || ! $meta_box['title'] ) {
2428 continue;
2429 }
2430
2431 // If a meta box is just here for back compat, don't show it in the block editor.
2432 if ( isset( $meta_box['args']['__back_compat_meta_box'] ) && $meta_box['args']['__back_compat_meta_box'] ) {
2433 continue;
2434 }
2435
2436 $meta_boxes_per_location[ $location ][] = array(
2437 'id' => $meta_box['id'],
2438 'title' => $meta_box['title'],
2439 );
2440 }
2441 }
2442 }
2443
2444 /*
2445 * Sadly we probably cannot add this data directly into editor settings.
2446 *
2447 * Some meta boxes need `admin_head` to fire for meta box registry.
2448 * `admin_head` fires after `admin_enqueue_scripts`, which is where we create
2449 * our editor instance.
2450 */
2451 $script = 'window._wpLoadBlockEditor.then( function() {
2452 wp.data.dispatch( \'core/edit-post\' ).setAvailableMetaBoxesPerLocation( ' . wp_json_encode( $meta_boxes_per_location, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ) . ' );
2453 } );';
2454
2455 wp_add_inline_script( 'wp-edit-post', $script );
2456
2457 /*
2458 * When `wp-edit-post` is output in the `<head>`, the inline script needs to be manually printed.
2459 * Otherwise, meta boxes will not display because inline scripts for `wp-edit-post`
2460 * will not be printed again after this point.
2461 */
2462 if ( wp_script_is( 'wp-edit-post', 'done' ) ) {
2463 printf( "<script type='text/javascript'>\n%s\n</script>\n", trim( $script ) );
2464 }
2465
2466 /*
2467 * If the 'postcustom' meta box is enabled, then we need to perform
2468 * some extra initialization on it.
2469 */
2470 $enable_custom_fields = (bool) get_user_meta( get_current_user_id(), 'enable_custom_fields', true );
2471
2472 if ( $enable_custom_fields ) {
2473 $script = "( function( $ ) {
2474 if ( $('#postcustom').length ) {
2475 $( '#the-list' ).wpList( {
2476 addBefore: function( s ) {
2477 s.data += '&post_id=$post->ID';
2478 return s;
2479 },
2480 addAfter: function() {
2481 $('table#list-table').show();
2482 }
2483 });
2484 }
2485 } )( jQuery );";
2486 wp_enqueue_script( 'wp-lists' );
2487 wp_add_inline_script( 'wp-lists', $script );
2488 }
2489
2490 /*
2491 * Refresh nonces used by the meta box loader.
2492 *
2493 * The logic is very similar to that provided by post.js for the classic editor.
2494 */
2495 $script = "( function( $ ) {
2496 var check, timeout;
2497
2498 function schedule() {
2499 check = false;
2500 window.clearTimeout( timeout );
2501 timeout = window.setTimeout( function() { check = true; }, 300000 );
2502 }
2503
2504 $( document ).on( 'heartbeat-send.wp-refresh-nonces', function( e, data ) {
2505 var post_id, \$authCheck = $( '#wp-auth-check-wrap' );
2506
2507 if ( check || ( \$authCheck.length && ! \$authCheck.hasClass( 'hidden' ) ) ) {
2508 if ( ( post_id = $( '#post_ID' ).val() ) && $( '#_wpnonce' ).val() ) {
2509 data['wp-refresh-metabox-loader-nonces'] = {
2510 post_id: post_id
2511 };
2512 }
2513 }
2514 }).on( 'heartbeat-tick.wp-refresh-nonces', function( e, data ) {
2515 var nonces = data['wp-refresh-metabox-loader-nonces'];
2516
2517 if ( nonces ) {
2518 if ( nonces.replace ) {
2519 if ( nonces.replace.metabox_loader_nonce && window._wpMetaBoxUrl && wp.url ) {
2520 window._wpMetaBoxUrl= wp.url.addQueryArgs( window._wpMetaBoxUrl, { 'meta-box-loader-nonce': nonces.replace.metabox_loader_nonce } );
2521 }
2522
2523 if ( nonces.replace._wpnonce ) {
2524 $( '#_wpnonce' ).val( nonces.replace._wpnonce );
2525 }
2526 }
2527 }
2528 }).ready( function() {
2529 schedule();
2530 });
2531 } )( jQuery );";
2532 wp_add_inline_script( 'heartbeat', $script );
2533
2534 // Reset meta box data.
2535 $wp_meta_boxes = $_original_meta_boxes;
2536}
2537
2538/**
2539 * Renders the hidden form required for the meta boxes form.
2540 *
2541 * @since 5.0.0
2542 *
2543 * @param WP_Post $post Current post object.
2544 */
2545function the_block_editor_meta_box_post_form_hidden_fields( $post ) {
2546 $form_extra = '';
2547 if ( 'auto-draft' === $post->post_status ) {
2548 $form_extra .= "<input type='hidden' id='auto_draft' name='auto_draft' value='1' />";
2549 }
2550 $form_action = 'editpost';
2551 $nonce_action = 'update-post_' . $post->ID;
2552 $form_extra .= "<input type='hidden' id='post_ID' name='post_ID' value='" . esc_attr( $post->ID ) . "' />";
2553 $referer = wp_get_referer();
2554 $current_user = wp_get_current_user();
2555 $user_id = $current_user->ID;
2556 wp_nonce_field( $nonce_action );
2557
2558 /*
2559 * Some meta boxes hook into these actions to add hidden input fields in the classic post form.
2560 * For backward compatibility, we can capture the output from these actions,
2561 * and extract the hidden input fields.
2562 */
2563 ob_start();
2564 /** This filter is documented in wp-admin/edit-form-advanced.php */
2565 do_action( 'edit_form_after_title', $post );
2566 /** This filter is documented in wp-admin/edit-form-advanced.php */
2567 do_action( 'edit_form_advanced', $post );
2568 $classic_output = ob_get_clean();
2569
2570 $classic_elements = wp_html_split( $classic_output );
2571 $hidden_inputs = '';
2572 foreach ( $classic_elements as $element ) {
2573 if ( ! str_starts_with( $element, '<input ' ) ) {
2574 continue;
2575 }
2576
2577 if ( preg_match( '/\stype=[\'"]hidden[\'"]\s/', $element ) ) {
2578 echo $element;
2579 }
2580 }
2581 ?>
2582 <input type="hidden" id="user-id" name="user_ID" value="<?php echo (int) $user_id; ?>" />
2583 <input type="hidden" id="hiddenaction" name="action" value="<?php echo esc_attr( $form_action ); ?>" />
2584 <input type="hidden" id="originalaction" name="originalaction" value="<?php echo esc_attr( $form_action ); ?>" />
2585 <input type="hidden" id="post_type" name="post_type" value="<?php echo esc_attr( $post->post_type ); ?>" />
2586 <input type="hidden" id="original_post_status" name="original_post_status" value="<?php echo esc_attr( $post->post_status ); ?>" />
2587 <input type="hidden" id="referredby" name="referredby" value="<?php echo $referer ? esc_url( $referer ) : ''; ?>" />
2588
2589 <?php
2590 if ( 'draft' !== get_post_status( $post ) ) {
2591 wp_original_referer_field( true, 'previous' );
2592 }
2593 echo $form_extra;
2594 wp_nonce_field( 'meta-box-order', 'meta-box-order-nonce', false );
2595 wp_nonce_field( 'closedpostboxes', 'closedpostboxesnonce', false );
2596 // Permalink title nonce.
2597 wp_nonce_field( 'samplepermalink', 'samplepermalinknonce', false );
2598
2599 /**
2600 * Adds hidden input fields to the meta box save form.
2601 *
2602 * Hook into this action to print `<input type="hidden" ... />` fields, which will be POSTed back to
2603 * the server when meta boxes are saved.
2604 *
2605 * @since 5.0.0
2606 *
2607 * @param WP_Post $post The post that is being edited.
2608 */
2609 do_action( 'block_editor_meta_box_hidden_fields', $post );
2610}
2611
2612/**
2613 * Disables block editor for wp_navigation type posts so they can be managed via the UI.
2614 *
2615 * @since 5.9.0
2616 * @access private
2617 *
2618 * @param bool $value Whether the CPT supports block editor or not.
2619 * @param string $post_type Post type.
2620 * @return bool Whether the block editor should be disabled or not.
2621 */
2622function _disable_block_editor_for_navigation_post_type( $value, $post_type ) {
2623 if ( 'wp_navigation' === $post_type ) {
2624 return false;
2625 }
2626
2627 return $value;
2628}
2629
2630/**
2631 * This callback disables the content editor for wp_navigation type posts.
2632 * Content editor cannot handle wp_navigation type posts correctly.
2633 * We cannot disable the "editor" feature in the wp_navigation's CPT definition
2634 * because it disables the ability to save navigation blocks via REST API.
2635 *
2636 * @since 5.9.0
2637 * @access private
2638 *
2639 * @param WP_Post $post An instance of WP_Post class.
2640 */
2641function _disable_content_editor_for_navigation_post_type( $post ) {
2642 $post_type = get_post_type( $post );
2643 if ( 'wp_navigation' !== $post_type ) {
2644 return;
2645 }
2646
2647 remove_post_type_support( $post_type, 'editor' );
2648}
2649
2650/**
2651 * This callback enables content editor for wp_navigation type posts.
2652 * We need to enable it back because we disable it to hide
2653 * the content editor for wp_navigation type posts.
2654 *
2655 * @since 5.9.0
2656 * @access private
2657 *
2658 * @see _disable_content_editor_for_navigation_post_type
2659 *
2660 * @param WP_Post $post An instance of WP_Post class.
2661 */
2662function _enable_content_editor_for_navigation_post_type( $post ) {
2663 $post_type = get_post_type( $post );
2664 if ( 'wp_navigation' !== $post_type ) {
2665 return;
2666 }
2667
2668 add_post_type_support( $post_type, 'editor' );
2669}
2670