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