post.php ➔ post_preview()   F
last analyzed

Complexity

Conditions 16
Paths 240

Size

Total Lines 46
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 16
eloc 26
c 2
b 0
f 0
nc 240
nop 0
dl 0
loc 46
rs 3.882

How to fix   Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
/**
3
 * WordPress Post Administration API.
4
 *
5
 * @package WordPress
6
 * @subpackage Administration
7
 */
8
9
/**
10
 * Rename $_POST data from form names to DB post columns.
11
 *
12
 * Manipulates $_POST directly.
13
 *
14
 * @package WordPress
15
 * @since 2.6.0
16
 *
17
 * @param bool $update Are we updating a pre-existing post?
18
 * @param array $post_data Array of post data. Defaults to the contents of $_POST.
19
 * @return object|bool WP_Error on failure, true on success.
20
 */
21
function _wp_translate_postdata( $update = false, $post_data = null ) {
22
23
	if ( empty($post_data) )
24
		$post_data = &$_POST;
25
26
	if ( $update )
27
		$post_data['ID'] = (int) $post_data['post_ID'];
28
29
	$ptype = get_post_type_object( $post_data['post_type'] );
30
31
	if ( $update && ! current_user_can( 'edit_post', $post_data['ID'] ) ) {
32 View Code Duplication
		if ( 'page' == $post_data['post_type'] )
33
			return new WP_Error( 'edit_others_pages', __( 'Sorry, you are not allowed to edit pages as this user.' ) );
34
		else
35
			return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to edit posts as this user.' ) );
36
	} elseif ( ! $update && ! current_user_can( $ptype->cap->create_posts ) ) {
37 View Code Duplication
		if ( 'page' == $post_data['post_type'] )
38
			return new WP_Error( 'edit_others_pages', __( 'Sorry, you are not allowed to create pages as this user.' ) );
39
		else
40
			return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to create posts as this user.' ) );
41
	}
42
43
	if ( isset( $post_data['content'] ) )
44
		$post_data['post_content'] = $post_data['content'];
45
46
	if ( isset( $post_data['excerpt'] ) )
47
		$post_data['post_excerpt'] = $post_data['excerpt'];
48
49
	if ( isset( $post_data['parent_id'] ) )
50
		$post_data['post_parent'] = (int) $post_data['parent_id'];
51
52
	if ( isset($post_data['trackback_url']) )
53
		$post_data['to_ping'] = $post_data['trackback_url'];
54
55
	$post_data['user_ID'] = get_current_user_id();
56
57
	if (!empty ( $post_data['post_author_override'] ) ) {
58
		$post_data['post_author'] = (int) $post_data['post_author_override'];
59
	} else {
60
		if (!empty ( $post_data['post_author'] ) ) {
61
			$post_data['post_author'] = (int) $post_data['post_author'];
62
		} else {
63
			$post_data['post_author'] = (int) $post_data['user_ID'];
64
		}
65
	}
66
67
	if ( isset( $post_data['user_ID'] ) && ( $post_data['post_author'] != $post_data['user_ID'] )
68
		 && ! current_user_can( $ptype->cap->edit_others_posts ) ) {
69
		if ( $update ) {
70 View Code Duplication
			if ( 'page' == $post_data['post_type'] )
71
				return new WP_Error( 'edit_others_pages', __( 'Sorry, you are not allowed to edit pages as this user.' ) );
72
			else
73
				return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to edit posts as this user.' ) );
74 View Code Duplication
		} else {
75
			if ( 'page' == $post_data['post_type'] )
76
				return new WP_Error( 'edit_others_pages', __( 'Sorry, you are not allowed to create pages as this user.' ) );
77
			else
78
				return new WP_Error( 'edit_others_posts', __( 'Sorry, you are not allowed to create posts as this user.' ) );
79
		}
80
	}
81
82 View Code Duplication
	if ( ! empty( $post_data['post_status'] ) ) {
83
		$post_data['post_status'] = sanitize_key( $post_data['post_status'] );
84
85
		// No longer an auto-draft
86
		if ( 'auto-draft' === $post_data['post_status'] ) {
87
			$post_data['post_status'] = 'draft';
88
		}
89
90
		if ( ! get_post_status_object( $post_data['post_status'] ) ) {
91
			unset( $post_data['post_status'] );
92
		}
93
	}
94
95
	// What to do based on which button they pressed
96 View Code Duplication
	if ( isset($post_data['saveasdraft']) && '' != $post_data['saveasdraft'] )
97
		$post_data['post_status'] = 'draft';
98
	if ( isset($post_data['saveasprivate']) && '' != $post_data['saveasprivate'] )
99
		$post_data['post_status'] = 'private';
100
	if ( isset($post_data['publish']) && ( '' != $post_data['publish'] ) && ( !isset($post_data['post_status']) || $post_data['post_status'] != 'private' ) )
101
		$post_data['post_status'] = 'publish';
102 View Code Duplication
	if ( isset($post_data['advanced']) && '' != $post_data['advanced'] )
103
		$post_data['post_status'] = 'draft';
104
	if ( isset($post_data['pending']) && '' != $post_data['pending'] )
105
		$post_data['post_status'] = 'pending';
106
107
	if ( isset( $post_data['ID'] ) )
108
		$post_id = $post_data['ID'];
109
	else
110
		$post_id = false;
111
	$previous_status = $post_id ? get_post_field( 'post_status', $post_id ) : false;
112
113
	if ( isset( $post_data['post_status'] ) && 'private' == $post_data['post_status'] && ! current_user_can( $ptype->cap->publish_posts ) ) {
114
		$post_data['post_status'] = $previous_status ? $previous_status : 'pending';
115
	}
116
117
	$published_statuses = array( 'publish', 'future' );
118
119
	// Posts 'submitted for approval' present are submitted to $_POST the same as if they were being published.
120
	// Change status from 'publish' to 'pending' if user lacks permissions to publish or to resave published posts.
121
	if ( isset($post_data['post_status']) && (in_array( $post_data['post_status'], $published_statuses ) && !current_user_can( $ptype->cap->publish_posts )) )
122
		if ( ! in_array( $previous_status, $published_statuses ) || !current_user_can( 'edit_post', $post_id ) )
123
			$post_data['post_status'] = 'pending';
124
125
	if ( ! isset( $post_data['post_status'] ) ) {
126
		$post_data['post_status'] = 'auto-draft' === $previous_status ? 'draft' : $previous_status;
127
	}
128
129
	if ( isset( $post_data['post_password'] ) && ! current_user_can( $ptype->cap->publish_posts ) ) {
130
		unset( $post_data['post_password'] );
131
	}
132
133
	if (!isset( $post_data['comment_status'] ))
134
		$post_data['comment_status'] = 'closed';
135
136
	if (!isset( $post_data['ping_status'] ))
137
		$post_data['ping_status'] = 'closed';
138
139 View Code Duplication
	foreach ( array('aa', 'mm', 'jj', 'hh', 'mn') as $timeunit ) {
140
		if ( !empty( $post_data['hidden_' . $timeunit] ) && $post_data['hidden_' . $timeunit] != $post_data[$timeunit] ) {
141
			$post_data['edit_date'] = '1';
142
			break;
143
		}
144
	}
145
146
	if ( !empty( $post_data['edit_date'] ) ) {
147
		$aa = $post_data['aa'];
148
		$mm = $post_data['mm'];
149
		$jj = $post_data['jj'];
150
		$hh = $post_data['hh'];
151
		$mn = $post_data['mn'];
152
		$ss = $post_data['ss'];
153
		$aa = ($aa <= 0 ) ? date('Y') : $aa;
154
		$mm = ($mm <= 0 ) ? date('n') : $mm;
155
		$jj = ($jj > 31 ) ? 31 : $jj;
156
		$jj = ($jj <= 0 ) ? date('j') : $jj;
157
		$hh = ($hh > 23 ) ? $hh -24 : $hh;
158
		$mn = ($mn > 59 ) ? $mn -60 : $mn;
159
		$ss = ($ss > 59 ) ? $ss -60 : $ss;
160
		$post_data['post_date'] = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $aa, $mm, $jj, $hh, $mn, $ss );
161
		$valid_date = wp_checkdate( $mm, $jj, $aa, $post_data['post_date'] );
162
		if ( !$valid_date ) {
163
			return new WP_Error( 'invalid_date', __( 'Whoops, the provided date is invalid.' ) );
164
		}
165
		$post_data['post_date_gmt'] = get_gmt_from_date( $post_data['post_date'] );
166
	}
167
168
	if ( isset( $post_data['post_category'] ) ) {
169
		$category_object = get_taxonomy( 'category' );
170
		if ( ! current_user_can( $category_object->cap->assign_terms ) ) {
171
			unset( $post_data['post_category'] );
172
		}
173
	}
174
175
	return $post_data;
176
}
177
178
/**
179
 * Update an existing post with values provided in $_POST.
180
 *
181
 * @since 1.5.0
182
 *
183
 * @global wpdb $wpdb WordPress database abstraction object.
184
 *
185
 * @param array $post_data Optional.
186
 * @return int Post ID.
187
 */
