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