class-acf-field-wysiwyg.php 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440
  1. <?php
  2. if ( ! class_exists( 'acf_field_wysiwyg' ) ) :
  3. class acf_field_wysiwyg extends acf_field {
  4. /*
  5. * __construct
  6. *
  7. * This function will setup the field type data
  8. *
  9. * @type function
  10. * @date 5/03/2014
  11. * @since 5.0.0
  12. *
  13. * @param n/a
  14. * @return n/a
  15. */
  16. function initialize() {
  17. // vars
  18. $this->name = 'wysiwyg';
  19. $this->label = __( 'Wysiwyg Editor', 'acf' );
  20. $this->category = 'content';
  21. $this->defaults = array(
  22. 'tabs' => 'all',
  23. 'toolbar' => 'full',
  24. 'media_upload' => 1,
  25. 'default_value' => '',
  26. 'delay' => 0,
  27. );
  28. // add acf_the_content filters
  29. $this->add_filters();
  30. // actions
  31. add_action( 'acf/enqueue_uploader', array( $this, 'acf_enqueue_uploader' ) );
  32. }
  33. /*
  34. * add_filters
  35. *
  36. * This function will add filters to 'acf_the_content'
  37. *
  38. * @type function
  39. * @date 20/09/2016
  40. * @since 5.4.0
  41. *
  42. * @param n/a
  43. * @return n/a
  44. */
  45. function add_filters() {
  46. // WordPress 5.5 introduced new function for applying image tags.
  47. $wp_filter_content_tags = function_exists( 'wp_filter_content_tags' ) ? 'wp_filter_content_tags' : 'wp_make_content_images_responsive';
  48. // Mimic filters added to "the_content" in "wp-includes/default-filters.php".
  49. add_filter( 'acf_the_content', 'capital_P_dangit', 11 );
  50. // add_filter( 'acf_the_content', 'do_blocks', 9 ); Not yet supported.
  51. add_filter( 'acf_the_content', 'wptexturize' );
  52. add_filter( 'acf_the_content', 'convert_smilies', 20 );
  53. add_filter( 'acf_the_content', 'wpautop' );
  54. add_filter( 'acf_the_content', 'shortcode_unautop' );
  55. // add_filter( 'acf_the_content', 'prepend_attachment' ); Causes double image on attachment page.
  56. add_filter( 'acf_the_content', $wp_filter_content_tags );
  57. add_filter( 'acf_the_content', 'do_shortcode', 11 );
  58. // Mimic filters added to "the_content" in "wp-includes/class-wp-embed.php"
  59. if ( isset( $GLOBALS['wp_embed'] ) ) {
  60. add_filter( 'acf_the_content', array( $GLOBALS['wp_embed'], 'run_shortcode' ), 8 );
  61. add_filter( 'acf_the_content', array( $GLOBALS['wp_embed'], 'autoembed' ), 8 );
  62. }
  63. }
  64. /*
  65. * get_toolbars
  66. *
  67. * This function will return an array of toolbars for the WYSIWYG field
  68. *
  69. * @type function
  70. * @date 18/04/2014
  71. * @since 5.0.0
  72. *
  73. * @param n/a
  74. * @return (array)
  75. */
  76. function get_toolbars() {
  77. // vars
  78. $editor_id = 'acf_content';
  79. $toolbars = array();
  80. // mce buttons (Full)
  81. $mce_buttons = array( 'formatselect', 'bold', 'italic', 'bullist', 'numlist', 'blockquote', 'alignleft', 'aligncenter', 'alignright', 'link', 'wp_more', 'spellchecker', 'fullscreen', 'wp_adv' );
  82. $mce_buttons_2 = array( 'strikethrough', 'hr', 'forecolor', 'pastetext', 'removeformat', 'charmap', 'outdent', 'indent', 'undo', 'redo', 'wp_help' );
  83. // mce buttons (Basic)
  84. $teeny_mce_buttons = array( 'bold', 'italic', 'underline', 'blockquote', 'strikethrough', 'bullist', 'numlist', 'alignleft', 'aligncenter', 'alignright', 'undo', 'redo', 'link', 'fullscreen' );
  85. // WP < 4.7
  86. if ( acf_version_compare( 'wp', '<', '4.7' ) ) {
  87. $mce_buttons = array( 'bold', 'italic', 'strikethrough', 'bullist', 'numlist', 'blockquote', 'hr', 'alignleft', 'aligncenter', 'alignright', 'link', 'unlink', 'wp_more', 'spellchecker', 'fullscreen', 'wp_adv' );
  88. $mce_buttons_2 = array( 'formatselect', 'underline', 'alignjustify', 'forecolor', 'pastetext', 'removeformat', 'charmap', 'outdent', 'indent', 'undo', 'redo', 'wp_help' );
  89. }
  90. // Full
  91. $toolbars['Full'] = array(
  92. 1 => apply_filters( 'mce_buttons', $mce_buttons, $editor_id ),
  93. 2 => apply_filters( 'mce_buttons_2', $mce_buttons_2, $editor_id ),
  94. 3 => apply_filters( 'mce_buttons_3', array(), $editor_id ),
  95. 4 => apply_filters( 'mce_buttons_4', array(), $editor_id ),
  96. );
  97. // Basic
  98. $toolbars['Basic'] = array(
  99. 1 => apply_filters( 'teeny_mce_buttons', $teeny_mce_buttons, $editor_id ),
  100. );
  101. // Filter for 3rd party
  102. $toolbars = apply_filters( 'acf/fields/wysiwyg/toolbars', $toolbars );
  103. // return
  104. return $toolbars;
  105. }
  106. /*
  107. * acf_enqueue_uploader
  108. *
  109. * Registers toolbars data for the WYSIWYG field.
  110. *
  111. * @type function
  112. * @date 16/12/2015
  113. * @since 5.3.2
  114. *
  115. * @param void
  116. * @return void
  117. */
  118. function acf_enqueue_uploader() {
  119. // vars
  120. $data = array();
  121. $toolbars = $this->get_toolbars();
  122. // loop
  123. if ( $toolbars ) {
  124. foreach ( $toolbars as $label => $rows ) {
  125. // vars
  126. $key = $label;
  127. $key = sanitize_title( $key );
  128. $key = str_replace( '-', '_', $key );
  129. // append
  130. $data[ $key ] = array();
  131. if ( $rows ) {
  132. foreach ( $rows as $i => $row ) {
  133. $data[ $key ][ $i ] = implode( ',', $row );
  134. }
  135. }
  136. }
  137. }
  138. // localize
  139. acf_localize_data(
  140. array(
  141. 'toolbars' => $data,
  142. )
  143. );
  144. }
  145. /**
  146. * Create the HTML interface for your field
  147. *
  148. * @param array $field An array holding all the field's data
  149. *
  150. * @type action
  151. * @since 3.6
  152. * @date 23/01/13
  153. */
  154. function render_field( $field ) {
  155. // enqueue
  156. acf_enqueue_uploader();
  157. // vars
  158. $id = uniqid( 'acf-editor-' );
  159. $default_editor = 'html';
  160. $show_tabs = true;
  161. // get height
  162. $height = acf_get_user_setting( 'wysiwyg_height', 300 );
  163. $height = max( $height, 300 ); // minimum height is 300
  164. // detect mode
  165. if ( ! user_can_richedit() ) {
  166. $show_tabs = false;
  167. } elseif ( $field['tabs'] == 'visual' ) {
  168. // case: visual tab only
  169. $default_editor = 'tinymce';
  170. $show_tabs = false;
  171. } elseif ( $field['tabs'] == 'text' ) {
  172. // case: text tab only
  173. $show_tabs = false;
  174. } elseif ( wp_default_editor() == 'tinymce' ) {
  175. // case: both tabs
  176. $default_editor = 'tinymce';
  177. }
  178. // must be logged in to upload
  179. if ( ! current_user_can( 'upload_files' ) ) {
  180. $field['media_upload'] = 0;
  181. }
  182. // mode
  183. $switch_class = ( $default_editor === 'html' ) ? 'html-active' : 'tmce-active';
  184. // filter
  185. add_filter( 'acf_the_editor_content', 'format_for_editor', 10, 2 );
  186. $field['value'] = is_string( $field['value'] ) ? $field['value'] : '';
  187. $field['value'] = apply_filters( 'acf_the_editor_content', $field['value'], $default_editor );
  188. // attr
  189. $wrap = array(
  190. 'id' => 'wp-' . $id . '-wrap',
  191. 'class' => 'acf-editor-wrap wp-core-ui wp-editor-wrap ' . $switch_class,
  192. 'data-toolbar' => $field['toolbar'],
  193. );
  194. // delay
  195. if ( $field['delay'] ) {
  196. $wrap['class'] .= ' delay';
  197. }
  198. // vars
  199. $textarea = acf_get_textarea_input(
  200. array(
  201. 'id' => $id,
  202. 'class' => 'wp-editor-area',
  203. 'name' => $field['name'],
  204. 'style' => $height ? "height:{$height}px;" : '',
  205. 'value' => '%s',
  206. )
  207. );
  208. ?>
  209. <div <?php echo acf_esc_attrs( $wrap ); ?>>
  210. <div id="wp-<?php echo esc_attr( $id ); ?>-editor-tools" class="wp-editor-tools hide-if-no-js">
  211. <?php if ( $field['media_upload'] ) : ?>
  212. <div id="wp-<?php echo esc_attr( $id ); ?>-media-buttons" class="wp-media-buttons">
  213. <?php
  214. if ( ! function_exists( 'media_buttons' ) ) {
  215. require ABSPATH . 'wp-admin/includes/media.php';
  216. }
  217. do_action( 'media_buttons', $id );
  218. ?>
  219. </div>
  220. <?php endif; ?>
  221. <?php if ( user_can_richedit() && $show_tabs ) : ?>
  222. <div class="wp-editor-tabs">
  223. <button id="<?php echo esc_attr( $id ); ?>-tmce" class="wp-switch-editor switch-tmce" data-wp-editor-id="<?php echo esc_attr( $id ); ?>" type="button"><?php echo __( 'Visual', 'acf' ); ?></button>
  224. <button id="<?php echo esc_attr( $id ); ?>-html" class="wp-switch-editor switch-html" data-wp-editor-id="<?php echo esc_attr( $id ); ?>" type="button"><?php echo _x( 'Text', 'Name for the Text editor tab (formerly HTML)', 'acf' ); ?></button>
  225. </div>
  226. <?php endif; ?>
  227. </div>
  228. <div id="wp-<?php echo esc_attr( $id ); ?>-editor-container" class="wp-editor-container">
  229. <?php if ( $field['delay'] ) : ?>
  230. <div class="acf-editor-toolbar"><?php _e( 'Click to initialize TinyMCE', 'acf' ); ?></div>
  231. <?php endif; ?>
  232. <?php printf( $textarea, $field['value'] ); ?>
  233. </div>
  234. </div>
  235. <?php
  236. }
  237. /*
  238. * render_field_settings()
  239. *
  240. * Create extra options for your field. This is rendered when editing a field.
  241. * The value of $field['name'] can be used (like bellow) to save extra data to the $field
  242. *
  243. * @type action
  244. * @since 3.6
  245. * @date 23/01/13
  246. *
  247. * @param $field - an array holding all the field's data
  248. */
  249. function render_field_settings( $field ) {
  250. acf_render_field_setting(
  251. $field,
  252. array(
  253. 'label' => __( 'Default Value', 'acf' ),
  254. 'instructions' => __( 'Appears when creating a new post', 'acf' ),
  255. 'type' => 'textarea',
  256. 'name' => 'default_value',
  257. )
  258. );
  259. }
  260. /**
  261. * Renders the field settings used in the "Presentation" tab.
  262. *
  263. * @since 6.0
  264. *
  265. * @param array $field The field settings array.
  266. * @return void
  267. */
  268. function render_field_presentation_settings( $field ) {
  269. $toolbars = $this->get_toolbars();
  270. $choices = array();
  271. if ( ! empty( $toolbars ) ) {
  272. foreach ( $toolbars as $k => $v ) {
  273. $label = $k;
  274. $name = sanitize_title( $label );
  275. $name = str_replace( '-', '_', $name );
  276. $choices[ $name ] = $label;
  277. }
  278. }
  279. acf_render_field_setting(
  280. $field,
  281. array(
  282. 'label' => __( 'Tabs', 'acf' ),
  283. 'instructions' => '',
  284. 'type' => 'select',
  285. 'name' => 'tabs',
  286. 'choices' => array(
  287. 'all' => __( 'Visual & Text', 'acf' ),
  288. 'visual' => __( 'Visual Only', 'acf' ),
  289. 'text' => __( 'Text Only', 'acf' ),
  290. ),
  291. )
  292. );
  293. acf_render_field_setting(
  294. $field,
  295. array(
  296. 'label' => __( 'Toolbar', 'acf' ),
  297. 'instructions' => '',
  298. 'type' => 'select',
  299. 'name' => 'toolbar',
  300. 'choices' => $choices,
  301. 'conditions' => array(
  302. 'field' => 'tabs',
  303. 'operator' => '!=',
  304. 'value' => 'text',
  305. ),
  306. )
  307. );
  308. acf_render_field_setting(
  309. $field,
  310. array(
  311. 'label' => __( 'Show Media Upload Buttons?', 'acf' ),
  312. 'instructions' => '',
  313. 'name' => 'media_upload',
  314. 'type' => 'true_false',
  315. 'ui' => 1,
  316. )
  317. );
  318. acf_render_field_setting(
  319. $field,
  320. array(
  321. 'label' => __( 'Delay initialization?', 'acf' ),
  322. 'instructions' => __( 'TinyMCE will not be initialized until field is clicked', 'acf' ),
  323. 'name' => 'delay',
  324. 'type' => 'true_false',
  325. 'ui' => 1,
  326. 'conditions' => array(
  327. 'field' => 'tabs',
  328. 'operator' => '!=',
  329. 'value' => 'text',
  330. ),
  331. )
  332. );
  333. }
  334. /**
  335. * This filter is applied to the $value after it is loaded from the db, and before it is returned to the template
  336. *
  337. * @type filter
  338. * @since 3.6
  339. * @date 23/01/13
  340. *
  341. * @param mixed $value The value which was loaded from the database
  342. * @param mixed $post_id The $post_id from which the value was loaded
  343. * @param array $field The field array holding all the field options
  344. *
  345. * @return mixed $value The modified value
  346. */
  347. function format_value( $value, $post_id, $field ) {
  348. // Bail early if no value or not a string.
  349. if ( empty( $value ) || ! is_string( $value ) ) {
  350. return $value;
  351. }
  352. $value = apply_filters( 'acf_the_content', $value );
  353. // Follow the_content function in /wp-includes/post-template.php
  354. return str_replace( ']]>', ']]&gt;', $value );
  355. }
  356. }
  357. // initialize
  358. acf_register_field_type( 'acf_field_wysiwyg' );
  359. endif; // class_exists check
  360. ?>