188
function edit_post( $post_data = null ) {
189
	global $wpdb;
190
191
	if ( empty($post_data) )
192
		$post_data = &$_POST;
193
194
	// Clear out any data in internal vars.
195
	unset( $post_data['filter'] );
196
197
	$post_ID = (int) $post_data['post_ID'];
198
	$post = get_post( $post_ID );
199
	$post_data['post_type'] = $post->post_type;
200
	$post_data['post_mime_type'] = $post->post_mime_type;
201
202 View Code Duplication
	if ( ! empty( $post_data['post_status'] ) ) {
203
		$post_data['post_status'] = sanitize_key( $post_data['post_status'] );
204
205
		if ( 'inherit' == $post_data['post_status'] ) {
206
			unset( $post_data['post_status'] );
207
		}
208
	}
209
210
	$ptype = get_post_type_object($post_data['post_type']);
211 View Code Duplication
	if ( !current_user_can( 'edit_post', $post_ID ) ) {
212
		if ( 'page' == $post_data['post_type'] )
213
			wp_die( __('Sorry, you are not allowed to edit this page.' ));
214
		else
215
			wp_die( __('Sorry, you are not allowed to edit this post.' ));
216
	}
217
218
	if ( post_type_supports( $ptype->name, 'revisions' ) ) {
219
		$revisions = wp_get_post_revisions( $post_ID, array( 'order' => 'ASC', 'posts_per_page' => 1 ) );
220
		$revision = current( $revisions );
221
222
		// Check if the revisions have been upgraded
223
		if ( $revisions && _wp_get_post_revision_version( $revision ) < 1 )
224
			_wp_upgrade_revisions_of_post( $post, wp_get_post_revisions( $post_ID ) );
0 ignored issues
show
Bug introduced by
It seems like $post defined by get_post($post_ID) on line 198 can also be of type array; however, _wp_upgrade_revisions_of_post() does only seem to accept object<WP_Post>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
225
	}
226
227 View Code Duplication
	if ( isset($post_data['visibility']) ) {
228
		switch ( $post_data['visibility'] ) {
229
			case 'public' :
230
				$post_data['post_password'] = '';
231
				break;
232
			case 'password' :
233
				unset( $post_data['sticky'] );
234
				break;
235
			case 'private' :
236
				$post_data['post_status'] = 'private';
237
				$post_data['post_password'] = '';
238
				unset( $post_data['sticky'] );
239
				break;
240
		}
241
	}
242
243
	$post_data = _wp_translate_postdata( true, $post_data );
244
	if ( is_wp_error($post_data) )
245
		wp_die( $post_data->get_error_message() );
246
247
	// Post Formats
248
	if ( isset( $post_data['post_format'] ) )
249
		set_post_format( $post_ID, $post_data['post_format'] );
250
251
	$format_meta_urls = array( 'url', 'link_url', 'quote_source_url' );
252
	foreach ( $format_meta_urls as $format_meta_url ) {
253
		$keyed = '_format_' . $format_meta_url;
254
		if ( isset( $post_data[ $keyed ] ) )
255
			update_post_meta( $post_ID, $keyed, wp_slash( esc_url_raw( wp_unslash( $post_data[ $keyed ] ) ) ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($post_data[$keyed]) targeting wp_unslash() can also be of type array; however, esc_url_raw() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
256
	}
257
258
	$format_keys = array( 'quote', 'quote_source_name', 'image', 'gallery', 'audio_embed', 'video_embed' );
259
260
	foreach ( $format_keys as $key ) {
261
		$keyed = '_format_' . $key;
262
		if ( isset( $post_data[ $keyed ] ) ) {
263
			if ( current_user_can( 'unfiltered_html' ) )
264
				update_post_meta( $post_ID, $keyed, $post_data[ $keyed ] );
265
			else
266
				update_post_meta( $post_ID, $keyed, wp_filter_post_kses( $post_data[ $keyed ] ) );
267
		}
268
	}
269
270
	if ( 'attachment' === $post_data['post_type'] && preg_match( '#^(audio|video)/#', $post_data['post_mime_type'] ) ) {
271
		$id3data = wp_get_attachment_metadata( $post_ID );
272
		if ( ! is_array( $id3data ) ) {
273
			$id3data = array();
274
		}
275
276
		foreach ( wp_get_attachment_id3_keys( $post, 'edit' ) as $key => $label ) {
0 ignored issues
show
Bug introduced by
It seems like $post defined by get_post($post_ID) on line 198 can also be of type array; however, wp_get_attachment_id3_keys() does only seem to accept object<WP_Post>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
277
			if ( isset( $post_data[ 'id3_' . $key ] ) ) {
278
				$id3data[ $key ] = sanitize_text_field( wp_unslash( $post_data[ 'id3_' . $key ] ) );
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($post_data['id3_' . $key]) targeting wp_unslash() can also be of type array; however, sanitize_text_field() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
279
			}
280
		}
281
		wp_update_attachment_metadata( $post_ID, $id3data );
282
	}
283
284
	// Meta Stuff
285
	if ( isset($post_data['meta']) && $post_data['meta'] ) {
286
		foreach ( $post_data['meta'] as $key => $value ) {
287
			if ( !$meta = get_post_meta_by_id( $key ) )
288
				continue;
289
			if ( $meta->post_id != $post_ID )
290
				continue;
291
			if ( is_protected_meta( $value['key'], 'post' ) || ! current_user_can( 'edit_post_meta', $post_ID, $value['key'] ) )
292
				continue;
293
			update_meta( $key, $value['key'], $value['value'] );
294
		}
295
	}
296
297
	if ( isset($post_data['deletemeta']) && $post_data['deletemeta'] ) {
298
		foreach ( $post_data['deletemeta'] as $key => $value ) {
299
			if ( !$meta = get_post_meta_by_id( $key ) )
300
				continue;
301
			if ( $meta->post_id != $post_ID )
302
				continue;
303
			if ( is_protected_meta( $meta->meta_key, 'post' ) || ! current_user_can( 'delete_post_meta', $post_ID, $meta->meta_key ) )
304
				continue;
305
			delete_meta( $key );
306
		}
307
	}
308
309
	// Attachment stuff
310
	if ( 'attachment' == $post_data['post_type'] ) {
311 View Code Duplication
		if ( isset( $post_data[ '_wp_attachment_image_alt' ] ) ) {
312
			$image_alt = wp_unslash( $post_data['_wp_attachment_image_alt'] );
313
			if ( $image_alt != get_post_meta( $post_ID, '_wp_attachment_image_alt', true ) ) {
314
				$image_alt = wp_strip_all_tags( $image_alt, true );
0 ignored issues
show
Bug introduced by
It seems like $image_alt can also be of type array; however, wp_strip_all_tags() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
315
				// update_meta expects slashed.
316
				update_post_meta( $post_ID, '_wp_attachment_image_alt', wp_slash( $image_alt ) );
317
			}
318
		}
319
320
		$attachment_data = isset( $post_data['attachments'][ $post_ID ] ) ? $post_data['attachments'][ $post_ID ] : array();
321
322
		/** This filter is documented in wp-admin/includes/media.php */
323
		$post_data = apply_filters( 'attachment_fields_to_save', $post_data, $attachment_data );
324
	}
325
326
	// Convert taxonomy input to term IDs, to avoid ambiguity.
327
	if ( isset( $post_data['tax_input'] ) ) {
328
		foreach ( (array) $post_data['tax_input'] as $taxonomy => $terms ) {
329
			// Hierarchical taxonomy data is already sent as term IDs, so no conversion is necessary.
330
			if ( is_taxonomy_hierarchical( $taxonomy ) ) {
331
				continue;
332
			}
333
334
			/*
335
			 * Assume that a 'tax_input' string is a comma-separated list of term names.
336
			 * Some languages may use a character other than a comma as a delimiter, so we standardize on
337
			 * commas before parsing the list.
338
			 */
339
			if ( ! is_array( $terms ) ) {
340
				$comma = _x( ',', 'tag delimiter' );
341
				if ( ',' !== $comma ) {
342
					$terms = str_replace( $comma, ',', $terms );
343
				}
344
				$terms = explode( ',', trim( $terms, " \n\t\r\0\x0B," ) );
345
			}
346
347
			$clean_terms = array();
348
			foreach ( $terms as $term ) {
349
				// Empty terms are invalid input.
350
				if ( empty( $term ) ) {
351
					continue;
352
				}
353
354
				$_term = get_terms( $taxonomy, array(
355
					'name' => $term,
356
					'fields' => 'ids',
357
					'hide_empty' => false,
358
				) );
359
360
				if ( ! empty( $_term ) ) {
361
					$clean_terms[] = intval( $_term[0] );
362
				} else {
363
					// No existing term was found, so pass the string. A new term will be created.
364
					$clean_terms[] = $term;
365
				}
366
			}
367
368
			$post_data['tax_input'][ $taxonomy ] = $clean_terms;
369
		}
370
	}
371
372
	add_meta( $post_ID );
373
374
	update_post_meta( $post_ID, '_edit_last', get_current_user_id() );
375
376
	$success = wp_update_post( $post_data );
377
	// If the save failed, see if we can sanity check the main fields and try again
378
	if ( ! $success && is_callable( array( $wpdb, 'strip_invalid_text_for_column' ) ) ) {
379
		$fields = array( 'post_title', 'post_content', 'post_excerpt' );
380
381
		foreach ( $fields as $field ) {
382
			if ( isset( $post_data[ $field ] ) ) {
383
				$post_data[ $field ] = $wpdb->strip_invalid_text_for_column( $wpdb->posts, $field, $post_data[ $field ] );
384
			}
385
		}
386
387
		wp_update_post( $post_data );
388
	}
389
390
	// Now that we have an ID we can fix any attachment anchor hrefs
391
	_fix_attachment_links( $post_ID );
392
393
	wp_set_post_lock( $post_ID );
394
395
	if ( current_user_can( $ptype->cap->edit_others_posts ) && current_user_can( $ptype->cap->publish_posts ) ) {
396
		if ( ! empty( $post_data['sticky'] ) )
397
			stick_post( $post_ID );
398
		else
399
			unstick_post( $post_ID );
400
	}
401
402
	return $post_ID;
403
}
404
405
/**
406
 * Process the post data for the bulk editing of posts.
407
 *
408
 * Updates all bulk edited posts/pages, adding (but not removing) tags and
409
 * categories. Skips pages when they would be their own parent or child.
410
 *
411
 * @since 2.7.0
412
 *
413
 * @global wpdb $wpdb WordPress database abstraction object.
414
 *
415
 * @param array $post_data Optional, the array of post data to process if not provided will use $_POST superglobal.
416
 * @return array
417
 */
418
function bulk_edit_posts( $post_data = null ) {
419
	global $wpdb;
420
421
	if ( empty($post_data) )
422
		$post_data = &$_POST;
423
424 View Code Duplication
	if ( isset($post_data['post_type']) )
425
		$ptype = get_post_type_object($post_data['post_type']);
426
	else
427
		$ptype = get_post_type_object('post');
428
429
	if ( !current_user_can( $ptype->cap->edit_posts ) ) {
430
		if ( 'page' == $ptype->name )
431
			wp_die( __('Sorry, you are not allowed to edit pages.'));
432
		else
433
			wp_die( __('Sorry, you are not allowed to edit posts.'));
434
	}
435
436
	if ( -1 == $post_data['_status'] ) {
437
		$post_data['post_status'] = null;
438
		unset($post_data['post_status']);
439
	} else {
440
		$post_data['post_status'] = $post_data['_status'];
441
	}
442
	unset($post_data['_status']);
443
444 View Code Duplication
	if ( ! empty( $post_data['post_status'] ) ) {
445
		$post_data['post_status'] = sanitize_key( $post_data['post_status'] );
446
447
		if ( 'inherit' == $post_data['post_status'] ) {
448
			unset( $post_data['post_status'] );
449
		}
450
	}
451
452
	$post_IDs = array_map( 'intval', (array) $post_data['post'] );
453
454
	$reset = array(
455
		'post_author', 'post_status', 'post_password',
456
		'post_parent', 'page_template', 'comment_status',
457
		'ping_status', 'keep_private', 'tax_input',
458
		'post_category', 'sticky', 'post_format',
459
	);
460
461
	foreach ( $reset as $field ) {
462
		if ( isset($post_data[$field]) && ( '' == $post_data[$field] || -1 == $post_data[$field] ) )
463
			unset($post_data[$field]);
464
	}
465
466
	if ( isset($post_data['post_category']) ) {
467
		if ( is_array($post_data['post_category']) && ! empty($post_data['post_category']) )
468
			$new_cats = array_map( 'absint', $post_data['post_category'] );
469
		else
470
			unset($post_data['post_category']);
471
	}
472
473
	$tax_input = array();
474
	if ( isset($post_data['tax_input'])) {
475
		foreach ( $post_data['tax_input'] as $tax_name => $terms ) {
0 ignored issues
show
Bug introduced by
The expression $post_data['tax_input'] of type string|array is not guaranteed to be traversable. How about adding an additional type check?

There are different options of fixing this problem.

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

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

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

Loading history...
476
			if ( empty($terms) )
477
				continue;
478
			if ( is_taxonomy_hierarchical( $tax_name ) ) {
479
				$tax_input[ $tax_name ] = array_map( 'absint', $terms );
480
			} else {
481
				$comma = _x( ',', 'tag delimiter' );
482
				if ( ',' !== $comma )
483
					$terms = str_replace( $comma, ',', $terms );
484
				$tax_input[ $tax_name ] = explode( ',', trim( $terms, " \n\t\r\0\x0B," ) );
485
			}
486
		}
487
	}
488
489
	if ( isset($post_data['post_parent']) && ($parent = (int) $post_data['post_parent']) ) {
490
		$pages = $wpdb->get_results("SELECT ID, post_parent FROM $wpdb->posts WHERE post_type = 'page'");
491
		$children = array();
492
493
		for ( $i = 0; $i < 50 && $parent > 0; $i++ ) {
494
			$children[] = $parent;
495
496
			foreach ( $pages as $page ) {
497
				if ( $page->ID == $parent ) {
498
					$parent = $page->post_parent;
499
					break;
500
				}
501
			}
502
		}
503
	}
504
505
	$updated = $skipped = $locked = array();
506
	$shared_post_data = $post_data;
507
508
	foreach ( $post_IDs as $post_ID ) {
509
		// Start with fresh post data with each iteration.
510
		$post_data = $shared_post_data;
511
512
		$post_type_object = get_post_type_object( get_post_type( $post_ID ) );
0 ignored issues
show
Security Bug introduced by
It seems like get_post_type($post_ID) targeting get_post_type() can also be of type false; however, get_post_type_object() does only seem to accept string, did you maybe forget to handle an error condition?
Loading history...
513
514
		if ( !isset( $post_type_object ) || ( isset($children) && in_array($post_ID, $children) ) || !current_user_can( 'edit_post', $post_ID ) ) {
0 ignored issues
show
Bug introduced by
The variable $children does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
515
			$skipped[] = $post_ID;
516
			continue;
517
		}
518
519
		if ( wp_check_post_lock( $post_ID ) ) {
520
			$locked[] = $post_ID;
521
			continue;
522
		}
523
524
		$post = get_post( $post_ID );
525
		$tax_names = get_object_taxonomies( $post );
0 ignored issues
show
Bug introduced by
It seems like $post defined by get_post($post_ID) on line 524 can also be of type null; however, get_object_taxonomies() does only seem to accept array|string|object<WP_Post>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
526
		foreach ( $tax_names as $tax_name ) {
527
			$taxonomy_obj = get_taxonomy($tax_name);
528
			if ( isset( $tax_input[$tax_name]) && current_user_can( $taxonomy_obj->cap->assign_terms ) )
529
				$new_terms = $tax_input[$tax_name];
530
			else
531
				$new_terms = array();
532
533
			if ( $taxonomy_obj->hierarchical )
534
				$current_terms = (array) wp_get_object_terms( $post_ID, $tax_name, array('fields' => 'ids') );
535
			else
536
				$current_terms = (array) wp_get_object_terms( $post_ID, $tax_name, array('fields' => 'names') );
537
538
			$post_data['tax_input'][$tax_name] = array_merge( $current_terms, $new_terms );
539
		}
540
541
		if ( isset($new_cats) && in_array( 'category', $tax_names ) ) {
542
			$cats = (array) wp_get_post_categories($post_ID);
543
			$post_data['post_category'] = array_unique( array_merge($cats, $new_cats) );
544
			unset( $post_data['tax_input']['category'] );
545
		}
546
547
		$post_data['post_type'] = $post->post_type;
548
		$post_data['post_mime_type'] = $post->post_mime_type;
549
		$post_data['guid'] = $post->guid;
550
551
		foreach ( array( 'comment_status', 'ping_status', 'post_author' ) as $field ) {
552
			if ( ! isset( $post_data[ $field ] ) ) {
553
				$post_data[ $field ] = $post->$field;
554
			}
555
		}
556
557
		$post_data['ID'] = $post_ID;
558
		$post_data['post_ID'] = $post_ID;
559
560
		$post_data = _wp_translate_postdata( true, $post_data );
561
		if ( is_wp_error( $post_data ) ) {
562
			$skipped[] = $post_ID;
563
			continue;
564
		}
565
566
		$updated[] = wp_update_post( $post_data );
567
568
		if ( isset( $post_data['sticky'] ) && current_user_can( $ptype->cap->edit_others_posts ) ) {
569
			if ( 'sticky' == $post_data['sticky'] )
570
				stick_post( $post_ID );
571
			else
572
				unstick_post( $post_ID );
573
		}
574
575
		if ( isset( $post_data['post_format'] ) )
576
			set_post_format( $post_ID, $post_data['post_format'] );
577
	}
578
579
	return array( 'updated' => $updated, 'skipped' => $skipped, 'locked' => $locked );
580
}
581
582
/**
583
 * Default post information to use when populating the "Write Post" form.
584
 *
585
 * @since 2.0.0
586
 *
587
 * @param string $post_type    Optional. A post type string. Default 'post'.
588
 * @param bool   $create_in_db Optional. Whether to insert the post into database. Default false.
589
 * @return WP_Post Post object containing all the default post data as attributes
590
 */
591
function get_default_post_to_edit( $post_type = 'post', $create_in_db = false ) {
592
	$post_title = '';
593
	if ( !empty( $_REQUEST['post_title'] ) )
594
		$post_title = esc_html( wp_unslash( $_REQUEST['post_title'] ));
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_REQUEST['post_title']) targeting wp_unslash() can also be of type array; however, esc_html() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
595
596
	$post_content = '';
597
	if ( !empty( $_REQUEST['content'] ) )
598
		$post_content = esc_html( wp_unslash( $_REQUEST['content'] ));
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_REQUEST['content']) targeting wp_unslash() can also be of type array; however, esc_html() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
599
600
	$post_excerpt = '';
601
	if ( !empty( $_REQUEST['excerpt'] ) )
602
		$post_excerpt = esc_html( wp_unslash( $_REQUEST['excerpt'] ));
0 ignored issues
show
Bug introduced by
It seems like wp_unslash($_REQUEST['excerpt']) targeting wp_unslash() can also be of type array; however, esc_html() does only seem to accept string, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
603
604
	if ( $create_in_db ) {
605
		$post_id = wp_insert_post( array( 'post_title' => __( 'Auto Draft' ), 'post_type' => $post_type, 'post_status' => 'auto-draft' ) );
606
		$post = get_post( $post_id );
0 ignored issues
show
Bug introduced by
It seems like $post_id defined by wp_insert_post(array('po...atus' => 'auto-draft')) on line 605 can also be of type object<WP_Error>; however, get_post() does only seem to accept integer|object<WP_Post>|null, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
607
		if ( current_theme_supports( 'post-formats' ) && post_type_supports( $post->post_type, 'post-formats' ) && get_option( 'default_post_format' ) )
608
			set_post_format( $post, get_option( 'default_post_format' ) );
0 ignored issues
show
Bug introduced by
It seems like $post defined by get_post($post_id) on line 606 can also be of type array; however, set_post_format() does only seem to accept integer|object, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
609
	} else {
610
		$post = new stdClass;
611
		$post->ID = 0;
612
		$post->post_author = '';
613
		$post->post_date = '';
614
		$post->post_date_gmt = '';
615
		$post->post_password = '';
616
		$post->post_name = '';
617
		$post->post_type = $post_type;
618
		$post->post_status = 'draft';
619
		$post->to_ping = '';
620
		$post->pinged = '';
621
		$post->comment_status = get_default_comment_status( $post_type );
622
		$post->ping_status = get_default_comment_status( $post_type, 'pingback' );
623
		$post->post_pingback = get_option( 'default_pingback_flag' );
624
		$post->post_category = get_option( 'default_category' );
625
		$post->page_template = 'default';
626
		$post->post_parent = 0;
627
		$post->menu_order = 0;
628
		$post = new WP_Post( $post );
629
	}
630
631
	/**
632
	 * Filters the default post content initially used in the "Write Post" form.
633
	 *
634
	 * @since 1.5.0
635
	 *
636
	 * @param string  $post_content Default post content.
637
	 * @param WP_Post $post         Post object.
638
	 */
639
	$post->post_content = apply_filters( 'default_content', $post_content, $post );
640
641
	/**
642
	 * Filters the default post title initially used in the "Write Post" form.
643
	 *
644
	 * @since 1.5.0
645
	 *
646
	 * @param string  $post_title Default post title.
647
	 * @param WP_Post $post       Post object.
648
	 */
649
	$post->post_title = apply_filters( 'default_title', $post_title, $post );
650
651
	/**
652
	 * Filters the default post excerpt initially used in the "Write Post" form.
653
	 *
654
	 * @since 1.5.0
655
	 *
656
	 * @param string  $post_excerpt Default post excerpt.
657
	 * @param WP_Post $post         Post object.
658
	 */
659
	$post->post_excerpt = apply_filters( 'default_excerpt', $post_excerpt, $post );
660
661
	return $post;
662
}
663
664
/**
665
 * Determine if a post exists based on title, content, and date
666
 *
667
 * @since 2.0.0
668
 *
669
 * @global wpdb $wpdb WordPress database abstraction object.
670
 *
671
 * @param string $title Post title
672
 * @param string $content Optional post content
673
 * @param string $date Optional post date
674
 * @return int Post ID if post exists, 0 otherwise.
675
 */
