From c02bdd66a7a2eadadfa5e05dedd1b4c52a774164 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Jan 2017 23:24:08 -0800 Subject: [PATCH 01/21] Add widgets initially as Shortcake Post Elements (without Form integration) --- php/class-js-widget-shortcode-controller.php | 89 ++++++++++++++++++++ php/class-js-widgets-plugin.php | 27 +++++- 2 files changed, 115 insertions(+), 1 deletion(-) create mode 100644 php/class-js-widget-shortcode-controller.php diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php new file mode 100644 index 0000000..c03e947 --- /dev/null +++ b/php/class-js-widget-shortcode-controller.php @@ -0,0 +1,89 @@ +plugin = $plugin; + $this->widget = $widget; + } + + /** + * Get shortcode tag. + * + * @return string Shortcode tag. + */ + public function get_shortcode_tag() { + return sprintf( 'widget_%s', $this->widget->id_base ); + } + + /** + * Register shortcodes for widgets. + * + * @global WP_Widget_Factory $wp_widget_factory + */ + public function register_shortcode() { + add_shortcode( $this->get_shortcode_tag(), array( $this, 'render_widget_shortcode' ) ); + } + + /** + * Render widget shortcode. + * + * @global array $wp_registered_sidebars + * + * @param array $atts Shortcode attributes. + * @return string Rendered shortcode. + */ + public function render_widget_shortcode( $atts ) { + global $wp_registered_sidebars; + reset( $wp_registered_sidebars ); + $args = current( $wp_registered_sidebars ); + + $atts = shortcode_atts( array( 'title' => '' ), $atts, $this->get_shortcode_tag() ); + $instance = $atts; // @todo There should be the JSON instance encoded in one attribute. + ob_start(); + $this->widget->render( $args, $instance ); + return ob_get_clean(); + } + + /** + * Register shortcode UI for widget shortcodes. + */ + public function register_shortcode_ui() { + shortcode_ui_register_for_shortcode( + $this->get_shortcode_tag(), + array( + 'label' => $this->widget->name, + ) + ); + } +} diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index 068f34f..26cc9f2 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -26,6 +26,13 @@ class JS_Widgets_Plugin { */ public $rest_api_namespace = 'js-widgets/v1'; + /** + * Registered controllers for widget shortcodes. + * + * @var JS_Widget_Shortcode_Controller[] + */ + public $widget_shortcodes = array(); + /** * Instances of JS_Widgets_REST_Controller for each widget type. * @@ -104,8 +111,9 @@ public function init() { add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_widget_form_template_scripts' ) ); add_action( 'admin_footer-widgets.php', array( $this, 'render_widget_form_template_scripts' ) ); add_action( 'customize_controls_init', array( $this, 'upgrade_customize_widget_controls' ) ); - add_action( 'widgets_init', array( $this, 'capture_original_instances' ), 94 ); add_action( 'widgets_init', array( $this, 'upgrade_core_widgets' ) ); + add_action( 'widgets_init', array( $this, 'register_widget_shortcodes' ), 90 ); + add_action( 'widgets_init', array( $this, 'capture_original_instances' ), 94 ); add_action( 'in_widget_form', array( $this, 'start_capturing_in_widget_form' ), 0, 3 ); add_action( 'in_widget_form', array( $this, 'stop_capturing_in_widget_form' ), 1000, 3 ); @@ -337,6 +345,23 @@ public function upgrade_core_widgets() { } } + /** + * Register widget shortcodes. + * + * @global WP_Widget_Factory $wp_widget_factory + */ + function register_widget_shortcodes() { + global $wp_widget_factory; + require_once __DIR__ . '/class-js-widget-shortcode-controller.php'; + foreach ( $wp_widget_factory->widgets as $widget ) { + if ( $widget instanceof WP_JS_Widget ) { + $widget_shortcode = new JS_Widget_Shortcode_Controller( $this, $widget ); + $widget_shortcode->register_shortcode(); + add_action( 'register_shortcode_ui', array( $widget_shortcode, 'register_shortcode_ui' ) ); + } + } + } + /** * Replace instances of `WP_Widget_Form_Customize_Control` for JS Widgets to exclude PHP-generated content. * From da55eefd6aca169062bcab9362f9644bb2a5c278 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Jan 2017 23:30:15 -0800 Subject: [PATCH 02/21] Allow shortcode attributes for the widget's instance properties --- php/class-js-widget-shortcode-controller.php | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php index c03e947..3e822e7 100644 --- a/php/class-js-widget-shortcode-controller.php +++ b/php/class-js-widget-shortcode-controller.php @@ -68,7 +68,11 @@ public function render_widget_shortcode( $atts ) { reset( $wp_registered_sidebars ); $args = current( $wp_registered_sidebars ); - $atts = shortcode_atts( array( 'title' => '' ), $atts, $this->get_shortcode_tag() ); + $atts = shortcode_atts( + $this->widget->get_default_instance(), + $atts, + $this->get_shortcode_tag() + ); $instance = $atts; // @todo There should be the JSON instance encoded in one attribute. ob_start(); $this->widget->render( $args, $instance ); From 0f9b8edb99fc2a30ed2c6356991795abafb7a239 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Wed, 11 Jan 2017 23:54:22 -0800 Subject: [PATCH 03/21] Supply proper args to render method via first-registered sidebar --- php/class-js-widget-shortcode-controller.php | 52 ++++++++++++++++++-- 1 file changed, 47 insertions(+), 5 deletions(-) diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php index 3e822e7..98dfa42 100644 --- a/php/class-js-widget-shortcode-controller.php +++ b/php/class-js-widget-shortcode-controller.php @@ -55,6 +55,52 @@ public function register_shortcode() { add_shortcode( $this->get_shortcode_tag(), array( $this, 'render_widget_shortcode' ) ); } + /** + * Get sidebar args needed for rendering a widget. + * + * This will by default use the args from the first registered sidebar. + * + * @see WP_JS_Widget::render() + * + * @return array { + * Sidebar args. + * + * @type string $name Name of the sidebar the widget is assigned to. + * @type string $id ID of the sidebar the widget is assigned to. + * @type string $description The sidebar description. + * @type string $class CSS class applied to the sidebar container. + * @type string $before_widget HTML markup to prepend to each widget in the sidebar. + * @type string $after_widget HTML markup to append to each widget in the sidebar. + * @type string $before_title HTML markup to prepend to the widget title when displayed. + * @type string $after_title HTML markup to append to the widget title when displayed. + * @type string $widget_id ID of the widget. + * @type string $widget_name Name of the widget. + * } + */ + public function get_sidebar_args() { + global $wp_registered_sidebars; + reset( $wp_registered_sidebars ); + + $sidebar = current( $wp_registered_sidebars ); + + $widget_id = sprintf( '%s-%d', $this->widget->id_base, -rand() ); + $args = array_merge( + $sidebar, + array( + 'widget_id' => $widget_id, + 'widget_name' => $this->widget->name, + ) + ); + + // Substitute HTML id and class attributes into before_widget. + $args['before_widget'] = sprintf( $args['before_widget'], $widget_id, $this->widget->widget_options['classname'] ); + + /** This filter is documented in wp-includes/widgets.php */ + $params = apply_filters( 'dynamic_sidebar_params', array( $args, array( 'number' => null ) ) ); + + return $params[0]; + } + /** * Render widget shortcode. * @@ -64,10 +110,6 @@ public function register_shortcode() { * @return string Rendered shortcode. */ public function render_widget_shortcode( $atts ) { - global $wp_registered_sidebars; - reset( $wp_registered_sidebars ); - $args = current( $wp_registered_sidebars ); - $atts = shortcode_atts( $this->widget->get_default_instance(), $atts, @@ -75,7 +117,7 @@ public function render_widget_shortcode( $atts ) { ); $instance = $atts; // @todo There should be the JSON instance encoded in one attribute. ob_start(); - $this->widget->render( $args, $instance ); + $this->widget->render( $this->get_sidebar_args(), $instance ); return ob_get_clean(); } From 2b885317d0a9e05de27e6d8c120460eb42808ddd Mon Sep 17 00:00:00 2001 From: Piotr Delawski Date: Thu, 12 Jan 2017 16:20:28 +0100 Subject: [PATCH 04/21] Re-use the dashicon associated with a given widget in the customizer to also be used for the Post Element browser in the editor. --- core-adapter-widgets/archives/class.php | 7 +++++++ core-adapter-widgets/calendar/class.php | 7 +++++++ core-adapter-widgets/categories/class.php | 7 +++++++ core-adapter-widgets/meta/class.php | 8 +++++++- core-adapter-widgets/nav_menu/class.php | 7 +++++++ core-adapter-widgets/pages/class.php | 7 +++++++ core-adapter-widgets/recent-comments/class.php | 7 +++++++ core-adapter-widgets/recent-posts/class.php | 7 +++++++ core-adapter-widgets/rss/class.php | 7 +++++++ core-adapter-widgets/search/class.php | 7 +++++++ core-adapter-widgets/tag_cloud/class.php | 7 +++++++ core-adapter-widgets/text/class.php | 7 +++++++ php/class-js-widget-shortcode-controller.php | 12 ++++++++++++ post-collection-widget/class-widget.php | 7 +++++++ 14 files changed, 103 insertions(+), 1 deletion(-) diff --git a/core-adapter-widgets/archives/class.php b/core-adapter-widgets/archives/class.php index 70e4a9d..362db76 100644 --- a/core-adapter-widgets/archives/class.php +++ b/core-adapter-widgets/archives/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_Archives extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-archive'; + /** * WP_JS_Widget_Archives constructor. * diff --git a/core-adapter-widgets/calendar/class.php b/core-adapter-widgets/calendar/class.php index 1579979..0daa738 100644 --- a/core-adapter-widgets/calendar/class.php +++ b/core-adapter-widgets/calendar/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_Calendar extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-calendar'; + /** * WP_JS_Widget_Calendar constructor. * diff --git a/core-adapter-widgets/categories/class.php b/core-adapter-widgets/categories/class.php index 156db5d..61d269e 100644 --- a/core-adapter-widgets/categories/class.php +++ b/core-adapter-widgets/categories/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_Categories extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-category'; + /** * WP_JS_Widget_Categories constructor. * diff --git a/core-adapter-widgets/meta/class.php b/core-adapter-widgets/meta/class.php index 04ba2c6..8bdd42a 100644 --- a/core-adapter-widgets/meta/class.php +++ b/core-adapter-widgets/meta/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_Meta extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-wordpress'; + /** * WP_JS_Widget_Meta constructor. * @@ -22,7 +29,6 @@ public function __construct( JS_Widgets_Plugin $plugin, WP_Widget_Meta $adapted_ parent::__construct( $plugin, $adapted_widget ); } - /** * Get instance schema properties. * diff --git a/core-adapter-widgets/nav_menu/class.php b/core-adapter-widgets/nav_menu/class.php index dcd02bb..cf857ce 100644 --- a/core-adapter-widgets/nav_menu/class.php +++ b/core-adapter-widgets/nav_menu/class.php @@ -14,6 +14,13 @@ */ class WP_JS_Widget_Nav_Menu extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-menu'; + /** * WP_JS_Widget_Nav_Menu constructor. * diff --git a/core-adapter-widgets/pages/class.php b/core-adapter-widgets/pages/class.php index e09c3cf..477ba6d 100644 --- a/core-adapter-widgets/pages/class.php +++ b/core-adapter-widgets/pages/class.php @@ -14,6 +14,13 @@ class WP_JS_Widget_Pages extends WP_Adapter_JS_Widget { const ID_LIST_PATTERN = '\d+(,\s*\d+)*'; + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-admin-page'; + /** * WP_JS_Widget_Pages constructor. * diff --git a/core-adapter-widgets/recent-comments/class.php b/core-adapter-widgets/recent-comments/class.php index 8361326..47829ad 100644 --- a/core-adapter-widgets/recent-comments/class.php +++ b/core-adapter-widgets/recent-comments/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_Recent_Comments extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-admin-comments'; + /** * WP_JS_Widget_Recent_Comments constructor. * diff --git a/core-adapter-widgets/recent-posts/class.php b/core-adapter-widgets/recent-posts/class.php index 74732c7..2825a14 100644 --- a/core-adapter-widgets/recent-posts/class.php +++ b/core-adapter-widgets/recent-posts/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_Recent_Posts extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-admin-post'; + /** * WP_JS_Widget_Recent_Posts constructor. * diff --git a/core-adapter-widgets/rss/class.php b/core-adapter-widgets/rss/class.php index e984949..3ba22ea 100644 --- a/core-adapter-widgets/rss/class.php +++ b/core-adapter-widgets/rss/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_RSS extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-rss'; + /** * WP_JS_Widget_RSS constructor. * diff --git a/core-adapter-widgets/search/class.php b/core-adapter-widgets/search/class.php index 7c32e2f..a37efaf 100644 --- a/core-adapter-widgets/search/class.php +++ b/core-adapter-widgets/search/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_Search extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-search'; + /** * WP_JS_Widget_Search constructor. * diff --git a/core-adapter-widgets/tag_cloud/class.php b/core-adapter-widgets/tag_cloud/class.php index 8651547..a1ecd15 100644 --- a/core-adapter-widgets/tag_cloud/class.php +++ b/core-adapter-widgets/tag_cloud/class.php @@ -12,6 +12,13 @@ */ class WP_JS_Widget_Tag_Cloud extends WP_Adapter_JS_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-tagcloud'; + /** * WP_JS_Widget_Tag_Cloud constructor. * diff --git a/core-adapter-widgets/text/class.php b/core-adapter-widgets/text/class.php index dc8c0c1..2b68e95 100644 --- a/core-adapter-widgets/text/class.php +++ b/core-adapter-widgets/text/class.php @@ -19,6 +19,13 @@ class WP_JS_Widget_Text extends WP_Adapter_JS_Widget { */ public $adapted_widget; + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-text'; + /** * Get instance schema properties. * diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php index 98dfa42..feb14cb 100644 --- a/php/class-js-widget-shortcode-controller.php +++ b/php/class-js-widget-shortcode-controller.php @@ -26,6 +26,13 @@ class JS_Widget_Shortcode_Controller { */ public $widget = array(); + /** + * Default icon name. + * + * @var string + */ + public $default_icon_name = 'dashicons-format-aside'; + /** * Constructor. * @@ -125,10 +132,15 @@ public function render_widget_shortcode( $atts ) { * Register shortcode UI for widget shortcodes. */ public function register_shortcode_ui() { + $icon_name = $this->default_icon_name; + if ( isset( $this->widget->icon_name ) ) { + $icon_name = $this->widget->icon_name; + } shortcode_ui_register_for_shortcode( $this->get_shortcode_tag(), array( 'label' => $this->widget->name, + 'listItemImage' => $icon_name, ) ); } diff --git a/post-collection-widget/class-widget.php b/post-collection-widget/class-widget.php index ed39ebe..fabe96e 100644 --- a/post-collection-widget/class-widget.php +++ b/post-collection-widget/class-widget.php @@ -26,6 +26,13 @@ class WP_JS_Widget_Post_Collection extends WP_JS_Widget { */ public $id_base = 'post-collection'; + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-admin-post'; + /** * Base query vars used in post lookup. * From 9dc0700a346a43104db5fd4714dbcdd313b3f76c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Jan 2017 11:16:02 -0800 Subject: [PATCH 05/21] Define icon_name on base WP_JS_Widget class --- php/class-js-widget-shortcode-controller.php | 13 +------------ php/class-wp-js-widget.php | 7 +++++++ 2 files changed, 8 insertions(+), 12 deletions(-) diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php index feb14cb..6aa1c52 100644 --- a/php/class-js-widget-shortcode-controller.php +++ b/php/class-js-widget-shortcode-controller.php @@ -26,13 +26,6 @@ class JS_Widget_Shortcode_Controller { */ public $widget = array(); - /** - * Default icon name. - * - * @var string - */ - public $default_icon_name = 'dashicons-format-aside'; - /** * Constructor. * @@ -132,15 +125,11 @@ public function render_widget_shortcode( $atts ) { * Register shortcode UI for widget shortcodes. */ public function register_shortcode_ui() { - $icon_name = $this->default_icon_name; - if ( isset( $this->widget->icon_name ) ) { - $icon_name = $this->widget->icon_name; - } shortcode_ui_register_for_shortcode( $this->get_shortcode_tag(), array( 'label' => $this->widget->name, - 'listItemImage' => $icon_name, + 'listItemImage' => $this->widget->icon_name, ) ); } diff --git a/php/class-wp-js-widget.php b/php/class-wp-js-widget.php index e926fcc..a3a8adf 100644 --- a/php/class-wp-js-widget.php +++ b/php/class-wp-js-widget.php @@ -12,6 +12,13 @@ */ abstract class WP_JS_Widget extends WP_Widget { + /** + * Icon name. + * + * @var string + */ + public $icon_name = 'dashicons-format-aside'; + /** * REST controller class that should be used for this widget. * From 2b35e8020a0e37db8f12622a78929f4012152596 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Jan 2017 11:46:40 -0800 Subject: [PATCH 06/21] Add skeleton for widget_form shortcake field type --- php/class-js-widget-shortcode-controller.php | 8 +++++ php/class-js-widgets-plugin.php | 32 ++++++++++++++++++-- 2 files changed, 38 insertions(+), 2 deletions(-) diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php index 6aa1c52..9419fc6 100644 --- a/php/class-js-widget-shortcode-controller.php +++ b/php/class-js-widget-shortcode-controller.php @@ -130,6 +130,14 @@ public function register_shortcode_ui() { array( 'label' => $this->widget->name, 'listItemImage' => $this->widget->icon_name, + 'attrs' => array( + array( + 'label' => __( 'JSON Widget Instance Data', 'js-widgets' ), + 'attr' => 'instance_data', + 'type' => 'widget_form', + 'encode' => true, + ), + ), ) ); } diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index 26cc9f2..eca91ea 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -112,12 +112,16 @@ public function init() { add_action( 'admin_footer-widgets.php', array( $this, 'render_widget_form_template_scripts' ) ); add_action( 'customize_controls_init', array( $this, 'upgrade_customize_widget_controls' ) ); add_action( 'widgets_init', array( $this, 'upgrade_core_widgets' ) ); - add_action( 'widgets_init', array( $this, 'register_widget_shortcodes' ), 90 ); add_action( 'widgets_init', array( $this, 'capture_original_instances' ), 94 ); add_action( 'in_widget_form', array( $this, 'start_capturing_in_widget_form' ), 0, 3 ); add_action( 'in_widget_form', array( $this, 'stop_capturing_in_widget_form' ), 1000, 3 ); + // Shortcake integration. + add_action( 'widgets_init', array( $this, 'register_widget_shortcodes' ), 90 ); + add_filter( 'shortcode_ui_fields', array( $this, 'filter_shortcode_ui_fields' ) ); + add_action( 'print_shortcode_ui_templates', array( $this, 'print_shortcode_ui_templates' ) ); + // @todo Add widget REST endpoint for getting the rendered value of widgets. Note originating context URL will need to be supplied when rendering some widgets. } @@ -350,7 +354,7 @@ public function upgrade_core_widgets() { * * @global WP_Widget_Factory $wp_widget_factory */ - function register_widget_shortcodes() { + public function register_widget_shortcodes() { global $wp_widget_factory; require_once __DIR__ . '/class-js-widget-shortcode-controller.php'; foreach ( $wp_widget_factory->widgets as $widget ) { @@ -362,6 +366,30 @@ function register_widget_shortcodes() { } } + /** + * Add widget_form as a new shortcode UI field. + * + * @param array $fields Shortcode fields. + * @return array Fields. + */ + public function filter_shortcode_ui_fields( $fields ) { + $fields['widget_form'] = array( + 'template' => 'shortcode-ui-field-widget_form', + ); + return $fields; + } + + /** + * Print shortcode UI templates. + */ + public function print_shortcode_ui_templates() { + ?> + + Date: Thu, 12 Jan 2017 11:57:16 -0800 Subject: [PATCH 07/21] Add skeleton for shortcode UI js-hooks integration --- js/shortcode-ui-js-widgets.js | 11 +++++++++++ php/class-js-widgets-plugin.php | 13 +++++++++++++ 2 files changed, 24 insertions(+) create mode 100644 js/shortcode-ui-js-widgets.js diff --git a/js/shortcode-ui-js-widgets.js b/js/shortcode-ui-js-widgets.js new file mode 100644 index 0000000..edb967b --- /dev/null +++ b/js/shortcode-ui-js-widgets.js @@ -0,0 +1,11 @@ +/* global console, wp */ + +wp.shortcake.hooks.addAction( 'shortcode-ui.render_edit', function() { + console.info( 'shortcode-ui.render_edit', this, arguments ); +} ); +wp.shortcake.hooks.addAction( 'shortcode-ui.render_new', function() { + console.info( 'shortcode-ui.render_new', this, arguments ); +} ); +wp.shortcake.hooks.addAction( 'shortcode-ui.render_destroy', function() { + console.info( 'shortcode-ui.render_destroy', this, arguments ); +} ); diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index eca91ea..5302eeb 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -121,6 +121,7 @@ public function init() { add_action( 'widgets_init', array( $this, 'register_widget_shortcodes' ), 90 ); add_filter( 'shortcode_ui_fields', array( $this, 'filter_shortcode_ui_fields' ) ); add_action( 'print_shortcode_ui_templates', array( $this, 'print_shortcode_ui_templates' ) ); + add_action( 'enqueue_shortcode_ui', array( $this, 'enqueue_shortcode_ui' ) ); // @todo Add widget REST endpoint for getting the rendered value of widgets. Note originating context URL will need to be supplied when rendering some widgets. } @@ -163,6 +164,11 @@ public function register_scripts( WP_Scripts $wp_scripts ) { $deps = array( 'admin-widgets', $this->script_handles['form'] ); $wp_scripts->add( $this->script_handles['admin-js-widgets'], $src, $deps, $this->version ); + $this->script_handles['shortcode-ui-js-widgets'] = 'shortcode-ui-js-widgets'; + $src = $plugin_dir_url . 'js/shortcode-ui-js-widgets.js'; + $deps = array( 'shortcode-ui' ); + $wp_scripts->add( $this->script_handles['shortcode-ui-js-widgets'], $src, $deps, $this->version ); + $this->script_handles['trac-39389-controls'] = 'js-widgets-trac-39389-controls'; $src = $plugin_dir_url . 'js/trac-39389-controls.js'; $deps = array( 'customize-widgets' ); @@ -284,6 +290,13 @@ function enqueue_widgets_admin_scripts( $hook_suffix ) { } } + /** + * Enqueue scripts for shortcode UI. + */ + function enqueue_shortcode_ui() { + wp_enqueue_script( $this->script_handles['shortcode-ui-js-widgets'] ); + } + /** * Enqueue scripts on the frontend. * From 6e581f6e24e58369f2fdbd9e05a0764be35247cd Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 12 Jan 2017 12:01:42 -0800 Subject: [PATCH 08/21] Add missing js-widget dependnecies for shortcode-ui --- php/class-js-widgets-plugin.php | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index 5302eeb..07be747 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -166,7 +166,7 @@ public function register_scripts( WP_Scripts $wp_scripts ) { $this->script_handles['shortcode-ui-js-widgets'] = 'shortcode-ui-js-widgets'; $src = $plugin_dir_url . 'js/shortcode-ui-js-widgets.js'; - $deps = array( 'shortcode-ui' ); + $deps = array( 'shortcode-ui', $this->script_handles['form'] ); $wp_scripts->add( $this->script_handles['shortcode-ui-js-widgets'], $src, $deps, $this->version ); $this->script_handles['trac-39389-controls'] = 'js-widgets-trac-39389-controls'; @@ -292,9 +292,19 @@ function enqueue_widgets_admin_scripts( $hook_suffix ) { /** * Enqueue scripts for shortcode UI. + * + * @global WP_Widget_Factory $wp_widget_factory */ function enqueue_shortcode_ui() { + global $wp_widget_factory; + wp_enqueue_script( $this->script_handles['shortcode-ui-js-widgets'] ); + + foreach ( $wp_widget_factory->widgets as $widget ) { + if ( $widget instanceof WP_JS_Widget ) { + $widget->enqueue_control_scripts(); + } + } } /** @@ -396,6 +406,8 @@ public function filter_shortcode_ui_fields( $fields ) { * Print shortcode UI templates. */ public function print_shortcode_ui_templates() { + $this->render_widget_form_template_scripts(); + ?> Date: Fri, 13 Jan 2017 14:16:54 +0100 Subject: [PATCH 10/21] Include widget base styles in `widget-form.css` so that forms look as expected inside Shortcode UI window. --- css/widget-form.css | 45 +++++++++++++++++++++++++++++++++++ js/shortcode-ui-js-widgets.js | 4 +++- 2 files changed, 48 insertions(+), 1 deletion(-) diff --git a/css/widget-form.css b/css/widget-form.css index 28aa0c6..ce3ec96 100644 --- a/css/widget-form.css +++ b/css/widget-form.css @@ -60,3 +60,48 @@ .js-widget-form-notifications-container .notice-warning.notice-alt { background-color: #fff8e5; } + +.js-widget-form-shortcode-ui input[type="text"], +.js-widget-form-shortcode-ui input[type="password"], +.js-widget-form-shortcode-ui input[type="color"], +.js-widget-form-shortcode-ui input[type="date"], +.js-widget-form-shortcode-ui input[type="datetime"], +.js-widget-form-shortcode-ui input[type="datetime-local"], +.js-widget-form-shortcode-ui input[type="email"], +.js-widget-form-shortcode-ui input[type="month"], +.js-widget-form-shortcode-ui input[type="number"], +.js-widget-form-shortcode-ui input[type="search"], +.js-widget-form-shortcode-ui input[type="tel"], +.js-widget-form-shortcode-ui input[type="text"], +.js-widget-form-shortcode-ui input[type="time"], +.js-widget-form-shortcode-ui input[type="url"], +.js-widget-form-shortcode-ui input[type="week"], +.js-widget-form-shortcode-ui select, +.js-widget-form-shortcode-ui textarea { + width: 25em; + padding: 3px 5px; + font-size: 14px; +} + +.js-widget-form-shortcode-ui input[type="checkbox"], +.js-widget-form-shortcode-ui input[type="radio"] { + border: 1px solid #b4b9be; + color: #555; +} +.js-widget-form-shortcode-ui input[type="checkbox"]:focus, +.js-widget-form-shortcode-ui input[type="radio"]:focus { + border-color: #5b9dd9; +} +.js-widget-form-shortcode-ui input[type="checkbox"] + label, +.js-widget-form-shortcode-ui input[type="radio"] + label { + display: inline; + clear: none; +} + +.js-widget-form-shortcode-ui label { + color: #555d66; +} + +.js-widget-form-shortcode-ui .select2-container { + max-width: 26.92em; /* 25em * 14px = 350px => 350px / 13px = 26.92em */ +} diff --git a/js/shortcode-ui-js-widgets.js b/js/shortcode-ui-js-widgets.js index 6cc198c..5a035d3 100644 --- a/js/shortcode-ui-js-widgets.js +++ b/js/shortcode-ui-js-widgets.js @@ -37,7 +37,9 @@ wp.shortcake.JSWidgets = (function( $ ) { // eslint-disable-line no-unused-vars // @todo syncInput is unexpectedly persisting a value after closing the lightbox without pressing Update, even though the shortcode attributes remain unchanged until Update pressed. instanceValue = new wp.customize.Value( syncInput.val() ? JSON.parse( syncInput.val() ) : {} ); - container = $( '
' ); + container = $( '
', { + 'class': 'js-widget-form-shortcode-ui' + } ); syncInput.after( container ); form = new FormConstructor( { model: instanceValue, From f800691a0e1c78e11ad3823196e157fbf5a303fc Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 17 Jan 2017 17:12:16 -0800 Subject: [PATCH 11/21] Override display:block style for labels in shortcake widget forms --- css/widget-form.css | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/css/widget-form.css b/css/widget-form.css index ce3ec96..19448e5 100644 --- a/css/widget-form.css +++ b/css/widget-form.css @@ -105,3 +105,9 @@ .js-widget-form-shortcode-ui .select2-container { max-width: 26.92em; /* 25em * 14px = 350px => 350px / 13px = 26.92em */ } + +/* Shortcake style fixes */ +.edit-shortcode-form .js-widget-form-notifications-container label { + clear: none; + display: inline; +} From 0aa05a5bdede35d52f1577c0c103ca3d5a766037 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Tue, 17 Jan 2017 23:07:06 -0800 Subject: [PATCH 12/21] Hide Edit Menu button when no menu is selected --- core-adapter-widgets/nav_menu/class.php | 2 +- core-adapter-widgets/nav_menu/form.js | 16 +++++++++++++++- 2 files changed, 16 insertions(+), 2 deletions(-) diff --git a/core-adapter-widgets/nav_menu/class.php b/core-adapter-widgets/nav_menu/class.php index cf857ce..c514735 100644 --- a/core-adapter-widgets/nav_menu/class.php +++ b/core-adapter-widgets/nav_menu/class.php @@ -108,7 +108,7 @@ public function render_form_template() { ), ) ); ?> -

+

diff --git a/core-adapter-widgets/nav_menu/form.js b/core-adapter-widgets/nav_menu/form.js index e7301a5..c4020c7 100644 --- a/core-adapter-widgets/nav_menu/form.js +++ b/core-adapter-widgets/nav_menu/form.js @@ -101,7 +101,7 @@ wp.widgets.formConstructor.nav_menu = (function( api, $ ) { initialize: function initialize( properties ) { var form = this; wp.widgets.Form.prototype.initialize.call( form, properties ); - _.bindAll( form, 'updateForm', 'handleEditButtonClick' ); + _.bindAll( form, 'updateForm', 'handleEditButtonClick', 'updateEditButtonVisibility' ); if ( _.isObject( form.config.nav_menus ) && 0 === classProps.navMenuCollection.length ) { _.each( form.config.nav_menus, function( name, id ) { @@ -120,10 +120,12 @@ wp.widgets.formConstructor.nav_menu = (function( api, $ ) { var form = this; wp.widgets.Form.prototype.render.call( form ); NavMenuWidgetForm.navMenuCollection.on( 'update change', form.updateForm ); + form.model.bind( form.updateEditButtonVisibility ); form.container.find( 'button.edit' ).on( 'click', form.handleEditButtonClick ); form.noMenusMessage = form.container.find( '.no-menus-message' ); form.menuSelection = form.container.find( '.menu-selection' ); form.updateForm(); + form.updateEditButtonVisibility(); }, /** @@ -135,6 +137,7 @@ wp.widgets.formConstructor.nav_menu = (function( api, $ ) { var form = this; form.container.find( 'button.edit' ).off( 'click', form.handleEditButtonClick ); NavMenuWidgetForm.navMenuCollection.off( 'update change', form.updateForm ); + form.model.unbind( form.updateEditButtonVisibility ); form.noMenusMessage = null; form.menuSelection = null; wp.widgets.Form.prototype.destruct.call( form ); @@ -182,6 +185,17 @@ wp.widgets.formConstructor.nav_menu = (function( api, $ ) { select.val( NavMenuWidgetForm.navMenuCollection.has( currentValue.nav_menu ) ? currentValue.nav_menu : 0 ); form.noMenusMessage.toggle( 0 === NavMenuWidgetForm.navMenuCollection.length ); form.menuSelection.toggle( 0 !== NavMenuWidgetForm.navMenuCollection.length ); + }, + + /** + * Update the visibility of the edit button based on whether a menu is selected. + * + * @returns {void} + */ + updateEditButtonVisibility: function updateEditButtonVisibility() { + var form = this, button; + button = form.container.find( '.edit-menu' ); + button.toggle( NavMenuWidgetForm.navMenuCollection.length > 0 && form.getValue().nav_menu > 0 ); } }, classProps ); From 3c33e2bf38e482831ecffaa53cc54c3be2a8b1de Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Sun, 22 Jan 2017 09:58:18 -0800 Subject: [PATCH 13/21] Prevent enqueueing trac-39389-preview script when user cannot manage widgets Fixes JS error due to customize-preview-widgets getting enqueued as well erroneously --- php/class-js-widgets-plugin.php | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index 46aaec9..20237a4 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -313,16 +313,17 @@ function enqueue_shortcode_ui() { * * @access public * @global WP_Widget_Factory $wp_widget_factory + * @global WP_Customize_Manager $wp_customize */ function enqueue_frontend_scripts() { - global $wp_widget_factory; + global $wp_widget_factory, $wp_customize; foreach ( $wp_widget_factory->widgets as $widget ) { if ( $widget instanceof WP_JS_Widget && ( is_active_widget( false, false, $widget->id_base ) || is_customize_preview() ) ) { $widget->enqueue_frontend_scripts(); } } - if ( is_customize_preview() ) { + if ( is_customize_preview() && ! empty( $wp_customize->widgets ) && current_user_can( 'edit_theme_options' ) ) { wp_enqueue_script( $this->script_handles['trac-39389-preview'] ); } } From 267498c4ff2b6f8229dd6e8f41c96fca593f5589 Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Mon, 13 Feb 2017 23:21:48 -0800 Subject: [PATCH 14/21] Extend editAttributeField View instead of hacking hidden input to inject component --- .jscsrc | 4 ++ js/shortcode-ui-js-widgets.js | 62 -------------------- js/shortcode-ui-view-widget-form-field.js | 47 +++++++++++++++ php/class-js-widget-shortcode-controller.php | 11 +++- php/class-js-widgets-plugin.php | 16 ++--- 5 files changed, 65 insertions(+), 75 deletions(-) delete mode 100644 js/shortcode-ui-js-widgets.js create mode 100644 js/shortcode-ui-view-widget-form-field.js diff --git a/.jscsrc b/.jscsrc index 29ae93e..55dc417 100644 --- a/.jscsrc +++ b/.jscsrc @@ -1,5 +1,9 @@ { "preset": "wordpress", + "requireCamelCaseOrUpperCaseIdentifiers": { + "ignoreProperties": true, + "allExcept": [ "Shortcode_UI" ] + }, "excludeFiles": [ "**/*.min.js", "**/*.jsx", diff --git a/js/shortcode-ui-js-widgets.js b/js/shortcode-ui-js-widgets.js deleted file mode 100644 index 5a035d3..0000000 --- a/js/shortcode-ui-js-widgets.js +++ /dev/null @@ -1,62 +0,0 @@ -/* global wp, jQuery, JSON, module */ -/* eslint-disable strict */ -/* eslint-disable complexity */ - -wp.shortcake.JSWidgets = (function( $ ) { // eslint-disable-line no-unused-vars - 'use strict'; - - var component = {}; - - component.init = function initShortcakeJSWidgets() { - wp.shortcake.hooks.addAction( 'shortcode-ui.render_new', component.embedForm ); - wp.shortcake.hooks.addAction( 'shortcode-ui.render_edit', component.embedForm ); - - // @todo Call form.destuct() on shortcode-ui.render_destroy? - }; - - /** - * Embed widget form. - * - * @param {Backbone.Model} shortcakeModel Shortcake model. - * @returns {void} - */ - component.embedForm = function embedForm( shortcakeModel ) { - var FormConstructor, form, syncInput, container, instanceValue; - - if ( ! shortcakeModel.get( 'widgetType' ) || ! wp.widgets.formConstructor[ shortcakeModel.get( 'widgetType' ) ] ) { - return; - } - - FormConstructor = wp.widgets.formConstructor[ shortcakeModel.get( 'widgetType' ) ]; - - syncInput = $( '.edit-shortcode-form .shortcode-ui-edit-' + shortcakeModel.get( 'shortcode_tag' ) ).find( 'input[name="encoded_json_instance"]' ); - if ( ! syncInput.length ) { - return; - } - - // @todo syncInput is unexpectedly persisting a value after closing the lightbox without pressing Update, even though the shortcode attributes remain unchanged until Update pressed. - instanceValue = new wp.customize.Value( syncInput.val() ? JSON.parse( syncInput.val() ) : {} ); - - container = $( '
', { - 'class': 'js-widget-form-shortcode-ui' - } ); - syncInput.after( container ); - form = new FormConstructor( { - model: instanceValue, - container: container - } ); - form.render(); - - instanceValue.bind( function( instanceData ) { - syncInput.val( JSON.stringify( instanceData ) ); - syncInput.trigger( 'input' ); - } ); - }; - - if ( 'undefined' !== typeof module ) { - module.exports = component; - } - - return component; - -})( jQuery ); diff --git a/js/shortcode-ui-view-widget-form-field.js b/js/shortcode-ui-view-widget-form-field.js new file mode 100644 index 0000000..a02153e --- /dev/null +++ b/js/shortcode-ui-view-widget-form-field.js @@ -0,0 +1,47 @@ +/* global wp, JSON, Shortcode_UI */ +/* eslint-disable strict */ +/* eslint consistent-this: [ "error", "view" ] */ +/* eslint-disable complexity */ + +Shortcode_UI.views.widgetFormField = (function( sui ) { + 'use strict'; + + /** + * Widget form. + * + * @class + */ + return sui.views.editAttributeField.extend( { + + events: {}, + + /** + * Render. + * + * @return {sui.views.editAttributeField} View. + */ + render: function() { + var view = this, FormConstructor, instanceValue, form; + + if ( ! view.shortcode.get( 'widgetType' ) || ! wp.widgets.formConstructor[ view.shortcode.get( 'widgetType' ) ] ) { + throw new Error( 'Unable to determine the widget type.' ); + } + + view.$el.addClass( 'js-widget-form-shortcode-ui' ); + instanceValue = new wp.customize.Value( view.getValue() ? JSON.parse( view.getValue() ) : {} ); + FormConstructor = wp.widgets.formConstructor[ view.shortcode.get( 'widgetType' ) ]; + form = new FormConstructor( { + model: instanceValue, + container: view.$el + } ); + instanceValue.bind( function( instanceData ) { + view.setValue( JSON.stringify( instanceData ) ); + } ); + form.render(); + + view.triggerCallbacks(); + return view; + } + } ); + +})( Shortcode_UI ); diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php index 315fb93..e0761bd 100644 --- a/php/class-js-widget-shortcode-controller.php +++ b/php/class-js-widget-shortcode-controller.php @@ -96,7 +96,12 @@ public function get_sidebar_args() { $args['before_widget'] = sprintf( $args['before_widget'], $widget_id, $this->widget->widget_options['classname'] ); /** This filter is documented in wp-includes/widgets.php */ - $params = apply_filters( 'dynamic_sidebar_params', array( $args, array( 'number' => null ) ) ); + $params = apply_filters( 'dynamic_sidebar_params', array( + $args, + array( + 'number' => null, + ), + ) ); return $params[0]; } @@ -111,7 +116,9 @@ public function get_sidebar_args() { */ public function render_widget_shortcode( $atts ) { $atts = shortcode_atts( - array( 'encoded_json_instance' => '' ), + array( + 'encoded_json_instance' => '', + ), $atts, $this->get_shortcode_tag() ); diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index 20237a4..c59a733 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -164,10 +164,10 @@ public function register_scripts( WP_Scripts $wp_scripts ) { $deps = array( 'admin-widgets', $this->script_handles['form'] ); $wp_scripts->add( $this->script_handles['admin-js-widgets'], $src, $deps, $this->version ); - $this->script_handles['shortcode-ui-js-widgets'] = 'shortcode-ui-js-widgets'; - $src = $plugin_dir_url . 'js/shortcode-ui-js-widgets.js'; + $this->script_handles['shortcode-ui-view-widget-form-field'] = 'shortcode-ui-view-widget-form-field'; + $src = $plugin_dir_url . 'js/shortcode-ui-view-widget-form-field.js'; $deps = array( 'shortcode-ui', $this->script_handles['form'] ); - $wp_scripts->add( $this->script_handles['shortcode-ui-js-widgets'], $src, $deps, $this->version ); + $wp_scripts->add( $this->script_handles['shortcode-ui-view-widget-form-field'], $src, $deps, $this->version ); $this->script_handles['trac-39389-controls'] = 'js-widgets-trac-39389-controls'; $src = $plugin_dir_url . 'js/trac-39389-controls.js'; @@ -298,8 +298,7 @@ function enqueue_widgets_admin_scripts( $hook_suffix ) { function enqueue_shortcode_ui() { global $wp_widget_factory; - wp_enqueue_script( $this->script_handles['shortcode-ui-js-widgets'] ); - wp_add_inline_script( $this->script_handles['shortcode-ui-js-widgets'], 'wp.shortcake.JSWidgets.init()' ); + wp_enqueue_script( $this->script_handles['shortcode-ui-view-widget-form-field'] ); foreach ( $wp_widget_factory->widgets as $widget ) { if ( $widget instanceof WP_JS_Widget ) { @@ -400,6 +399,7 @@ public function register_widget_shortcodes() { public function filter_shortcode_ui_fields( $fields ) { $fields['widget_form'] = array( 'template' => 'shortcode-ui-field-widget_form', + 'view' => 'widgetFormField', ); return $fields; } @@ -409,12 +409,6 @@ public function filter_shortcode_ui_fields( $fields ) { */ public function print_shortcode_ui_templates() { $this->render_widget_form_template_scripts(); - - ?> - - Date: Thu, 16 Feb 2017 10:59:33 -0800 Subject: [PATCH 15/21] Fix excluding pages in the Pages widget --- core-adapter-widgets/pages/class.php | 17 +++++++++++++++++ core-adapter-widgets/pages/form.js | 2 +- 2 files changed, 18 insertions(+), 1 deletion(-) diff --git a/core-adapter-widgets/pages/class.php b/core-adapter-widgets/pages/class.php index 477ba6d..335c497 100644 --- a/core-adapter-widgets/pages/class.php +++ b/core-adapter-widgets/pages/class.php @@ -90,6 +90,23 @@ public function sanitize_exclude( $value, $request, $param ) { return join( ',', wp_parse_id_list( $value ) ); // String as needed by WP_Widget_Pages. } + /** + * Render widget. + * + * @param array $args Widget args. + * @param array $instance Widget instance. + * @return void + */ + public function render( $args, $instance ) { + + // Convert an array exclude into a string exclude since wp_list_pages() requires it. + if ( isset( $instance['exclude'] ) && is_array( $instance['exclude'] ) ) { + $instance['exclude'] = join( ',', $instance['exclude'] ); + } + + $this->adapted_widget->widget( $args, $instance ); + } + /** * Sanitize instance data. * diff --git a/core-adapter-widgets/pages/form.js b/core-adapter-widgets/pages/form.js index 5a63922..6f344da 100644 --- a/core-adapter-widgets/pages/form.js +++ b/core-adapter-widgets/pages/form.js @@ -81,8 +81,8 @@ wp.widgets.formConstructor.pages = (function( api ) { excludeIds.push( id ); } } ); + form.model._value.exclude = excludeIds; } - form.model._value.exclude = excludeIds; form.syncedProperties.exclude = form.createSyncedPropertyValue( form.model, 'exclude' ); } From 3a64d1a757b018c5f3162e12224d65182316bc5e Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 16 Feb 2017 15:35:13 -0800 Subject: [PATCH 16/21] Ensure attributes passed to shortcodes get validated and sanitized --- core-adapter-widgets/pages/class.php | 17 ----------------- php/class-js-widget-shortcode-controller.php | 7 +++++-- 2 files changed, 5 insertions(+), 19 deletions(-) diff --git a/core-adapter-widgets/pages/class.php b/core-adapter-widgets/pages/class.php index 335c497..477ba6d 100644 --- a/core-adapter-widgets/pages/class.php +++ b/core-adapter-widgets/pages/class.php @@ -90,23 +90,6 @@ public function sanitize_exclude( $value, $request, $param ) { return join( ',', wp_parse_id_list( $value ) ); // String as needed by WP_Widget_Pages. } - /** - * Render widget. - * - * @param array $args Widget args. - * @param array $instance Widget instance. - * @return void - */ - public function render( $args, $instance ) { - - // Convert an array exclude into a string exclude since wp_list_pages() requires it. - if ( isset( $instance['exclude'] ) && is_array( $instance['exclude'] ) ) { - $instance['exclude'] = join( ',', $instance['exclude'] ); - } - - $this->adapted_widget->widget( $args, $instance ); - } - /** * Sanitize instance data. * diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php index e0761bd..a4986d3 100644 --- a/php/class-js-widget-shortcode-controller.php +++ b/php/class-js-widget-shortcode-controller.php @@ -126,8 +126,11 @@ public function render_widget_shortcode( $atts ) { $instance_data = array(); if ( ! empty( $atts['encoded_json_instance'] ) ) { $decoded_instance_data = json_decode( urldecode( $atts['encoded_json_instance'] ), true ); - if ( is_array( $decoded_instance_data ) ) { - $instance_data = $decoded_instance_data; + if ( is_array( $decoded_instance_data ) && true === $this->widget->validate( $decoded_instance_data ) ) { + $instance_data = $this->widget->sanitize( $decoded_instance_data, array() ); + if ( is_wp_error( $instance_data ) ) { + $instance_data = array(); + } } } From d703fd234c59f475110e1af254526a1a6869358c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 16 Feb 2017 16:40:02 -0800 Subject: [PATCH 17/21] Separate out shortcode UI logic into separate class --- php/class-js-widgets-plugin.php | 78 +++------------------- php/class-js-widgets-shortcode-ui.php | 96 +++++++++++++++++++++++++++ 2 files changed, 106 insertions(+), 68 deletions(-) create mode 100644 php/class-js-widgets-shortcode-ui.php diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index c59a733..246863d 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -26,13 +26,6 @@ class JS_Widgets_Plugin { */ public $rest_api_namespace = 'js-widgets/v1'; - /** - * Registered controllers for widget shortcodes. - * - * @var JS_Widget_Shortcode_Controller[] - */ - public $widget_shortcodes = array(); - /** * Instances of JS_Widgets_REST_Controller for each widget type. * @@ -78,6 +71,13 @@ class JS_Widgets_Plugin { */ public $script_handles = array(); + /** + * Shortcode UI (Shortcake) integration. + * + * @var Shortcode_UI + */ + public $shortcode_ui; + /** * Plugin constructor. */ @@ -118,12 +118,9 @@ public function init() { add_action( 'in_widget_form', array( $this, 'stop_capturing_in_widget_form' ), 1000, 3 ); // Shortcake integration. - add_action( 'widgets_init', array( $this, 'register_widget_shortcodes' ), 90 ); - add_filter( 'shortcode_ui_fields', array( $this, 'filter_shortcode_ui_fields' ) ); - add_action( 'print_shortcode_ui_templates', array( $this, 'print_shortcode_ui_templates' ) ); - add_action( 'enqueue_shortcode_ui', array( $this, 'enqueue_shortcode_ui' ) ); - - // @todo Add widget REST endpoint for getting the rendered value of widgets. Note originating context URL will need to be supplied when rendering some widgets. + require_once __DIR__ . '/class-js-widgets-shortcode-ui.php'; + $this->shortcode_ui = new JS_Widgets_Shortcode_UI( $this ); + $this->shortcode_ui->add_hooks(); } /** @@ -290,23 +287,6 @@ function enqueue_widgets_admin_scripts( $hook_suffix ) { } } - /** - * Enqueue scripts for shortcode UI. - * - * @global WP_Widget_Factory $wp_widget_factory - */ - function enqueue_shortcode_ui() { - global $wp_widget_factory; - - wp_enqueue_script( $this->script_handles['shortcode-ui-view-widget-form-field'] ); - - foreach ( $wp_widget_factory->widgets as $widget ) { - if ( $widget instanceof WP_JS_Widget ) { - $widget->enqueue_control_scripts(); - } - } - } - /** * Enqueue scripts on the frontend. * @@ -373,44 +353,6 @@ public function upgrade_core_widgets() { } } - /** - * Register widget shortcodes. - * - * @global WP_Widget_Factory $wp_widget_factory - */ - public function register_widget_shortcodes() { - global $wp_widget_factory; - require_once __DIR__ . '/class-js-widget-shortcode-controller.php'; - foreach ( $wp_widget_factory->widgets as $widget ) { - if ( $widget instanceof WP_JS_Widget ) { - $widget_shortcode = new JS_Widget_Shortcode_Controller( $this, $widget ); - $widget_shortcode->register_shortcode(); - add_action( 'register_shortcode_ui', array( $widget_shortcode, 'register_shortcode_ui' ) ); - } - } - } - - /** - * Add widget_form as a new shortcode UI field. - * - * @param array $fields Shortcode fields. - * @return array Fields. - */ - public function filter_shortcode_ui_fields( $fields ) { - $fields['widget_form'] = array( - 'template' => 'shortcode-ui-field-widget_form', - 'view' => 'widgetFormField', - ); - return $fields; - } - - /** - * Print shortcode UI templates. - */ - public function print_shortcode_ui_templates() { - $this->render_widget_form_template_scripts(); - } - /** * Replace instances of `WP_Widget_Form_Customize_Control` for JS Widgets to exclude PHP-generated content. * diff --git a/php/class-js-widgets-shortcode-ui.php b/php/class-js-widgets-shortcode-ui.php new file mode 100644 index 0000000..fbc0898 --- /dev/null +++ b/php/class-js-widgets-shortcode-ui.php @@ -0,0 +1,96 @@ +plugin = $plugin; + } + + /** + * Add hooks. + */ + public function add_hooks() { + add_action( 'widgets_init', array( $this, 'register_widget_shortcodes' ), 90 ); + add_filter( 'shortcode_ui_fields', array( $this, 'filter_shortcode_ui_fields' ) ); + add_action( 'print_shortcode_ui_templates', array( $this, 'print_shortcode_ui_templates' ) ); + add_action( 'enqueue_shortcode_ui', array( $this, 'enqueue_shortcode_ui' ) ); + } + + /** + * Enqueue scripts for shortcode UI. + * + * @global WP_Widget_Factory $wp_widget_factory + */ + function enqueue_shortcode_ui() { + global $wp_widget_factory; + + wp_enqueue_script( $this->plugin->script_handles['shortcode-ui-view-widget-form-field'] ); + + foreach ( $wp_widget_factory->widgets as $widget ) { + if ( $widget instanceof WP_JS_Widget ) { + $widget->enqueue_control_scripts(); + } + } + } + + /** + * Register widget shortcodes. + * + * @global WP_Widget_Factory $wp_widget_factory + */ + public function register_widget_shortcodes() { + global $wp_widget_factory; + require_once __DIR__ . '/class-js-widget-shortcode-controller.php'; + foreach ( $wp_widget_factory->widgets as $widget ) { + if ( $widget instanceof WP_JS_Widget ) { + $widget_shortcode = new JS_Widget_Shortcode_Controller( $this->plugin, $widget ); + $widget_shortcode->register_shortcode(); + add_action( 'register_shortcode_ui', array( $widget_shortcode, 'register_shortcode_ui' ) ); + } + } + } + + /** + * Add widget_form as a new shortcode UI field. + * + * @param array $fields Shortcode fields. + * @return array Fields. + */ + public function filter_shortcode_ui_fields( $fields ) { + $fields['widget_form'] = array( + 'template' => 'shortcode-ui-field-widget_form', + 'view' => 'widgetFormField', + ); + return $fields; + } + + /** + * Print shortcode UI templates. + */ + public function print_shortcode_ui_templates() { + $this->plugin->render_widget_form_template_scripts(); + } + +} From 69f68a09a6960c9e8962d16c94a0037ca1554dac Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 16 Feb 2017 16:44:09 -0800 Subject: [PATCH 18/21] Fix phpcs issues --- php/class-js-widgets-rest-controller.php | 37 ++++++++++++++++++------ php/class-wp-js-widget.php | 6 ++-- 2 files changed, 32 insertions(+), 11 deletions(-) diff --git a/php/class-js-widgets-rest-controller.php b/php/class-js-widgets-rest-controller.php index 355833b..4e61201 100644 --- a/php/class-js-widgets-rest-controller.php +++ b/php/class-js-widgets-rest-controller.php @@ -181,7 +181,9 @@ public function register_routes() { 'callback' => array( $this, 'get_item' ), 'permission_callback' => array( $this, 'get_item_permissions_check' ), 'args' => array( - 'context' => $this->get_context_param( array( 'default' => 'view' ) ), + 'context' => $this->get_context_param( array( + 'default' => 'view', + ) ), ), ), array( @@ -267,7 +269,10 @@ public function get_endpoint_args_for_item_schema( $method = WP_REST_Server::CRE // Only use required / default from arg_options on CREATABLE/EDITABLE endpoints. if ( ! $is_create_or_edit ) { - $params['arg_options'] = array_diff_key( $params['arg_options'], array( 'required' => '', 'default' => '' ) ); + $params['arg_options'] = array_diff_key( $params['arg_options'], array( + 'required' => '', + 'default' => '', + ) ); } $endpoint_args[ $field_id ] = array_merge( $endpoint_args[ $field_id ], $params['arg_options'] ); @@ -397,7 +402,9 @@ public function get_item_permissions_check( $request ) { public function get_items_permissions_check( $request ) { if ( 'edit' === $request['context'] && ! $this->current_user_can_manage_widgets() ) { - return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit widgets.', 'js-widgets' ), array( 'status' => rest_authorization_required_code() ) ); + return new WP_Error( 'rest_forbidden_context', __( 'Sorry, you are not allowed to edit widgets.', 'js-widgets' ), array( + 'status' => rest_authorization_required_code(), + ) ); } return true; @@ -445,7 +452,9 @@ public function delete_item_permissions_check( $request ) { public function get_item( $request ) { $instances = $this->widget->get_settings(); if ( ! array_key_exists( $request['widget_number'], $instances ) ) { - return new WP_Error( 'rest_widget_invalid_number', __( 'Unknown widget.', 'js-widgets' ), array( 'status' => 404 ) ); + return new WP_Error( 'rest_widget_invalid_number', __( 'Unknown widget.', 'js-widgets' ), array( + 'status' => 404, + ) ); } $instance = $instances[ $request['widget_number'] ]; @@ -464,16 +473,22 @@ public function get_item( $request ) { public function update_item( $request ) { $instances = $this->widget->get_settings(); if ( ! array_key_exists( $request['widget_number'], $instances ) ) { - return new WP_Error( 'rest_widget_invalid_number', __( 'Unknown widget.', 'js-widgets' ), array( 'status' => 404 ) ); + return new WP_Error( 'rest_widget_invalid_number', __( 'Unknown widget.', 'js-widgets' ), array( + 'status' => 404, + ) ); } $old_instance = $instances[ $request['widget_number'] ]; $expected_id = $this->get_object_id( $request['widget_number'] ); if ( ! empty( $request['id'] ) && $expected_id !== $request['id'] ) { - return new WP_Error( 'rest_widget_unexpected_id', __( 'Widget ID mismatch.', 'js-widgets' ), array( 'status' => 400 ) ); + return new WP_Error( 'rest_widget_unexpected_id', __( 'Widget ID mismatch.', 'js-widgets' ), array( + 'status' => 400, + ) ); } if ( ! empty( $request['type'] ) && $this->get_object_type() !== $request['type'] ) { - return new WP_Error( 'rest_widget_unexpected_type', __( 'Widget type mismatch.', 'js-widgets' ), array( 'status' => 400 ) ); + return new WP_Error( 'rest_widget_unexpected_type', __( 'Widget type mismatch.', 'js-widgets' ), array( + 'status' => 400, + ) ); } // Note that $new_instance has gone through the validate and sanitize callbacks defined on the instance schema. @@ -485,7 +500,9 @@ public function update_item( $request ) { return $instance; } if ( ! is_array( $instance ) ) { - return new WP_Error( 'rest_widget_sanitize_failed', __( 'Sanitization failed.', 'js-widgets' ), array( 'status' => 400 ) ); + return new WP_Error( 'rest_widget_sanitize_failed', __( 'Sanitization failed.', 'js-widgets' ), array( + 'status' => 400, + ) ); } $instances[ $request['widget_number'] ] = $instance; @@ -530,7 +547,9 @@ public function prepare_item_for_response( $instance, $request, $widget_number = $widget_number = $request['widget_number']; } if ( empty( $widget_number ) ) { - return new WP_Error( 'rest_widget_unavailable_widget_number', __( 'Unknown widget number.', 'js-widgets' ), array( 'status' => 500 ) ); + return new WP_Error( 'rest_widget_unavailable_widget_number', __( 'Unknown widget number.', 'js-widgets' ), array( + 'status' => 500, + ) ); } // Just in case. diff --git a/php/class-wp-js-widget.php b/php/class-wp-js-widget.php index a3a8adf..8498a79 100644 --- a/php/class-wp-js-widget.php +++ b/php/class-wp-js-widget.php @@ -613,12 +613,13 @@ protected function render_form_field_template( $args = array() ) { } elseif ( 'integer' === $schema_type || 'number' === $schema_type ) { $default_input_attrs['type'] = 'number'; } elseif ( 'string' === $schema_type && isset( $field_schema['format'] ) ) { + + // @todo Support date-time format. if ( 'uri' === $field_schema['format'] ) { $default_input_attrs['type'] = 'url'; } elseif ( 'email' === $field_schema['format'] ) { $default_input_attrs['type'] = 'email'; } - // @todo Support date-time format. } if ( 'integer' === $schema_type ) { @@ -673,9 +674,10 @@ protected function render_form_field_template( $args = array() ) { - + From fda18fe02eeab5ede5b4aa74013c3e92ea9c044c Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 16 Feb 2017 23:21:16 -0800 Subject: [PATCH 19/21] Remove leftover id_base exporting --- php/class-wp-adapter-js-widget.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/php/class-wp-adapter-js-widget.php b/php/class-wp-adapter-js-widget.php index 200ce68..596510f 100644 --- a/php/class-wp-adapter-js-widget.php +++ b/php/class-wp-adapter-js-widget.php @@ -87,11 +87,6 @@ public function enqueue_control_scripts() { wp_json_encode( $this->id_base ), wp_json_encode( $this->get_form_config() ) ) ); - wp_add_inline_script( $handle, sprintf( - 'wp.widgets.formConstructor[ %s ].prototype.id_base = %s;', - wp_json_encode( $this->id_base ), - wp_json_encode( $this->id_base ) - ) ); } /** From a4c06ae363eb13cd453578828eafba56a77daf6f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 16 Feb 2017 23:22:30 -0800 Subject: [PATCH 20/21] Ensure frontend assets are enqueued for widgets as shortcodes --- php/class-js-widget-shortcode-controller.php | 1 + php/class-js-widgets-shortcode-ui.php | 93 ++++++++++++++++++++ php/class-wp-js-widget.php | 6 ++ 3 files changed, 100 insertions(+) diff --git a/php/class-js-widget-shortcode-controller.php b/php/class-js-widget-shortcode-controller.php index a4986d3..2dcff28 100644 --- a/php/class-js-widget-shortcode-controller.php +++ b/php/class-js-widget-shortcode-controller.php @@ -135,6 +135,7 @@ public function render_widget_shortcode( $atts ) { } ob_start(); + $this->widget->enqueue_frontend_scripts(); $this->widget->render( $this->get_sidebar_args(), $instance_data ); return ob_get_clean(); } diff --git a/php/class-js-widgets-shortcode-ui.php b/php/class-js-widgets-shortcode-ui.php index fbc0898..67c7e5e 100644 --- a/php/class-js-widgets-shortcode-ui.php +++ b/php/class-js-widgets-shortcode-ui.php @@ -32,10 +32,45 @@ public function __construct( JS_Widgets_Plugin $plugin ) { * Add hooks. */ public function add_hooks() { + add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_the_loop_shortcode_dependencies' ) ); add_action( 'widgets_init', array( $this, 'register_widget_shortcodes' ), 90 ); add_filter( 'shortcode_ui_fields', array( $this, 'filter_shortcode_ui_fields' ) ); add_action( 'print_shortcode_ui_templates', array( $this, 'print_shortcode_ui_templates' ) ); add_action( 'enqueue_shortcode_ui', array( $this, 'enqueue_shortcode_ui' ) ); + + add_action( 'shortcode_ui_before_do_shortcode', array( $this, 'before_do_shortcode' ) ); + add_action( 'shortcode_ui_after_do_shortcode', array( $this, 'after_do_shortcode' ) ); + } + + /** + * Enqueue scripts and styles for widgets that appear as shortcodes. + * + * @global WP_Widget_Factory $wp_widget_factory + * @global WP_Query $the_wp_query + */ + function enqueue_the_loop_shortcode_dependencies() { + global $wp_the_query, $wp_query, $wp_widget_factory; + + if ( empty( $wp_the_query ) ) { + return; + } + + $widgets_by_id_base = array(); + foreach ( $wp_widget_factory->widgets as $widget ) { + if ( $widget instanceof WP_JS_Widget ) { + $widgets_by_id_base[ $widget->id_base ] = $widget; + } + } + + $pattern = '#' . sprintf( '\[widget_(%s)', join( '|', array_keys( $widgets_by_id_base ) ) ) . '#'; + $all_content = join( ' ', wp_list_pluck( $wp_query->posts, 'post_content' ) ); + if ( ! preg_match_all( $pattern, $all_content, $matches ) ) { + return; + } + foreach ( $matches[1] as $matched_id_base ) { + $widget = $widgets_by_id_base[ $matched_id_base ]; + $widget->enqueue_frontend_scripts(); + } } /** @@ -93,4 +128,62 @@ public function print_shortcode_ui_templates() { $this->plugin->render_widget_form_template_scripts(); } + /** + * Whether footer scripts should be printed. + * + * @var bool + */ + protected $should_print_footer_scripts = false; + + /** + * Backup of suspended WP_Scripts. + * + * @var WP_Scripts + */ + protected $suspended_wp_scripts; + + /** + * Backup of suspended WP_Styles. + * + * @var WP_Styles + */ + protected $suspended_wp_styles; + + /** + * Handle printing before shortcode. + * + * @param string $shortcode Shortcode. + * @global WP_Widget_Factory $wp_widget_factory + */ + public function before_do_shortcode( $shortcode ) { + global $wp_scripts, $wp_styles; + + $this->should_print_footer_scripts = (bool) preg_match( '#^\[widget_(?P.+?)\s#', $shortcode ); + if ( ! $this->should_print_footer_scripts ) { + return; + } + + // Reset enqueued assets so that only the widget's specific assets will be enqueued. + $this->suspended_wp_scripts = $wp_scripts; + $this->suspended_wp_styles = $wp_styles; + $wp_scripts = null; + $wp_styles = null; + } + + /** + * Print scripts and styles that the widget depends on. + */ + public function after_do_shortcode() { + global $wp_scripts, $wp_styles; + if ( ! $this->should_print_footer_scripts ) { + return; + } + + // Prints head scripts and styles as well as footer scripts and required templates. + wp_print_footer_scripts(); + + // Restore enqueued scripts and styles. + $wp_scripts = $this->suspended_wp_scripts; + $wp_styles = $this->suspended_wp_styles; + } } diff --git a/php/class-wp-js-widget.php b/php/class-wp-js-widget.php index 8498a79..81a475d 100644 --- a/php/class-wp-js-widget.php +++ b/php/class-wp-js-widget.php @@ -453,6 +453,12 @@ public function validate( $value ) { * @param array $instance The settings for the particular instance of the widget. */ final public function widget( $args, $instance ) { + /* + * Make sure frontend scripts and styles get enqueued if not already done. + * This is particularly important in the case of a widget used in a shortcode. + */ + $this->enqueue_frontend_scripts(); + $this->render( $args, $instance ); } From ae6ba7895a5b4607289c3058af172897e09bd83f Mon Sep 17 00:00:00 2001 From: Weston Ruter Date: Thu, 16 Feb 2017 23:58:10 -0800 Subject: [PATCH 21/21] Ensure dashicon related to a JS Widget gets applied in the available widgets panel --- php/class-js-widgets-plugin.php | 26 ++++++++++++++++++++++++++ php/class-js-widgets-shortcode-ui.php | 6 +++++- 2 files changed, 31 insertions(+), 1 deletion(-) diff --git a/php/class-js-widgets-plugin.php b/php/class-js-widgets-plugin.php index 246863d..f83311f 100644 --- a/php/class-js-widgets-plugin.php +++ b/php/class-js-widgets-plugin.php @@ -107,6 +107,7 @@ public function init() { add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_frontend_scripts' ) ); add_action( 'rest_api_init', array( $this, 'rest_api_init' ), 100 ); add_action( 'customize_controls_enqueue_scripts', array( $this, 'enqueue_customize_controls_scripts' ) ); + add_action( 'customize_controls_print_scripts', array( $this, 'print_available_widget_icon_styles' ), 100 ); add_action( 'admin_enqueue_scripts', array( $this, 'enqueue_widgets_admin_scripts' ) ); add_action( 'customize_controls_print_footer_scripts', array( $this, 'render_widget_form_template_scripts' ) ); add_action( 'admin_footer-widgets.php', array( $this, 'render_widget_form_template_scripts' ) ); @@ -250,6 +251,31 @@ function enqueue_customize_controls_scripts() { wp_enqueue_script( $this->script_handles['trac-39389-controls'] ); } + /** + * Print the CSS styles to ensure the defined Dashicon $icon_name in the available JS widgets panel. + * + * This is somewhat hacky to parse the Dashicons CSS to obtain the necessary CSS properties + * to output into the page + * + * @global WP_Widget_Factory $wp_widget_factory + */ + function print_available_widget_icon_styles() { + global $wp_widget_factory; + + $dashicons_content = file_get_contents( ABSPATH . WPINC . '/css/dashicons.css' ); + + echo ''; + } + /** * Enqueue scripts for the widgets admin screen. * diff --git a/php/class-js-widgets-shortcode-ui.php b/php/class-js-widgets-shortcode-ui.php index 67c7e5e..b4cefc2 100644 --- a/php/class-js-widgets-shortcode-ui.php +++ b/php/class-js-widgets-shortcode-ui.php @@ -153,7 +153,8 @@ public function print_shortcode_ui_templates() { * Handle printing before shortcode. * * @param string $shortcode Shortcode. - * @global WP_Widget_Factory $wp_widget_factory + * @global WP_Scripts $wp_scripts + * @global WP_Styles $wp_styles */ public function before_do_shortcode( $shortcode ) { global $wp_scripts, $wp_styles; @@ -172,6 +173,9 @@ public function before_do_shortcode( $shortcode ) { /** * Print scripts and styles that the widget depends on. + * + * @global WP_Scripts $wp_scripts + * @global WP_Styles $wp_styles */ public function after_do_shortcode() { global $wp_scripts, $wp_styles;