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