676
function post_exists($title, $content = '', $date = '') {
677
	global $wpdb;
678
679
	$post_title = wp_unslash( sanitize_post_field( 'post_title', $title, 0, 'db' ) );
680
	$post_content = wp_unslash( sanitize_post_field( 'post_content', $content, 0, 'db' ) );
681
	$post_date = wp_unslash( sanitize_post_field( 'post_date', $date, 0, 'db' ) );
682
683
	$query = "SELECT ID FROM $wpdb->posts WHERE 1=1";
684
	$args = array();
685
686
	if ( !empty ( $date ) ) {
687
		$query .= ' AND post_date = %s';
688
		$args[] = $post_date;
689
	}
690
691
	if ( !empty ( $title ) ) {
692
		$query .= ' AND post_title = %s';
693
		$args[] = $post_title;
694
	}
695
696
	if ( !empty ( $content ) ) {
697
		$query .= ' AND post_content = %s';
698
		$args[] = $post_content;
699
	}
700
701
	if ( !empty ( $args ) )
702
		return (int) $wpdb->get_var( $wpdb->prepare($query, $args) );
703
704
	return 0;
705
}
706
707
/**
708
 * Creates a new post from the "Write Post" form using $_POST information.
709
 *
710
 * @since 2.1.0
711
 *
712
 * @global WP_User $current_user
713
 *
714
 * @return int|WP_Error
715
 */
