]> scripts.mit.edu Git - autoinstalls/wordpress.git/blob - wp-admin/includes/post.php
bb6f469fccaa362587f22e24784bf18936ae04f5
[autoinstalls/wordpress.git] / wp-admin / includes / post.php
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         $post_data['post_content'] = isset($post_data['content']) ? $post_data['content'] : '';
29         $post_data['post_excerpt'] = isset($post_data['excerpt']) ? $post_data['excerpt'] : '';
30         $post_data['post_parent'] = isset($post_data['parent_id'])? $post_data['parent_id'] : '';
31         if ( isset($post_data['trackback_url']) )
32                 $post_data['to_ping'] = $post_data['trackback_url'];
33
34         if ( !isset($post_data['user_ID']) )
35                 $post_data['user_ID'] = $GLOBALS['user_ID'];
36
37         if (!empty ( $post_data['post_author_override'] ) ) {
38                 $post_data['post_author'] = (int) $post_data['post_author_override'];
39         } else {
40                 if (!empty ( $post_data['post_author'] ) ) {
41                         $post_data['post_author'] = (int) $post_data['post_author'];
42                 } else {
43                         $post_data['post_author'] = (int) $post_data['user_ID'];
44                 }
45         }
46
47         $ptype = get_post_type_object( $post_data['post_type'] );
48         if ( isset($post_data['user_ID']) && ($post_data['post_author'] != $post_data['user_ID']) ) {
49                 if ( !current_user_can( $ptype->cap->edit_others_posts ) ) {
50                         if ( 'page' == $post_data['post_type'] ) {
51                                 return new WP_Error( 'edit_others_pages', $update ?
52                                         __( 'You are not allowed to edit pages as this user.' ) :
53                                         __( 'You are not allowed to create pages as this user.' )
54                                 );
55                         } else {
56                                 return new WP_Error( 'edit_others_posts', $update ?
57                                         __( 'You are not allowed to edit posts as this user.' ) :
58                                         __( 'You are not allowed to post as this user.' )
59                                 );
60                         }
61                 }
62         }
63
64         // What to do based on which button they pressed
65         if ( isset($post_data['saveasdraft']) && '' != $post_data['saveasdraft'] )
66                 $post_data['post_status'] = 'draft';
67         if ( isset($post_data['saveasprivate']) && '' != $post_data['saveasprivate'] )
68                 $post_data['post_status'] = 'private';
69         if ( isset($post_data['publish']) && ( '' != $post_data['publish'] ) && ( !isset($post_data['post_status']) || $post_data['post_status'] != 'private' ) )
70                 $post_data['post_status'] = 'publish';
71         if ( isset($post_data['advanced']) && '' != $post_data['advanced'] )
72                 $post_data['post_status'] = 'draft';
73         if ( isset($post_data['pending']) && '' != $post_data['pending'] )
74                 $post_data['post_status'] = 'pending';
75
76         if ( isset( $post_data['ID'] ) )
77                 $post_id = $post_data['ID'];
78         else
79                 $post_id = false;
80         $previous_status = $post_id ? get_post_field( 'post_status', $post_id ) : false;
81
82         // Posts 'submitted for approval' present are submitted to $_POST the same as if they were being published.
83         // Change status from 'publish' to 'pending' if user lacks permissions to publish or to resave published posts.
84         if ( isset($post_data['post_status']) && ('publish' == $post_data['post_status'] && !current_user_can( $ptype->cap->publish_posts )) )
85                 if ( $previous_status != 'publish' || !current_user_can( 'edit_post', $post_id ) )
86                         $post_data['post_status'] = 'pending';
87
88         if ( ! isset($post_data['post_status']) )
89                 $post_data['post_status'] = $previous_status;
90
91         if (!isset( $post_data['comment_status'] ))
92                 $post_data['comment_status'] = 'closed';
93
94         if (!isset( $post_data['ping_status'] ))
95                 $post_data['ping_status'] = 'closed';
96
97         foreach ( array('aa', 'mm', 'jj', 'hh', 'mn') as $timeunit ) {
98                 if ( !empty( $post_data['hidden_' . $timeunit] ) && $post_data['hidden_' . $timeunit] != $post_data[$timeunit] ) {
99                         $post_data['edit_date'] = '1';
100                         break;
101                 }
102         }
103
104         if ( !empty( $post_data['edit_date'] ) ) {
105                 $aa = $post_data['aa'];
106                 $mm = $post_data['mm'];
107                 $jj = $post_data['jj'];
108                 $hh = $post_data['hh'];
109                 $mn = $post_data['mn'];
110                 $ss = $post_data['ss'];
111                 $aa = ($aa <= 0 ) ? date('Y') : $aa;
112                 $mm = ($mm <= 0 ) ? date('n') : $mm;
113                 $jj = ($jj > 31 ) ? 31 : $jj;
114                 $jj = ($jj <= 0 ) ? date('j') : $jj;
115                 $hh = ($hh > 23 ) ? $hh -24 : $hh;
116                 $mn = ($mn > 59 ) ? $mn -60 : $mn;
117                 $ss = ($ss > 59 ) ? $ss -60 : $ss;
118                 $post_data['post_date'] = sprintf( "%04d-%02d-%02d %02d:%02d:%02d", $aa, $mm, $jj, $hh, $mn, $ss );
119                 $post_data['post_date_gmt'] = get_gmt_from_date( $post_data['post_date'] );
120         }
121
122         return $post_data;
123 }
124
125 /**
126  * Update an existing post with values provided in $_POST.
127  *
128  * @since 1.5.0
129  *
130  * @param array $post_data Optional.
131  * @return int Post ID.
132  */
133 function edit_post( $post_data = null ) {
134
135         if ( empty($post_data) )
136                 $post_data = &$_POST;
137
138         $post_ID = (int) $post_data['post_ID'];
139         $post = get_post( $post_ID );
140         $post_data['post_type'] = $post->post_type;
141         $post_data['post_mime_type'] = $post->post_mime_type;
142
143         $ptype = get_post_type_object($post_data['post_type']);
144         if ( !current_user_can( $ptype->cap->edit_post, $post_ID ) ) {
145                 if ( 'page' == $post_data['post_type'] )
146                         wp_die( __('You are not allowed to edit this page.' ));
147                 else
148                         wp_die( __('You are not allowed to edit this post.' ));
149         }
150
151         // Autosave shouldn't save too soon after a real save
152         if ( 'autosave' == $post_data['action'] ) {
153                 $post =& get_post( $post_ID );
154                 $now = time();
155                 $then = strtotime($post->post_date_gmt . ' +0000');
156                 $delta = AUTOSAVE_INTERVAL / 2;
157                 if ( ($now - $then) < $delta )
158                         return $post_ID;
159         }
160
161         $post_data = _wp_translate_postdata( true, $post_data );
162         if ( is_wp_error($post_data) )
163                 wp_die( $post_data->get_error_message() );
164         if ( 'autosave' != $post_data['action']  && 'auto-draft' == $post_data['post_status'] )
165                 $post_data['post_status'] = 'draft';
166
167         if ( isset($post_data['visibility']) ) {
168                 switch ( $post_data['visibility'] ) {
169                         case 'public' :
170                                 $post_data['post_password'] = '';
171                                 break;
172                         case 'password' :
173                                 unset( $post_data['sticky'] );
174                                 break;
175                         case 'private' :
176                                 $post_data['post_status'] = 'private';
177                                 $post_data['post_password'] = '';
178                                 unset( $post_data['sticky'] );
179                                 break;
180                 }
181         }
182
183         // Post Formats
184         if ( current_theme_supports( 'post-formats' ) && isset( $post_data['post_format'] ) ) {
185                 $formats = get_theme_support( 'post-formats' );
186                 if ( is_array( $formats ) ) {
187                         $formats = $formats[0];
188                         if ( in_array( $post_data['post_format'], $formats ) ) {
189                                 set_post_format( $post_ID, $post_data['post_format'] );
190                         } elseif ( '0' == $post_data['post_format'] ) {
191                                 set_post_format( $post_ID, false );
192                         }
193                 }
194         }
195
196         // Meta Stuff
197         if ( isset($post_data['meta']) && $post_data['meta'] ) {
198                 foreach ( $post_data['meta'] as $key => $value ) {
199                         if ( !$meta = get_post_meta_by_id( $key ) )
200                                 continue;
201                         if ( $meta->post_id != $post_ID )
202                                 continue;
203                         if ( is_protected_meta( $value['key'] ) )
204                                 continue;
205                         update_meta( $key, $value['key'], $value['value'] );
206                 }
207         }
208
209         if ( isset($post_data['deletemeta']) && $post_data['deletemeta'] ) {
210                 foreach ( $post_data['deletemeta'] as $key => $value ) {
211                         if ( !$meta = get_post_meta_by_id( $key ) )
212                                 continue;
213                         if ( $meta->post_id != $post_ID )
214                                 continue;
215                         if ( is_protected_meta( $meta->meta_key ) )
216                                 continue;
217                         delete_meta( $key );
218                 }
219         }
220
221         add_meta( $post_ID );
222
223         update_post_meta( $post_ID, '_edit_last', $GLOBALS['current_user']->ID );
224
225         wp_update_post( $post_data );
226
227         // Reunite any orphaned attachments with their parent
228         if ( !$draft_ids = get_user_option( 'autosave_draft_ids' ) )
229                 $draft_ids = array();
230         if ( $draft_temp_id = (int) array_search( $post_ID, $draft_ids ) )
231                 _relocate_children( $draft_temp_id, $post_ID );
232
233         // Now that we have an ID we can fix any attachment anchor hrefs
234         _fix_attachment_links( $post_ID );
235
236         wp_set_post_lock( $post_ID, $GLOBALS['current_user']->ID );
237
238         if ( current_user_can( $ptype->cap->edit_others_posts ) ) {
239                 if ( ! empty( $post_data['sticky'] ) )
240                         stick_post( $post_ID );
241                 else
242                         unstick_post( $post_ID );
243         }
244
245         return $post_ID;
246 }
247
248 /**
249  * Process the post data for the bulk editing of posts.
250  *
251  * Updates all bulk edited posts/pages, adding (but not removing) tags and
252  * categories. Skips pages when they would be their own parent or child.
253  *
254  * @since 2.7.0
255  *
256  * @param array $post_data Optional, the array of post data to process if not provided will use $_POST superglobal.
257  * @return array
258  */
259 function bulk_edit_posts( $post_data = null ) {
260         global $wpdb;
261
262         if ( empty($post_data) )
263                 $post_data = &$_POST;
264
265         if ( isset($post_data['post_type']) )
266                 $ptype = get_post_type_object($post_data['post_type']);
267         else
268                 $ptype = get_post_type_object('post');
269
270         if ( !current_user_can( $ptype->cap->edit_posts ) ) {
271                 if ( 'page' == $ptype->name )
272                         wp_die( __('You are not allowed to edit pages.'));
273                 else
274                         wp_die( __('You are not allowed to edit posts.'));
275         }
276
277         if ( -1 == $post_data['_status'] ) {
278                 $post_data['post_status'] = null;
279                 unset($post_data['post_status']);
280         } else {
281                 $post_data['post_status'] = $post_data['_status'];
282         }
283         unset($post_data['_status']);
284
285         $post_IDs = array_map( 'intval', (array) $post_data['post'] );
286
287         $reset = array( 'post_author', 'post_status', 'post_password', 'post_parent', 'page_template', 'comment_status', 'ping_status', 'keep_private', 'tax_input', 'post_category', 'sticky' );
288         foreach ( $reset as $field ) {
289                 if ( isset($post_data[$field]) && ( '' == $post_data[$field] || -1 == $post_data[$field] ) )
290                         unset($post_data[$field]);
291         }
292
293         if ( isset($post_data['post_category']) ) {
294                 if ( is_array($post_data['post_category']) && ! empty($post_data['post_category']) )
295                         $new_cats = array_map( 'absint', $post_data['post_category'] );
296                 else
297                         unset($post_data['post_category']);
298         }
299
300         $tax_input = array();
301         if ( isset($post_data['tax_input'])) {
302                 foreach ( $post_data['tax_input'] as $tax_name => $terms ) {
303                         if ( empty($terms) )
304                                 continue;
305                         if ( is_taxonomy_hierarchical( $tax_name ) )
306                                 $tax_input[$tax_name] = array_map( 'absint', $terms );
307                         else {
308                                 $tax_input[$tax_name] = preg_replace( '/\s*,\s*/', ',', rtrim( trim($terms), ' ,' ) );
309                                 $tax_input[$tax_name] = explode(',', $tax_input[$tax_name]);
310                         }
311                 }
312         }
313
314         if ( isset($post_data['post_parent']) && ($parent = (int) $post_data['post_parent']) ) {
315                 $pages = $wpdb->get_results("SELECT ID, post_parent FROM $wpdb->posts WHERE post_type = 'page'");
316                 $children = array();
317
318                 for ( $i = 0; $i < 50 && $parent > 0; $i++ ) {
319                         $children[] = $parent;
320
321                         foreach ( $pages as $page ) {
322                                 if ( $page->ID == $parent ) {
323                                         $parent = $page->post_parent;
324                                         break;
325                                 }
326                         }
327                 }
328         }
329
330         $updated = $skipped = $locked = array();
331         foreach ( $post_IDs as $post_ID ) {
332                 $post_type_object = get_post_type_object( get_post_type( $post_ID ) );
333
334                 if ( !isset( $post_type_object ) || ( isset($children) && in_array($post_ID, $children) ) || !current_user_can( $post_type_object->cap->edit_post, $post_ID ) ) {
335                         $skipped[] = $post_ID;
336                         continue;
337                 }
338
339                 if ( wp_check_post_lock( $post_ID ) ) {
340                         $locked[] = $post_ID;
341                         continue;
342                 }
343
344                 $tax_names = get_object_taxonomies( get_post($post_ID) );
345                 foreach ( $tax_names as $tax_name ) {
346                         $taxonomy_obj = get_taxonomy($tax_name);
347                         if (  isset( $tax_input[$tax_name]) && current_user_can( $taxonomy_obj->cap->assign_terms ) )
348                                 $new_terms = $tax_input[$tax_name];
349                         else
350                                 $new_terms = array();
351
352                         if ( $taxonomy_obj->hierarchical )
353                                 $current_terms = (array) wp_get_object_terms( $post_ID, $tax_name, array('fields' => 'ids') );
354                         else
355                                 $current_terms = (array) wp_get_object_terms( $post_ID, $tax_name, array('fields' => 'names') );
356
357                         $post_data['tax_input'][$tax_name] = array_merge( $current_terms, $new_terms );
358                 }
359
360                 if ( isset($new_cats) && in_array( 'category', $tax_names ) ) {
361                         $cats = (array) wp_get_post_categories($post_ID);
362                         $post_data['post_category'] = array_unique( array_merge($cats, $new_cats) );
363                         unset( $post_data['tax_input']['category'] );
364                 }
365
366                 $post_data['ID'] = $post_ID;
367                 $updated[] = wp_update_post( $post_data );
368
369                 if ( isset( $post_data['sticky'] ) && current_user_can( $ptype->cap->edit_others_posts ) ) {
370                         if ( 'sticky' == $post_data['sticky'] )
371                                 stick_post( $post_ID );
372                         else
373                                 unstick_post( $post_ID );
374                 }
375
376         }
377
378         return array( 'updated' => $updated, 'skipped' => $skipped, 'locked' => $locked );
379 }
380
381 /**
382  * Default post information to use when populating the "Write Post" form.
383  *
384  * @since 2.0.0
385  *
386  * @param string $post_type A post type string, defaults to 'post'.
387  * @return object stdClass object containing all the default post data as attributes
388  */
389 function get_default_post_to_edit( $post_type = 'post', $create_in_db = false ) {
390         global $wpdb;
391
392         $post_title = '';
393         if ( !empty( $_REQUEST['post_title'] ) )
394                 $post_title = esc_html( stripslashes( $_REQUEST['post_title'] ));
395
396         $post_content = '';
397         if ( !empty( $_REQUEST['content'] ) )
398                 $post_content = esc_html( stripslashes( $_REQUEST['content'] ));
399
400         $post_excerpt = '';
401         if ( !empty( $_REQUEST['excerpt'] ) )
402                 $post_excerpt = esc_html( stripslashes( $_REQUEST['excerpt'] ));
403
404         if ( $create_in_db ) {
405                 // Cleanup old auto-drafts more than 7 days old
406                 $old_posts = $wpdb->get_col( "SELECT ID FROM $wpdb->posts WHERE post_status = 'auto-draft' AND DATE_SUB( NOW(), INTERVAL 7 DAY ) > post_date" );
407                 foreach ( (array) $old_posts as $delete )
408                         wp_delete_post( $delete, true ); // Force delete
409                 $post_id = wp_insert_post( array( 'post_title' => __( 'Auto Draft' ), 'post_type' => $post_type, 'post_status' => 'auto-draft' ) );
410                 $post = get_post( $post_id );
411                 if ( current_theme_supports( 'post-formats' ) && post_type_supports( $post->post_type, 'post-formats' ) && get_option( 'default_post_format' ) )
412                         set_post_format( $post, get_option( 'default_post_format' ) );
413         } else {
414                 $post->ID = 0;
415                 $post->post_author = '';
416                 $post->post_date = '';
417                 $post->post_date_gmt = '';
418                 $post->post_password = '';
419                 $post->post_type = $post_type;
420                 $post->post_status = 'draft';
421                 $post->to_ping = '';
422                 $post->pinged = '';
423                 $post->comment_status = get_option( 'default_comment_status' );
424                 $post->ping_status = get_option( 'default_ping_status' );
425                 $post->post_pingback = get_option( 'default_pingback_flag' );
426                 $post->post_category = get_option( 'default_category' );
427                 $post->page_template = 'default';
428                 $post->post_parent = 0;
429                 $post->menu_order = 0;
430         }
431
432         $post->post_content = apply_filters( 'default_content', $post_content, $post );
433         $post->post_title   = apply_filters( 'default_title',   $post_title, $post   );
434         $post->post_excerpt = apply_filters( 'default_excerpt', $post_excerpt, $post );
435         $post->post_name = '';
436
437         return $post;
438 }
439
440 /**
441  * Get the default page information to use.
442  *
443  * @since 2.5.0
444  *
445  * @return object stdClass object containing all the default post data as attributes
446  */
447 function get_default_page_to_edit() {
448         $page = get_default_post_to_edit();
449         $page->post_type = 'page';
450         return $page;
451 }
452
453 /**
454  * Get an existing post and format it for editing.
455  *
456  * @since 2.0.0
457  *
458  * @param unknown_type $id
459  * @return unknown
460  */
461 function get_post_to_edit( $id ) {
462
463         $post = get_post( $id, OBJECT, 'edit' );
464
465         if ( $post->post_type == 'page' )
466                 $post->page_template = get_post_meta( $id, '_wp_page_template', true );
467
468         return $post;
469 }
470
471 /**
472  * Determine if a post exists based on title, content, and date
473  *
474  * @since 2.0.0
475  *
476  * @param string $title Post title
477  * @param string $content Optional post content
478  * @param string $date Optional post date
479  * @return int Post ID if post exists, 0 otherwise.
480  */
481 function post_exists($title, $content = '', $date = '') {
482         global $wpdb;
483
484         $post_title = stripslashes( sanitize_post_field( 'post_title', $title, 0, 'db' ) );
485         $post_content = stripslashes( sanitize_post_field( 'post_content', $content, 0, 'db' ) );
486         $post_date = stripslashes( sanitize_post_field( 'post_date', $date, 0, 'db' ) );
487
488         $query = "SELECT ID FROM $wpdb->posts WHERE 1=1";
489         $args = array();
490
491         if ( !empty ( $date ) ) {
492                 $query .= ' AND post_date = %s';
493                 $args[] = $post_date;
494         }
495
496         if ( !empty ( $title ) ) {
497                 $query .= ' AND post_title = %s';
498                 $args[] = $post_title;
499         }
500
501         if ( !empty ( $content ) ) {
502                 $query .= 'AND post_content = %s';
503                 $args[] = $post_content;
504         }
505
506         if ( !empty ( $args ) )
507                 return $wpdb->get_var( $wpdb->prepare($query, $args) );
508
509         return 0;
510 }
511
512 /**
513  * Creates a new post from the "Write Post" form using $_POST information.
514  *
515  * @since 2.1.0
516  *
517  * @return unknown
518  */
519 function wp_write_post() {
520         global $user_ID;
521
522
523         if ( isset($_POST['post_type']) )
524                 $ptype = get_post_type_object($_POST['post_type']);
525         else
526                 $ptype = get_post_type_object('post');
527
528         if ( !current_user_can( $ptype->cap->edit_posts ) ) {
529                 if ( 'page' == $ptype->name )
530                         return new WP_Error( 'edit_pages', __( 'You are not allowed to create pages on this site.' ) );
531                 else
532                         return new WP_Error( 'edit_posts', __( 'You are not allowed to create posts or drafts on this site.' ) );
533         }
534
535         $_POST['post_mime_type'] = '';
536
537         // Check for autosave collisions
538         // Does this need to be updated? ~ Mark
539         $temp_id = false;
540         if ( isset($_POST['temp_ID']) ) {
541                 $temp_id = (int) $_POST['temp_ID'];
542                 if ( !$draft_ids = get_user_option( 'autosave_draft_ids' ) )
543                         $draft_ids = array();
544                 foreach ( $draft_ids as $temp => $real )
545                         if ( time() + $temp > 86400 ) // 1 day: $temp is equal to -1 * time( then )
546                                 unset($draft_ids[$temp]);
547
548                 if ( isset($draft_ids[$temp_id]) ) { // Edit, don't write
549                         $_POST['post_ID'] = $draft_ids[$temp_id];
550                         unset($_POST['temp_ID']);
551                         update_user_option( $user_ID, 'autosave_draft_ids', $draft_ids );
552                         return edit_post();
553                 }
554         }
555
556         $translated = _wp_translate_postdata( false );
557         if ( is_wp_error($translated) )
558                 return $translated;
559
560         if ( isset($_POST['visibility']) ) {
561                 switch ( $_POST['visibility'] ) {
562                         case 'public' :
563                                 $_POST['post_password'] = '';
564                                 break;
565                         case 'password' :
566                                 unset( $_POST['sticky'] );
567                                 break;
568                         case 'private' :
569                                 $_POST['post_status'] = 'private';
570                                 $_POST['post_password'] = '';
571                                 unset( $_POST['sticky'] );
572                                 break;
573                 }
574         }
575
576         // Create the post.
577         $post_ID = wp_insert_post( $_POST );
578         if ( is_wp_error( $post_ID ) )
579                 return $post_ID;
580
581         if ( empty($post_ID) )
582                 return 0;
583
584         add_meta( $post_ID );
585
586         add_post_meta( $post_ID, '_edit_last', $GLOBALS['current_user']->ID );
587
588         // Reunite any orphaned attachments with their parent
589         // Does this need to be udpated? ~ Mark
590         if ( !$draft_ids = get_user_option( 'autosave_draft_ids' ) )
591                 $draft_ids = array();
592         if ( $draft_temp_id = (int) array_search( $post_ID, $draft_ids ) )
593                 _relocate_children( $draft_temp_id, $post_ID );
594         if ( $temp_id && $temp_id != $draft_temp_id )
595                 _relocate_children( $temp_id, $post_ID );
596
597         // Update autosave collision detection
598         if ( $temp_id ) {
599                 $draft_ids[$temp_id] = $post_ID;
600                 update_user_option( $user_ID, 'autosave_draft_ids', $draft_ids );
601         }
602
603         // Now that we have an ID we can fix any attachment anchor hrefs
604         _fix_attachment_links( $post_ID );
605
606         wp_set_post_lock( $post_ID, $GLOBALS['current_user']->ID );
607
608         return $post_ID;
609 }
610
611 /**
612  * Calls wp_write_post() and handles the errors.
613  *
614  * @since 2.0.0
615  *
616  * @return unknown
617  */
618 function write_post() {
619         $result = wp_write_post();
620         if ( is_wp_error( $result ) )
621                 wp_die( $result->get_error_message() );
622         else
623                 return $result;
624 }
625
626 //
627 // Post Meta
628 //
629
630 /**
631  * {@internal Missing Short Description}}
632  *
633  * @since 1.2.0
634  *
635  * @param unknown_type $post_ID
636  * @return unknown
637  */
638 function add_meta( $post_ID ) {
639         global $wpdb;
640         $post_ID = (int) $post_ID;
641
642         $metakeyselect = isset($_POST['metakeyselect']) ? stripslashes( trim( $_POST['metakeyselect'] ) ) : '';
643         $metakeyinput = isset($_POST['metakeyinput']) ? stripslashes( trim( $_POST['metakeyinput'] ) ) : '';
644         $metavalue = isset($_POST['metavalue']) ? maybe_serialize( stripslashes_deep( $_POST['metavalue'] ) ) : '';
645         if ( is_string($metavalue) )
646                 $metavalue = trim( $metavalue );
647
648         if ( ('0' === $metavalue || !empty ( $metavalue ) ) && ((('#NONE#' != $metakeyselect) && !empty ( $metakeyselect) ) || !empty ( $metakeyinput) ) ) {
649                 // We have a key/value pair. If both the select and the
650                 // input for the key have data, the input takes precedence:
651
652                 if ('#NONE#' != $metakeyselect)
653                         $metakey = $metakeyselect;
654
655                 if ( $metakeyinput)
656                         $metakey = $metakeyinput; // default
657
658                 if ( is_protected_meta( $metakey ) )
659                         return false;
660
661                 wp_cache_delete($post_ID, 'post_meta');
662                 $wpdb->insert( $wpdb->postmeta, array( 'post_id' => $post_ID, 'meta_key' => $metakey, 'meta_value' => $metavalue ) );
663                 $meta_id = $wpdb->insert_id;
664                 do_action( 'added_postmeta', $meta_id, $post_ID, $metakey, $metavalue );
665
666                 return $meta_id;
667         }
668         return false;
669 } // add_meta
670
671 /**
672  * {@internal Missing Short Description}}
673  *
674  * @since 1.2.0
675  *
676  * @param unknown_type $mid
677  * @return unknown
678  */
679 function delete_meta( $mid ) {
680         global $wpdb;
681         $mid = (int) $mid;
682
683         $post_id = $wpdb->get_var( $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_id = %d", $mid) );
684
685         do_action( 'delete_postmeta', $mid );
686         wp_cache_delete($post_id, 'post_meta');
687         $rval = $wpdb->query( $wpdb->prepare("DELETE FROM $wpdb->postmeta WHERE meta_id = %d", $mid) );
688         do_action( 'deleted_postmeta', $mid );
689
690         return $rval;
691 }
692
693 /**
694  * Get a list of previously defined keys.
695  *
696  * @since 1.2.0
697  *
698  * @return unknown
699  */
700 function get_meta_keys() {
701         global $wpdb;
702
703         $keys = $wpdb->get_col( "
704                         SELECT meta_key
705                         FROM $wpdb->postmeta
706                         GROUP BY meta_key
707                         ORDER BY meta_key" );
708
709         return $keys;
710 }
711
712 /**
713  * {@internal Missing Short Description}}
714  *
715  * @since 2.1.0
716  *
717  * @param unknown_type $mid
718  * @return unknown
719  */
720 function get_post_meta_by_id( $mid ) {
721         global $wpdb;
722         $mid = (int) $mid;
723
724         $meta = $wpdb->get_row( $wpdb->prepare("SELECT * FROM $wpdb->postmeta WHERE meta_id = %d", $mid) );
725         if ( empty($meta) )
726                 return false;
727         if ( is_serialized_string( $meta->meta_value ) )
728                 $meta->meta_value = maybe_unserialize( $meta->meta_value );
729         return $meta;
730 }
731
732 /**
733  * {@internal Missing Short Description}}
734  *
735  * Some postmeta stuff.
736  *
737  * @since 1.2.0
738  *
739  * @param unknown_type $postid
740  * @return unknown
741  */
742 function has_meta( $postid ) {
743         global $wpdb;
744
745         return $wpdb->get_results( $wpdb->prepare("SELECT meta_key, meta_value, meta_id, post_id
746                         FROM $wpdb->postmeta WHERE post_id = %d
747                         ORDER BY meta_key,meta_id", $postid), ARRAY_A );
748
749 }
750
751 /**
752  * {@internal Missing Short Description}}
753  *
754  * @since 1.2.0
755  *
756  * @param unknown_type $meta_id
757  * @param unknown_type $meta_key Expect Slashed
758  * @param unknown_type $meta_value Expect Slashed
759  * @return unknown
760  */
761 function update_meta( $meta_id, $meta_key, $meta_value ) {
762         global $wpdb;
763
764         $meta_key = stripslashes($meta_key);
765
766         if ( is_protected_meta( $meta_key ) )
767                 return false;
768
769         if ( '' === trim( $meta_value ) )
770                 return false;
771
772         $post_id = $wpdb->get_var( $wpdb->prepare("SELECT post_id FROM $wpdb->postmeta WHERE meta_id = %d", $meta_id) );
773
774         $meta_value = maybe_serialize( stripslashes_deep( $meta_value ) );
775         $meta_id = (int) $meta_id;
776
777         $data  = compact( 'meta_key', 'meta_value' );
778         $where = compact( 'meta_id' );
779
780         do_action( 'update_postmeta', $meta_id, $post_id, $meta_key, $meta_value );
781         $rval = $wpdb->update( $wpdb->postmeta, $data, $where );
782         wp_cache_delete($post_id, 'post_meta');
783         do_action( 'updated_postmeta', $meta_id, $post_id, $meta_key, $meta_value );
784
785         return $rval;
786 }
787
788 //
789 // Private
790 //
791
792 /**
793  * Replace hrefs of attachment anchors with up-to-date permalinks.
794  *
795  * @since 2.3.0
796  * @access private
797  *
798  * @param unknown_type $post_ID
799  * @return unknown
800  */
801 function _fix_attachment_links( $post_ID ) {
802         global $_fix_attachment_link_id;
803
804         $post = & get_post( $post_ID, ARRAY_A );
805
806         $search = "#<a[^>]+rel=('|\")[^'\"]*attachment[^>]*>#ie";
807
808         // See if we have any rel="attachment" links
809         if ( 0 == preg_match_all( $search, $post['post_content'], $anchor_matches, PREG_PATTERN_ORDER ) )
810                 return;
811
812         $i = 0;
813         $search = "#[\s]+rel=(\"|')(.*?)wp-att-(\d+)\\1#i";
814         foreach ( $anchor_matches[0] as $anchor ) {
815                 if ( 0 == preg_match( $search, $anchor, $id_matches ) )
816                         continue;
817
818                 $id = (int) $id_matches[3];
819
820                 // While we have the attachment ID, let's adopt any orphans.
821                 $attachment = & get_post( $id, ARRAY_A );
822                 if ( ! empty( $attachment) && ! is_object( get_post( $attachment['post_parent'] ) ) ) {
823                         $attachment['post_parent'] = $post_ID;
824                         // Escape data pulled from DB.
825                         $attachment = add_magic_quotes( $attachment );
826                         wp_update_post( $attachment );
827                 }
828
829                 $post_search[$i] = $anchor;
830                  $_fix_attachment_link_id = $id;
831                 $post_replace[$i] = preg_replace_callback( "#href=(\"|')[^'\"]*\\1#", '_fix_attachment_links_replace_cb', $anchor );
832                 ++$i;
833         }
834
835         $post['post_content'] = str_replace( $post_search, $post_replace, $post['post_content'] );
836
837         // Escape data pulled from DB.
838         $post = add_magic_quotes( $post);
839
840         return wp_update_post( $post);
841 }
842
843 function _fix_attachment_links_replace_cb($match) {
844         global $_fix_attachment_link_id;
845         return stripslashes( 'href='.$match[1] ).get_attachment_link( $_fix_attachment_link_id ).stripslashes( $match[1] );
846 }
847
848 /**
849  * Move child posts to a new parent.
850  *
851  * @since 2.3.0
852  * @access private
853  *
854  * @param unknown_type $old_ID
855  * @param unknown_type $new_ID
856  * @return unknown
857  */
858 function _relocate_children( $old_ID, $new_ID ) {
859         global $wpdb;
860         $old_ID = (int) $old_ID;
861         $new_ID = (int) $new_ID;
862
863         $children = $wpdb->get_col( $wpdb->prepare("
864                 SELECT post_id
865                 FROM $wpdb->postmeta
866                 WHERE meta_key = '_wp_attachment_temp_parent'
867                 AND meta_value = %d", $old_ID) );
868
869         foreach ( $children as $child_id ) {
870                 $wpdb->update($wpdb->posts, array('post_parent' => $new_ID), array('ID' => $child_id) );
871                 delete_post_meta($child_id, '_wp_attachment_temp_parent');
872         }
873 }
874
875 /**
876  * Get all the possible statuses for a post_type
877  *
878  * @since 2.5.0
879  *
880  * @param string $type The post_type you want the statuses for
881  * @return array As array of all the statuses for the supplied post type
882  */
883 function get_available_post_statuses($type = 'post') {
884         $stati = wp_count_posts($type);
885
886         return array_keys(get_object_vars($stati));
887 }
888
889 /**
890  * Run the wp query to fetch the posts for listing on the edit posts page
891  *
892  * @since 2.5.0
893  *
894  * @param array|bool $q Array of query variables to use to build the query or false to use $_GET superglobal.
895  * @return array
896  */
897 function wp_edit_posts_query( $q = false ) {
898         if ( false === $q )
899                 $q = $_GET;
900         $q['m'] = isset($q['m']) ? (int) $q['m'] : 0;
901         $q['cat'] = isset($q['cat']) ? (int) $q['cat'] : 0;
902         $post_stati  = get_post_stati();
903
904         if ( isset($q['post_type']) && in_array( $q['post_type'], get_post_types() ) )
905                 $post_type = $q['post_type'];
906         else
907                 $post_type = 'post';
908
909         $avail_post_stati = get_available_post_statuses($post_type);
910
911         if ( isset($q['post_status']) && in_array( $q['post_status'], $post_stati ) ) {
912                 $post_status = $q['post_status'];
913                 $perm = 'readable';
914         }
915
916         if ( isset($q['orderby']) )
917                 $orderby = $q['orderby'];
918         elseif ( isset($q['post_status']) && in_array($q['post_status'], array('pending', 'draft')) )
919                 $orderby = 'modified';
920
921         if ( isset($q['order']) )
922                 $order = $q['order'];
923         elseif ( isset($q['post_status']) && 'pending' == $q['post_status'] )
924                 $order = 'ASC';
925
926         $per_page = 'edit_' . $post_type . '_per_page';
927         $posts_per_page = (int) get_user_option( $per_page );
928         if ( empty( $posts_per_page ) || $posts_per_page < 1 )
929                 $posts_per_page = 20;
930
931         $posts_per_page = apply_filters( $per_page, $posts_per_page );
932         $posts_per_page = apply_filters( 'edit_posts_per_page', $posts_per_page, $post_type );
933
934         $query = compact('post_type', 'post_status', 'perm', 'order', 'orderby', 'posts_per_page');
935
936         // Hierarchical types require special args.
937         if ( is_post_type_hierarchical( $post_type ) && !isset($orderby) ) {
938                 $query['orderby'] = 'menu_order title';
939                 $query['order'] = 'asc';
940                 $query['posts_per_page'] = -1;
941                 $query['posts_per_archive_page'] = -1;
942         }
943
944         if ( ! empty( $q['show_sticky'] ) )
945                 $query['post__in'] = (array) get_option( 'sticky_posts' );
946
947         wp( $query );
948
949         return $avail_post_stati;
950 }
951
952 /**
953  * Get default post mime types
954  *
955  * @since 2.9.0
956  *
957  * @return array
958  */
959 function get_post_mime_types() {
960         $post_mime_types = array(       //      array( adj, noun )
961                 'image' => array(__('Images'), __('Manage Images'), _n_noop('Image <span class="count">(%s)</span>', 'Images <span class="count">(%s)</span>')),
962                 'audio' => array(__('Audio'), __('Manage Audio'), _n_noop('Audio <span class="count">(%s)</span>', 'Audio <span class="count">(%s)</span>')),
963                 'video' => array(__('Video'), __('Manage Video'), _n_noop('Video <span class="count">(%s)</span>', 'Video <span class="count">(%s)</span>')),
964         );
965
966         return apply_filters('post_mime_types', $post_mime_types);
967 }
968
969 /**
970  * {@internal Missing Short Description}}
971  *
972  * @since 2.5.0
973  *
974  * @param unknown_type $type
975  * @return unknown
976  */
977 function get_available_post_mime_types($type = 'attachment') {
978         global $wpdb;
979
980         $types = $wpdb->get_col($wpdb->prepare("SELECT DISTINCT post_mime_type FROM $wpdb->posts WHERE post_type = %s", $type));
981         return $types;
982 }
983
984 /**
985  * {@internal Missing Short Description}}
986  *
987  * @since 2.5.0
988  *
989  * @param unknown_type $q
990  * @return unknown
991  */
992 function wp_edit_attachments_query( $q = false ) {
993         if ( false === $q )
994                 $q = $_GET;
995
996         $q['m']   = isset( $q['m'] ) ? (int) $q['m'] : 0;
997         $q['cat'] = isset( $q['cat'] ) ? (int) $q['cat'] : 0;
998         $q['post_type'] = 'attachment';
999         $post_type = get_post_type_object( 'attachment' );
1000         $states = array( 'inherit' );
1001         if ( current_user_can( $post_type->cap->read_private_posts ) )
1002                 $states[] = 'private';
1003
1004         $q['post_status'] = isset( $q['status'] ) && 'trash' == $q['status'] ? 'trash' : $states;
1005         $media_per_page = (int) get_user_option( 'upload_per_page' );
1006         if ( empty( $media_per_page ) || $media_per_page < 1 )
1007                 $media_per_page = 20;
1008         $q['posts_per_page'] = apply_filters( 'upload_per_page', $media_per_page );
1009
1010         $post_mime_types = get_post_mime_types();
1011         $avail_post_mime_types = get_available_post_mime_types('attachment');
1012
1013         if ( isset($q['post_mime_type']) && !array_intersect( (array) $q['post_mime_type'], array_keys($post_mime_types) ) )
1014                 unset($q['post_mime_type']);
1015
1016         if ( isset($q['detached']) )
1017                 add_filter('posts_where', '_edit_attachments_query_helper');
1018
1019         wp( $q );
1020
1021         if ( isset($q['detached']) )
1022                 remove_filter('posts_where', '_edit_attachments_query_helper');
1023
1024         return array($post_mime_types, $avail_post_mime_types);
1025 }
1026
1027 function _edit_attachments_query_helper($where) {
1028         return $where .= ' AND post_parent < 1';
1029 }
1030
1031 /**
1032  * {@internal Missing Short Description}}
1033  *
1034  * @uses get_user_option()
1035  * @since 2.5.0
1036  *
1037  * @param unknown_type $id
1038  * @param unknown_type $page
1039  * @return unknown
1040  */
1041 function postbox_classes( $id, $page ) {
1042         if ( isset( $_GET['edit'] ) && $_GET['edit'] == $id )
1043                 return '';
1044
1045         if ( $closed = get_user_option('closedpostboxes_'.$page ) ) {
1046                 if ( !is_array( $closed ) ) {
1047                         return '';
1048                 }
1049                 return in_array( $id, $closed )? 'closed' : '';
1050         } else {
1051                 return '';
1052         }
1053 }
1054
1055 /**
1056  * {@internal Missing Short Description}}
1057  *
1058  * @since 2.5.0
1059  *
1060  * @param int|object $id    Post ID or post object.
1061  * @param string $title (optional) Title
1062  * @param string $name (optional) Name
1063  * @return array With two entries of type string
1064  */
1065 function get_sample_permalink($id, $title = null, $name = null) {
1066         $post = &get_post($id);
1067         if ( !$post->ID )
1068                 return array('', '');
1069
1070         $ptype = get_post_type_object($post->post_type);
1071
1072         $original_status = $post->post_status;
1073         $original_date = $post->post_date;
1074         $original_name = $post->post_name;
1075
1076         // Hack: get_permalink would return ugly permalink for
1077         // drafts, so we will fake, that our post is published
1078         if ( in_array($post->post_status, array('draft', 'pending')) ) {
1079                 $post->post_status = 'publish';
1080                 $post->post_name = sanitize_title($post->post_name ? $post->post_name : $post->post_title, $post->ID);
1081         }
1082
1083         // If the user wants to set a new name -- override the current one
1084         // Note: if empty name is supplied -- use the title instead, see #6072
1085         if ( !is_null($name) )
1086                 $post->post_name = sanitize_title($name ? $name : $title, $post->ID);
1087
1088         $post->post_name = wp_unique_post_slug($post->post_name, $post->ID, $post->post_status, $post->post_type, $post->post_parent);
1089
1090         $post->filter = 'sample';
1091
1092         $permalink = get_permalink($post, true);
1093
1094         // Replace custom post_type Token with generic pagename token for ease of use.
1095         $permalink = str_replace("%$post->post_type%", '%pagename%', $permalink);
1096
1097         // Handle page hierarchy
1098         if ( $ptype->hierarchical ) {
1099                 $uri = get_page_uri($post);
1100                 $uri = untrailingslashit($uri);
1101                 $uri = strrev( stristr( strrev( $uri ), '/' ) );
1102                 $uri = untrailingslashit($uri);
1103                 if ( !empty($uri) )
1104                         $uri .= '/';
1105                 $permalink = str_replace('%pagename%', "{$uri}%pagename%", $permalink);
1106         }
1107
1108         $permalink = array($permalink, apply_filters('editable_slug', $post->post_name));
1109         $post->post_status = $original_status;
1110         $post->post_date = $original_date;
1111         $post->post_name = $original_name;
1112         unset($post->filter);
1113
1114         return $permalink;
1115 }
1116
1117 /**
1118  * sample permalink html
1119  *
1120  * intended to be used for the inplace editor of the permalink post slug on in the post (and page?) editor.
1121  *
1122  * @since 2.5.0
1123  *
1124  * @param int|object $id Post ID or post object.
1125  * @param string $new_title (optional) New title
1126  * @param string $new_slug (optional) New slug
1127  * @return string intended to be used for the inplace editor of the permalink post slug on in the post (and page?) editor.
1128  */
1129 function get_sample_permalink_html( $id, $new_title = null, $new_slug = null ) {
1130         global $wpdb;
1131         $post = &get_post($id);
1132
1133         list($permalink, $post_name) = get_sample_permalink($post->ID, $new_title, $new_slug);
1134
1135         if ( 'publish' == $post->post_status ) {
1136                 $ptype = get_post_type_object($post->post_type);
1137                 $view_post = $ptype->labels->view_item;
1138                 $title = __('Click to edit this part of the permalink');
1139         } else {
1140                 $title = __('Temporary permalink. Click to edit this part.');
1141         }
1142
1143         if ( false === strpos($permalink, '%postname%') && false === strpos($permalink, '%pagename%') ) {
1144                 $return = '<strong>' . __('Permalink:') . "</strong>\n" . '<span id="sample-permalink">' . $permalink . "</span>\n";
1145                 if ( '' == get_option( 'permalink_structure' ) && current_user_can( 'manage_options' ) && !( 'page' == get_option('show_on_front') && $id == get_option('page_on_front') ) )
1146                         $return .= '<span id="change-permalinks"><a href="options-permalink.php" class="button" target="_blank">' . __('Change Permalinks') . "</a></span>\n";
1147                 if ( isset($view_post) )
1148                         $return .= "<span id='view-post-btn'><a href='$permalink' class='button' target='_blank'>$view_post</a></span>\n";
1149
1150                 $return = apply_filters('get_sample_permalink_html', $return, $id, $new_title, $new_slug);
1151
1152                 return $return;
1153         }
1154
1155         if ( function_exists('mb_strlen') ) {
1156                 if ( mb_strlen($post_name) > 30 ) {
1157                         $post_name_abridged = mb_substr($post_name, 0, 14). '&hellip;' . mb_substr($post_name, -14);
1158                 } else {
1159                         $post_name_abridged = $post_name;
1160                 }
1161         } else {
1162                 if ( strlen($post_name) > 30 ) {
1163                         $post_name_abridged = substr($post_name, 0, 14). '&hellip;' . substr($post_name, -14);
1164                 } else {
1165                         $post_name_abridged = $post_name;
1166                 }
1167         }
1168
1169         $post_name_html = '<span id="editable-post-name" title="' . $title . '">' . $post_name_abridged . '</span>';
1170         $display_link = str_replace(array('%pagename%','%postname%'), $post_name_html, $permalink);
1171         $view_link = str_replace(array('%pagename%','%postname%'), $post_name, $permalink);
1172         $return =  '<strong>' . __('Permalink:') . "</strong>\n";
1173         $return .= '<span id="sample-permalink">' . $display_link . "</span>\n";
1174         $return .= '&lrm;'; // Fix bi-directional text display defect in RTL languages.
1175         $return .= '<span id="edit-slug-buttons"><a href="#post_name" class="edit-slug button hide-if-no-js" onclick="editPermalink(' . $id . '); return false;">' . __('Edit') . "</a></span>\n";
1176         $return .= '<span id="editable-post-name-full">' . $post_name . "</span>\n";
1177         if ( isset($view_post) )
1178                 $return .= "<span id='view-post-btn'><a href='$view_link' class='button' target='_blank'>$view_post</a></span>\n";
1179
1180         $return = apply_filters('get_sample_permalink_html', $return, $id, $new_title, $new_slug);
1181
1182         return $return;
1183 }
1184
1185 /**
1186  * Output HTML for the post thumbnail meta-box.
1187  *
1188  * @since 2.9.0
1189  *
1190  * @param int $thumbnail_id ID of the attachment used for thumbnail
1191  * @return string html
1192  */
1193 function _wp_post_thumbnail_html( $thumbnail_id = NULL ) {
1194         global $content_width, $_wp_additional_image_sizes, $post_ID;
1195         $set_thumbnail_link = '<p class="hide-if-no-js"><a title="' . esc_attr__( 'Set featured image' ) . '" href="' . esc_url( get_upload_iframe_src('image') ) . '" id="set-post-thumbnail" class="thickbox">%s</a></p>';
1196         $content = sprintf($set_thumbnail_link, esc_html__( 'Set featured image' ));
1197
1198         if ( $thumbnail_id && get_post( $thumbnail_id ) ) {
1199                 $old_content_width = $content_width;
1200                 $content_width = 266;
1201                 if ( !isset( $_wp_additional_image_sizes['post-thumbnail'] ) )
1202                         $thumbnail_html = wp_get_attachment_image( $thumbnail_id, array( $content_width, $content_width ) );
1203                 else
1204                         $thumbnail_html = wp_get_attachment_image( $thumbnail_id, 'post-thumbnail' );
1205                 if ( !empty( $thumbnail_html ) ) {
1206                         $ajax_nonce = wp_create_nonce( "set_post_thumbnail-$post_ID" );
1207                         $content = sprintf($set_thumbnail_link, $thumbnail_html);
1208                         $content .= '<p class="hide-if-no-js"><a href="#" id="remove-post-thumbnail" onclick="WPRemoveThumbnail(\'' . $ajax_nonce . '\');return false;">' . esc_html__( 'Remove featured image' ) . '</a></p>';
1209                 }
1210                 $content_width = $old_content_width;
1211         }
1212
1213         return apply_filters( 'admin_post_thumbnail_html', $content );
1214 }
1215
1216 /**
1217  * Check to see if the post is currently being edited by another user.
1218  *
1219  * @since 2.5.0
1220  *
1221  * @param int $post_id ID of the post to check for editing
1222  * @return bool|int False: not locked or locked by current user. Int: user ID of user with lock.
1223  */
1224 function wp_check_post_lock( $post_id ) {
1225         if ( !$post = get_post( $post_id ) )
1226                 return false;
1227
1228         if ( !$lock = get_post_meta( $post->ID, '_edit_lock', true ) )
1229                 return false;
1230
1231         $lock = explode( ':', $lock );
1232         $time = $lock[0];
1233         $user = isset( $lock[1] ) ? $lock[1] : get_post_meta( $post->ID, '_edit_last', true );
1234
1235         $time_window = apply_filters( 'wp_check_post_lock_window', AUTOSAVE_INTERVAL * 2 );
1236
1237         if ( $time && $time > time() - $time_window && $user != get_current_user_id() )
1238                 return $user;
1239         return false;
1240 }
1241
1242 /**
1243  * Mark the post as currently being edited by the current user
1244  *
1245  * @since 2.5.0
1246  *
1247  * @param int $post_id ID of the post to being edited
1248  * @return bool Returns false if the post doesn't exist of there is no current user
1249  */
1250 function wp_set_post_lock( $post_id ) {
1251         if ( !$post = get_post( $post_id ) )
1252                 return false;
1253         if ( 0 == ($user_id = get_current_user_id()) )
1254                 return false;
1255
1256         $now = time();
1257         $lock = "$now:$user_id";
1258
1259         update_post_meta( $post->ID, '_edit_lock', $lock );
1260 }
1261
1262 /**
1263  * Outputs the notice message to say that someone else is editing this post at the moment.
1264  *
1265  * @since 2.8.5
1266  * @return none
1267  */
1268 function _admin_notice_post_locked() {
1269         global $post;
1270
1271         $lock = explode( ':', get_post_meta( $post->ID, '_edit_lock', true ) );
1272         $user = isset( $lock[1] ) ? $lock[1] : get_post_meta( $post->ID, '_edit_last', true );
1273         $last_user = get_userdata( $user );
1274         $last_user_name = $last_user ? $last_user->display_name : __('Somebody');
1275
1276         switch ($post->post_type) {
1277                 case 'post':
1278                         $message = __( 'Warning: %s is currently editing this post' );
1279                         break;
1280                 case 'page':
1281                         $message = __( 'Warning: %s is currently editing this page' );
1282                         break;
1283                 default:
1284                         $message = __( 'Warning: %s is currently editing this.' );
1285         }
1286
1287         $message = sprintf( $message, esc_html( $last_user_name ) );
1288         echo "<div class='error'><p>$message</p></div>";
1289 }
1290
1291 /**
1292  * Creates autosave data for the specified post from $_POST data.
1293  *
1294  * @package WordPress
1295  * @subpackage Post_Revisions
1296  * @since 2.6.0
1297  *
1298  * @uses _wp_translate_postdata()
1299  * @uses _wp_post_revision_fields()
1300  *
1301  * @return unknown
1302  */
1303 function wp_create_post_autosave( $post_id ) {
1304         $translated = _wp_translate_postdata( true );
1305         if ( is_wp_error( $translated ) )
1306                 return $translated;
1307
1308         // Only store one autosave.  If there is already an autosave, overwrite it.
1309         if ( $old_autosave = wp_get_post_autosave( $post_id ) ) {
1310                 $new_autosave = _wp_post_revision_fields( $_POST, true );
1311                 $new_autosave['ID'] = $old_autosave->ID;
1312                 $new_autosave['post_author'] = get_current_user_id();
1313                 return wp_update_post( $new_autosave );
1314         }
1315
1316         // _wp_put_post_revision() expects unescaped.
1317         $_POST = stripslashes_deep($_POST);
1318
1319         // Otherwise create the new autosave as a special post revision
1320         return _wp_put_post_revision( $_POST, true );
1321 }
1322
1323 /**
1324  * Save draft or manually autosave for showing preview.
1325  *
1326  * @package WordPress
1327  * @since 2.7.0
1328  *
1329  * @uses wp_write_post()
1330  * @uses edit_post()
1331  * @uses get_post()
1332  * @uses current_user_can()
1333  * @uses wp_create_post_autosave()
1334  *
1335  * @return str URL to redirect to show the preview
1336  */
1337 function post_preview() {
1338
1339         $post_ID = (int) $_POST['post_ID'];
1340         $status = get_post_status( $post_ID );
1341         if ( 'auto-draft' == $status )
1342                 wp_die( __('Preview not available. Please save as a draft first.') );
1343
1344         if ( isset($_POST['catslist']) )
1345                 $_POST['post_category'] = explode(",", $_POST['catslist']);
1346
1347         if ( isset($_POST['tags_input']) )
1348                 $_POST['tags_input'] = explode(",", $_POST['tags_input']);
1349
1350         if ( $_POST['post_type'] == 'page' || empty($_POST['post_category']) )
1351                 unset($_POST['post_category']);
1352
1353         $_POST['ID'] = $post_ID;
1354         $post = get_post($post_ID);
1355
1356         if ( 'page' == $post->post_type ) {
1357                 if ( !current_user_can('edit_page', $post_ID) )
1358                         wp_die(__('You are not allowed to edit this page.'));
1359         } else {
1360                 if ( !current_user_can('edit_post', $post_ID) )
1361                         wp_die(__('You are not allowed to edit this post.'));
1362         }
1363
1364         if ( 'draft' == $post->post_status ) {
1365                 $id = edit_post();
1366         } else { // Non drafts are not overwritten.  The autosave is stored in a special post revision.
1367                 $id = wp_create_post_autosave( $post->ID );
1368                 if ( ! is_wp_error($id) )
1369                         $id = $post->ID;
1370         }
1371
1372         if ( is_wp_error($id) )
1373                 wp_die( $id->get_error_message() );
1374
1375         if ( $_POST['post_status'] == 'draft'  ) {
1376                 $url = add_query_arg( 'preview', 'true', get_permalink($id) );
1377         } else {
1378                 $nonce = wp_create_nonce('post_preview_' . $id);
1379                 $url = add_query_arg( array( 'preview' => 'true', 'preview_id' => $id, 'preview_nonce' => $nonce ), get_permalink($id) );
1380         }
1381
1382         return $url;
1383 }
1384
1385 /**
1386  * Adds the TinyMCE editor used on the Write and Edit screens.
1387  *
1388  * @package WordPress
1389  * @since 2.7.0
1390  *
1391  * TinyMCE is loaded separately from other Javascript by using wp-tinymce.php. It outputs concatenated
1392  * and optionaly pre-compressed version of the core and all default plugins. Additional plugins are loaded
1393  * directly by TinyMCE using non-blocking method. Custom plugins can be refreshed by adding a query string
1394  * to the URL when queueing them with the mce_external_plugins filter.
1395  *
1396  * @param bool $teeny optional Output a trimmed down version used in Press This.
1397  * @param mixed $settings optional An array that can add to or overwrite the default TinyMCE settings.
1398  */
1399 function wp_tiny_mce( $teeny = false, $settings = false ) {
1400         global $concatenate_scripts, $compress_scripts, $tinymce_version, $editor_styles;
1401
1402         if ( ! user_can_richedit() )
1403                 return;
1404
1405         $baseurl = includes_url('js/tinymce');
1406
1407         $mce_locale = ( '' == get_locale() ) ? 'en' : strtolower( substr(get_locale(), 0, 2) ); // only ISO 639-1
1408
1409         /*
1410         The following filter allows localization scripts to change the languages displayed in the spellchecker's drop-down menu.
1411         By default it uses Google's spellchecker API, but can be configured to use PSpell/ASpell if installed on the server.
1412         The + sign marks the default language. More information:
1413         http://wiki.moxiecode.com/index.php/TinyMCE:Plugins/spellchecker
1414         */
1415         $mce_spellchecker_languages = apply_filters('mce_spellchecker_languages', '+English=en,Danish=da,Dutch=nl,Finnish=fi,French=fr,German=de,Italian=it,Polish=pl,Portuguese=pt,Spanish=es,Swedish=sv');
1416
1417         if ( $teeny ) {
1418                 $plugins = apply_filters( 'teeny_mce_plugins', array('inlinepopups', 'fullscreen', 'wordpress', 'wplink', 'wpdialogs') );
1419                 $ext_plugins = '';
1420         } else {
1421                 $plugins = array( 'inlinepopups', 'spellchecker', 'paste', 'wordpress', 'fullscreen', 'wpeditimage', 'wpgallery', 'tabfocus', 'wplink', 'wpdialogs' );
1422
1423                 /*
1424                 The following filter takes an associative array of external plugins for TinyMCE in the form 'plugin_name' => 'url'.
1425                 It adds the plugin's name to TinyMCE's plugins init and the call to PluginManager to load the plugin.
1426                 The url should be absolute and should include the js file name to be loaded. Example:
1427                 array( 'myplugin' => 'http://my-site.com/wp-content/plugins/myfolder/mce_plugin.js' )
1428                 If the plugin uses a button, it should be added with one of the "$mce_buttons" filters.
1429                 */
1430                 $mce_external_plugins = apply_filters('mce_external_plugins', array());
1431
1432                 $ext_plugins = '';
1433                 if ( ! empty($mce_external_plugins) ) {
1434
1435                         /*
1436                         The following filter loads external language files for TinyMCE plugins.
1437                         It takes an associative array 'plugin_name' => 'path', where path is the
1438                         include path to the file. The language file should follow the same format as
1439                         /tinymce/langs/wp-langs.php and should define a variable $strings that
1440                         holds all translated strings.
1441                         When this filter is not used, the function will try to load {mce_locale}.js.
1442                         If that is not found, en.js will be tried next.
1443                         */
1444                         $mce_external_languages = apply_filters('mce_external_languages', array());
1445
1446                         $loaded_langs = array();
1447                         $strings = '';
1448
1449                         if ( ! empty($mce_external_languages) ) {
1450                                 foreach ( $mce_external_languages as $name => $path ) {
1451                                         if ( @is_file($path) && @is_readable($path) ) {
1452                                                 include_once($path);
1453                                                 $ext_plugins .= $strings . "\n";
1454                                                 $loaded_langs[] = $name;
1455                                         }
1456                                 }
1457                         }
1458
1459                         foreach ( $mce_external_plugins as $name => $url ) {
1460
1461                                 if ( is_ssl() ) $url = str_replace('http://', 'https://', $url);
1462
1463                                 $plugins[] = '-' . $name;
1464
1465                                 $plugurl = dirname($url);
1466                                 $strings = $str1 = $str2 = '';
1467                                 if ( ! in_array($name, $loaded_langs) ) {
1468                                         $path = str_replace( WP_PLUGIN_URL, '', $plugurl );
1469                                         $path = WP_PLUGIN_DIR . $path . '/langs/';
1470
1471                                         if ( function_exists('realpath') )
1472                                                 $path = trailingslashit( realpath($path) );
1473
1474                                         if ( @is_file($path . $mce_locale . '.js') )
1475                                                 $strings .= @file_get_contents($path . $mce_locale . '.js') . "\n";
1476
1477                                         if ( @is_file($path . $mce_locale . '_dlg.js') )
1478                                                 $strings .= @file_get_contents($path . $mce_locale . '_dlg.js') . "\n";
1479
1480                                         if ( 'en' != $mce_locale && empty($strings) ) {
1481                                                 if ( @is_file($path . 'en.js') ) {
1482                                                         $str1 = @file_get_contents($path . 'en.js');
1483                                                         $strings .= preg_replace( '/([\'"])en\./', '$1' . $mce_locale . '.', $str1, 1 ) . "\n";
1484                                                 }
1485
1486                                                 if ( @is_file($path . 'en_dlg.js') ) {
1487                                                         $str2 = @file_get_contents($path . 'en_dlg.js');
1488                                                         $strings .= preg_replace( '/([\'"])en\./', '$1' . $mce_locale . '.', $str2, 1 ) . "\n";
1489                                                 }
1490                                         }
1491
1492                                         if ( ! empty($strings) )
1493                                                 $ext_plugins .= "\n" . $strings . "\n";
1494                                 }
1495
1496                                 $ext_plugins .= 'tinyMCEPreInit.load_ext("' . $plugurl . '", "' . $mce_locale . '");' . "\n";
1497                                 $ext_plugins .= 'tinymce.PluginManager.load("' . $name . '", "' . $url . '");' . "\n";
1498                         }
1499                 }
1500         }
1501
1502         if ( $teeny ) {
1503                 $mce_buttons = apply_filters( 'teeny_mce_buttons', array('bold, italic, underline, blockquote, separator, strikethrough, bullist, numlist,justifyleft, justifycenter, justifyright, undo, redo, link, unlink, fullscreen') );
1504                 $mce_buttons = implode($mce_buttons, ',');
1505                 $mce_buttons_2 = $mce_buttons_3 = $mce_buttons_4 = '';
1506         } else {
1507                 $mce_buttons = apply_filters('mce_buttons', array('bold', 'italic', 'strikethrough', '|', 'bullist', 'numlist', 'blockquote', '|', 'justifyleft', 'justifycenter', 'justifyright', '|', 'link', 'unlink', 'wp_more', '|', 'spellchecker', 'fullscreen', 'wp_adv' ));
1508                 $mce_buttons = implode($mce_buttons, ',');
1509
1510                 $mce_buttons_2 = array( 'formatselect', 'underline', 'justifyfull', 'forecolor', '|', 'pastetext', 'pasteword', 'removeformat', '|', 'charmap', '|', 'outdent', 'indent', '|', 'undo', 'redo', 'wp_help' );
1511                 $mce_buttons_2 = apply_filters('mce_buttons_2', $mce_buttons_2);
1512                 $mce_buttons_2 = implode($mce_buttons_2, ',');
1513
1514                 $mce_buttons_3 = apply_filters('mce_buttons_3', array());
1515                 $mce_buttons_3 = implode($mce_buttons_3, ',');
1516
1517                 $mce_buttons_4 = apply_filters('mce_buttons_4', array());
1518                 $mce_buttons_4 = implode($mce_buttons_4, ',');
1519         }
1520         $no_captions = (bool) apply_filters( 'disable_captions', '' );
1521
1522         // TinyMCE init settings
1523         $initArray = array (
1524                 'mode' => 'specific_textareas',
1525                 'editor_selector' => 'theEditor',
1526                 'width' => '100%',
1527                 'theme' => 'advanced',
1528                 'skin' => 'wp_theme',
1529                 'theme_advanced_buttons1' => $mce_buttons,
1530                 'theme_advanced_buttons2' => $mce_buttons_2,
1531                 'theme_advanced_buttons3' => $mce_buttons_3,
1532                 'theme_advanced_buttons4' => $mce_buttons_4,
1533                 'language' => $mce_locale,
1534                 'spellchecker_languages' => $mce_spellchecker_languages,
1535                 'theme_advanced_toolbar_location' => 'top',
1536                 'theme_advanced_toolbar_align' => 'left',
1537                 'theme_advanced_statusbar_location' => 'bottom',
1538                 'theme_advanced_resizing' => true,
1539                 'theme_advanced_resize_horizontal' => false,
1540                 'dialog_type' => 'modal',
1541                 'formats' => "{
1542                         alignleft : [
1543                                 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
1544                                 {selector : 'img,table', classes : 'alignleft'}
1545                         ],
1546                         aligncenter : [
1547                                 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
1548                                 {selector : 'img,table', classes : 'aligncenter'}
1549                         ],
1550                         alignright : [
1551                                 {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
1552                                 {selector : 'img,table', classes : 'alignright'}
1553                         ],
1554                         strikethrough : {inline : 'del'}
1555                 }",
1556                 'relative_urls' => false,
1557                 'remove_script_host' => false,
1558                 'convert_urls' => false,
1559                 'apply_source_formatting' => false,
1560                 'remove_linebreaks' => true,
1561                 'gecko_spellcheck' => true,
1562                 'entities' => '38,amp,60,lt,62,gt',
1563                 'accessibility_focus' => true,
1564                 'tabfocus_elements' => 'major-publishing-actions',
1565                 'media_strict' => false,
1566                 'paste_remove_styles' => true,
1567                 'paste_remove_spans' => true,
1568                 'paste_strip_class_attributes' => 'all',
1569                 'paste_text_use_dialog' => true,
1570                 'wpeditimage_disable_captions' => $no_captions,
1571                 'plugins' => implode( ',', $plugins ),
1572         );
1573
1574         if ( ! empty( $editor_styles ) && is_array( $editor_styles ) ) {
1575                 $mce_css = array();
1576                 $style_uri = get_stylesheet_directory_uri();
1577                 if ( ! is_child_theme() ) {
1578                         foreach ( $editor_styles as $file )
1579                                 $mce_css[] = "$style_uri/$file";
1580                 } else {
1581                         $style_dir    = get_stylesheet_directory();
1582                         $template_uri = get_template_directory_uri();
1583                         $template_dir = get_template_directory();
1584                         foreach ( $editor_styles as $file ) {
1585                                 if ( file_exists( "$template_dir/$file" ) )
1586                                         $mce_css[] = "$template_uri/$file";
1587                                 if ( file_exists( "$style_dir/$file" ) )
1588                                         $mce_css[] = "$style_uri/$file";
1589                         }
1590                 }
1591                 $mce_css = implode( ',', $mce_css );
1592         } else {
1593                 $mce_css = '';
1594         }
1595
1596         $mce_css = trim( apply_filters( 'mce_css', $mce_css ), ' ,' );
1597
1598         if ( ! empty($mce_css) )
1599                 $initArray['content_css'] = $mce_css;
1600
1601         if ( is_array($settings) )
1602                 $initArray = array_merge($initArray, $settings);
1603
1604         // For people who really REALLY know what they're doing with TinyMCE
1605         // You can modify initArray to add, remove, change elements of the config before tinyMCE.init
1606         // Setting "valid_elements", "invalid_elements" and "extended_valid_elements" can be done through "tiny_mce_before_init".
1607         // Best is to use the default cleanup by not specifying valid_elements, as TinyMCE contains full set of XHTML 1.0.
1608         if ( $teeny ) {
1609                 $initArray = apply_filters('teeny_mce_before_init', $initArray);
1610         } else {
1611                 $initArray = apply_filters('tiny_mce_before_init', $initArray);
1612         }
1613
1614         if ( empty($initArray['theme_advanced_buttons3']) && !empty($initArray['theme_advanced_buttons4']) ) {
1615                 $initArray['theme_advanced_buttons3'] = $initArray['theme_advanced_buttons4'];
1616                 $initArray['theme_advanced_buttons4'] = '';
1617         }
1618
1619         if ( ! isset($concatenate_scripts) )
1620                 script_concat_settings();
1621
1622         $language = $initArray['language'];
1623
1624         $compressed = $compress_scripts && $concatenate_scripts && isset($_SERVER['HTTP_ACCEPT_ENCODING'])
1625                 && false !== strpos( strtolower($_SERVER['HTTP_ACCEPT_ENCODING']), 'gzip');
1626
1627         /**
1628          * Deprecated
1629          *
1630          * The tiny_mce_version filter is not needed since external plugins are loaded directly by TinyMCE.
1631          * These plugins can be refreshed by appending query string to the URL passed to mce_external_plugins filter.
1632          * If the plugin has a popup dialog, a query string can be added to the button action that opens it (in the plugin's code).
1633          */
1634         $version = apply_filters('tiny_mce_version', '');
1635         $version = 'ver=' . $tinymce_version . $version;
1636
1637         if ( 'en' != $language )
1638                 include_once(ABSPATH . WPINC . '/js/tinymce/langs/wp-langs.php');
1639
1640         $mce_options = '';
1641         foreach ( $initArray as $k => $v ) {
1642                 if ( is_bool($v) ) {
1643                         $val = $v ? 'true' : 'false';
1644                         $mce_options .= $k . ':' . $val . ', ';
1645                         continue;
1646                 } elseif ( !empty($v) && is_string($v) && ( '{' == $v{0} || '[' == $v{0} ) ) {
1647                         $mce_options .= $k . ':' . $v . ', ';
1648                         continue;
1649                 }
1650
1651                 $mce_options .= $k . ':"' . $v . '", ';
1652         }
1653
1654         $mce_options = rtrim( trim($mce_options), '\n\r,' ); ?>
1655
1656 <script type="text/javascript">
1657 /* <![CDATA[ */
1658 tinyMCEPreInit = {
1659         base : "<?php echo $baseurl; ?>",
1660         suffix : "",
1661         query : "<?php echo $version; ?>",
1662         mceInit : {<?php echo $mce_options; ?>},
1663         load_ext : function(url,lang){var sl=tinymce.ScriptLoader;sl.markDone(url+'/langs/'+lang+'.js');sl.markDone(url+'/langs/'+lang+'_dlg.js');}
1664 };
1665 /* ]]> */
1666 </script>
1667
1668 <?php
1669         if ( $compressed )
1670                 echo "<script type='text/javascript' src='$baseurl/wp-tinymce.php?c=1&amp;$version'></script>\n";
1671         else
1672                 echo "<script type='text/javascript' src='$baseurl/tiny_mce.js?$version'></script>\n";
1673
1674         if ( 'en' != $language && isset($lang) )
1675                 echo "<script type='text/javascript'>\n$lang\n</script>\n";
1676         else
1677                 echo "<script type='text/javascript' src='$baseurl/langs/wp-langs-en.js?$version'></script>\n";
1678 ?>
1679
1680 <script type="text/javascript">
1681 /* <![CDATA[ */
1682 <?php
1683         if ( $ext_plugins )
1684                 echo "$ext_plugins\n";
1685
1686         if ( ! $compressed ) {
1687 ?>
1688 (function(){var t=tinyMCEPreInit,sl=tinymce.ScriptLoader,ln=t.mceInit.language,th=t.mceInit.theme,pl=t.mceInit.plugins;sl.markDone(t.base+'/langs/'+ln+'.js');sl.markDone(t.base+'/themes/'+th+'/langs/'+ln+'.js');sl.markDone(t.base+'/themes/'+th+'/langs/'+ln+'_dlg.js');tinymce.each(pl.split(','),function(n){if(n&&n.charAt(0)!='-'){sl.markDone(t.base+'/plugins/'+n+'/langs/'+ln+'.js');sl.markDone(t.base+'/plugins/'+n+'/langs/'+ln+'_dlg.js');}});})();
1689 <?php } ?>
1690 tinyMCE.init(tinyMCEPreInit.mceInit);
1691 /* ]]> */
1692 </script>
1693 <?php
1694
1695         // Load additional inline scripts based on active plugins.
1696         if ( in_array( 'wpdialogs', $plugins ) ) {
1697                 wp_print_scripts( array( 'wpdialogs-popup' ) );
1698                 wp_print_styles('wp-jquery-ui-dialog');
1699         }
1700         if ( in_array( 'wplink', $plugins ) ) {
1701                 require_once ABSPATH . 'wp-admin/includes/internal-linking.php';
1702                 add_action('tiny_mce_preload_dialogs', 'wp_link_dialog');
1703                 wp_print_scripts('wplink');
1704                 wp_print_styles('wplink');
1705         }
1706 }
1707 function wp_tiny_mce_preload_dialogs() { ?>
1708         <div id="preloaded-dialogs" style="display:none;">
1709 <?php   do_action('tiny_mce_preload_dialogs'); ?>
1710         </div>
1711 <?php }