PageRenderTime 61ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

/wp-includes/class-wp-editor.php

https://github.com/markjaquith/WordPress
PHP | 1914 lines | 1179 code | 242 blank | 493 comment | 160 complexity | 17096533704e5f6f6e30ddd47d12f4b0 MD5 | raw file
Possible License(s): GPL-2.0, AGPL-1.0, LGPL-2.1

Large files files are truncated, but you can click here to view the full file

  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. final class _WP_Editors {
  11. public static $mce_locale;
  12. private static $mce_settings = array();
  13. private static $qt_settings = array();
  14. private static $plugins = array();
  15. private static $qt_buttons = array();
  16. private static $ext_plugins;
  17. private static $baseurl;
  18. private static $first_init;
  19. private static $this_tinymce = false;
  20. private static $this_quicktags = false;
  21. private static $has_tinymce = false;
  22. private static $has_quicktags = false;
  23. private static $has_medialib = false;
  24. private static $editor_buttons_css = true;
  25. private static $drag_drop_upload = false;
  26. private static $translation;
  27. private static $tinymce_scripts_printed = false;
  28. private static $link_dialog_printed = false;
  29. private function __construct() {}
  30. /**
  31. * Parse default arguments for the editor instance.
  32. *
  33. * @since 3.3.0
  34. *
  35. * @param string $editor_id HTML ID for the textarea and TinyMCE and Quicktags instances.
  36. * Should not contain square brackets.
  37. * @param array $settings {
  38. * Array of editor arguments.
  39. *
  40. * @type bool $wpautop Whether to use wpautop(). Default true.
  41. * @type bool $media_buttons Whether to show the Add Media/other media buttons.
  42. * @type string $default_editor When both TinyMCE and Quicktags are used, set which
  43. * editor is shown on page load. Default empty.
  44. * @type bool $drag_drop_upload Whether to enable drag & drop on the editor uploading. Default false.
  45. * Requires the media modal.
  46. * @type string $textarea_name Give the textarea a unique name here. Square brackets
  47. * can be used here. Default $editor_id.
  48. * @type int $textarea_rows Number rows in the editor textarea. Default 20.
  49. * @type string|int $tabindex Tabindex value to use. Default empty.
  50. * @type string $tabfocus_elements The previous and next element ID to move the focus to
  51. * when pressing the Tab key in TinyMCE. Default ':prev,:next'.
  52. * @type string $editor_css Intended for extra styles for both Visual and Text editors.
  53. * Should include `<style>` tags, and can use "scoped". Default empty.
  54. * @type string $editor_class Extra classes to add to the editor textarea element. Default empty.
  55. * @type bool $teeny Whether to output the minimal editor config. Examples include
  56. * Press This and the Comment editor. Default false.
  57. * @type bool $dfw Deprecated in 4.1. Unused.
  58. * @type bool|array $tinymce Whether to load TinyMCE. Can be used to pass settings directly to
  59. * TinyMCE using an array. Default true.
  60. * @type bool|array $quicktags Whether to load Quicktags. Can be used to pass settings directly to
  61. * Quicktags using an array. Default true.
  62. * }
  63. * @return array Parsed arguments array.
  64. */
  65. public static function parse_settings( $editor_id, $settings ) {
  66. /**
  67. * Filters the wp_editor() settings.
  68. *
  69. * @since 4.0.0
  70. *
  71. * @see _WP_Editors::parse_settings()
  72. *
  73. * @param array $settings Array of editor arguments.
  74. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block'
  75. * when called from block editor's Classic block.
  76. */
  77. $settings = apply_filters( 'wp_editor_settings', $settings, $editor_id );
  78. $set = wp_parse_args(
  79. $settings,
  80. array(
  81. // Disable autop if the current post has blocks in it.
  82. 'wpautop' => ! has_blocks(),
  83. 'media_buttons' => true,
  84. 'default_editor' => '',
  85. 'drag_drop_upload' => false,
  86. 'textarea_name' => $editor_id,
  87. 'textarea_rows' => 20,
  88. 'tabindex' => '',
  89. 'tabfocus_elements' => ':prev,:next',
  90. 'editor_css' => '',
  91. 'editor_class' => '',
  92. 'teeny' => false,
  93. '_content_editor_dfw' => false,
  94. 'tinymce' => true,
  95. 'quicktags' => true,
  96. )
  97. );
  98. self::$this_tinymce = ( $set['tinymce'] && user_can_richedit() );
  99. if ( self::$this_tinymce ) {
  100. if ( false !== strpos( $editor_id, '[' ) ) {
  101. self::$this_tinymce = false;
  102. _deprecated_argument( 'wp_editor()', '3.9.0', 'TinyMCE editor IDs cannot have brackets.' );
  103. }
  104. }
  105. self::$this_quicktags = (bool) $set['quicktags'];
  106. if ( self::$this_tinymce ) {
  107. self::$has_tinymce = true;
  108. }
  109. if ( self::$this_quicktags ) {
  110. self::$has_quicktags = true;
  111. }
  112. if ( empty( $set['editor_height'] ) ) {
  113. return $set;
  114. }
  115. if ( 'content' === $editor_id && empty( $set['tinymce']['wp_autoresize_on'] ) ) {
  116. // A cookie (set when a user resizes the editor) overrides the height.
  117. $cookie = (int) get_user_setting( 'ed_size' );
  118. if ( $cookie ) {
  119. $set['editor_height'] = $cookie;
  120. }
  121. }
  122. if ( $set['editor_height'] < 50 ) {
  123. $set['editor_height'] = 50;
  124. } elseif ( $set['editor_height'] > 5000 ) {
  125. $set['editor_height'] = 5000;
  126. }
  127. return $set;
  128. }
  129. /**
  130. * Outputs the HTML for a single instance of the editor.
  131. *
  132. * @since 3.3.0
  133. *
  134. * @param string $content Initial content for the editor.
  135. * @param string $editor_id HTML ID for the textarea and TinyMCE and Quicktags instances.
  136. * Should not contain square brackets.
  137. * @param array $settings See _WP_Editors::parse_settings() for description.
  138. */
  139. public static function editor( $content, $editor_id, $settings = array() ) {
  140. $set = self::parse_settings( $editor_id, $settings );
  141. $editor_class = ' class="' . trim( esc_attr( $set['editor_class'] ) . ' wp-editor-area' ) . '"';
  142. $tabindex = $set['tabindex'] ? ' tabindex="' . (int) $set['tabindex'] . '"' : '';
  143. $default_editor = 'html';
  144. $buttons = '';
  145. $autocomplete = '';
  146. $editor_id_attr = esc_attr( $editor_id );
  147. if ( $set['drag_drop_upload'] ) {
  148. self::$drag_drop_upload = true;
  149. }
  150. if ( ! empty( $set['editor_height'] ) ) {
  151. $height = ' style="height: ' . (int) $set['editor_height'] . 'px"';
  152. } else {
  153. $height = ' rows="' . (int) $set['textarea_rows'] . '"';
  154. }
  155. if ( ! current_user_can( 'upload_files' ) ) {
  156. $set['media_buttons'] = false;
  157. }
  158. if ( self::$this_tinymce ) {
  159. $autocomplete = ' autocomplete="off"';
  160. if ( self::$this_quicktags ) {
  161. $default_editor = $set['default_editor'] ? $set['default_editor'] : wp_default_editor();
  162. // 'html' is used for the "Text" editor tab.
  163. if ( 'html' !== $default_editor ) {
  164. $default_editor = 'tinymce';
  165. }
  166. $buttons .= '<button type="button" id="' . $editor_id_attr . '-tmce" class="wp-switch-editor switch-tmce"' .
  167. ' data-wp-editor-id="' . $editor_id_attr . '">' . _x( 'Visual', 'Name for the Visual editor tab' ) . "</button>\n";
  168. $buttons .= '<button type="button" id="' . $editor_id_attr . '-html" class="wp-switch-editor switch-html"' .
  169. ' data-wp-editor-id="' . $editor_id_attr . '">' . _x( 'Text', 'Name for the Text editor tab (formerly HTML)' ) . "</button>\n";
  170. } else {
  171. $default_editor = 'tinymce';
  172. }
  173. }
  174. $switch_class = 'html' === $default_editor ? 'html-active' : 'tmce-active';
  175. $wrap_class = 'wp-core-ui wp-editor-wrap ' . $switch_class;
  176. if ( $set['_content_editor_dfw'] ) {
  177. $wrap_class .= ' has-dfw';
  178. }
  179. echo '<div id="wp-' . $editor_id_attr . '-wrap" class="' . $wrap_class . '">';
  180. if ( self::$editor_buttons_css ) {
  181. wp_print_styles( 'editor-buttons' );
  182. self::$editor_buttons_css = false;
  183. }
  184. if ( ! empty( $set['editor_css'] ) ) {
  185. echo $set['editor_css'] . "\n";
  186. }
  187. if ( ! empty( $buttons ) || $set['media_buttons'] ) {
  188. echo '<div id="wp-' . $editor_id_attr . '-editor-tools" class="wp-editor-tools hide-if-no-js">';
  189. if ( $set['media_buttons'] ) {
  190. self::$has_medialib = true;
  191. if ( ! function_exists( 'media_buttons' ) ) {
  192. require ABSPATH . 'wp-admin/includes/media.php';
  193. }
  194. echo '<div id="wp-' . $editor_id_attr . '-media-buttons" class="wp-media-buttons">';
  195. /**
  196. * Fires after the default media button(s) are displayed.
  197. *
  198. * @since 2.5.0
  199. *
  200. * @param string $editor_id Unique editor identifier, e.g. 'content'.
  201. */
  202. do_action( 'media_buttons', $editor_id );
  203. echo "</div>\n";
  204. }
  205. echo '<div class="wp-editor-tabs">' . $buttons . "</div>\n";
  206. echo "</div>\n";
  207. }
  208. $quicktags_toolbar = '';
  209. if ( self::$this_quicktags ) {
  210. if ( 'content' === $editor_id && ! empty( $GLOBALS['current_screen'] ) && 'post' === $GLOBALS['current_screen']->base ) {
  211. $toolbar_id = 'ed_toolbar';
  212. } else {
  213. $toolbar_id = 'qt_' . $editor_id_attr . '_toolbar';
  214. }
  215. $quicktags_toolbar = '<div id="' . $toolbar_id . '" class="quicktags-toolbar hide-if-no-js"></div>';
  216. }
  217. /**
  218. * Filters the HTML markup output that displays the editor.
  219. *
  220. * @since 2.1.0
  221. *
  222. * @param string $output Editor's HTML markup.
  223. */
  224. $the_editor = apply_filters(
  225. 'the_editor',
  226. '<div id="wp-' . $editor_id_attr . '-editor-container" class="wp-editor-container">' .
  227. $quicktags_toolbar .
  228. '<textarea' . $editor_class . $height . $tabindex . $autocomplete . ' cols="40" name="' . esc_attr( $set['textarea_name'] ) . '" ' .
  229. 'id="' . $editor_id_attr . '">%s</textarea></div>'
  230. );
  231. // Prepare the content for the Visual or Text editor, only when TinyMCE is used (back-compat).
  232. if ( self::$this_tinymce ) {
  233. add_filter( 'the_editor_content', 'format_for_editor', 10, 2 );
  234. }
  235. /**
  236. * Filters the default editor content.
  237. *
  238. * @since 2.1.0
  239. *
  240. * @param string $content Default editor content.
  241. * @param string $default_editor The default editor for the current user.
  242. * Either 'html' or 'tinymce'.
  243. */
  244. $content = apply_filters( 'the_editor_content', $content, $default_editor );
  245. // Remove the filter as the next editor on the same page may not need it.
  246. if ( self::$this_tinymce ) {
  247. remove_filter( 'the_editor_content', 'format_for_editor' );
  248. }
  249. // Back-compat for the `htmledit_pre` and `richedit_pre` filters.
  250. if ( 'html' === $default_editor && has_filter( 'htmledit_pre' ) ) {
  251. /** This filter is documented in wp-includes/deprecated.php */
  252. $content = apply_filters_deprecated( 'htmledit_pre', array( $content ), '4.3.0', 'format_for_editor' );
  253. } elseif ( 'tinymce' === $default_editor && has_filter( 'richedit_pre' ) ) {
  254. /** This filter is documented in wp-includes/deprecated.php */
  255. $content = apply_filters_deprecated( 'richedit_pre', array( $content ), '4.3.0', 'format_for_editor' );
  256. }
  257. if ( false !== stripos( $content, 'textarea' ) ) {
  258. $content = preg_replace( '%</textarea%i', '&lt;/textarea', $content );
  259. }
  260. printf( $the_editor, $content );
  261. echo "\n</div>\n\n";
  262. self::editor_settings( $editor_id, $set );
  263. }
  264. /**
  265. * @since 3.3.0
  266. *
  267. * @param string $editor_id Unique editor identifier, e.g. 'content'.
  268. * @param array $set Array of editor arguments.
  269. */
  270. public static function editor_settings( $editor_id, $set ) {
  271. if ( empty( self::$first_init ) ) {
  272. if ( is_admin() ) {
  273. add_action( 'admin_print_footer_scripts', array( __CLASS__, 'editor_js' ), 50 );
  274. add_action( 'admin_print_footer_scripts', array( __CLASS__, 'force_uncompressed_tinymce' ), 1 );
  275. add_action( 'admin_print_footer_scripts', array( __CLASS__, 'enqueue_scripts' ), 1 );
  276. } else {
  277. add_action( 'wp_print_footer_scripts', array( __CLASS__, 'editor_js' ), 50 );
  278. add_action( 'wp_print_footer_scripts', array( __CLASS__, 'force_uncompressed_tinymce' ), 1 );
  279. add_action( 'wp_print_footer_scripts', array( __CLASS__, 'enqueue_scripts' ), 1 );
  280. }
  281. }
  282. if ( self::$this_quicktags ) {
  283. $qtInit = array(
  284. 'id' => $editor_id,
  285. 'buttons' => '',
  286. );
  287. if ( is_array( $set['quicktags'] ) ) {
  288. $qtInit = array_merge( $qtInit, $set['quicktags'] );
  289. }
  290. if ( empty( $qtInit['buttons'] ) ) {
  291. $qtInit['buttons'] = 'strong,em,link,block,del,ins,img,ul,ol,li,code,more,close';
  292. }
  293. if ( $set['_content_editor_dfw'] ) {
  294. $qtInit['buttons'] .= ',dfw';
  295. }
  296. /**
  297. * Filters the Quicktags settings.
  298. *
  299. * @since 3.3.0
  300. *
  301. * @param array $qtInit Quicktags settings.
  302. * @param string $editor_id Unique editor identifier, e.g. 'content'.
  303. */
  304. $qtInit = apply_filters( 'quicktags_settings', $qtInit, $editor_id );
  305. self::$qt_settings[ $editor_id ] = $qtInit;
  306. self::$qt_buttons = array_merge( self::$qt_buttons, explode( ',', $qtInit['buttons'] ) );
  307. }
  308. if ( self::$this_tinymce ) {
  309. if ( empty( self::$first_init ) ) {
  310. $baseurl = self::get_baseurl();
  311. $mce_locale = self::get_mce_locale();
  312. $ext_plugins = '';
  313. if ( $set['teeny'] ) {
  314. /**
  315. * Filters the list of teenyMCE plugins.
  316. *
  317. * @since 2.7.0
  318. * @since 3.3.0 The `$editor_id` parameter was added.
  319. *
  320. * @param array $plugins An array of teenyMCE plugins.
  321. * @param string $editor_id Unique editor identifier, e.g. 'content'.
  322. */
  323. $plugins = apply_filters(
  324. 'teeny_mce_plugins',
  325. array(
  326. 'colorpicker',
  327. 'lists',
  328. 'fullscreen',
  329. 'image',
  330. 'wordpress',
  331. 'wpeditimage',
  332. 'wplink',
  333. ),
  334. $editor_id
  335. );
  336. } else {
  337. /**
  338. * Filters the list of TinyMCE external plugins.
  339. *
  340. * The filter takes an associative array of external plugins for
  341. * TinyMCE in the form 'plugin_name' => 'url'.
  342. *
  343. * The url should be absolute, and should include the js filename
  344. * to be loaded. For example:
  345. * 'myplugin' => 'http://mysite.com/wp-content/plugins/myfolder/mce_plugin.js'.
  346. *
  347. * If the external plugin adds a button, it should be added with
  348. * one of the 'mce_buttons' filters.
  349. *
  350. * @since 2.5.0
  351. * @since 5.3.0 The `$editor_id` parameter was added.
  352. *
  353. * @param array $external_plugins An array of external TinyMCE plugins.
  354. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block'
  355. * when called from block editor's Classic block.
  356. */
  357. $mce_external_plugins = apply_filters( 'mce_external_plugins', array(), $editor_id );
  358. $plugins = array(
  359. 'charmap',
  360. 'colorpicker',
  361. 'hr',
  362. 'lists',
  363. 'media',
  364. 'paste',
  365. 'tabfocus',
  366. 'textcolor',
  367. 'fullscreen',
  368. 'wordpress',
  369. 'wpautoresize',
  370. 'wpeditimage',
  371. 'wpemoji',
  372. 'wpgallery',
  373. 'wplink',
  374. 'wpdialogs',
  375. 'wptextpattern',
  376. 'wpview',
  377. );
  378. if ( ! self::$has_medialib ) {
  379. $plugins[] = 'image';
  380. }
  381. /**
  382. * Filters the list of default TinyMCE plugins.
  383. *
  384. * The filter specifies which of the default plugins included
  385. * in WordPress should be added to the TinyMCE instance.
  386. *
  387. * @since 3.3.0
  388. * @since 5.3.0 The `$editor_id` parameter was added.
  389. *
  390. * @param array $plugins An array of default TinyMCE plugins.
  391. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block'
  392. * when called from block editor's Classic block.
  393. */
  394. $plugins = array_unique( apply_filters( 'tiny_mce_plugins', $plugins, $editor_id ) );
  395. $key = array_search( 'spellchecker', $plugins, true );
  396. if ( false !== $key ) {
  397. // Remove 'spellchecker' from the internal plugins if added with 'tiny_mce_plugins' filter to prevent errors.
  398. // It can be added with 'mce_external_plugins'.
  399. unset( $plugins[ $key ] );
  400. }
  401. if ( ! empty( $mce_external_plugins ) ) {
  402. /**
  403. * Filters the translations loaded for external TinyMCE 3.x plugins.
  404. *
  405. * The filter takes an associative array ('plugin_name' => 'path')
  406. * where 'path' is the include path to the file.
  407. *
  408. * The language file should follow the same format as wp_mce_translation(),
  409. * and should define a variable ($strings) that holds all translated strings.
  410. *
  411. * @since 2.5.0
  412. * @since 5.3.0 The `$editor_id` parameter was added.
  413. *
  414. * @param array $translations Translations for external TinyMCE plugins.
  415. * @param string $editor_id Unique editor identifier, e.g. 'content'.
  416. */
  417. $mce_external_languages = apply_filters( 'mce_external_languages', array(), $editor_id );
  418. $loaded_langs = array();
  419. $strings = '';
  420. if ( ! empty( $mce_external_languages ) ) {
  421. foreach ( $mce_external_languages as $name => $path ) {
  422. if ( @is_file( $path ) && @is_readable( $path ) ) {
  423. include_once $path;
  424. $ext_plugins .= $strings . "\n";
  425. $loaded_langs[] = $name;
  426. }
  427. }
  428. }
  429. foreach ( $mce_external_plugins as $name => $url ) {
  430. if ( in_array( $name, $plugins, true ) ) {
  431. unset( $mce_external_plugins[ $name ] );
  432. continue;
  433. }
  434. $url = set_url_scheme( $url );
  435. $mce_external_plugins[ $name ] = $url;
  436. $plugurl = dirname( $url );
  437. $strings = '';
  438. // Try to load langs/[locale].js and langs/[locale]_dlg.js.
  439. if ( ! in_array( $name, $loaded_langs, true ) ) {
  440. $path = str_replace( content_url(), '', $plugurl );
  441. $path = WP_CONTENT_DIR . $path . '/langs/';
  442. $path = trailingslashit( realpath( $path ) );
  443. if ( @is_file( $path . $mce_locale . '.js' ) ) {
  444. $strings .= @file_get_contents( $path . $mce_locale . '.js' ) . "\n";
  445. }
  446. if ( @is_file( $path . $mce_locale . '_dlg.js' ) ) {
  447. $strings .= @file_get_contents( $path . $mce_locale . '_dlg.js' ) . "\n";
  448. }
  449. if ( 'en' !== $mce_locale && empty( $strings ) ) {
  450. if ( @is_file( $path . 'en.js' ) ) {
  451. $str1 = @file_get_contents( $path . 'en.js' );
  452. $strings .= preg_replace( '/([\'"])en\./', '$1' . $mce_locale . '.', $str1, 1 ) . "\n";
  453. }
  454. if ( @is_file( $path . 'en_dlg.js' ) ) {
  455. $str2 = @file_get_contents( $path . 'en_dlg.js' );
  456. $strings .= preg_replace( '/([\'"])en\./', '$1' . $mce_locale . '.', $str2, 1 ) . "\n";
  457. }
  458. }
  459. if ( ! empty( $strings ) ) {
  460. $ext_plugins .= "\n" . $strings . "\n";
  461. }
  462. }
  463. $ext_plugins .= 'tinyMCEPreInit.load_ext("' . $plugurl . '", "' . $mce_locale . '");' . "\n";
  464. }
  465. }
  466. }
  467. self::$plugins = $plugins;
  468. self::$ext_plugins = $ext_plugins;
  469. $settings = self::default_settings();
  470. $settings['plugins'] = implode( ',', $plugins );
  471. if ( ! empty( $mce_external_plugins ) ) {
  472. $settings['external_plugins'] = wp_json_encode( $mce_external_plugins );
  473. }
  474. /** This filter is documented in wp-admin/includes/media.php */
  475. if ( apply_filters( 'disable_captions', '' ) ) {
  476. $settings['wpeditimage_disable_captions'] = true;
  477. }
  478. $mce_css = $settings['content_css'];
  479. /*
  480. * The `editor-style.css` added by the theme is generally intended for the editor instance on the Edit Post screen.
  481. * Plugins that use wp_editor() on the front-end can decide whether to add the theme stylesheet
  482. * by using `get_editor_stylesheets()` and the `mce_css` or `tiny_mce_before_init` filters, see below.
  483. */
  484. if ( is_admin() ) {
  485. $editor_styles = get_editor_stylesheets();
  486. if ( ! empty( $editor_styles ) ) {
  487. // Force urlencoding of commas.
  488. foreach ( $editor_styles as $key => $url ) {
  489. if ( strpos( $url, ',' ) !== false ) {
  490. $editor_styles[ $key ] = str_replace( ',', '%2C', $url );
  491. }
  492. }
  493. $mce_css .= ',' . implode( ',', $editor_styles );
  494. }
  495. }
  496. /**
  497. * Filters the comma-delimited list of stylesheets to load in TinyMCE.
  498. *
  499. * @since 2.1.0
  500. *
  501. * @param string $stylesheets Comma-delimited list of stylesheets.
  502. */
  503. $mce_css = trim( apply_filters( 'mce_css', $mce_css ), ' ,' );
  504. if ( ! empty( $mce_css ) ) {
  505. $settings['content_css'] = $mce_css;
  506. } else {
  507. unset( $settings['content_css'] );
  508. }
  509. self::$first_init = $settings;
  510. }
  511. if ( $set['teeny'] ) {
  512. $mce_buttons = array(
  513. 'bold',
  514. 'italic',
  515. 'underline',
  516. 'blockquote',
  517. 'strikethrough',
  518. 'bullist',
  519. 'numlist',
  520. 'alignleft',
  521. 'aligncenter',
  522. 'alignright',
  523. 'undo',
  524. 'redo',
  525. 'link',
  526. 'fullscreen',
  527. );
  528. /**
  529. * Filters the list of teenyMCE buttons (Text tab).
  530. *
  531. * @since 2.7.0
  532. * @since 3.3.0 The `$editor_id` parameter was added.
  533. *
  534. * @param array $mce_buttons An array of teenyMCE buttons.
  535. * @param string $editor_id Unique editor identifier, e.g. 'content'.
  536. */
  537. $mce_buttons = apply_filters( 'teeny_mce_buttons', $mce_buttons, $editor_id );
  538. $mce_buttons_2 = array();
  539. $mce_buttons_3 = array();
  540. $mce_buttons_4 = array();
  541. } else {
  542. $mce_buttons = array(
  543. 'formatselect',
  544. 'bold',
  545. 'italic',
  546. 'bullist',
  547. 'numlist',
  548. 'blockquote',
  549. 'alignleft',
  550. 'aligncenter',
  551. 'alignright',
  552. 'link',
  553. 'wp_more',
  554. 'spellchecker',
  555. );
  556. if ( ! wp_is_mobile() ) {
  557. if ( $set['_content_editor_dfw'] ) {
  558. $mce_buttons[] = 'wp_adv';
  559. $mce_buttons[] = 'dfw';
  560. } else {
  561. $mce_buttons[] = 'fullscreen';
  562. $mce_buttons[] = 'wp_adv';
  563. }
  564. } else {
  565. $mce_buttons[] = 'wp_adv';
  566. }
  567. /**
  568. * Filters the first-row list of TinyMCE buttons (Visual tab).
  569. *
  570. * @since 2.0.0
  571. * @since 3.3.0 The `$editor_id` parameter was added.
  572. *
  573. * @param array $mce_buttons First-row list of buttons.
  574. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block'
  575. * when called from block editor's Classic block.
  576. */
  577. $mce_buttons = apply_filters( 'mce_buttons', $mce_buttons, $editor_id );
  578. $mce_buttons_2 = array(
  579. 'strikethrough',
  580. 'hr',
  581. 'forecolor',
  582. 'pastetext',
  583. 'removeformat',
  584. 'charmap',
  585. 'outdent',
  586. 'indent',
  587. 'undo',
  588. 'redo',
  589. );
  590. if ( ! wp_is_mobile() ) {
  591. $mce_buttons_2[] = 'wp_help';
  592. }
  593. /**
  594. * Filters the second-row list of TinyMCE buttons (Visual tab).
  595. *
  596. * @since 2.0.0
  597. * @since 3.3.0 The `$editor_id` parameter was added.
  598. *
  599. * @param array $mce_buttons_2 Second-row list of buttons.
  600. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block'
  601. * when called from block editor's Classic block.
  602. */
  603. $mce_buttons_2 = apply_filters( 'mce_buttons_2', $mce_buttons_2, $editor_id );
  604. /**
  605. * Filters the third-row list of TinyMCE buttons (Visual tab).
  606. *
  607. * @since 2.0.0
  608. * @since 3.3.0 The `$editor_id` parameter was added.
  609. *
  610. * @param array $mce_buttons_3 Third-row list of buttons.
  611. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block'
  612. * when called from block editor's Classic block.
  613. */
  614. $mce_buttons_3 = apply_filters( 'mce_buttons_3', array(), $editor_id );
  615. /**
  616. * Filters the fourth-row list of TinyMCE buttons (Visual tab).
  617. *
  618. * @since 2.5.0
  619. * @since 3.3.0 The `$editor_id` parameter was added.
  620. *
  621. * @param array $mce_buttons_4 Fourth-row list of buttons.
  622. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block'
  623. * when called from block editor's Classic block.
  624. */
  625. $mce_buttons_4 = apply_filters( 'mce_buttons_4', array(), $editor_id );
  626. }
  627. $body_class = $editor_id;
  628. $post = get_post();
  629. if ( $post ) {
  630. $body_class .= ' post-type-' . sanitize_html_class( $post->post_type ) . ' post-status-' . sanitize_html_class( $post->post_status );
  631. if ( post_type_supports( $post->post_type, 'post-formats' ) ) {
  632. $post_format = get_post_format( $post );
  633. if ( $post_format && ! is_wp_error( $post_format ) ) {
  634. $body_class .= ' post-format-' . sanitize_html_class( $post_format );
  635. } else {
  636. $body_class .= ' post-format-standard';
  637. }
  638. }
  639. $page_template = get_page_template_slug( $post );
  640. if ( false !== $page_template ) {
  641. $page_template = empty( $page_template ) ? 'default' : str_replace( '.', '-', basename( $page_template, '.php' ) );
  642. $body_class .= ' page-template-' . sanitize_html_class( $page_template );
  643. }
  644. }
  645. $body_class .= ' locale-' . sanitize_html_class( strtolower( str_replace( '_', '-', get_user_locale() ) ) );
  646. if ( ! empty( $set['tinymce']['body_class'] ) ) {
  647. $body_class .= ' ' . $set['tinymce']['body_class'];
  648. unset( $set['tinymce']['body_class'] );
  649. }
  650. $mceInit = array(
  651. 'selector' => "#$editor_id",
  652. 'wpautop' => (bool) $set['wpautop'],
  653. 'indent' => ! $set['wpautop'],
  654. 'toolbar1' => implode( ',', $mce_buttons ),
  655. 'toolbar2' => implode( ',', $mce_buttons_2 ),
  656. 'toolbar3' => implode( ',', $mce_buttons_3 ),
  657. 'toolbar4' => implode( ',', $mce_buttons_4 ),
  658. 'tabfocus_elements' => $set['tabfocus_elements'],
  659. 'body_class' => $body_class,
  660. );
  661. // Merge with the first part of the init array.
  662. $mceInit = array_merge( self::$first_init, $mceInit );
  663. if ( is_array( $set['tinymce'] ) ) {
  664. $mceInit = array_merge( $mceInit, $set['tinymce'] );
  665. }
  666. /*
  667. * For people who really REALLY know what they're doing with TinyMCE
  668. * You can modify $mceInit to add, remove, change elements of the config
  669. * before tinyMCE.init. Setting "valid_elements", "invalid_elements"
  670. * and "extended_valid_elements" can be done through this filter. Best
  671. * is to use the default cleanup by not specifying valid_elements,
  672. * as TinyMCE checks against the full set of HTML 5.0 elements and attributes.
  673. */
  674. if ( $set['teeny'] ) {
  675. /**
  676. * Filters the teenyMCE config before init.
  677. *
  678. * @since 2.7.0
  679. * @since 3.3.0 The `$editor_id` parameter was added.
  680. *
  681. * @param array $mceInit An array with teenyMCE config.
  682. * @param string $editor_id Unique editor identifier, e.g. 'content'.
  683. */
  684. $mceInit = apply_filters( 'teeny_mce_before_init', $mceInit, $editor_id );
  685. } else {
  686. /**
  687. * Filters the TinyMCE config before init.
  688. *
  689. * @since 2.5.0
  690. * @since 3.3.0 The `$editor_id` parameter was added.
  691. *
  692. * @param array $mceInit An array with TinyMCE config.
  693. * @param string $editor_id Unique editor identifier, e.g. 'content'. Accepts 'classic-block'
  694. * when called from block editor's Classic block.
  695. */
  696. $mceInit = apply_filters( 'tiny_mce_before_init', $mceInit, $editor_id );
  697. }
  698. if ( empty( $mceInit['toolbar3'] ) && ! empty( $mceInit['toolbar4'] ) ) {
  699. $mceInit['toolbar3'] = $mceInit['toolbar4'];
  700. $mceInit['toolbar4'] = '';
  701. }
  702. self::$mce_settings[ $editor_id ] = $mceInit;
  703. } // End if self::$this_tinymce.
  704. }
  705. /**
  706. * @since 3.3.0
  707. *
  708. * @param array $init
  709. * @return string
  710. */
  711. private static function _parse_init( $init ) {
  712. $options = '';
  713. foreach ( $init as $key => $value ) {
  714. if ( is_bool( $value ) ) {
  715. $val = $value ? 'true' : 'false';
  716. $options .= $key . ':' . $val . ',';
  717. continue;
  718. } elseif ( ! empty( $value ) && is_string( $value ) && (
  719. ( '{' === $value[0] && '}' === $value[ strlen( $value ) - 1 ] ) ||
  720. ( '[' === $value[0] && ']' === $value[ strlen( $value ) - 1 ] ) ||
  721. preg_match( '/^\(?function ?\(/', $value ) ) ) {
  722. $options .= $key . ':' . $value . ',';
  723. continue;
  724. }
  725. $options .= $key . ':"' . $value . '",';
  726. }
  727. return '{' . trim( $options, ' ,' ) . '}';
  728. }
  729. /**
  730. * @since 3.3.0
  731. *
  732. * @param bool $default_scripts Optional. Whether default scripts should be enqueued. Default false.
  733. */
  734. public static function enqueue_scripts( $default_scripts = false ) {
  735. if ( $default_scripts || self::$has_tinymce ) {
  736. wp_enqueue_script( 'editor' );
  737. }
  738. if ( $default_scripts || self::$has_quicktags ) {
  739. wp_enqueue_script( 'quicktags' );
  740. wp_enqueue_style( 'buttons' );
  741. }
  742. if ( $default_scripts || in_array( 'wplink', self::$plugins, true ) || in_array( 'link', self::$qt_buttons, true ) ) {
  743. wp_enqueue_script( 'wplink' );
  744. wp_enqueue_script( 'jquery-ui-autocomplete' );
  745. }
  746. if ( self::$has_medialib ) {
  747. add_thickbox();
  748. wp_enqueue_script( 'media-upload' );
  749. wp_enqueue_script( 'wp-embed' );
  750. } elseif ( $default_scripts ) {
  751. wp_enqueue_script( 'media-upload' );
  752. }
  753. /**
  754. * Fires when scripts and styles are enqueued for the editor.
  755. *
  756. * @since 3.9.0
  757. *
  758. * @param array $to_load An array containing boolean values whether TinyMCE
  759. * and Quicktags are being loaded.
  760. */
  761. do_action(
  762. 'wp_enqueue_editor',
  763. array(
  764. 'tinymce' => ( $default_scripts || self::$has_tinymce ),
  765. 'quicktags' => ( $default_scripts || self::$has_quicktags ),
  766. )
  767. );
  768. }
  769. /**
  770. * Enqueue all editor scripts.
  771. * For use when the editor is going to be initialized after page load.
  772. *
  773. * @since 4.8.0
  774. */
  775. public static function enqueue_default_editor() {
  776. // We are past the point where scripts can be enqueued properly.
  777. if ( did_action( 'wp_enqueue_editor' ) ) {
  778. return;
  779. }
  780. self::enqueue_scripts( true );
  781. // Also add wp-includes/css/editor.css.
  782. wp_enqueue_style( 'editor-buttons' );
  783. if ( is_admin() ) {
  784. add_action( 'admin_print_footer_scripts', array( __CLASS__, 'force_uncompressed_tinymce' ), 1 );
  785. add_action( 'admin_print_footer_scripts', array( __CLASS__, 'print_default_editor_scripts' ), 45 );
  786. } else {
  787. add_action( 'wp_print_footer_scripts', array( __CLASS__, 'force_uncompressed_tinymce' ), 1 );
  788. add_action( 'wp_print_footer_scripts', array( __CLASS__, 'print_default_editor_scripts' ), 45 );
  789. }
  790. }
  791. /**
  792. * Print (output) all editor scripts and default settings.
  793. * For use when the editor is going to be initialized after page load.
  794. *
  795. * @since 4.8.0
  796. */
  797. public static function print_default_editor_scripts() {
  798. $user_can_richedit = user_can_richedit();
  799. if ( $user_can_richedit ) {
  800. $settings = self::default_settings();
  801. $settings['toolbar1'] = 'bold,italic,bullist,numlist,link';
  802. $settings['wpautop'] = false;
  803. $settings['indent'] = true;
  804. $settings['elementpath'] = false;
  805. if ( is_rtl() ) {
  806. $settings['directionality'] = 'rtl';
  807. }
  808. /*
  809. * In production all plugins are loaded (they are in wp-editor.js.gz).
  810. * The 'wpview', 'wpdialogs', and 'media' TinyMCE plugins are not initialized by default.
  811. * Can be added from js by using the 'wp-before-tinymce-init' event.
  812. */
  813. $settings['plugins'] = implode(
  814. ',',
  815. array(
  816. 'charmap',
  817. 'colorpicker',
  818. 'hr',
  819. 'lists',
  820. 'paste',
  821. 'tabfocus',
  822. 'textcolor',
  823. 'fullscreen',
  824. 'wordpress',
  825. 'wpautoresize',
  826. 'wpeditimage',
  827. 'wpemoji',
  828. 'wpgallery',
  829. 'wplink',
  830. 'wptextpattern',
  831. )
  832. );
  833. $settings = self::_parse_init( $settings );
  834. } else {
  835. $settings = '{}';
  836. }
  837. ?>
  838. <script type="text/javascript">
  839. window.wp = window.wp || {};
  840. window.wp.editor = window.wp.editor || {};
  841. window.wp.editor.getDefaultSettings = function() {
  842. return {
  843. tinymce: <?php echo $settings; ?>,
  844. quicktags: {
  845. buttons: 'strong,em,link,ul,ol,li,code'
  846. }
  847. };
  848. };
  849. <?php
  850. if ( $user_can_richedit ) {
  851. $suffix = SCRIPT_DEBUG ? '' : '.min';
  852. $baseurl = self::get_baseurl();
  853. ?>
  854. var tinyMCEPreInit = {
  855. baseURL: "<?php echo $baseurl; ?>",
  856. suffix: "<?php echo $suffix; ?>",
  857. mceInit: {},
  858. qtInit: {},
  859. load_ext: function(url,lang){var sl=tinymce.ScriptLoader;sl.markDone(url+'/langs/'+lang+'.js');sl.markDone(url+'/langs/'+lang+'_dlg.js');}
  860. };
  861. <?php
  862. }
  863. ?>
  864. </script>
  865. <?php
  866. if ( $user_can_richedit ) {
  867. self::print_tinymce_scripts();
  868. }
  869. /**
  870. * Fires when the editor scripts are loaded for later initialization,
  871. * after all scripts and settings are printed.
  872. *
  873. * @since 4.8.0
  874. */
  875. do_action( 'print_default_editor_scripts' );
  876. self::wp_link_dialog();
  877. }
  878. /**
  879. * Returns the TinyMCE locale.
  880. *
  881. * @since 4.8.0
  882. *
  883. * @return string
  884. */
  885. public static function get_mce_locale() {
  886. if ( empty( self::$mce_locale ) ) {
  887. $mce_locale = get_user_locale();
  888. self::$mce_locale = empty( $mce_locale ) ? 'en' : strtolower( substr( $mce_locale, 0, 2 ) ); // ISO 639-1.
  889. }
  890. return self::$mce_locale;
  891. }
  892. /**
  893. * Returns the TinyMCE base URL.
  894. *
  895. * @since 4.8.0
  896. *
  897. * @return string
  898. */
  899. public static function get_baseurl() {
  900. if ( empty( self::$baseurl ) ) {
  901. self::$baseurl = includes_url( 'js/tinymce' );
  902. }
  903. return self::$baseurl;
  904. }
  905. /**
  906. * Returns the default TinyMCE settings.
  907. * Doesn't include plugins, buttons, editor selector.
  908. *
  909. * @since 4.8.0
  910. *
  911. * @global string $tinymce_version
  912. *
  913. * @return array
  914. */
  915. private static function default_settings() {
  916. global $tinymce_version;
  917. $shortcut_labels = array();
  918. foreach ( self::get_translation() as $name => $value ) {
  919. if ( is_array( $value ) ) {
  920. $shortcut_labels[ $name ] = $value[1];
  921. }
  922. }
  923. $settings = array(
  924. 'theme' => 'modern',
  925. 'skin' => 'lightgray',
  926. 'language' => self::get_mce_locale(),
  927. 'formats' => '{' .
  928. 'alignleft: [' .
  929. '{selector: "p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li", styles: {textAlign:"left"}},' .
  930. '{selector: "img,table,dl.wp-caption", classes: "alignleft"}' .
  931. '],' .
  932. 'aligncenter: [' .
  933. '{selector: "p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li", styles: {textAlign:"center"}},' .
  934. '{selector: "img,table,dl.wp-caption", classes: "aligncenter"}' .
  935. '],' .
  936. 'alignright: [' .
  937. '{selector: "p,h1,h2,h3,h4,h5,h6,td,th,div,ul,ol,li", styles: {textAlign:"right"}},' .
  938. '{selector: "img,table,dl.wp-caption", classes: "alignright"}' .
  939. '],' .
  940. 'strikethrough: {inline: "del"}' .
  941. '}',
  942. 'relative_urls' => false,
  943. 'remove_script_host' => false,
  944. 'convert_urls' => false,
  945. 'browser_spellcheck' => true,
  946. 'fix_list_elements' => true,
  947. 'entities' => '38,amp,60,lt,62,gt',
  948. 'entity_encoding' => 'raw',
  949. 'keep_styles' => false,
  950. 'cache_suffix' => 'wp-mce-' . $tinymce_version,
  951. 'resize' => 'vertical',
  952. 'menubar' => false,
  953. 'branding' => false,
  954. // Limit the preview styles in the menu/toolbar.
  955. 'preview_styles' => 'font-family font-size font-weight font-style text-decoration text-transform',
  956. 'end_container_on_empty_block' => true,
  957. 'wpeditimage_html5_captions' => true,
  958. 'wp_lang_attr' => get_bloginfo( 'language' ),
  959. 'wp_keep_scroll_position' => false,
  960. 'wp_shortcut_labels' => wp_json_encode( $shortcut_labels ),
  961. );
  962. $suffix = SCRIPT_DEBUG ? '' : '.min';
  963. $version = 'ver=' . get_bloginfo( 'version' );
  964. // Default stylesheets.
  965. $settings['content_css'] = includes_url( "css/dashicons$suffix.css?$version" ) . ',' .
  966. includes_url( "js/tinymce/skins/wordpress/wp-content.css?$version" );
  967. return $settings;
  968. }
  969. /**
  970. * @since 4.7.0
  971. *
  972. * @return array
  973. */
  974. private static function get_translation() {
  975. if ( empty( self::$translation ) ) {
  976. self::$translation = array(
  977. // Default TinyMCE strings.
  978. 'New document' => __( 'New document' ),
  979. 'Formats' => _x( 'Formats', 'TinyMCE' ),
  980. 'Headings' => _x( 'Headings', 'TinyMCE' ),
  981. 'Heading 1' => array( __( 'Heading 1' ), 'access1' ),
  982. 'Heading 2' => array( __( 'Heading 2' ), 'access2' ),
  983. 'Heading 3' => array( __( 'Heading 3' ), 'access3' ),
  984. 'Heading 4' => array( __( 'Heading 4' ), 'access4' ),
  985. 'Heading 5' => array( __( 'Heading 5' ), 'access5' ),
  986. 'Heading 6' => array( __( 'Heading 6' ), 'access6' ),
  987. /* translators: Block tags. */
  988. 'Blocks' => _x( 'Blocks', 'TinyMCE' ),
  989. 'Paragraph' => array( __( 'Paragraph' ), 'access7' ),
  990. 'Blockquote' => array( __( 'Blockquote' ), 'accessQ' ),
  991. 'Div' => _x( 'Div', 'HTML tag' ),
  992. 'Pre' => _x( 'Pre', 'HTML tag' ),
  993. 'Preformatted' => _x( 'Preformatted', 'HTML tag' ),
  994. 'Address' => _x( 'Address', 'HTML tag' ),
  995. 'Inline' => _x( 'Inline', 'HTML elements' ),
  996. 'Underline' => array( __( 'Underline' ), 'metaU' ),
  997. 'Strikethrough' => array( __( 'Strikethrough' ), 'accessD' ),
  998. 'Subscript' => __( 'Subscript' ),
  999. 'Superscript' => __( 'Superscript' ),
  1000. 'Clear formatting' => __( 'Clear formatting' ),
  1001. 'Bold' => array( __( 'Bold' ), 'metaB' ),
  1002. 'Italic' => array( __( 'Italic' ), 'metaI' ),
  1003. 'Code' => array( __( 'Code' ), 'accessX' ),
  1004. 'Source code' => __( 'Source code' ),
  1005. 'Font Family' => __( 'Font Family' ),
  1006. 'Font Sizes' => __( 'Font Sizes' ),
  1007. 'Align center' => array( __( 'Align center' ), 'accessC' ),
  1008. 'Align right' => array( __( 'Align right' ), 'accessR' ),
  1009. 'Align left' => array( __( 'Align left' ), 'accessL' ),
  1010. 'Justify' => array( __( 'Justify' ), 'accessJ' ),
  1011. 'Increase indent' => __( 'Increase indent' ),
  1012. 'Decrease indent' => __( 'Decrease indent' ),
  1013. 'Cut' => array( __( 'Cut' ), 'metaX' ),
  1014. 'Copy' => array( __( 'Copy' ), 'metaC' ),
  1015. 'Paste' => array( __( 'Paste' ), 'metaV' ),
  1016. 'Select all' => array( __( 'Select all' ), 'metaA' ),
  1017. 'Undo' => array( __( 'Undo' ), 'metaZ' ),
  1018. 'Redo' => array( __( 'Redo' ), 'metaY' ),
  1019. 'Ok' => __( 'OK' ),
  1020. 'Cancel' => __( 'Cancel' ),
  1021. 'Close' => __( 'Close' ),
  1022. 'Visual aids' => __( 'Visual aids' ),
  1023. 'Bullet list' => array( __( 'Bulleted list' ), 'accessU' ),
  1024. 'Numbered list' => array( __( 'Numbered list' ), 'accessO' ),
  1025. 'Square' => _x( 'Square', 'list style' ),
  1026. 'Default' => _x( 'Default', 'list style' ),
  1027. 'Circle' => _x( 'Circle', 'list style' ),
  1028. 'Disc' => _x( 'Disc', 'list style' ),
  1029. 'Lower Greek' => _x( 'Lower Greek', 'list style' ),
  1030. 'Lower Alpha' => _x( 'Lower Alpha', 'list style' ),
  1031. 'Upper Alpha' => _x( 'Upper Alpha', 'list style' ),
  1032. 'Upper Roman' => _x( 'Upper Roman', 'list style' ),
  1033. 'Lower Roman' => _x( 'Lower Roman', 'list style' ),
  1034. // Anchor plugin.
  1035. 'Name' => _x( 'Name', 'Name of link anchor (TinyMCE)' ),
  1036. 'Anchor' => _x( 'Anchor', 'Link anchor (TinyMCE)' ),
  1037. 'Anchors' => _x( 'Anchors', 'Link anchors (TinyMCE)' ),
  1038. 'Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.' =>
  1039. __( 'Id should start with a letter, followed only by letters, numbers, dashes, dots, colons or underscores.' ),
  1040. 'Id' => _x( 'Id', 'Id for link anchor (TinyMCE)' ),
  1041. // Fullpage plugin.
  1042. 'Document properties' => __( 'Document properties' ),
  1043. 'Robots' => __( 'Robots' ),
  1044. 'Title' => __( 'Title' ),
  1045. 'Keywords' => __( 'Keywords' ),
  1046. 'Encoding' => __( 'Encoding' ),
  1047. 'Description' => __( 'Description' ),
  1048. 'Author' => __( 'Author' ),
  1049. // Media, image plugins.
  1050. 'Image' => __( 'Image' ),
  1051. 'Insert/edit image' => array( __( 'Insert/edit image' ), 'accessM' ),
  1052. 'General' => __( 'General' ),
  1053. 'Advanced' => __( 'Advanced' ),
  1054. 'Source' => __( 'Source' ),
  1055. 'Border' => __( 'Border' ),
  1056. 'Constrain proportions' => __( 'Constrain proportions' ),
  1057. 'Vertical space' => __( 'Vertical space' ),
  1058. 'Image description' => __( 'Image description' ),
  1059. 'Style' => __( 'Style' ),
  1060. 'Dimensions' => __( 'Dimensions' ),
  1061. 'Insert image' => __( 'Insert image' ),
  1062. 'Date/time' => __( 'Date/time' ),
  1063. 'Insert date/time' => __( 'Insert date/time' ),
  1064. 'Table of Contents' => __( 'Table of Contents' ),
  1065. 'Insert/Edit code sample' => __( 'Insert/edit code sample' ),
  1066. 'Language' => __( 'Language' ),
  1067. 'Media' => __( 'Media' ),
  1068. 'Insert/edit media' => __( 'Insert/edit media' ),
  1069. 'Poster' => __( 'Poster' ),
  1070. 'Alternative source' => __( 'Alternative source' ),
  1071. 'Paste your embed code below:' => __( 'Paste your embed code below:' ),
  1072. 'Insert video' => __( 'Insert video' ),
  1073. 'Embed' => __( 'Embed' ),
  1074. // Each of these have a corresponding plugin.
  1075. 'Special character' => __( 'Special character' ),
  1076. 'Right to left' => _x( 'Right to left', 'editor button' ),
  1077. 'Left to right' => _x( 'Left to right', 'editor button' ),
  1078. 'Emoticons' => __( 'Emoticons' ),
  1079. 'Nonbreaking space' => __( 'Nonbreaking space' ),
  1080. 'Page break' => __( 'Page break' ),
  1081. 'Paste as text' => __( 'Paste as text' ),
  1082. 'Preview' => __( 'Preview' ),
  1083. 'Print' => __( 'Print' ),
  1084. 'Save' => __( 'Save' ),
  1085. 'Fullscreen' => __( 'Fullscreen' ),
  1086. 'Horizontal line' => __( 'Horizontal line' ),
  1087. 'Horizontal space' => __( 'Horizontal space' ),
  1088. 'Restore last draft' => __( 'Restore last draft' ),
  1089. 'Insert/edit link' => array( __( 'Insert/edit link' ), 'metaK' ),
  1090. 'Remove link' => array( __( 'Remove link' ), 'accessS' ),
  1091. // Link plugin.
  1092. 'Link' => __( 'Link' ),
  1093. 'Insert link' => __( 'Insert link' ),
  1094. 'Target' => __( 'Target' ),
  1095. 'New window' => __( 'New window' ),
  1096. 'Text to display' => __( 'Text to display' ),
  1097. 'Url' => __( 'URL' ),
  1098. 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?' =>
  1099. __( 'The URL you entered seems to be an email address. Do you want to add the required mailto: prefix?' ),
  1100. 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?' =>
  1101. __( 'The URL you entered seems to be an external link. Do you want to add the required http:// prefix?' ),
  1102. 'Color' => __( 'Color' ),
  1103. 'Custom color' => __( 'Custom color' ),
  1104. 'Custom...' => _x( 'Custom...', 'label for custom color' ), // No ellipsis.
  1105. 'No color' => __( 'No color' ),
  1106. 'R' => _x( 'R', 'Short for red in RGB' ),
  1107. 'G' => _x( 'G', 'Short for green in RGB' ),
  1108. 'B' => _x( 'B', 'Short for blue in RGB' ),
  1109. // Spelling, search/replace plugins.
  1110. 'Could not find the specified string.' => __( 'Could not find the specified string.' ),
  1111. 'Replace' => _x( 'Replace', 'find/replace' ),
  1112. 'Next' => _x( 'Next', 'find/replace' ),
  1113. /* translators: Previous. */
  1114. 'Prev' => _x( 'Prev', 'find/replace' ),
  1115. 'Whole words' => _x( 'Whole words', 'find/replace' ),
  1116. 'Find and replace' => __( 'Find and replace' ),
  1117. 'Replace with' => _x( 'Replace with', 'find/replace' ),
  1118. 'Find' => _x( 'Find', 'find/replace' ),
  1119. 'Replace all' => _x( 'Replace all', 'find/replace' ),
  1120. 'Match case' => __( 'Match case' ),
  1121. 'Spellcheck' => __( 'Check Spelling' ),
  1122. 'Finish' => _x( 'Finish', 'spellcheck' ),
  1123. 'Ignore all' => _x( 'Ignore all', 'spellcheck' ),
  1124. 'Ignore' => _x( 'Ignore', 'spellcheck' ),
  1125. 'Add to Dictionary' => __( 'Add to Dictionary' ),
  1126. // TinyMCE tables.
  1127. 'Insert table' => __( 'Insert table' ),
  1128. 'Delete table' => __( 'Delete table' ),
  1129. 'Table properties' => __( 'Table properties' ),
  1130. 'Row properties' => __( 'Table row properties' ),
  1131. 'Cell properties' => __( 'Table cell properties' ),
  1132. 'Border color' => __( 'Border color' ),
  1133. 'Row' => __( 'Row' ),
  1134. 'Rows' => __( 'Rows' ),
  1135. 'Column' => __( 'Column' ),
  1136. 'Cols' => __( 'Columns' ),
  1137. 'Cell' => _x( 'Cell', 'table cell' ),
  1138. 'Header cell' => __( 'Header cell' ),
  1139. 'Header' => _x( 'Header', 'table header' ),
  1140. 'Body' => _x( 'Body', 'table body' ),
  1141. 'Footer' => _x( 'Footer', 'table footer' ),
  1142. 'Insert row before' => __( 'Insert row before' ),
  1143. 'Insert row after' => __( 'Insert row after' ),
  1144. 'Insert column before' => __( 'Insert column before' ),
  1145. 'Insert column after' => __( 'Insert column after' ),
  1146. 'Paste row before' => __( 'Paste table row before' ),
  1147. 'Paste row after' => __( 'Paste table row after' ),
  1148. 'Delete row' => __( 'Delete row' ),
  1149. 'Delete column' => __( 'Delete column' ),
  1150. 'Cut row' => __( 'Cut table row' ),
  1151. 'Copy row' => __( 'Copy table row' ),
  1152. 'Merge cells' => __( 'Merge table cells' ),
  1153. 'Split cell' => __( 'Split table cell' ),
  1154. 'Height' => __( 'Height' ),
  1155. 'Width' => __( 'Width' ),
  1156. 'Caption' => __( 'Caption' ),
  1157. 'Alignment' => __( 'Alignment' ),
  1158. 'H Align' => _x( 'H Align', 'horizontal table cell alignment' ),
  1159. 'Left' => __( 'Left' ),
  1160. 'Center' => __( 'Center' ),
  1161. 'Right' => __( 'Right' ),
  1162. 'None' => _x( 'None', 'table cell alignment attribute' ),
  1163. 'V Align' => _x( 'V Align', 'vertical table cell alignment' ),
  1164. 'Top'

Large files files are truncated, but you can click here to view the full file