716
function wp_write_post() {
717 View Code Duplication
	if ( isset($_POST['post_type']) )
718
		$ptype = get_post_type_object($_POST['post_type']);
719
	else
720
		$ptype = get_post_type_object('post');
721
722
	if ( !current_user_can( $ptype->cap->edit_posts ) ) {
723
		if ( 'page' == $ptype->name )
724
			return new WP_Error( 'edit_pages', __( 'Sorry, you are not allowed to create pages on this site.' ) );
725
		else
726
			return new WP_Error( 'edit_posts', __( 'Sorry, you are not allowed to create posts or drafts on this site.' ) );
727
	}
728
729
	$_POST['post_mime_type'] = '';
730
731
	// Clear out any data in internal vars.
732
	unset( $_POST['filter'] );
733
734
	// Edit don't write if we have a post id.
735
	if ( isset( $_POST['post_ID'] ) )
736
		return edit_post();
737
738 View Code Duplication
	if ( isset($_POST['visibility']) ) {
739
		switch ( $_POST['visibility'] ) {
740
			case 'public' :
741
				$_POST['post_password'] = '';
742
				break;
743
			case 'password' :
744
				unset( $_POST['sticky'] );
745
				break;
746
			case 'private' :
747
				$_POST['post_status'] = 'private';
748
				$_POST['post_password'] = '';
749
				unset( $_POST['sticky'] );
750
				break;
751
		}
752
	}
753
754
	$translated = _wp_translate_postdata( false );
755
	if ( is_wp_error($translated) )
756
		return $translated;
757
758
	// Create the post.
759
	$post_ID = wp_insert_post( $_POST );
760
	if ( is_wp_error( $post_ID ) )
761
		return $post_ID;
762
763
	if ( empty($post_ID) )
764
		return 0;
765
766
	add_meta( $post_ID );
0 ignored issues
show
Bug introduced by
It seems like $post_ID defined by wp_insert_post($_POST) on line 759 can also be of type object<WP_Error>; however, add_meta() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
767
768
	add_post_meta( $post_ID, '_edit_last', $GLOBALS['current_user']->ID );
0 ignored issues
show
Bug introduced by
It seems like $post_ID defined by wp_insert_post($_POST) on line 759 can also be of type object<WP_Error>; however, add_post_meta() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
769
770
	// Now that we have an ID we can fix any attachment anchor hrefs
771
	_fix_attachment_links( $post_ID );
772
773
	wp_set_post_lock( $post_ID );
0 ignored issues
show
Bug introduced by
It seems like $post_ID defined by wp_insert_post($_POST) on line 759 can also be of type object<WP_Error>; however, wp_set_post_lock() does only seem to accept integer, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
774
775
	return $post_ID;
776
}
777
778
/**
779
 * Calls wp_write_post() and handles the errors.
780
 *
781
 * @since 2.0.0
782
 *
783
 * @return int|null
784
 */
785
function write_post() {
786
	$result = wp_write_post();
787
	if ( is_wp_error( $result ) )
788
		wp_die( $result->get_error_message() );
789
	else
790
		return $result;
791
}
792
793
//
794
// Post Meta
795
//
796
797
/**
798
 * Add post meta data defined in $_POST superglobal for post with given ID.
799
 *
800
 * @since 1.2.0
801
 *
802
 * @param int $post_ID
803
 * @return int|bool
804
 */
805
function add_meta( $post_ID ) {
806
	$post_ID = (int) $post_ID;
807
808
	$metakeyselect = isset($_POST['metakeyselect']) ? wp_unslash( trim( $_POST['metakeyselect'] ) ) : '';
809
	$metakeyinput = isset($_POST['metakeyinput']) ? wp_unslash( trim( $_POST['metakeyinput'] ) ) : '';
810
	$metavalue = isset($_POST['metavalue']) ? $_POST['metavalue'] : '';
811
	if ( is_string( $metavalue ) )
812
		$metavalue = trim( $metavalue );
813
814
	if ( ('0' === $metavalue || ! empty ( $metavalue ) ) && ( ( ( '#NONE#' != $metakeyselect ) && !empty ( $metakeyselect) ) || !empty ( $metakeyinput ) ) ) {
815
		/*
816
		 * We have a key/value pair. If both the select and the input
817
		 * for the key have data, the input takes precedence.
818
		 */
819
 		if ( '#NONE#' != $metakeyselect )
820
			$metakey = $metakeyselect;
821
822
		if ( $metakeyinput )
823
			$metakey = $metakeyinput; // default
824
825
		if ( is_protected_meta( $metakey, 'post' ) || ! current_user_can( 'add_post_meta', $post_ID, $metakey ) )
0 ignored issues
show
Bug introduced by
The variable $metakey does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
Bug introduced by
It seems like $metakey can also be of type array; however, is_protected_meta() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
826
			return false;
827
828
		$metakey = wp_slash( $metakey );
829
830
		return add_post_meta( $post_ID, $metakey, $metavalue );
0 ignored issues
show
Bug introduced by
It seems like $metakey defined by wp_slash($metakey) on line 828 can also be of type array; however, add_post_meta() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
831
	}
832
833
	return false;
834
} // add_meta
835
836
/**
837
 * Delete post meta data by meta ID.
838
 *
839
 * @since 1.2.0
840
 *
841
 * @param int $mid
842
 * @return bool
843
 */
844
function delete_meta( $mid ) {
845
	return delete_metadata_by_mid( 'post' , $mid );
846
}
847
848
/**
849
 * Get a list of previously defined keys.
850
 *
851
 * @since 1.2.0
852
 *
853
 * @global wpdb $wpdb WordPress database abstraction object.
854
 *
855
 * @return mixed
856
 */
