Wordpress 3.6
[autoinstalls/wordpress.git] / wp-includes / class-wp-editor.php
1 <?php
2 /**
3  * Facilitates adding of the WordPress editor as used on the Write and Edit screens.
4  *
5  * @package WordPress
6  * @since 3.3.0
7  *
8  * Private, not included by default. See wp_editor() in wp-includes/general-template.php.
9  */
10
11 final class _WP_Editors {
12         public static $mce_locale;
13
14         private static $mce_settings = array();
15         private static $qt_settings = array();
16         private static $plugins = array();
17         private static $qt_buttons = array();
18         private static $ext_plugins;
19         private static $baseurl;
20         private static $first_init;
21         private static $this_tinymce = false;
22         private static $this_quicktags = false;
23         private static $has_tinymce = false;
24         private static $has_quicktags = false;
25         private static $has_medialib = false;
26         private static $editor_buttons_css = true;
27
28         private function __construct() {}
29
30         public static function parse_settings($editor_id, $settings) {
31                 $set = wp_parse_args( $settings,  array(
32                         'wpautop' => true, // use wpautop?
33                         'media_buttons' => true, // show insert/upload button(s)
34                         'textarea_name' => $editor_id, // set the textarea name to something different, square brackets [] can be used here
35                         'textarea_rows' => 20,
36                         'tabindex' => '',
37                         'tabfocus_elements' => ':prev,:next', // the previous and next element ID to move the focus to when pressing the Tab key in TinyMCE
38                         'editor_css' => '', // intended for extra styles for both visual and Text editors buttons, needs to include the <style> tags, can use "scoped".
39                         'editor_class' => '', // add extra class(es) to the editor textarea
40                         'teeny' => false, // output the minimal editor config used in Press This
41                         'dfw' => false, // replace the default fullscreen with DFW (needs specific DOM elements and css)
42                         'tinymce' => true, // load TinyMCE, can be used to pass settings directly to TinyMCE using an array()
43                         'quicktags' => true // load Quicktags, can be used to pass settings directly to Quicktags using an array()
44                 ) );
45
46                 self::$this_tinymce = ( $set['tinymce'] && user_can_richedit() );
47                 self::$this_quicktags = (bool) $set['quicktags'];
48
49                 if ( self::$this_tinymce )
50                         self::$has_tinymce = true;
51
52                 if ( self::$this_quicktags )
53                         self::$has_quicktags = true;
54
55                 if ( empty( $set['editor_height'] ) )
56                         return $set;
57
58                 if ( 'content' === $editor_id ) {
59                         // A cookie (set when a user resizes the editor) overrides the height.
60                         $cookie = (int) get_user_setting( 'ed_size' );
61
62                         // Upgrade an old TinyMCE cookie if it is still around, and the new one isn't.
63                         if ( ! $cookie && isset( $_COOKIE['TinyMCE_content_size'] ) ) {
64                                 parse_str( $_COOKIE['TinyMCE_content_size'], $cookie );
65                                 $cookie = $cookie['ch'];
66                         }
67
68                         if ( $cookie )
69                                 $set['editor_height'] = $cookie;
70                 }
71
72                 if ( $set['editor_height'] < 50 )
73                         $set['editor_height'] = 50;
74                 elseif ( $set['editor_height'] > 5000 )
75                         $set['editor_height'] = 5000;
76
77                 return $set;
78         }
79
80         /**
81          * Outputs the HTML for a single instance of the editor.
82          *
83          * @param string $content The initial content of the editor.
84          * @param string $editor_id ID for the textarea and TinyMCE and Quicktags instances (can contain only ASCII letters and numbers).
85          * @param array $settings See the _parse_settings() method for description.
86          */
87         public static function editor( $content, $editor_id, $settings = array() ) {
88
89                 $set = self::parse_settings($editor_id, $settings);
90                 $editor_class = ' class="' . trim( $set['editor_class'] . ' wp-editor-area' ) . '"';
91                 $tabindex = $set['tabindex'] ? ' tabindex="' . (int) $set['tabindex'] . '"' : '';
92                 $switch_class = 'html-active';
93                 $toolbar = $buttons = '';
94
95                 if ( ! empty( $set['editor_height'] ) )
96                         $height = ' style="height: ' . $set['editor_height'] . 'px"';
97                 else
98                         $height = ' rows="' . $set['textarea_rows'] . '"';
99
100                 if ( !current_user_can( 'upload_files' ) )
101                         $set['media_buttons'] = false;
102
103                 if ( self::$this_quicktags && self::$this_tinymce ) {
104                         $switch_class = 'html-active';
105
106                         // 'html' and 'switch-html' are used for the "Text" editor tab.
107                         if ( 'html' == wp_default_editor() ) {
108                                 add_filter('the_editor_content', 'wp_htmledit_pre');
109                         } else {
110                                 add_filter('the_editor_content', 'wp_richedit_pre');
111                                 $switch_class = 'tmce-active';
112                         }
113
114                         $buttons .= '<a id="' . $editor_id . '-html" class="wp-switch-editor switch-html" onclick="switchEditors.switchto(this);">' . _x( 'Text', 'Name for the Text editor tab (formerly HTML)' ) . "</a>\n";
115                         $buttons .= '<a id="' . $editor_id . '-tmce" class="wp-switch-editor switch-tmce" onclick="switchEditors.switchto(this);">' . __('Visual') . "</a>\n";
116                 }
117
118                 echo '<div id="wp-' . $editor_id . '-wrap" class="wp-core-ui wp-editor-wrap ' . $switch_class . '">';
119
120                 if ( self::$editor_buttons_css ) {
121                         wp_print_styles('editor-buttons');
122                         self::$editor_buttons_css = false;
123                 }
124
125                 if ( !empty($set['editor_css']) )
126                         echo $set['editor_css'] . "\n";
127
128                 if ( !empty($buttons) || $set['media_buttons'] ) {
129                         echo '<div id="wp-' . $editor_id . '-editor-tools" class="wp-editor-tools hide-if-no-js">';
130                         echo $buttons;
131
132                         if ( $set['media_buttons'] ) {
133                                 self::$has_medialib = true;
134
135                                 if ( !function_exists('media_buttons') )
136                                         include(ABSPATH . 'wp-admin/includes/media.php');
137
138                                 echo '<div id="wp-' . $editor_id . '-media-buttons" class="wp-media-buttons">';
139                                 do_action('media_buttons', $editor_id);
140                                 echo "</div>\n";
141                         }
142                         echo "</div>\n";
143                 }
144
145                 $the_editor = apply_filters('the_editor', '<div id="wp-' . $editor_id . '-editor-container" class="wp-editor-container"><textarea' . $editor_class . $height . $tabindex . ' cols="40" name="' . $set['textarea_name'] . '" id="' . $editor_id . '">%s</textarea></div>');
146                 $content = apply_filters('the_editor_content', $content);
147
148                 printf($the_editor, $content);
149                 echo "\n</div>\n\n";
150
151                 self::editor_settings($editor_id, $set);
152         }
153
154         public static function editor_settings($editor_id, $set) {
155                 $first_run = false;
156
157                 if ( empty(self::$first_init) ) {
158                         if ( is_admin() ) {
159                                 add_action( 'admin_print_footer_scripts', array( __CLASS__, 'editor_js'), 50 );
160                                 add_action( 'admin_footer', array( __CLASS__, 'enqueue_scripts'), 1 );
161                         } else {
162                                 add_action( 'wp_print_footer_scripts', array( __CLASS__, 'editor_js'), 50 );
163                                 add_action( 'wp_footer', array( __CLASS__, 'enqueue_scripts'), 1 );
164                         }
165                 }
166
167                 if ( self::$this_quicktags ) {
168
169                         $qtInit = array(
170                                 'id' => $editor_id,
171                                 'buttons' => ''
172                         );
173
174                         if ( is_array($set['quicktags']) )
175                                 $qtInit = array_merge($qtInit, $set['quicktags']);
176
177                         if ( empty($qtInit['buttons']) )
178                                 $qtInit['buttons'] = 'strong,em,link,block,del,ins,img,ul,ol,li,code,more,close';
179
180                         if ( $set['dfw'] )
181                                 $qtInit['buttons'] .= ',fullscreen';
182
183                         $qtInit = apply_filters('quicktags_settings', $qtInit, $editor_id);
184                         self::$qt_settings[$editor_id] = $qtInit;
185
186                         self::$qt_buttons = array_merge( self::$qt_buttons, explode(',', $qtInit['buttons']) );
187                 }
188
189                 if ( self::$this_tinymce ) {
190
191                         if ( empty(self::$first_init) ) {
192                                 self::$baseurl = includes_url('js/tinymce');
193                                 self::$mce_locale = $mce_locale = ( '' == get_locale() ) ? 'en' : strtolower( substr(get_locale(), 0, 2) ); // only ISO 639-1
194                                 $no_captions = (bool) apply_filters( 'disable_captions', '' );
195                                 $plugins = array( 'inlinepopups', 'tabfocus', 'paste', 'media', 'fullscreen', 'wordpress', 'wpeditimage', 'wpgallery', 'wplink', 'wpdialogs' );
196                                 $first_run = true;
197                                 $ext_plugins = '';
198
199                                 if ( $set['teeny'] ) {
200                                         self::$plugins = $plugins = apply_filters( 'teeny_mce_plugins', array('inlinepopups', 'fullscreen', 'wordpress', 'wplink', 'wpdialogs' ), $editor_id );
201                                 } else {
202                                         /*
203                                         The following filter takes an associative array of external plugins for TinyMCE in the form 'plugin_name' => 'url'.
204                                         It adds the plugin's name to TinyMCE's plugins init and the call to PluginManager to load the plugin.
205                                         The url should be absolute and should include the js file name to be loaded. Example:
206                                         array( 'myplugin' => 'http://my-site.com/wp-content/plugins/myfolder/mce_plugin.js' )
207                                         If the plugin uses a button, it should be added with one of the "$mce_buttons" filters.
208                                         */
209                                         $mce_external_plugins = apply_filters('mce_external_plugins', array());
210
211                                         if ( ! empty($mce_external_plugins) ) {
212
213                                                 /*
214                                                 The following filter loads external language files for TinyMCE plugins.
215                                                 It takes an associative array 'plugin_name' => 'path', where path is the
216                                                 include path to the file. The language file should follow the same format as
217                                                 /tinymce/langs/wp-langs.php and should define a variable $strings that
218                                                 holds all translated strings.
219                                                 When this filter is not used, the function will try to load {mce_locale}.js.
220                                                 If that is not found, en.js will be tried next.
221                                                 */
222                                                 $mce_external_languages = apply_filters('mce_external_languages', array());
223
224                                                 $loaded_langs = array();
225                                                 $strings = '';
226
227                                                 if ( ! empty($mce_external_languages) ) {
228                                                         foreach ( $mce_external_languages as $name => $path ) {
229                                                                 if ( @is_file($path) && @is_readable($path) ) {
230                                                                         include_once($path);
231                                                                         $ext_plugins .= $strings . "\n";
232                                                                         $loaded_langs[] = $name;
233                                                                 }
234                                                         }
235                                                 }
236
237                                                 foreach ( $mce_external_plugins as $name => $url ) {
238
239                                                         $url = set_url_scheme( $url );
240
241                                                         $plugins[] = '-' . $name;
242
243                                                         $plugurl = dirname($url);
244                                                         $strings = $str1 = $str2 = '';
245                                                         if ( ! in_array($name, $loaded_langs) ) {
246                                                                 $path = str_replace( content_url(), '', $plugurl );
247                                                                 $path = WP_CONTENT_DIR . $path . '/langs/';
248
249                                                                 if ( function_exists('realpath') )
250                                                                         $path = trailingslashit( realpath($path) );
251
252                                                                 if ( @is_file($path . $mce_locale . '.js') )
253                                                                         $strings .= @file_get_contents($path . $mce_locale . '.js') . "\n";
254
255                                                                 if ( @is_file($path . $mce_locale . '_dlg.js') )
256                                                                         $strings .= @file_get_contents($path . $mce_locale . '_dlg.js') . "\n";
257
258                                                                 if ( 'en' != $mce_locale && empty($strings) ) {
259                                                                         if ( @is_file($path . 'en.js') ) {
260                                                                                 $str1 = @file_get_contents($path . 'en.js');
261                                                                                 $strings .= preg_replace( '/([\'"])en\./', '$1' . $mce_locale . '.', $str1, 1 ) . "\n";
262                                                                         }
263
264                                                                         if ( @is_file($path . 'en_dlg.js') ) {
265                                                                                 $str2 = @file_get_contents($path . 'en_dlg.js');
266                                                                                 $strings .= preg_replace( '/([\'"])en\./', '$1' . $mce_locale . '.', $str2, 1 ) . "\n";
267                                                                         }
268                                                                 }
269
270                                                                 if ( ! empty($strings) )
271                                                                         $ext_plugins .= "\n" . $strings . "\n";
272                                                         }
273
274                                                         $ext_plugins .= 'tinyMCEPreInit.load_ext("' . $plugurl . '", "' . $mce_locale . '");' . "\n";
275                                                         $ext_plugins .= 'tinymce.PluginManager.load("' . $name . '", "' . $url . '");' . "\n";
276                                                 }
277                                         }
278
279                                         $plugins = array_unique( apply_filters('tiny_mce_plugins', $plugins) );
280                                 }
281
282                                 if ( $set['dfw'] )
283                                         $plugins[] = 'wpfullscreen';
284
285                                 self::$plugins = $plugins;
286                                 self::$ext_plugins = $ext_plugins;
287
288                                 if ( in_array( 'spellchecker', $plugins ) ) {
289                                         /*
290                                         translators: These languages show up in the spellchecker drop-down menu, in the order specified, and with the first
291                                         language listed being the default language. They must be comma-separated and take the format of name=code, where name
292                                         is the language name (which you may internationalize), and code is a valid ISO 639 language code. Please test the
293                                         spellchecker with your values.
294                                         */
295                                         $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' );
296
297                                         /*
298                                         The following filter allows localization scripts to change the languages displayed in the spellchecker's drop-down menu.
299                                         By default it uses Google's spellchecker API, but can be configured to use PSpell/ASpell if installed on the server.
300                                         The + sign marks the default language. More: http://www.tinymce.com/wiki.php/Plugin:spellchecker.
301                                         */
302                                         $mce_spellchecker_languages = apply_filters( 'mce_spellchecker_languages', '+' . $mce_spellchecker_languages );
303                                 }
304
305                                 self::$first_init = array(
306                                         'mode' => 'exact',
307                                         'width' => '100%',
308                                         'theme' => 'advanced',
309                                         'skin' => 'wp_theme',
310                                         'language' => self::$mce_locale,
311                                         'theme_advanced_toolbar_location' => 'top',
312                                         'theme_advanced_toolbar_align' => 'left',
313                                         'theme_advanced_statusbar_location' => 'bottom',
314                                         'theme_advanced_resizing' => true,
315                                         'theme_advanced_resize_horizontal' => false,
316                                         'dialog_type' => 'modal',
317                                         'formats' => "{
318                                                 alignleft : [
319                                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'left'}},
320                                                         {selector : 'img,table', classes : 'alignleft'}
321                                                 ],
322                                                 aligncenter : [
323                                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'center'}},
324                                                         {selector : 'img,table', classes : 'aligncenter'}
325                                                 ],
326                                                 alignright : [
327                                                         {selector : 'p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li', styles : {textAlign : 'right'}},
328                                                         {selector : 'img,table', classes : 'alignright'}
329                                                 ],
330                                                 strikethrough : {inline : 'del'}
331                                         }",
332                                         'relative_urls' => false,
333                                         'remove_script_host' => false,
334                                         'convert_urls' => false,
335                                         'remove_linebreaks' => true,
336                                         'gecko_spellcheck' => true,
337                                         'fix_list_elements' => true,
338                                         'keep_styles' => false,
339                                         'entities' => '38,amp,60,lt,62,gt',
340                                         'accessibility_focus' => true,
341                                         'media_strict' => false,
342                                         'paste_remove_styles' => true,
343                                         'paste_remove_spans' => true,
344                                         'paste_strip_class_attributes' => 'all',
345                                         'paste_text_use_dialog' => true,
346                                         'webkit_fake_resize' => false,
347                                         'preview_styles' => 'font-family font-weight text-decoration text-transform',
348                                         'schema' => 'html5',
349                                         'wpeditimage_disable_captions' => $no_captions,
350                                         'wp_fullscreen_content_css' => self::$baseurl . '/plugins/wpfullscreen/css/wp-fullscreen.css',
351                                         'plugins' => implode( ',', $plugins )
352                                 );
353
354                                 if ( in_array( 'spellchecker', $plugins ) ) {
355                                         self::$first_init['spellchecker_rpc_url'] = self::$baseurl . '/plugins/spellchecker/rpc.php';
356                                         self::$first_init['spellchecker_languages'] = $mce_spellchecker_languages;
357                                 }
358
359                                 // load editor_style.css if the current theme supports it
360                                 if ( ! empty( $GLOBALS['editor_styles'] ) && is_array( $GLOBALS['editor_styles'] ) ) {
361                                         $editor_styles = $GLOBALS['editor_styles'];
362
363                                         $mce_css = array();
364                                         $editor_styles = array_unique( array_filter( $editor_styles ) );
365                                         $style_uri = get_stylesheet_directory_uri();
366                                         $style_dir = get_stylesheet_directory();
367
368                                         // Support externally referenced styles (like, say, fonts).
369                                         foreach ( $editor_styles as $key => $file ) {
370                                                 if ( preg_match( '~^(https?:)?//~', $file ) ) {
371                                                         $mce_css[] = esc_url_raw( $file );
372                                                         unset( $editor_styles[ $key ] );
373                                                 }
374                                         }
375
376                                         // Look in a parent theme first, that way child theme CSS overrides.
377                                         if ( is_child_theme() ) {
378                                                 $template_uri = get_template_directory_uri();
379                                                 $template_dir = get_template_directory();
380
381                                                 foreach ( $editor_styles as $key => $file ) {
382                                                         if ( $file && file_exists( "$template_dir/$file" ) )
383                                                                 $mce_css[] = "$template_uri/$file";
384                                                 }
385                                         }
386
387                                         foreach ( $editor_styles as $file ) {
388                                                 if ( $file && file_exists( "$style_dir/$file" ) )
389                                                         $mce_css[] = "$style_uri/$file";
390                                         }
391
392                                         $mce_css = implode( ',', $mce_css );
393                                 } else {
394                                         $mce_css = '';
395                                 }
396
397                                 $mce_css = trim( apply_filters( 'mce_css', $mce_css ), ' ,' );
398
399                                 if ( ! empty($mce_css) )
400                                         self::$first_init['content_css'] = $mce_css;
401                         }
402
403                         if ( $set['teeny'] ) {
404                                 $mce_buttons = apply_filters( 'teeny_mce_buttons', array('bold', 'italic', 'underline', 'blockquote', 'strikethrough', 'bullist', 'numlist', 'justifyleft', 'justifycenter', 'justifyright', 'undo', 'redo', 'link', 'unlink', 'fullscreen'), $editor_id );
405                                 $mce_buttons_2 = $mce_buttons_3 = $mce_buttons_4 = array();
406                         } else {
407                                 $mce_buttons = apply_filters('mce_buttons', array('bold', 'italic', 'strikethrough', 'bullist', 'numlist', 'blockquote', 'justifyleft', 'justifycenter', 'justifyright', 'link', 'unlink', 'wp_more', 'spellchecker', 'fullscreen', 'wp_adv' ), $editor_id);
408                                 $mce_buttons_2 = apply_filters('mce_buttons_2', array( 'formatselect', 'underline', 'justifyfull', 'forecolor', 'pastetext', 'pasteword', 'removeformat', 'charmap', 'outdent', 'indent', 'undo', 'redo', 'wp_help' ), $editor_id);
409                                 $mce_buttons_3 = apply_filters('mce_buttons_3', array(), $editor_id);
410                                 $mce_buttons_4 = apply_filters('mce_buttons_4', array(), $editor_id);
411                         }
412
413                         $body_class = $editor_id;
414
415                         if ( $post = get_post() ) {
416                                 $body_class .= ' post-type-' . sanitize_html_class( $post->post_type ) . ' post-status-' . sanitize_html_class( $post->post_status );
417                                 if ( post_type_supports( $post->post_type, 'post-formats' ) ) {
418                                         $post_format = get_post_format( $post );
419                                         if ( $post_format && ! is_wp_error( $post_format ) )
420                                                 $body_class .= ' post-format-' . sanitize_html_class( $post_format );
421                                         else
422                                                 $body_class .= ' post-format-standard';
423                                 }
424                         }
425
426                         if ( !empty($set['tinymce']['body_class']) ) {
427                                 $body_class .= ' ' . $set['tinymce']['body_class'];
428                                 unset($set['tinymce']['body_class']);
429                         }
430
431                         if ( $set['dfw'] ) {
432                                 // replace the first 'fullscreen' with 'wp_fullscreen'
433                                 if ( ($key = array_search('fullscreen', $mce_buttons)) !== false )
434                                         $mce_buttons[$key] = 'wp_fullscreen';
435                                 elseif ( ($key = array_search('fullscreen', $mce_buttons_2)) !== false )
436                                         $mce_buttons_2[$key] = 'wp_fullscreen';
437                                 elseif ( ($key = array_search('fullscreen', $mce_buttons_3)) !== false )
438                                         $mce_buttons_3[$key] = 'wp_fullscreen';
439                                 elseif ( ($key = array_search('fullscreen', $mce_buttons_4)) !== false )
440                                         $mce_buttons_4[$key] = 'wp_fullscreen';
441                         }
442
443                         $mceInit = array (
444                                 'elements' => $editor_id,
445                                 'wpautop' => (bool) $set['wpautop'],
446                                 'remove_linebreaks' => (bool) $set['wpautop'],
447                                 'apply_source_formatting' => (bool) !$set['wpautop'],
448                                 'theme_advanced_buttons1' => implode($mce_buttons, ','),
449                                 'theme_advanced_buttons2' => implode($mce_buttons_2, ','),
450                                 'theme_advanced_buttons3' => implode($mce_buttons_3, ','),
451                                 'theme_advanced_buttons4' => implode($mce_buttons_4, ','),
452                                 'tabfocus_elements' => $set['tabfocus_elements'],
453                                 'body_class' => $body_class
454                         );
455
456                         // The main editor doesn't use the TinyMCE resizing cookie.
457                         $mceInit['theme_advanced_resizing_use_cookie'] = 'content' !== $editor_id || empty( $set['editor_height'] );
458
459                         if ( $first_run )
460                                 $mceInit = array_merge(self::$first_init, $mceInit);
461
462                         if ( is_array($set['tinymce']) )
463                                 $mceInit = array_merge($mceInit, $set['tinymce']);
464
465                         // For people who really REALLY know what they're doing with TinyMCE
466                         // You can modify $mceInit to add, remove, change elements of the config before tinyMCE.init
467                         // Setting "valid_elements", "invalid_elements" and "extended_valid_elements" can be done through this filter.
468                         // Best is to use the default cleanup by not specifying valid_elements, as TinyMCE contains full set of XHTML 1.0.
469                         if ( $set['teeny'] ) {
470                                 $mceInit = apply_filters('teeny_mce_before_init', $mceInit, $editor_id);
471                         } else {
472                                 $mceInit = apply_filters('tiny_mce_before_init', $mceInit, $editor_id);
473                         }
474
475                         if ( empty($mceInit['theme_advanced_buttons3']) && !empty($mceInit['theme_advanced_buttons4']) ) {
476                                 $mceInit['theme_advanced_buttons3'] = $mceInit['theme_advanced_buttons4'];
477                                 $mceInit['theme_advanced_buttons4'] = '';
478                         }
479
480                         self::$mce_settings[$editor_id] = $mceInit;
481                 } // end if self::$this_tinymce
482         }
483
484         private static function _parse_init($init) {
485                 $options = '';
486
487                 foreach ( $init as $k => $v ) {
488                         if ( is_bool($v) ) {
489                                 $val = $v ? 'true' : 'false';
490                                 $options .= $k . ':' . $val . ',';
491                                 continue;
492                         } elseif ( !empty($v) && is_string($v) && ( ('{' == $v{0} && '}' == $v{strlen($v) - 1}) || ('[' == $v{0} && ']' == $v{strlen($v) - 1}) || preg_match('/^\(?function ?\(/', $v) ) ) {
493                                 $options .= $k . ':' . $v . ',';
494                                 continue;
495                         }
496                         $options .= $k . ':"' . $v . '",';
497                 }
498
499                 return '{' . trim( $options, ' ,' ) . '}';
500         }
501
502         public static function enqueue_scripts() {
503                 wp_enqueue_script('word-count');
504
505                 if ( self::$has_tinymce )
506                         wp_enqueue_script('editor');
507
508                 if ( self::$has_quicktags )
509                         wp_enqueue_script('quicktags');
510
511                 if ( in_array('wplink', self::$plugins, true) || in_array('link', self::$qt_buttons, true) ) {
512                         wp_enqueue_script('wplink');
513                         wp_enqueue_script('wpdialogs-popup');
514                         wp_enqueue_style('wp-jquery-ui-dialog');
515                 }
516
517                 if ( in_array('wpfullscreen', self::$plugins, true) || in_array('fullscreen', self::$qt_buttons, true) )
518                         wp_enqueue_script('wp-fullscreen');
519
520                 if ( self::$has_medialib ) {
521                         add_thickbox();
522                         wp_enqueue_script('media-upload');
523                 }
524         }
525
526         public static function editor_js() {
527                 global $tinymce_version, $concatenate_scripts, $compress_scripts;
528
529                 /**
530                  * Filter "tiny_mce_version" is deprecated
531                  *
532                  * The tiny_mce_version filter is not needed since external plugins are loaded directly by TinyMCE.
533                  * These plugins can be refreshed by appending query string to the URL passed to "mce_external_plugins" filter.
534                  * 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).
535                  */
536                 $version = 'ver=' . $tinymce_version;
537                 $tmce_on = !empty(self::$mce_settings);
538
539                 if ( ! isset($concatenate_scripts) )
540                         script_concat_settings();
541
542                 $compressed = $compress_scripts && $concatenate_scripts && isset($_SERVER['HTTP_ACCEPT_ENCODING'])
543                         && false !== stripos($_SERVER['HTTP_ACCEPT_ENCODING'], 'gzip');
544
545                 if ( $tmce_on && 'en' != self::$mce_locale )
546                         include_once(ABSPATH . WPINC . '/js/tinymce/langs/wp-langs.php');
547
548                 $mceInit = $qtInit = '';
549                 if ( $tmce_on ) {
550                         foreach ( self::$mce_settings as $editor_id => $init ) {
551                                 $options = self::_parse_init( $init );
552                                 $mceInit .= "'$editor_id':{$options},";
553                         }
554                         $mceInit = '{' . trim($mceInit, ',') . '}';
555                 } else {
556                         $mceInit = '{}';
557                 }
558
559                 if ( !empty(self::$qt_settings) ) {
560                         foreach ( self::$qt_settings as $editor_id => $init ) {
561                                 $options = self::_parse_init( $init );
562                                 $qtInit .= "'$editor_id':{$options},";
563                         }
564                         $qtInit = '{' . trim($qtInit, ',') . '}';
565                 } else {
566                         $qtInit = '{}';
567                 }
568
569                 $ref = array(
570                         'plugins' => implode( ',', self::$plugins ),
571                         'theme' => 'advanced',
572                         'language' => self::$mce_locale
573                 );
574
575                 $suffix = ( defined('SCRIPT_DEBUG') && SCRIPT_DEBUG ) ? '_src' : '';
576
577                 do_action('before_wp_tiny_mce', self::$mce_settings);
578 ?>
579
580         <script type="text/javascript">
581                 tinyMCEPreInit = {
582                         base : "<?php echo self::$baseurl; ?>",
583                         suffix : "<?php echo $suffix; ?>",
584                         query : "<?php echo $version; ?>",
585                         mceInit : <?php echo $mceInit; ?>,
586                         qtInit : <?php echo $qtInit; ?>,
587                         ref : <?php echo self::_parse_init( $ref ); ?>,
588                         load_ext : function(url,lang){var sl=tinymce.ScriptLoader;sl.markDone(url+'/langs/'+lang+'.js');sl.markDone(url+'/langs/'+lang+'_dlg.js');}
589                 };
590         </script>
591 <?php
592
593                 $baseurl = self::$baseurl;
594
595                 if ( $tmce_on ) {
596                         if ( $compressed ) {
597                                 echo "<script type='text/javascript' src='{$baseurl}/wp-tinymce.php?c=1&amp;$version'></script>\n";
598                         } else {
599                                 echo "<script type='text/javascript' src='{$baseurl}/tiny_mce.js?$version'></script>\n";
600                                 echo "<script type='text/javascript' src='{$baseurl}/wp-tinymce-schema.js?$version'></script>\n";
601                         }
602
603                         if ( 'en' != self::$mce_locale && isset($lang) )
604                                 echo "<script type='text/javascript'>\n$lang\n</script>\n";
605                         else
606                                 echo "<script type='text/javascript' src='{$baseurl}/langs/wp-langs-en.js?$version'></script>\n";
607                 }
608
609                 $mce = ( self::$has_tinymce && wp_default_editor() == 'tinymce' ) || ! self::$has_quicktags;
610 ?>
611
612         <script type="text/javascript">
613                 var wpActiveEditor;
614
615                 (function(){
616                         var init, ed, qt, first_init, DOM, el, i, mce = <?php echo (int) $mce; ?>;
617
618                         if ( typeof(tinymce) == 'object' ) {
619                                 DOM = tinymce.DOM;
620                                 // mark wp_theme/ui.css as loaded
621                                 DOM.files[tinymce.baseURI.getURI() + '/themes/advanced/skins/wp_theme/ui.css'] = true;
622
623                                 DOM.events.add( DOM.select('.wp-editor-wrap'), 'mousedown', function(e){
624                                         if ( this.id )
625                                                 wpActiveEditor = this.id.slice(3, -5);
626                                 });
627
628                                 for ( ed in tinyMCEPreInit.mceInit ) {
629                                         if ( first_init ) {
630                                                 init = tinyMCEPreInit.mceInit[ed] = tinymce.extend( {}, first_init, tinyMCEPreInit.mceInit[ed] );
631                                         } else {
632                                                 init = first_init = tinyMCEPreInit.mceInit[ed];
633                                         }
634
635                                         if ( mce )
636                                                 try { tinymce.init(init); } catch(e){}
637                                 }
638                         } else {
639                                 if ( tinyMCEPreInit.qtInit ) {
640                                         for ( i in tinyMCEPreInit.qtInit ) {
641                                                 el = tinyMCEPreInit.qtInit[i].id;
642                                                 if ( el )
643                                                         document.getElementById('wp-'+el+'-wrap').onmousedown = function(){ wpActiveEditor = this.id.slice(3, -5); }
644                                         }
645                                 }
646                         }
647
648                         if ( typeof(QTags) == 'function' ) {
649                                 for ( qt in tinyMCEPreInit.qtInit ) {
650                                         try { quicktags( tinyMCEPreInit.qtInit[qt] ); } catch(e){}
651                                 }
652                         }
653                 })();
654                 <?php
655
656                 if ( self::$ext_plugins )
657                         echo self::$ext_plugins . "\n";
658
659                 if ( ! $compressed && $tmce_on ) {
660                         ?>
661                         (function(){var t=tinyMCEPreInit,sl=tinymce.ScriptLoader,ln=t.ref.language,th=t.ref.theme,pl=t.ref.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');sl.markDone(t.base+'/themes/advanced/skins/wp_theme/ui.css');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');}});})();
662                         <?php
663                 }
664
665                 if ( !is_admin() )
666                         echo 'var ajaxurl = "' . admin_url( 'admin-ajax.php', 'relative' ) . '";';
667
668                 ?>
669                 </script>
670                 <?php
671
672                 if ( in_array('wplink', self::$plugins, true) || in_array('link', self::$qt_buttons, true) )
673                         self::wp_link_dialog();
674
675                 if ( in_array('wpfullscreen', self::$plugins, true) || in_array('fullscreen', self::$qt_buttons, true) )
676                         self::wp_fullscreen_html();
677
678                 do_action('after_wp_tiny_mce', self::$mce_settings);
679         }
680
681         public static function wp_fullscreen_html() {
682                 global $content_width;
683                 $post = get_post();
684
685                 $width = isset($content_width) && 800 > $content_width ? $content_width : 800;
686                 $width = $width + 22; // compensate for the padding and border
687                 $dfw_width = get_user_setting( 'dfw_width', $width );
688                 $save = isset($post->post_status) && $post->post_status == 'publish' ? __('Update') : __('Save');
689         ?>
690         <div id="wp-fullscreen-body"<?php if ( is_rtl() ) echo ' class="rtl"'; ?>>
691         <div id="fullscreen-topbar">
692                 <div id="wp-fullscreen-toolbar">
693                         <div id="wp-fullscreen-close"><a href="#" onclick="fullscreen.off();return false;"><?php _e('Exit fullscreen'); ?></a></div>
694                         <div id="wp-fullscreen-central-toolbar" style="width:<?php echo $width; ?>px;">
695
696                         <div id="wp-fullscreen-mode-bar"><div id="wp-fullscreen-modes">
697                                 <a href="#" onclick="fullscreen.switchmode('tinymce');return false;"><?php _e( 'Visual' ); ?></a>
698                                 <a href="#" onclick="fullscreen.switchmode('html');return false;"><?php _ex( 'Text', 'Name for the Text editor tab (formerly HTML)' ); ?></a>
699                         </div></div>
700
701                         <div id="wp-fullscreen-button-bar"><div id="wp-fullscreen-buttons" class="wp_themeSkin">
702         <?php
703
704                 $buttons = array(
705                         // format: title, onclick, show in both editors
706                         'bold' => array( 'title' => __('Bold (Ctrl + B)'), 'onclick' => 'fullscreen.b();', 'both' => false ),
707                         'italic' => array( 'title' => __('Italic (Ctrl + I)'), 'onclick' => 'fullscreen.i();', 'both' => false ),
708                         '0' => 'separator',
709                         'bullist' => array( 'title' => __('Unordered list (Alt + Shift + U)'), 'onclick' => 'fullscreen.ul();', 'both' => false ),
710                         'numlist' => array( 'title' => __('Ordered list (Alt + Shift + O)'), 'onclick' => 'fullscreen.ol();', 'both' => false ),
711                         '1' => 'separator',
712                         'blockquote' => array( 'title' => __('Blockquote (Alt + Shift + Q)'), 'onclick' => 'fullscreen.blockquote();', 'both' => false ),
713                         'image' => array( 'title' => __('Insert/edit image (Alt + Shift + M)'), 'onclick' => "fullscreen.medialib();", 'both' => true ),
714                         '2' => 'separator',
715                         'link' => array( 'title' => __('Insert/edit link (Alt + Shift + A)'), 'onclick' => 'fullscreen.link();', 'both' => true ),
716                         'unlink' => array( 'title' => __('Unlink (Alt + Shift + S)'), 'onclick' => 'fullscreen.unlink();', 'both' => false ),
717                         '3' => 'separator',
718                         'help' => array( 'title' => __('Help (Alt + Shift + H)'), 'onclick' => 'fullscreen.help();', 'both' => false )
719                 );
720
721                 $buttons = apply_filters( 'wp_fullscreen_buttons', $buttons );
722
723                 foreach ( $buttons as $button => $args ) {
724                         if ( 'separator' == $args ) { ?>
725                                 <div><span aria-orientation="vertical" role="separator" class="mceSeparator"></span></div>
726         <?php           continue;
727                         } ?>
728
729                         <div<?php if ( $args['both'] ) { ?> class="wp-fullscreen-both"<?php } ?>>
730                         <a title="<?php echo $args['title']; ?>" onclick="<?php echo $args['onclick']; ?>return false;" class="mceButton mceButtonEnabled mce_<?php echo $button; ?>" href="#" id="wp_fs_<?php echo $button; ?>" role="button" aria-pressed="false">
731                         <span class="mceIcon mce_<?php echo $button; ?>"></span>
732                         </a>
733                         </div>
734         <?php
735                 } ?>
736
737                         </div></div>
738
739                         <div id="wp-fullscreen-save">
740                                 <input type="button" class="button-primary right" value="<?php echo $save; ?>" onclick="fullscreen.save();" />
741                                 <span class="spinner"></span>
742                                 <span class="fs-saved"><?php if ( $post->post_status == 'publish' ) _e('Updated.'); else _e('Saved.'); ?></span>
743                         </div>
744
745                         </div>
746                 </div>
747         </div>
748
749         <div id="wp-fullscreen-wrap" style="width:<?php echo $dfw_width; ?>px;">
750                 <?php if ( post_type_supports($post->post_type, 'title') ) { ?>
751                 <label id="wp-fullscreen-title-prompt-text" for="wp-fullscreen-title"><?php echo apply_filters( 'enter_title_here', __( 'Enter title here' ), $post ); ?></label>
752                 <input type="text" id="wp-fullscreen-title" value="" autocomplete="off" />
753                 <?php } ?>
754
755                 <div id="wp-fullscreen-container">
756                         <textarea id="wp_mce_fullscreen"></textarea>
757                 </div>
758
759                 <div id="wp-fullscreen-status">
760                         <div id="wp-fullscreen-count"><?php printf( __( 'Word count: %s' ), '<span class="word-count">0</span>' ); ?></div>
761                         <div id="wp-fullscreen-tagline"><?php _e('Just write.'); ?></div>
762                 </div>
763         </div>
764         </div>
765
766         <div class="fullscreen-overlay" id="fullscreen-overlay"></div>
767         <div class="fullscreen-overlay fullscreen-fader fade-600" id="fullscreen-fader"></div>
768         <?php
769         }
770
771         /**
772          * Performs post queries for internal linking.
773          *
774          * @since 3.1.0
775          *
776          * @param array $args Optional. Accepts 'pagenum' and 's' (search) arguments.
777          * @return array Results.
778          */
779         public static function wp_link_query( $args = array() ) {
780                 $pts = get_post_types( array( 'public' => true ), 'objects' );
781                 $pt_names = array_keys( $pts );
782
783                 $query = array(
784                         'post_type' => $pt_names,
785                         'suppress_filters' => true,
786                         'update_post_term_cache' => false,
787                         'update_post_meta_cache' => false,
788                         'post_status' => 'publish',
789                         'order' => 'DESC',
790                         'orderby' => 'post_date',
791                         'posts_per_page' => 20,
792                 );
793
794                 $args['pagenum'] = isset( $args['pagenum'] ) ? absint( $args['pagenum'] ) : 1;
795
796                 if ( isset( $args['s'] ) )
797                         $query['s'] = $args['s'];
798
799                 $query['offset'] = $args['pagenum'] > 1 ? $query['posts_per_page'] * ( $args['pagenum'] - 1 ) : 0;
800
801                 // Do main query.
802                 $get_posts = new WP_Query;
803                 $posts = $get_posts->query( $query );
804                 // Check if any posts were found.
805                 if ( ! $get_posts->post_count )
806                         return false;
807
808                 // Build results.
809                 $results = array();
810                 foreach ( $posts as $post ) {
811                         if ( 'post' == $post->post_type )
812                                 $info = mysql2date( __( 'Y/m/d' ), $post->post_date );
813                         else
814                                 $info = $pts[ $post->post_type ]->labels->singular_name;
815
816                         $results[] = array(
817                                 'ID' => $post->ID,
818                                 'title' => trim( esc_html( strip_tags( get_the_title( $post ) ) ) ),
819                                 'permalink' => get_permalink( $post->ID ),
820                                 'info' => $info,
821                         );
822                 }
823
824                 return $results;
825         }
826
827         /**
828          * Dialog for internal linking.
829          *
830          * @since 3.1.0
831          */
832         public static function wp_link_dialog() {
833         ?>
834         <div style="display:none;">
835         <form id="wp-link" tabindex="-1">
836         <?php wp_nonce_field( 'internal-linking', '_ajax_linking_nonce', false ); ?>
837         <div id="link-selector">
838                 <div id="link-options">
839                         <p class="howto"><?php _e( 'Enter the destination URL' ); ?></p>
840                         <div>
841                                 <label><span><?php _e( 'URL' ); ?></span><input id="url-field" type="text" name="href" /></label>
842                         </div>
843                         <div>
844                                 <label><span><?php _e( 'Title' ); ?></span><input id="link-title-field" type="text" name="linktitle" /></label>
845                         </div>
846                         <div class="link-target">
847                                 <label><input type="checkbox" id="link-target-checkbox" /> <?php _e( 'Open link in a new window/tab' ); ?></label>
848                         </div>
849                 </div>
850                 <?php $show_internal = '1' == get_user_setting( 'wplink', '0' ); ?>
851                 <p class="howto toggle-arrow <?php if ( $show_internal ) echo 'toggle-arrow-active'; ?>" id="internal-toggle"><?php _e( 'Or link to existing content' ); ?></p>
852                 <div id="search-panel"<?php if ( ! $show_internal ) echo ' style="display:none"'; ?>>
853                         <div class="link-search-wrapper">
854                                 <label>
855                                         <span class="search-label"><?php _e( 'Search' ); ?></span>
856                                         <input type="search" id="search-field" class="link-search-field" autocomplete="off" />
857                                         <span class="spinner"></span>
858                                 </label>
859                         </div>
860                         <div id="search-results" class="query-results">
861                                 <ul></ul>
862                                 <div class="river-waiting">
863                                         <span class="spinner"></span>
864                                 </div>
865                         </div>
866                         <div id="most-recent-results" class="query-results">
867                                 <div class="query-notice"><em><?php _e( 'No search term specified. Showing recent items.' ); ?></em></div>
868                                 <ul></ul>
869                                 <div class="river-waiting">
870                                         <span class="spinner"></span>
871                                 </div>
872                         </div>
873                 </div>
874         </div>
875         <div class="submitbox">
876                 <div id="wp-link-update">
877                         <input type="submit" value="<?php esc_attr_e( 'Add Link' ); ?>" class="button-primary" id="wp-link-submit" name="wp-link-submit">
878                 </div>
879                 <div id="wp-link-cancel">
880                         <a class="submitdelete deletion" href="#"><?php _e( 'Cancel' ); ?></a>
881                 </div>
882         </div>
883         </form>
884         </div>
885         <?php
886         }
887 }