暫無描述

Shortcode.php 11KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506
  1. <?php
  2. /*******************************************************************************
  3. * Copyright (c) 2019, Code Atlantic LLC
  4. ******************************************************************************/
  5. if ( ! defined( 'ABSPATH' ) ) {
  6. exit;
  7. }
  8. /**
  9. * Class PUM_Shortcode
  10. *
  11. * This is a base class for all popup maker & extension shortcodes.
  12. */
  13. abstract class PUM_Shortcode {
  14. /**
  15. * Per instance version for compatibility fixes.
  16. *
  17. * @var int
  18. */
  19. public $version = 1;
  20. /**
  21. * Used to force ajax rendering of the shortcode.
  22. *
  23. * @var bool
  24. */
  25. public $ajax_rendering = false;
  26. /**
  27. * Shortcode supports inner content.
  28. *
  29. * @var bool
  30. */
  31. public $has_content = false;
  32. /**
  33. * Section/Tab where the content editor will be placed.
  34. *
  35. * @var string
  36. */
  37. public $inner_content_section = 'general';
  38. /**
  39. * Field priority of the content editor.
  40. *
  41. * @var int
  42. */
  43. public $inner_content_priority = 5;
  44. /**
  45. * @deprecated 1.7.0
  46. * @var string
  47. */
  48. public $field_prefix = 'attrs';
  49. /**
  50. * @deprecated 1.7.0
  51. * @var string
  52. */
  53. public $field_name_format = '{$prefix}[{$field}]';
  54. /**
  55. * Current version used for compatibility fixes.
  56. *
  57. * @var int
  58. */
  59. public $current_version = 2;
  60. /**
  61. * Class constructor will set the needed filter and action hooks
  62. *
  63. * @param array $args
  64. */
  65. public function __construct( $args = array() ) {
  66. if ( ! did_action( 'init' ) ) {
  67. add_action( 'init', array( $this, 'register' ) );
  68. } elseif ( ! did_action( 'admin_head' ) && current_action() != 'init' ) {
  69. add_action( 'admin_head', array( $this, 'register' ) );
  70. } else {
  71. $this->register();
  72. }
  73. }
  74. /**
  75. * Register this shortcode with Shortcode UI & Shortcake.
  76. */
  77. public function register() {
  78. add_shortcode( $this->tag(), array( $this, 'handler' ) );
  79. add_action( 'print_media_templates', array( $this, 'render_template' ) );
  80. add_action( 'register_shortcode_ui', array( $this, 'register_shortcode_ui' ) );
  81. PUM_Shortcodes::instance()->add_shortcode( $this );
  82. }
  83. /**
  84. * The shortcode tag.
  85. */
  86. abstract public function tag();
  87. /**
  88. * @return mixed
  89. */
  90. public static function init() {
  91. $class = get_called_class();
  92. return new $class;
  93. }
  94. /**
  95. * Shortcode handler
  96. *
  97. * @param array $atts shortcode attributes
  98. * @param string $content shortcode content
  99. *
  100. * @return string
  101. */
  102. abstract public function handler( $atts, $content = null );
  103. public function _tabs() {
  104. $tabs = $this->version < 2 && method_exists( $this, 'sections' ) ? $this->sections() : $this->tabs();
  105. return apply_filters( 'pum_shortcode_tabs', $tabs, $this->tag() );
  106. }
  107. public function _subtabs() {
  108. $subtabs = $this->version >= 2 && method_exists( $this, 'subtabs' ) ? $this->subtabs() : false;
  109. foreach ( $this->_tabs() as $tab_id => $tab_label ) {
  110. if ( empty( $subtabs[ $tab_id ] ) || ! is_array( $subtabs[ $tab_id ] ) ) {
  111. $subtabs[ $tab_id ] = array(
  112. 'main' => $tab_label,
  113. );
  114. }
  115. }
  116. return apply_filters( 'pum_shortcode_subtabs', $subtabs, $this->tag() );
  117. }
  118. /**
  119. * Sections.
  120. *
  121. * @deprecated 1.7.0 Use $this->tabs() instead.
  122. *
  123. * @todo Once all shortcodes are v2+ remove $this->sections()
  124. *
  125. * @return array
  126. */
  127. public function sections() {
  128. return array(
  129. 'general' => __( 'General', 'popup-maker' ),
  130. 'options' => __( 'Options', 'popup-maker' ),
  131. );
  132. }
  133. /**
  134. * Returns a list of tabs for this shortcodes editor.
  135. *
  136. * @return array
  137. */
  138. public function tabs() {
  139. return array(
  140. 'general' => __( 'General', 'popup-maker' ),
  141. 'options' => __( 'Options', 'popup-maker' ),
  142. );
  143. }
  144. /**
  145. * Returns a list of tabs for this shortcodes editor.
  146. *
  147. * @return array
  148. */
  149. public function subtabs() {
  150. return array(
  151. 'general' => array(
  152. 'main' => __( 'General', 'popup-maker' ),
  153. ),
  154. 'options' => array(
  155. 'main' => __( 'Options', 'popup-maker' ),
  156. ),
  157. );
  158. }
  159. /**
  160. * Gets preprocessed shortcode attributes.
  161. *
  162. * @param $atts
  163. *
  164. * @return array
  165. */
  166. public function shortcode_atts( $atts ) {
  167. if ( ! is_array( $atts ) ) {
  168. $atts = array();
  169. }
  170. foreach( $atts as $key => $value ) {
  171. /**
  172. * Fix for truthy & value-less arguments such as [shortcode argument]
  173. */
  174. if ( is_int( $key ) ) {
  175. unset( $atts[ $key ] );
  176. $atts[ $value ] = true;
  177. }
  178. }
  179. return shortcode_atts( $this->defaults(), $atts, $this->tag() );
  180. }
  181. /**
  182. * Array of default attribute values.
  183. *
  184. * @todo Convert this to pull from the std of $this->fields.
  185. *
  186. * @return array
  187. */
  188. public function defaults() {
  189. $defaults = array();
  190. $fields = PUM_Admin_Helpers::flatten_fields_array( $this->fields() );
  191. foreach ( $fields as $key => $field ) {
  192. $defaults[ $key ] = isset( $field['std'] ) ? $field['std'] : null;
  193. }
  194. return apply_filters( 'pum_shortcode_defaults', $defaults, $this );
  195. }
  196. /**
  197. * Render the template based on shortcode classes methods.
  198. */
  199. public function render_template() {
  200. if ( $this->version >= 2 && $this->get_template() !== false ) {
  201. echo '<script type="text/html" id="tmpl-pum-shortcode-view-' . $this->tag() . '">';
  202. $this->style_block();
  203. $this->template();
  204. echo '</script>';
  205. } else {
  206. /** @deprecated, here in case shortcode doesn't yet have the new $this->template() method. */
  207. $this->_template();
  208. }
  209. }
  210. /**
  211. * Returns the inner contents of the JS templates.
  212. *
  213. * @todo Once all shortcodes have been updated to use template over _template make this abstract.
  214. *
  215. * @return bool|string
  216. */
  217. public function template() {
  218. return false;
  219. }
  220. /**
  221. * Render the template based on shortcode classes methods.
  222. */
  223. public function style_block() {
  224. $styles = $this->get_template_styles();
  225. if ( $styles !== false ) {
  226. echo '<style>' . $styles . '</style>';
  227. }
  228. }
  229. /**
  230. * @deprecated 1.7.0 Use template() instead.
  231. */
  232. public function _template() {
  233. }
  234. /**
  235. * Render the template based on shortcode classes methods.
  236. *
  237. * @return string|false
  238. */
  239. public function get_template_styles() {
  240. ob_start();
  241. $this->template_styles();
  242. /** $this->_template_styles() is @deprecated and here in case shortcode doesn't yet have the new $this->template() method. */
  243. echo $this->_template_styles();
  244. $styles = ob_get_clean();
  245. return ! empty( $styles ) ? $styles : false;
  246. }
  247. /**
  248. * Returns the styles for inner contents of the JS templates.
  249. *
  250. * @todo Once all shortcodes have been updated to use template over _template make this abstract.
  251. */
  252. public function template_styles() {}
  253. /**
  254. * @deprecated 1.7.0 use template_styles() instead.
  255. *
  256. * @return string
  257. */
  258. public function _template_styles() {
  259. return '';
  260. }
  261. /**
  262. * Returns the inner contents of the JS templates.
  263. *
  264. * @todo Once all shortcodes have been updated to use template over _template make this abstract.
  265. *
  266. * @return bool|string
  267. */
  268. public function get_template() {
  269. ob_start();
  270. $this->template();
  271. $template = ob_get_clean();
  272. return ! empty( $template ) ? $template : false;
  273. }
  274. /**
  275. * Register this shortcode in shortcake ui.
  276. */
  277. public function register_shortcode_ui() {
  278. if ( ! is_admin() || ! function_exists( 'shortcode_ui_register_for_shortcode' ) ) {
  279. return;
  280. }
  281. $shortcode_ui_args = array(
  282. 'label' => $this->label(),
  283. 'listItemImage' => $this->icon(),
  284. 'post_type' => apply_filters( 'pum_shortcode_post_types', $this->post_types(), $this ),
  285. 'attrs' => array(),
  286. );
  287. /**
  288. * Register UI for the "inner content" of the shortcode. Optional.
  289. * If no UI is registered for the inner content, then any inner content
  290. * data present will be backed up during editing.
  291. */
  292. if ( $this->has_content ) {
  293. $shortcode_ui_args['inner_content'] = $this->inner_content_labels();
  294. }
  295. $fields = PUM_Admin_Helpers::flatten_fields_array( $this->_fields() );
  296. if ( count( $fields ) ) {
  297. foreach ( $fields as $field_id => $field ) {
  298. // Don't register inner content fields.
  299. if ( '_inner_content' == $field_id ) {
  300. continue;
  301. }
  302. //text, checkbox, textarea, radio, select, email, url, number, date, attachment, color, post_select
  303. switch ( $field['type'] ) {
  304. case 'select':
  305. $shortcode_ui_args['attrs'][] = array(
  306. 'label' => esc_html( $field['label'] ),
  307. 'attr' => $field_id,
  308. 'type' => 'select',
  309. 'options' => $field['options'],
  310. );
  311. break;
  312. case 'postselect':
  313. case 'objectselect':
  314. if ( empty( $field['post_type'] ) ) {
  315. break;
  316. }
  317. $shortcode_ui_args['attrs'][] = array(
  318. 'label' => esc_html( $field['label'] ),
  319. 'attr' => $field_id,
  320. 'type' => 'post_select',
  321. 'options' => isset( $field['options'] ) ? $field['options'] : array(),
  322. 'query' => array( 'post_type' => $field['post_type'] ),
  323. );
  324. break;
  325. case 'taxonomyselect':
  326. break;
  327. case 'text';
  328. default:
  329. $shortcode_ui_args['attrs'][] = array(
  330. 'label' => $field['label'],
  331. 'attr' => $field_id,
  332. 'type' => 'text',
  333. 'value' => ! empty( $field['std'] ) ? $field['std'] : '',
  334. //'encode' => true,
  335. 'meta' => array(
  336. 'placeholder' => $field['placeholder'],
  337. ),
  338. );
  339. break;
  340. }
  341. }
  342. }
  343. /**
  344. * Register UI for your shortcode
  345. *
  346. * @param string $shortcode_tag
  347. * @param array $ui_args
  348. */
  349. shortcode_ui_register_for_shortcode( $this->tag(), $shortcode_ui_args );
  350. }
  351. /**
  352. * How the shortcode should be labeled in the UI. Required argument.
  353. *
  354. * @return string
  355. */
  356. abstract public function label();
  357. /**
  358. * Include an icon with your shortcode. Optional.
  359. * Use a dashicon, or full URL to image.
  360. *
  361. * Only used by Shortcake
  362. *
  363. * @return string
  364. */
  365. public function icon() {
  366. return 'dashicons-editor-quote';
  367. }
  368. /**
  369. * Limit this shortcode UI to specific post_types. Optional.
  370. *
  371. * @return array
  372. */
  373. public function post_types() {
  374. return array( 'post', 'page', 'popup' );
  375. }
  376. /**
  377. * @todo Remove the inner function calls and just have this function define them directly.
  378. *
  379. * @return array
  380. */
  381. public function inner_content_labels() {
  382. return array(
  383. 'label' => $this->label(),
  384. 'description' => $this->description(),
  385. );
  386. }
  387. /**
  388. * Used internally to merge the inner content field with existing fields.
  389. *
  390. * @return array
  391. */
  392. public function _fields() {
  393. $fields = apply_filters( 'pum_shortcode_fields', $this->fields(), $this );
  394. if ( $this->has_content ) {
  395. $inner_content_labels = $this->inner_content_labels();
  396. $fields[ $this->inner_content_section ]['main']['_inner_content'] = array(
  397. 'label' => $inner_content_labels['label'],
  398. 'desc' => $inner_content_labels['description'],
  399. 'section' => $this->inner_content_section,
  400. 'type' => 'textarea',
  401. 'priority' => $this->inner_content_priority,
  402. );
  403. }
  404. $fields = PUM_Admin_Helpers::parse_tab_fields( $fields, array(
  405. 'has_subtabs' => $this->version >= 2,
  406. 'name' => 'attrs[%s]',
  407. ) );
  408. if ( $this->version < 2 ) {
  409. foreach ( $fields as $tab_id => $tab_fields ) {
  410. foreach ( $tab_fields as $field_id => $field ) {
  411. /**
  412. * Apply field compatibility fixes for shortcodes still on v1.
  413. */
  414. if ( ! empty( $field['type'] ) && in_array( $field['type'], array( 'select', 'postselect', 'radio', 'multicheck' ) ) ) {
  415. $fields[ $tab_id ][ $field_id ]['options'] = ! empty( $field['options'] ) ? array_flip( $field['options'] ) : array();
  416. }
  417. }
  418. }
  419. }
  420. return $fields;
  421. }
  422. /**
  423. * @return string
  424. */
  425. abstract public function description();
  426. /**
  427. * Array of fields by tab.
  428. *
  429. * @return array
  430. */
  431. abstract public function fields();
  432. }