857
function get_meta_keys() {
858
	global $wpdb;
859
860
	$keys = $wpdb->get_col( "
861
			SELECT meta_key
862
			FROM $wpdb->postmeta
863
			GROUP BY meta_key
864
			ORDER BY meta_key" );
865
866
	return $keys;
867
}
868
869
/**
870
 * Get post meta data by meta ID.
871
 *
872
 * @since 2.1.0
873
 *
874
 * @param int $mid
875
 * @return object|bool
876
 */
877
function get_post_meta_by_id( $mid ) {
878
	return get_metadata_by_mid( 'post', $mid );
879
}
880
881
/**
882
 * Get meta data for the given post ID.
883
 *
884
 * @since 1.2.0
885
 *
886
 * @global wpdb $wpdb WordPress database abstraction object.
887
 *
888
 * @param int $postid
889
 * @return mixed
890
 */
891
function has_meta( $postid ) {
892
	global $wpdb;
893
894
	return $wpdb->get_results( $wpdb->prepare("SELECT meta_key, meta_value, meta_id, post_id
895
			FROM $wpdb->postmeta WHERE post_id = %d
896
			ORDER BY meta_key,meta_id", $postid), ARRAY_A );
897
}
898
899
/**
900
 * Update post meta data by meta ID.
901
 *
902
 * @since 1.2.0
903
 *
904
 * @param int    $meta_id
905
 * @param string $meta_key Expect Slashed
906
 * @param string $meta_value Expect Slashed
907
 * @return bool
908
 */
909
function update_meta( $meta_id, $meta_key, $meta_value ) {
910
	$meta_key = wp_unslash( $meta_key );
911
	$meta_value = wp_unslash( $meta_value );
912
913
	return update_metadata_by_mid( 'post', $meta_id, $meta_value, $meta_key );
0 ignored issues
show
Bug introduced by
It seems like $meta_value defined by wp_unslash($meta_value) on line 911 can also be of type array; however, update_metadata_by_mid() does only seem to accept string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
Bug introduced by
It seems like $meta_key defined by wp_unslash($meta_key) on line 910 can also be of type array; however, update_metadata_by_mid() does only seem to accept false|string, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
914
}
915
916
//
917
// Private
918
//
919
920
/**
921
 * Replace hrefs of attachment anchors with up-to-date permalinks.
922
 *
923
 * @since 2.3.0
924
 * @access private
925
 *
926
 * @param int|object $post Post ID or post object.
927
 * @return void|int|WP_Error Void if nothing fixed. 0 or WP_Error on update failure. The post ID on update success.
928
 */
929
function _fix_attachment_links( $post ) {
930
	$post = get_post( $post, ARRAY_A );
0 ignored issues
show
Bug introduced by
It seems like $post defined by get_post($post, ARRAY_A) on line 930 can also be of type object; however, get_post() does only seem to accept integer|object<WP_Post>|null, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
931
	$content = $post['post_content'];
932
933
	// Don't run if no pretty permalinks or post is not published, scheduled, or privately published.
934
	if ( ! get_option( 'permalink_structure' ) || ! in_array( $post['post_status'], array( 'publish', 'future', 'private' ) ) )
935
		return;
936
937
	// Short if there aren't any links or no '?attachment_id=' strings (strpos cannot be zero)
938
	if ( !strpos($content, '?attachment_id=') || !preg_match_all( '/<a ([^>]+)>[\s\S]+?<\/a>/', $content, $link_matches ) )
939
		return;
940
941
	$site_url = get_bloginfo('url');
942
	$site_url = substr( $site_url, (int) strpos($site_url, '://') ); // remove the http(s)
943
	$replace = '';
944
945
	foreach ( $link_matches[1] as $key => $value ) {
946
		if ( !strpos($value, '?attachment_id=') || !strpos($value, 'wp-att-')
947
			|| !preg_match( '/href=(["\'])[^"\']*\?attachment_id=(\d+)[^"\']*\\1/', $value, $url_match )
948
			|| !preg_match( '/rel=["\'][^"\']*wp-att-(\d+)/', $value, $rel_match ) )
949
				continue;
950
951
		$quote = $url_match[1]; // the quote (single or double)
952
		$url_id = (int) $url_match[2];
953
		$rel_id = (int) $rel_match[1];
954
955
		if ( !$url_id || !$rel_id || $url_id != $rel_id || strpos($url_match[0], $site_url) === false )
956
			continue;
957
958
		$link = $link_matches[0][$key];
959
		$replace = str_replace( $url_match[0], 'href=' . $quote . get_attachment_link( $url_id ) . $quote, $link );
960
961
		$content = str_replace( $link, $replace, $content );
962
	}
963
964
	if ( $replace ) {
965
		$post['post_content'] = $content;
966
		// Escape data pulled from DB.
967
		$post = add_magic_quotes($post);
0 ignored issues
show
Bug introduced by
It seems like $post can also be of type object<WP_Post>; however, add_magic_quotes() does only seem to accept array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
968
969
		return wp_update_post($post);
970
	}
971
}
972
973
/**
974
 * Get all the possible statuses for a post_type
975
 *
976
 * @since 2.5.0
977
 *
978
 * @param string $type The post_type you want the statuses for
979
 * @return array As array of all the statuses for the supplied post type
980
 */
981
function get_available_post_statuses($type = 'post') {
982
	$stati = wp_count_posts($type);
983
984
	return array_keys(get_object_vars($stati));
985
}
986
987
/**
988
 * Run the wp query to fetch the posts for listing on the edit posts page
989
 *
990
 * @since 2.5.0
991
 *
992
 * @param array|bool $q Array of query variables to use to build the query or false to use $_GET superglobal.
993
 * @return array
994
 */
995
function wp_edit_posts_query( $q = false ) {
996
	if ( false === $q )
997
		$q = $_GET;
998
	$q['m'] = isset($q['m']) ? (int) $q['m'] : 0;
999
	$q['cat'] = isset($q['cat']) ? (int) $q['cat'] : 0;
1000
	$post_stati  = get_post_stati();
1001
1002
	if ( isset($q['post_type']) && in_array( $q['post_type'], get_post_types() ) )
1003
		$post_type = $q['post_type'];
1004
	else
1005
		$post_type = 'post';
1006
1007
	$avail_post_stati = get_available_post_statuses($post_type);
1008
1009
	if ( isset($q['post_status']) && in_array( $q['post_status'], $post_stati ) ) {
1010
		$post_status = $q['post_status'];
1011
		$perm = 'readable';
1012
	}
1013
1014
	if ( isset( $q['orderby'] ) ) {
1015
		$orderby = $q['orderby'];
1016
	} elseif ( isset( $q['post_status'] ) && in_array( $q['post_status'], array( 'pending', 'draft' ) ) ) {
1017
		$orderby = 'modified';
1018
	}
1019
1020
	if ( isset( $q['order'] ) ) {
1021
		$order = $q['order'];
1022
	} elseif ( isset( $q['post_status'] ) && 'pending' == $q['post_status'] ) {
1023
		$order = 'ASC';
1024
	}
1025
1026
	$per_page = "edit_{$post_type}_per_page";
1027
	$posts_per_page = (int) get_user_option( $per_page );
1028
	if ( empty( $posts_per_page ) || $posts_per_page < 1 )
1029
		$posts_per_page = 20;
1030
1031
	/**
1032
	 * Filters the number of items per page to show for a specific 'per_page' type.
1033
	 *
1034
	 * The dynamic portion of the hook name, `$post_type`, refers to the post type.
1035
	 *
1036
	 * Some examples of filter hooks generated here include: 'edit_attachment_per_page',
1037
	 * 'edit_post_per_page', 'edit_page_per_page', etc.
1038
	 *
1039
	 * @since 3.0.0
1040
	 *
1041
	 * @param int $posts_per_page Number of posts to display per page for the given post
1042
	 *                            type. Default 20.
1043
	 */
1044
	$posts_per_page = apply_filters( "edit_{$post_type}_per_page", $posts_per_page );
1045
1046
	/**
1047
	 * Filters the number of posts displayed per page when specifically listing "posts".
1048
	 *
1049
	 * @since 2.8.0
1050
	 *
1051
	 * @param int    $posts_per_page Number of posts to be displayed. Default 20.
1052
	 * @param string $post_type      The post type.
1053
	 */
1054
	$posts_per_page = apply_filters( 'edit_posts_per_page', $posts_per_page, $post_type );
1055
1056
	$query = compact('post_type', 'post_status', 'perm', 'order', 'orderby', 'posts_per_page');
1057
1058
	// Hierarchical types require special args.
1059
	if ( is_post_type_hierarchical( $post_type ) && !isset($orderby) ) {
1060
		$query['orderby'] = 'menu_order title';
1061
		$query['order'] = 'asc';
1062
		$query['posts_per_page'] = -1;
1063
		$query['posts_per_archive_page'] = -1;
1064
		$query['fields'] = 'id=>parent';
1065
	}
1066
1067
	if ( ! empty( $q['show_sticky'] ) )
1068
		$query['post__in'] = (array) get_option( 'sticky_posts' );
1069
1070
	wp( $query );
1071
1072
	return $avail_post_stati;
1073
}
1074
1075
/**
1076
 * Get all available post MIME types for a given post type.
1077
 *
1078
 * @since 2.5.0
1079
 *
1080
 * @global wpdb $wpdb WordPress database abstraction object.
1081
 *
1082
 * @param string $type
1083
 * @return mixed
1084
 */
1085
function get_available_post_mime_types($type = 'attachment') {
1086
	global $wpdb;
1087
1088
	$types = $wpdb->get_col($wpdb->prepare("SELECT DISTINCT post_mime_type FROM $wpdb->posts WHERE post_type = %s", $type));
1089
	return $types;
1090
}
1091
1092
/**
1093
 * Get the query variables for the current attachments request.
1094
 *
1095
 * @since 4.2.0
1096
 *
1097
 * @param array|false $q Optional. Array of query variables to use to build the query or false
1098
 *                       to use $_GET superglobal. Default false.
1099
 * @return array The parsed query vars.
1100
 */
1101
function wp_edit_attachments_query_vars( $q = false ) {
1102
	if ( false === $q ) {
1103
		$q = $_GET;
1104
	}
1105
	$q['m']   = isset( $q['m'] ) ? (int) $q['m'] : 0;
1106
	$q['cat'] = isset( $q['cat'] ) ? (int) $q['cat'] : 0;
1107
	$q['post_type'] = 'attachment';
1108
	$post_type = get_post_type_object( 'attachment' );
1109
	$states = 'inherit';
1110
	if ( current_user_can( $post_type->cap->read_private_posts ) ) {
1111
		$states .= ',private';
1112
	}
1113
1114
	$q['post_status'] = isset( $q['status'] ) && 'trash' == $q['status'] ? 'trash' : $states;
1115
	$q['post_status'] = isset( $q['attachment-filter'] ) && 'trash' == $q['attachment-filter'] ? 'trash' : $states;
1116
1117
	$media_per_page = (int) get_user_option( 'upload_per_page' );
1118
	if ( empty( $media_per_page ) || $media_per_page < 1 ) {
1119
		$media_per_page = 20;
1120
	}
1121
1122
	/**
1123
	 * Filters the number of items to list per page when listing media items.
1124
	 *
1125
	 * @since 2.9.0
1126
	 *
1127
	 * @param int $media_per_page Number of media to list. Default 20.
1128
	 */
1129
	$q['posts_per_page'] = apply_filters( 'upload_per_page', $media_per_page );
1130
1131
	$post_mime_types = get_post_mime_types();
1132
	if ( isset($q['post_mime_type']) && !array_intersect( (array) $q['post_mime_type'], array_keys($post_mime_types) ) ) {
1133
		unset($q['post_mime_type']);
1134
	}
1135
1136
	foreach ( array_keys( $post_mime_types ) as $type ) {
1137
		if ( isset( $q['attachment-filter'] ) && "post_mime_type:$type" == $q['attachment-filter'] ) {
1138
			$q['post_mime_type'] = $type;
1139
			break;
1140
		}
1141
	}
1142
1143
	if ( isset( $q['detached'] ) || ( isset( $q['attachment-filter'] ) && 'detached' == $q['attachment-filter'] ) ) {
1144
		$q['post_parent'] = 0;
1145
	}
1146
1147
	return $q;
1148
}
1149
1150
/**
1151
 * Executes a query for attachments. An array of WP_Query arguments
1152
 * can be passed in, which will override the arguments set by this function.
1153
 *
1154
 * @since 2.5.0
1155
 *
1156
 * @param array|false $q Array of query variables to use to build the query or false to use $_GET superglobal.
1157
 * @return array
1158
 */
1159
function wp_edit_attachments_query( $q = false ) {
1160
	wp( wp_edit_attachments_query_vars( $q ) );
1161
1162
	$post_mime_types = get_post_mime_types();
1163
	$avail_post_mime_types = get_available_post_mime_types( 'attachment' );
1164
1165
	return array( $post_mime_types, $avail_post_mime_types );
1166
}
1167
1168
/**
1169
 * Returns the list of classes to be used by a meta box.
1170
 *
1171
 * @since 2.5.0
1172
 *
1173
 * @param string $id
1174
 * @param string $page
1175
 * @return string
1176
 */
1177
function postbox_classes( $id, $page ) {
1178
	if ( isset( $_GET['edit'] ) && $_GET['edit'] == $id ) {
1179
		$classes = array( '' );
1180
	} elseif ( $closed = get_user_option('closedpostboxes_'.$page ) ) {
1181
		if ( !is_array( $closed ) ) {
1182
			$classes = array( '' );
1183
		} else {
1184
			$classes = in_array( $id, $closed ) ? array( 'closed' ) : array( '' );
1185
		}
1186
	} else {
1187
		$classes = array( '' );
1188
	}
1189
1190
	/**
1191
	 * Filters the postbox classes for a specific screen and screen ID combo.
1192
	 *
1193
	 * The dynamic portions of the hook name, `$page` and `$id`, refer to
1194
	 * the screen and screen ID, respectively.
1195
	 *
1196
	 * @since 3.2.0
1197
	 *
1198
	 * @param array $classes An array of postbox classes.
1199
	 */
1200
	$classes = apply_filters( "postbox_classes_{$page}_{$id}", $classes );
1201
	return implode( ' ', $classes );
1202
}
1203
1204
/**
1205
 * Get a sample permalink based off of the post name.
1206
 *
1207
 * @since 2.5.0
1208
 *
1209
 * @param int    $id    Post ID or post object.
1210
 * @param string $title Optional. Title to override the post's current title when generating the post name. Default null.
1211
 * @param string $name  Optional. Name to override the post name. Default null.
1212
 * @return array Array containing the sample permalink with placeholder for the post name, and the post name.
1213
 */
1214
function get_sample_permalink($id, $title = null, $name = null) {
1215
	$post = get_post( $id );
1216
	if ( ! $post )
1217
		return array( '', '' );
1218
1219
	$ptype = get_post_type_object($post->post_type);
1220
1221
	$original_status = $post->post_status;
1222
	$original_date = $post->post_date;
1223
	$original_name = $post->post_name;
1224
1225
	// Hack: get_permalink() would return ugly permalink for drafts, so we will fake that our post is published.
1226
	if ( in_array( $post->post_status, array( 'draft', 'pending', 'future' ) ) ) {
1227
		$post->post_status = 'publish';
1228
		$post->post_name = sanitize_title($post->post_name ? $post->post_name : $post->post_title, $post->ID);
1229
	}
1230
1231
	// If the user wants to set a new name -- override the current one
1232
	// Note: if empty name is supplied -- use the title instead, see #6072
1233
	if ( !is_null($name) )
1234
		$post->post_name = sanitize_title($name ? $name : $title, $post->ID);
1235
1236
	$post->post_name = wp_unique_post_slug($post->post_name, $post->ID, $post->post_status, $post->post_type, $post->post_parent);
1237
1238
	$post->filter = 'sample';
1239
1240
	$permalink = get_permalink($post, true);
0 ignored issues
show
Documentation introduced by
$post is of type object<WP_Post>|array, but the function expects a integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1241
1242
	// Replace custom post_type Token with generic pagename token for ease of use.
1243
	$permalink = str_replace("%$post->post_type%", '%pagename%', $permalink);
1244
1245
	// Handle page hierarchy
1246
	if ( $ptype->hierarchical ) {
1247
		$uri = get_page_uri($post);
0 ignored issues
show
Documentation introduced by
$post is of type object<WP_Post>|array, but the function expects a integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1248
		if ( $uri ) {
1249
			$uri = untrailingslashit($uri);
1250
			$uri = strrev( stristr( strrev( $uri ), '/' ) );
1251
			$uri = untrailingslashit($uri);
1252
		}
1253
1254
		/** This filter is documented in wp-admin/edit-tag-form.php */
1255
		$uri = apply_filters( 'editable_slug', $uri, $post );
1256
		if ( !empty($uri) )
1257
			$uri .= '/';
1258
		$permalink = str_replace('%pagename%', "{$uri}%pagename%", $permalink);
1259
	}
1260
1261
	/** This filter is documented in wp-admin/edit-tag-form.php */
1262
	$permalink = array( $permalink, apply_filters( 'editable_slug', $post->post_name, $post ) );
1263
	$post->post_status = $original_status;
1264
	$post->post_date = $original_date;
1265
	$post->post_name = $original_name;
1266
	unset($post->filter);
1267
1268
	/**
1269
	 * Filters the sample permalink.
1270
	 *
1271
	 * @since 4.4.0
1272
	 *
1273
	 * @param array   $permalink Array containing the sample permalink with placeholder for the post name, and the post name.
1274
	 * @param int     $post_id   Post ID.
1275
	 * @param string  $title     Post title.
1276
	 * @param string  $name      Post name (slug).
1277
	 * @param WP_Post $post      Post object.
1278
	 */
1279
	return apply_filters( 'get_sample_permalink', $permalink, $post->ID, $title, $name, $post );
1280
}
1281
1282
/**
1283
 * Returns the HTML of the sample permalink slug editor.
1284
 *
1285
 * @since 2.5.0
1286
 *
1287
 * @param int    $id        Post ID or post object.
1288
 * @param string $new_title Optional. New title. Default null.
1289
 * @param string $new_slug  Optional. New slug. Default null.
1290
 * @return string The HTML of the sample permalink slug editor.
1291
 */
1292
function get_sample_permalink_html( $id, $new_title = null, $new_slug = null ) {
1293
	$post = get_post( $id );
1294
	if ( ! $post )
1295
		return '';
1296
1297
	list($permalink, $post_name) = get_sample_permalink($post->ID, $new_title, $new_slug);
1298
1299
	$view_link = false;
1300
	$preview_target = '';
1301
1302
	if ( current_user_can( 'read_post', $post->ID ) ) {
1303
		if ( 'draft' === $post->post_status ) {
1304
			$view_link = get_preview_post_link( $post );
0 ignored issues
show
Bug introduced by
It seems like $post defined by get_post($id) on line 1293 can also be of type array; however, get_preview_post_link() does only seem to accept integer|object<WP_Post>|null, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1305
			$preview_target = " target='wp-preview-{$post->ID}'";
1306
		} else {
1307
			if ( 'publish' === $post->post_status || 'attachment' === $post->post_type ) {
1308
				$view_link = get_permalink( $post );
0 ignored issues
show
Documentation introduced by
$post is of type object<WP_Post>|array, but the function expects a integer.

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

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

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

function acceptsInteger($int) { }

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

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1309
			} else {
1310
				// Allow non-published (private, future) to be viewed at a pretty permalink.
1311
				$view_link = str_replace( array( '%pagename%', '%postname%' ), $post->post_name, $permalink );
1312
			}
1313
		}
1314
	}
1315
1316
	// Permalinks without a post/page name placeholder don't have anything to edit
1317
	if ( false === strpos( $permalink, '%postname%' ) && false === strpos( $permalink, '%pagename%' ) ) {
1318
		$return = '<strong>' . __( 'Permalink:' ) . "</strong>\n";
1319
1320
		if ( false !== $view_link ) {
1321
			$display_link = urldecode( $view_link );
1322
			$return .= '<a id="sample-permalink" href="' . esc_url( $view_link ) . '"' . $preview_target . '>' . esc_html( $display_link ) . "</a>\n";
1323
		} else {
1324
			$return .= '<span id="sample-permalink">' . $permalink . "</span>\n";
1325
		}
1326
1327
		// Encourage a pretty permalink setting
1328
		if ( '' == get_option( 'permalink_structure' ) && current_user_can( 'manage_options' ) && !( 'page' == get_option('show_on_front') && $id == get_option('page_on_front') ) ) {
1329
			$return .= '<span id="change-permalinks"><a href="options-permalink.php" class="button button-small" target="_blank">' . __('Change Permalinks') . "</a></span>\n";
1330
		}
1331
	} else {
1332
		if ( mb_strlen( $post_name ) > 34 ) {
1333
			$post_name_abridged = mb_substr( $post_name, 0, 16 ) . '&hellip;' . mb_substr( $post_name, -16 );
1334
		} else {
1335
			$post_name_abridged = $post_name;
1336
		}
1337
1338
		$post_name_html = '<span id="editable-post-name">' . esc_html( $post_name_abridged ) . '</span>';
1339
		$display_link = str_replace( array( '%pagename%', '%postname%' ), $post_name_html, esc_html( urldecode( $permalink ) ) );
1340
1341
		$return = '<strong>' . __( 'Permalink:' ) . "</strong>\n";
1342
		$return .= '<span id="sample-permalink"><a href="' . esc_url( $view_link ) . '"' . $preview_target . '>' . $display_link . "</a></span>\n";
1343
		$return .= '&lrm;'; // Fix bi-directional text display defect in RTL languages.
1344
		$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";
1345
		$return .= '<span id="editable-post-name-full">' . esc_html( $post_name ) . "</span>\n";
1346
	}
1347
1348
	/**
1349
	 * Filters the sample permalink HTML markup.
1350
	 *
1351
	 * @since 2.9.0
1352
	 * @since 4.4.0 Added `$post` parameter.
1353
	 *
1354
	 * @param string  $return    Sample permalink HTML markup.
1355
	 * @param int     $post_id   Post ID.
1356
	 * @param string  $new_title New sample permalink title.
1357
	 * @param string  $new_slug  New sample permalink slug.
1358
	 * @param WP_Post $post      Post object.
1359
	 */
1360
	$return = apply_filters( 'get_sample_permalink_html', $return, $post->ID, $new_title, $new_slug, $post );
1361
1362
	return $return;
1363
}
1364
1365
/**
1366
 * Output HTML for the post thumbnail meta-box.
1367
 *
1368
 * @since 2.9.0
1369
 *
1370
 * @param int $thumbnail_id ID of the attachment used for thumbnail
1371
 * @param mixed $post The post ID or object associated with the thumbnail, defaults to global $post.
1372
 * @return string html
1373
 */
1374
function _wp_post_thumbnail_html( $thumbnail_id = null, $post = null ) {
1375
	$_wp_additional_image_sizes = wp_get_additional_image_sizes();
1376
1377
	$post               = get_post( $post );
1378
	$post_type_object   = get_post_type_object( $post->post_type );
1379
	$set_thumbnail_link = '<p class="hide-if-no-js"><a href="%s" id="set-post-thumbnail"%s class="thickbox">%s</a></p>';
1380
	$upload_iframe_src  = get_upload_iframe_src( 'image', $post->ID );
1381
1382
	$content = sprintf( $set_thumbnail_link,
1383
		esc_url( $upload_iframe_src ),
1384
		'', // Empty when there's no featured image set, `aria-describedby` attribute otherwise.
1385
		esc_html( $post_type_object->labels->set_featured_image )
1386
	);
1387
1388
	if ( $thumbnail_id && get_post( $thumbnail_id ) ) {
0 ignored issues
show
Bug Best Practice introduced by
The expression $thumbnail_id of type integer|null is loosely compared to true; this is ambiguous if the integer can be zero. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
1389
		$size = isset( $_wp_additional_image_sizes['post-thumbnail'] ) ? 'post-thumbnail' : array( 266, 266 );
1390
1391
		/**
1392
		 * Filters the size used to display the post thumbnail image in the 'Featured Image' meta box.
1393
		 *
1394
		 * Note: When a theme adds 'post-thumbnail' support, a special 'post-thumbnail'
1395
		 * image size is registered, which differs from the 'thumbnail' image size
1396
		 * managed via the Settings > Media screen. See the `$size` parameter description
1397
		 * for more information on default values.
1398
		 *
1399
		 * @since 4.4.0
1400
		 *
1401
		 * @param string|array $size         Post thumbnail image size to display in the meta box. Accepts any valid
1402
		 *                                   image size, or an array of width and height values in pixels (in that order).
1403
		 *                                   If the 'post-thumbnail' size is set, default is 'post-thumbnail'. Otherwise,
1404
		 *                                   default is an array with 266 as both the height and width values.
1405
		 * @param int          $thumbnail_id Post thumbnail attachment ID.
1406
		 * @param WP_Post      $post         The post object associated with the thumbnail.
1407
		 */
1408
		$size = apply_filters( 'admin_post_thumbnail_size', $size, $thumbnail_id, $post );
1409
1410
		$thumbnail_html = wp_get_attachment_image( $thumbnail_id, $size );
1411
1412
		if ( ! empty( $thumbnail_html ) ) {
1413
			$content = sprintf( $set_thumbnail_link,
1414
				esc_url( $upload_iframe_src ),
1415
				' aria-describedby="set-post-thumbnail-desc"',
1416
				$thumbnail_html
1417
			);
1418
			$content .= '<p class="hide-if-no-js howto" id="set-post-thumbnail-desc">' . __( 'Click the image to edit or update' ) . '</p>';
1419
			$content .= '<p class="hide-if-no-js"><a href="#" id="remove-post-thumbnail">' . esc_html( $post_type_object->labels->remove_featured_image ) . '</a></p>';
1420
		}
1421
	}
1422
1423
	$content .= '<input type="hidden" id="_thumbnail_id" name="_thumbnail_id" value="' . esc_attr( $thumbnail_id ? $thumbnail_id : '-1' ) . '" />';
1424
1425
	/**
1426
	 * Filters the admin post thumbnail HTML markup to return.
1427
	 *
1428
	 * @since 2.9.0
1429
	 * @since 3.5.0 Added the `$post_id` parameter.
1430
	 * @since 4.6.0 Added the `$thumbnail_id` parameter.
1431
	 *
1432
	 * @param string $content      Admin post thumbnail HTML markup.
1433
	 * @param int    $post_id      Post ID.
1434
	 * @param int    $thumbnail_id Thumbnail ID.
1435
	 */
1436
	return apply_filters( 'admin_post_thumbnail_html', $content, $post->ID, $thumbnail_id );
1437
}
1438
1439
/**
1440
 * Check to see if the post is currently being edited by another user.
1441
 *
1442
 * @since 2.5.0
1443
 *
1444
 * @param int $post_id ID of the post to check for editing
1445
 * @return integer False: not locked or locked by current user. Int: user ID of user with lock.
1446
 */
1447
function wp_check_post_lock( $post_id ) {
1448
	if ( !$post = get_post( $post_id ) )
1449
		return false;
1450
1451
	if ( !$lock = get_post_meta( $post->ID, '_edit_lock', true ) )
1452
		return false;
1453
1454
	$lock = explode( ':', $lock );
1455
	$time = $lock[0];
1456
	$user = isset( $lock[1] ) ? $lock[1] : get_post_meta( $post->ID, '_edit_last', true );
1457
1458
	/** This filter is documented in wp-admin/includes/ajax-actions.php */
1459
	$time_window = apply_filters( 'wp_check_post_lock_window', 150 );
1460
1461
	if ( $time && $time > time() - $time_window && $user != get_current_user_id() )
1462
		return $user;
1463
	return false;
1464
}
1465
1466
/**
1467
 * Mark the post as currently being edited by the current user
1468
 *
1469
 * @since 2.5.0
1470
 *
1471
 * @param int $post_id ID of the post to being edited
1472
 * @return bool|array Returns false if the post doesn't exist of there is no current user, or
1473
 * 	an array of the lock time and the user ID.
1474
 */
1475
function wp_set_post_lock( $post_id ) {
1476
	if ( !$post = get_post( $post_id ) )
1477
		return false;
1478
	if ( 0 == ($user_id = get_current_user_id()) )
1479
		return false;
1480
1481
	$now = time();
1482
	$lock = "$now:$user_id";
1483
1484
	update_post_meta( $post->ID, '_edit_lock', $lock );
1485
	return array( $now, $user_id );
1486
}
1487
1488
/**
1489
 * Outputs the HTML for the notice to say that someone else is editing or has taken over editing of this post.
1490
 *
1491
 * @since 2.8.5
1492
 * @return none
1493
 */
1494
function _admin_notice_post_locked() {
1495
	if ( ! $post = get_post() )
1496
		return;
1497
1498
	$user = null;
1499
	if (  $user_id = wp_check_post_lock( $post->ID ) )
1500
		$user = get_userdata( $user_id );
1501
1502
	if ( $user ) {
1503
1504
		/**
1505
		 * Filters whether to show the post locked dialog.
1506
		 *
1507
		 * Returning a falsey value to the filter will short-circuit displaying the dialog.
1508
		 *
1509
		 * @since 3.6.0
1510
		 *
1511
		 * @param bool         $display Whether to display the dialog. Default true.
1512
		 * @param WP_User|bool $user    WP_User object on success, false otherwise.
1513
		 */
1514
		if ( ! apply_filters( 'show_post_locked_dialog', true, $post, $user ) )
1515
			return;
1516
1517
		$locked = true;
1518
	} else {
1519
		$locked = false;
1520
	}
1521
1522
	if ( $locked && ( $sendback = wp_get_referer() ) &&
1523
		false === strpos( $sendback, 'post.php' ) && false === strpos( $sendback, 'post-new.php' ) ) {
1524
1525
		$sendback_text = __('Go back');
1526
	} else {
1527
		$sendback = admin_url( 'edit.php' );
1528
1529
		if ( 'post' != $post->post_type )
1530
			$sendback = add_query_arg( 'post_type', $post->post_type, $sendback );
1531
1532
		$sendback_text = get_post_type_object( $post->post_type )->labels->all_items;
1533
	}
1534
1535
	$hidden = $locked ? '' : ' hidden';
1536
1537
	?>
1538
	<div id="post-lock-dialog" class="notification-dialog-wrap<?php echo $hidden; ?>">
1539
	<div class="notification-dialog-background"></div>
1540
	<div class="notification-dialog">
1541
	<?php
1542
1543
	if ( $locked ) {
1544
		$query_args = array();
1545
		if ( get_post_type_object( $post->post_type )->public ) {
1546
			if ( 'publish' == $post->post_status || $user->ID != $post->post_author ) {
1547
				// Latest content is in autosave
1548
				$nonce = wp_create_nonce( 'post_preview_' . $post->ID );
1549
				$query_args['preview_id'] = $post->ID;
1550
				$query_args['preview_nonce'] = $nonce;
1551
			}
1552
		}
1553
1554
		$preview_link = get_preview_post_link( $post->ID, $query_args );
1555
1556
		/**
1557
		 * Filters whether to allow the post lock to be overridden.
1558
		 *
1559
		 * Returning a falsey value to the filter will disable the ability
1560
		 * to override the post lock.
1561
		 *
1562
		 * @since 3.6.0
1563
		 *
1564
		 * @param bool    $override Whether to allow overriding post locks. Default true.
1565
		 * @param WP_Post $post     Post object.
1566
		 * @param WP_User $user     User object.
1567
		 */
1568
		$override = apply_filters( 'override_post_lock', true, $post, $user );
1569
		$tab_last = $override ? '' : ' wp-tab-last';
1570
1571
		?>
1572
		<div class="post-locked-message">
1573
		<div class="post-locked-avatar"><?php echo get_avatar( $user->ID, 64 ); ?></div>
1574
		<p class="currently-editing wp-tab-first" tabindex="0">
1575
		<?php
1576
			_e( 'This content is currently locked.' );
1577
			if ( $override )
1578
				printf( ' ' . __( 'If you take over, %s will be blocked from continuing to edit.' ), esc_html( $user->display_name ) );
1579
		?>
1580
		</p>
1581
		<?php
1582
		/**
1583
		 * Fires inside the post locked dialog before the buttons are displayed.
1584
		 *
1585
		 * @since 3.6.0
1586
		 *
1587
		 * @param WP_Post $post Post object.
1588
		 */
1589
		do_action( 'post_locked_dialog', $post );
1590
		?>
1591
		<p>
1592
		<a class="button" href="<?php echo esc_url( $sendback ); ?>"><?php echo $sendback_text; ?></a>
1593
		<?php if ( $preview_link ) { ?>
0 ignored issues
show
Bug Best Practice introduced by
The expression $preview_link of type string|null is loosely compared to true; this is ambiguous if the string can be empty. You might want to explicitly use !== null instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For string values, the empty string '' is a special case, in particular the following results might be unexpected:

''   == false // true
''   == null  // true
'ab' == false // false
'ab' == null  // false

// It is often better to use strict comparison
'' === false // false
'' === null  // false
Loading history...
1594
		<a class="button<?php echo $tab_last; ?>" href="<?php echo esc_url( $preview_link ); ?>"><?php _e('Preview'); ?></a>
1595
		<?php
1596
		}
1597
1598
		// Allow plugins to prevent some users overriding the post lock
1599
		if ( $override ) {
1600
			?>
1601
			<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>
1602
			<?php
1603
		}
1604
1605
		?>
1606
		</p>
1607
		</div>
1608
		<?php
1609
	} else {
1610
		?>
1611
		<div class="post-taken-over">
1612
			<div class="post-locked-avatar"></div>
1613
			<p class="wp-tab-first" tabindex="0">
1614
			<span class="currently-editing"></span><br />
1615
			<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&hellip;' ); ?></span>
1616
			<span class="locked-saved hidden"><?php _e('Your latest changes were saved as a revision.'); ?></span>
1617
			</p>
1618
			<?php
1619
			/**
1620
			 * Fires inside the dialog displayed when a user has lost the post lock.
1621
			 *
1622
			 * @since 3.6.0
1623
			 *
1624
			 * @param WP_Post $post Post object.
1625
			 */
1626
			do_action( 'post_lock_lost_dialog', $post );
1627
			?>
1628
			<p><a class="button button-primary wp-tab-last" href="<?php echo esc_url( $sendback ); ?>"><?php echo $sendback_text; ?></a></p>
1629
		</div>
1630
		<?php
1631
	}
1632
1633
	?>
1634
	</div>
1635
	</div>
1636
	<?php
1637
}
1638
1639
/**
1640
 * Creates autosave data for the specified post from $_POST data.
1641
 *
1642
 * @package WordPress
1643
 * @subpackage Post_Revisions
1644
 * @since 2.6.0
1645
 *
1646
 * @param mixed $post_data Associative array containing the post data or int post ID.
1647
 * @return mixed The autosave revision ID. WP_Error or 0 on error.
1648
 */
1649
function wp_create_post_autosave( $post_data ) {
1650
	if ( is_numeric( $post_data ) ) {
1651
		$post_id = $post_data;
1652
		$post_data = $_POST;
1653
	} else {
1654
		$post_id = (int) $post_data['post_ID'];
1655
	}
1656
1657
	$post_data = _wp_translate_postdata( true, $post_data );
1658
	if ( is_wp_error( $post_data ) )
1659
		return $post_data;
1660
1661
	$post_author = get_current_user_id();
1662
1663
	// Store one autosave per author. If there is already an autosave, overwrite it.
1664
	if ( $old_autosave = wp_get_post_autosave( $post_id, $post_author ) ) {
1665
		$new_autosave = _wp_post_revision_data( $post_data, true );
0 ignored issues
show
Bug introduced by
It seems like $post_data defined by _wp_translate_postdata(true, $post_data) on line 1657 can also be of type object<WP_Error>; however, _wp_post_revision_data() does only seem to accept array|object<WP_Post>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1666
		$new_autosave['ID'] = $old_autosave->ID;
1667
		$new_autosave['post_author'] = $post_author;
1668
1669
		// If the new autosave has the same content as the post, delete the autosave.
1670
		$post = get_post( $post_id );
1671
		$autosave_is_different = false;
1672
		foreach ( array_intersect( array_keys( $new_autosave ), array_keys( _wp_post_revision_fields( $post ) ) ) as $field ) {
0 ignored issues
show
Bug introduced by
It seems like $post defined by get_post($post_id) on line 1670 can also be of type null; however, _wp_post_revision_fields() does only seem to accept array|object<WP_Post>, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1673
			if ( normalize_whitespace( $new_autosave[ $field ] ) != normalize_whitespace( $post->$field ) ) {
1674
				$autosave_is_different = true;
1675
				break;
1676
			}
1677
		}
1678
1679
		if ( ! $autosave_is_different ) {
1680
			wp_delete_post_revision( $old_autosave->ID );
1681
			return 0;
1682
		}
1683
1684
		/**
1685
		 * Fires before an autosave is stored.
1686
		 *
1687
		 * @since 4.1.0
1688
		 *
1689
		 * @param array $new_autosave Post array - the autosave that is about to be saved.
1690
		 */
1691
		do_action( 'wp_creating_autosave', $new_autosave );
1692
1693
		return wp_update_post( $new_autosave );
1694
	}
1695
1696
	// _wp_put_post_revision() expects unescaped.
1697
	$post_data = wp_unslash( $post_data );
0 ignored issues
show
Bug introduced by
It seems like $post_data can also be of type object<WP_Error>; however, wp_unslash() does only seem to accept string|array, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1698
1699
	// Otherwise create the new autosave as a special post revision
1700
	return _wp_put_post_revision( $post_data, true );
0 ignored issues
show
Bug introduced by
It seems like $post_data defined by wp_unslash($post_data) on line 1697 can also be of type string; however, _wp_put_post_revision() does only seem to accept integer|object<WP_Post>|array|null, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1701
}
1702
1703
/**
1704
 * Save draft or manually autosave for showing preview.
1705
 *
1706
 * @package WordPress
1707
 * @since 2.7.0
1708
 *
1709
 * @return str URL to redirect to show the preview
1710
 */
1711
function post_preview() {
1712
1713
	$post_ID = (int) $_POST['post_ID'];
1714
	$_POST['ID'] = $post_ID;
1715
1716
	if ( ! $post = get_post( $post_ID ) ) {
1717
		wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
1718
	}
1719
1720
	if ( ! current_user_can( 'edit_post', $post->ID ) ) {
1721
		wp_die( __( 'Sorry, you are not allowed to edit this post.' ) );
1722
	}
1723
1724
	$is_autosave = false;
1725
1726
	if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() == $post->post_author && ( 'draft' == $post->post_status || 'auto-draft' == $post->post_status ) ) {
1727
		$saved_post_id = edit_post();
1728
	} else {
1729
		$is_autosave = true;
1730
1731
		if ( isset( $_POST['post_status'] ) && 'auto-draft' == $_POST['post_status'] )
1732
			$_POST['post_status'] = 'draft';
1733
1734
		$saved_post_id = wp_create_post_autosave( $post->ID );
1735
	}
1736
1737
	if ( is_wp_error( $saved_post_id ) )
1738
		wp_die( $saved_post_id->get_error_message() );
1739
1740
	$query_args = array();
1741
1742
	if ( $is_autosave && $saved_post_id ) {
1743
		$query_args['preview_id'] = $post->ID;
1744
		$query_args['preview_nonce'] = wp_create_nonce( 'post_preview_' . $post->ID );
1745
1746
		if ( isset( $_POST['post_format'] ) ) {
1747
			$query_args['post_format'] = empty( $_POST['post_format'] ) ? 'standard' : sanitize_key( $_POST['post_format'] );
1748
		}
1749
1750
		if ( isset( $_POST['_thumbnail_id'] ) ) {
1751
			$query_args['_thumbnail_id'] = ( intval( $_POST['_thumbnail_id'] ) <= 0 ) ? '-1' : intval( $_POST['_thumbnail_id'] );
1752
		}
1753
	}
1754
1755
	return get_preview_post_link( $post, $query_args );
0 ignored issues
show
Bug introduced by
It seems like $post defined by get_post($post_ID) on line 1716 can also be of type array; however, get_preview_post_link() does only seem to accept integer|object<WP_Post>|null, maybe add an additional type check?

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

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

    return array();
}

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

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

Loading history...
1756
}
1757
1758
/**
1759
 * Save a post submitted with XHR
1760
 *
1761
 * Intended for use with heartbeat and autosave.js
1762
 *
1763
 * @since 3.9.0
1764
 *
1765
 * @param array $post_data Associative array of the submitted post data.
1766
 * @return mixed The value 0 or WP_Error on failure. The saved post ID on success.
1767
 *               The ID can be the draft post_id or the autosave revision post_id.
1768
 */
1769
function wp_autosave( $post_data ) {
1770
	// Back-compat
1771
	if ( ! defined( 'DOING_AUTOSAVE' ) )
1772
		define( 'DOING_AUTOSAVE', true );
1773
1774
	$post_id = (int) $post_data['post_id'];
1775
	$post_data['ID'] = $post_data['post_ID'] = $post_id;
1776
1777
	if ( false === wp_verify_nonce( $post_data['_wpnonce'], 'update-post_' . $post_id ) ) {
1778
		return new WP_Error( 'invalid_nonce', __( 'Error while saving.' ) );
1779
	}
1780
1781
	$post = get_post( $post_id );
1782
1783
	if ( ! current_user_can( 'edit_post', $post->ID ) ) {
1784
		return new WP_Error( 'edit_posts', __( 'Sorry, you are not allowed to edit this item.' ) );
1785
	}
1786
1787
	if ( 'auto-draft' == $post->post_status )
1788
		$post_data['post_status'] = 'draft';
1789
1790
	if ( $post_data['post_type'] != 'page' && ! empty( $post_data['catslist'] ) )
1791
		$post_data['post_category'] = explode( ',', $post_data['catslist'] );
1792
1793
	if ( ! wp_check_post_lock( $post->ID ) && get_current_user_id() == $post->post_author && ( 'auto-draft' == $post->post_status || 'draft' == $post->post_status ) ) {
1794
		// Drafts and auto-drafts are just overwritten by autosave for the same user if the post is not locked
1795
		return edit_post( wp_slash( $post_data ) );
0 ignored issues
show
Bug introduced by
It seems like wp_slash($post_data) targeting wp_slash() can also be of type string; however, edit_post() does only seem to accept array|null, maybe add an additional type check?

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

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

An additional type check may prevent trouble.

Loading history...
1796
	} else {
1797
		// Non drafts or other users drafts are not overwritten. The autosave is stored in a special post revision for each user.
1798
		return wp_create_post_autosave( wp_slash( $post_data ) );
1799
	}
1800
}
1801
1802
/**
1803
 * Redirect to previous page.
1804
 *
1805
 * @param int $post_id Optional. Post ID.
1806
 */
1807
function redirect_post($post_id = '') {
1808
	if ( isset($_POST['save']) || isset($_POST['publish']) ) {
1809
		$status = get_post_status( $post_id );
1810
1811
		if ( isset( $_POST['publish'] ) ) {
1812
			switch ( $status ) {
1813
				case 'pending':
1814
					$message = 8;
1815
					break;
1816
				case 'future':
1817
					$message = 9;
1818
					break;
1819
				default:
1820
					$message = 6;
1821
			}
1822
		} else {
1823
			$message = 'draft' == $status ? 10 : 1;
1824
		}
1825
1826
		$location = add_query_arg( 'message', $message, get_edit_post_link( $post_id, 'url' ) );
1827
	} elseif ( isset($_POST['addmeta']) && $_POST['addmeta'] ) {
1828
		$location = add_query_arg( 'message', 2, wp_get_referer() );
1829
		$location = explode('#', $location);
1830
		$location = $location[0] . '#postcustom';
1831
	} elseif ( isset($_POST['deletemeta']) && $_POST['deletemeta'] ) {
1832
		$location = add_query_arg( 'message', 3, wp_get_referer() );
1833
		$location = explode('#', $location);
1834
		$location = $location[0] . '#postcustom';
1835
	} else {
1836
		$location = add_query_arg( 'message', 4, get_edit_post_link( $post_id, 'url' ) );
1837
	}
1838
1839
	/**
1840
	 * Filters the post redirect destination URL.
1841
	 *
1842
	 * @since 2.9.0
1843
	 *
1844
	 * @param string $location The destination URL.
1845
	 * @param int    $post_id  The post ID.
1846
	 */
1847
	wp_redirect( apply_filters( 'redirect_post_location', $location, $post_id ) );
1848
	exit;
1849
}
1850