Skip to content

Commit

Permalink
[not verified] Widget visibility: supporting load conditions for lega…
Browse files Browse the repository at this point in the history
…cy widgets in gutenberg widgets screen (#20255)

Co-authored-by: Harris Papazolgou <[email protected]>
Co-authored-by: cpap <[email protected]>
  • Loading branch information
3 people authored and sdixon194 committed Jul 14, 2021
1 parent fc3bc9b commit 4b3956c
Show file tree
Hide file tree
Showing 4 changed files with 196 additions and 44 deletions.
4 changes: 4 additions & 0 deletions projects/plugins/jetpack/changelog/fix-widget-visibility
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
Significance: patch
Type: bugfix

Allow the use of widget visibility conditions in gutenberg based widget editing
178 changes: 139 additions & 39 deletions projects/plugins/jetpack/modules/widget-visibility/widget-conditions.php
Original file line number Diff line number Diff line change
Expand Up @@ -3,31 +3,114 @@
use Automattic\Jetpack\Assets;

/**
* Hide or show widgets conditionally.
* Hide or show legacy widgets conditionally.
*
* This class has two responsiblities - administrating the conditions in which legacy widgets may be hidden or shown
* and hiding/showing the legacy widgets on the front-end of the site, depending upon the evaluation of those conditions.
*
* Administrating the conditions can be done in one of four different WordPress screens, plus direct use of the API and
* is supplemented with a legacy widget preview screen. The four different admin screens are
*
* Gutenberg widget experience - widget admin (widgets.php + API + legacy widget preview)
* Gutenberg widget experience - Customizer (customizer screen/API + API + legacy widget preview)
* Classic widget experience - widget admin (widgets.php + admin-ajax XHR requests)
* Classic widget experience - Customizer (customizer screen/API)
*
* An introduction to the API endpoints can be found here: https://make.wordpress.org/core/2021/06/29/rest-api-changes-in-wordpress-5-8/
*/

class Jetpack_Widget_Conditions {
static $passed_template_redirect = false;

public static function init() {
if ( is_admin() ) {
add_action( 'sidebar_admin_setup', array( __CLASS__, 'widget_admin_setup' ) );
add_filter( 'widget_update_callback', array( __CLASS__, 'widget_update' ), 10, 3 );
global $pagenow;

// The Gutenberg based widget experience will show a preview of legacy widgets by including a URL beginning
// widgets.php?legacy-widget-preview inside an iframe. Previews don't need widget editing loaded and also don't
// want to run the filter - if the widget is filtered out it'll be empty, which would be confusing.
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
if ( isset( $_GET['legacy-widget-preview'] ) ) {
return;
}

// If action is posted and it's save-widget then it's relevant to widget conditions, otherwise it's something
// else and it's not worth registering hooks.
// phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( isset( $_POST['action'] ) && ! in_array( $_POST['action'], array( 'save-widget', 'update-widget' ), true ) ) {
return;
}

// API call to *list* the widget types doesn't use editing visibility or display widgets.
if ( false !== strpos( $_SERVER['REQUEST_URI'], '/wp-json/wp/v2/widget-types?' ) ) {
return;
}

$add_data_assets_to_page = false;
$add_html_to_form = false;
$handle_widget_updates = false;

$using_classic_experience = ( ! function_exists( 'wp_use_widgets_block_editor' ) || ! wp_use_widgets_block_editor() );
if ( $using_classic_experience &&
(
is_customize_preview() || 'widgets.php' === $pagenow ||
// phpcs:ignore WordPress.Security.NonceVerification.Missing
( 'admin-ajax.php' === $pagenow && array_key_exists( 'action', $_POST ) && 'save-widget' === $_POST['action'] )
)
) {
$add_data_assets_to_page = true;
$add_html_to_form = true;
$handle_widget_updates = true;
} else {
// On a screen that is hosting the API in the gutenberg editing experience.
if ( is_customize_preview() || 'widgets.php' === $pagenow ) {
$add_data_assets_to_page = true;
}

// Encoding for a particular widget end point.
if ( 1 === preg_match( '|/wp-json/wp/v2/widget-types/.*/encode|', $_SERVER['REQUEST_URI'] ) ) {
$add_html_to_form = true;
$handle_widget_updates = true;
}

// Batch API is usually saving but could be anything.
if ( false !== strpos( $_SERVER['REQUEST_URI'], '/wp-json/batch/v1' ) ) {
$handle_widget_updates = true;
$add_html_to_form = true;
}

// Saving widgets via non-batch API. This isn't used within WordPress but could be used by third parties in theory.
if ( 'GET' !== $_SERVER['REQUEST_METHOD'] && false !== strpos( $_SERVER['REQUEST_URI'], '/wp/v2/widgets' ) ) {
$handle_widget_updates = true;
$add_html_to_form = true;
}
}

if ( $add_html_to_form ) {
add_action( 'in_widget_form', array( __CLASS__, 'widget_conditions_admin' ), 10, 3 );
} elseif ( ! in_array( $GLOBALS['pagenow'], array( 'wp-login.php', 'wp-register.php' ) ) ) {
}

if ( $handle_widget_updates ) {
add_filter( 'widget_update_callback', array( __CLASS__, 'widget_update' ), 10, 3 );
}

if ( $add_data_assets_to_page ) {
add_action( 'sidebar_admin_setup', array( __CLASS__, 'widget_admin_setup' ) );
}

if ( ! $add_html_to_form && ! $handle_widget_updates && ! $add_data_assets_to_page &&
! in_array( $pagenow, array( 'wp-login.php', 'wp-register.php' ), true )
) {
// Not hit any known widget admin endpoint, register widget display hooks instead.
add_filter( 'widget_display_callback', array( __CLASS__, 'filter_widget' ) );
add_filter( 'sidebars_widgets', array( __CLASS__, 'sidebars_widgets' ) );
add_action( 'template_redirect', array( __CLASS__, 'template_redirect' ) );
}
}

/**
* Prepare the interface for editing widgets - loading css, javascript & data
*/
public static function widget_admin_setup() {
// Return early if we are not in the block editor.
if ( wp_should_load_block_editor_scripts_and_styles() ) {
return;
}

wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.css', __FILE__ ) );
wp_enqueue_style( 'widget-conditions', plugins_url( 'widget-conditions/widget-conditions.css', __FILE__ ), array( 'widgets' ), JETPACK__VERSION );
wp_style_add_data( 'widget-conditions', 'rtl', 'replace' );
wp_enqueue_script(
'widget-conditions',
Expand All @@ -36,7 +119,7 @@ public static function widget_admin_setup() {
'modules/widget-visibility/widget-conditions/widget-conditions.js'
),
array( 'jquery', 'jquery-ui-core' ),
20191128,
JETPACK__VERSION,
true
);

Expand Down Expand Up @@ -295,9 +378,9 @@ public static function get_pages() {
/**
* Add the widget conditions to each widget in the admin.
*
* @param $widget unused.
* @param $return unused.
* @param array $instance The widget settings.
* @param WP_Widget $widget Widget to add conditions settings to.
* @param null $return unused.
* @param array $instance The widget settings.
*/
public static function widget_conditions_admin( $widget, $return, $instance ) {
$conditions = array();
Expand Down Expand Up @@ -327,6 +410,9 @@ public static function widget_conditions_admin( $widget, $return, $instance ) {
class="
widget-conditional
<?php
// $_POST['widget-conditions-visible'] is used in the classic widget experience to decide whether to
// display the visibility panel open, e.g. when saving. In the gutenberg widget experience the POST
// value will always be empty, but this is fine - it doesn't rerender the HTML when saving anyway.
if (
empty( $_POST['widget-conditions-visible'] )
|| $_POST['widget-conditions-visible'] == '0'
Expand Down Expand Up @@ -357,10 +443,19 @@ class="
<?php
if ( ! isset( $_POST['widget-conditions-visible'] ) ) {
?>
<a href="#" class="button display-options"><?php _e( 'Visibility', 'jetpack' ); ?></a><?php } ?>
<a href="#" class="button display-options"><?php esc_html_e( 'Visibility', 'jetpack' ); ?></a><?php } ?>
<div class="widget-conditional-inner">
<div class="condition-top">
<?php printf( _x( '%s if:', 'placeholder: dropdown menu to select widget visibility; hide if or show if', 'jetpack' ), '<select name="conditions[action]"><option value="show" ' . selected( $conditions['action'], 'show', false ) . '>' . esc_html_x( 'Show', 'Used in the "%s if:" translation for the widget visibility dropdown', 'jetpack' ) . '</option><option value="hide" ' . selected( $conditions['action'], 'hide', false ) . '>' . esc_html_x( 'Hide', 'Used in the "%s if:" translation for the widget visibility dropdown', 'jetpack' ) . '</option></select>' ); ?>
<?php
printf(
// translators: %s is a HTML select widget for widget visibility, 'show' and 'hide' are it's options. It will read like 'show if' or 'hide if'.
esc_html_x( '%s if:', 'placeholder: dropdown menu to select widget visibility; hide if or show if', 'jetpack' ),
'<select name="' . esc_attr( $widget->get_field_name( 'conditions[action]' ) ) . '">
<option value="show" ' . selected( $conditions['action'], 'show', false ) . '>' . esc_html_x( 'Show', 'Used in the "%s if:" translation for the widget visibility dropdown', 'jetpack' ) . '</option>
<option value="hide" ' . selected( $conditions['action'], 'hide', false ) . '>' . esc_html_x( 'Hide', 'Used in the "%s if:" translation for the widget visibility dropdown', 'jetpack' ) . '</option>
</select>'
);
?>
</div><!-- .condition-top -->

<div class="conditions">
Expand All @@ -378,7 +473,7 @@ class="
?>
<div class="condition" data-rule-major="<?php echo esc_attr( $rule['major'] ); ?>" data-rule-minor="<?php echo esc_attr( $rule['minor'] ); ?>" data-rule-has-children="<?php echo esc_attr( $rule['has_children'] ); ?>">
<div class="selection alignleft">
<select class="conditions-rule-major" name="conditions[rules_major][]">
<select class="conditions-rule-major" name="<?php echo esc_attr( $widget->get_field_name( 'conditions[rules_major][]' ) ); ?>">
<option value="" <?php selected( '', $rule['major'] ); ?>><?php echo esc_html_x( '-- Select --', 'Used as the default option in a dropdown list', 'jetpack' ); ?></option>
<option value="category" <?php selected( 'category', $rule['major'] ); ?>><?php esc_html_e( 'Category', 'jetpack' ); ?></option>
<option value="author" <?php selected( 'author', $rule['major'] ); ?>><?php echo esc_html_x( 'Author', 'Noun, as in: "The author of this post is..."', 'jetpack' ); ?></option>
Expand All @@ -398,7 +493,7 @@ class="

<?php _ex( 'is', 'Widget Visibility: {Rule Major [Page]} is {Rule Minor [Search results]}', 'jetpack' ); ?>

<select class="conditions-rule-minor" name="conditions[rules_minor][]"
<select class="conditions-rule-minor" name="<?php echo esc_attr( $widget->get_field_name( 'conditions[rules_minor][]' ) ); ?>"
<?php
if ( ! $rule['major'] ) {
?>
Expand All @@ -418,7 +513,7 @@ class="
?>
style="display: none;"<?php } ?>>
<label>
<input type="checkbox" name="conditions[page_children][<?php echo $rule_index; ?>]" value="has" <?php checked( $rule['has_children'], true ); ?> />
<input type="checkbox" name="<?php echo esc_attr( $widget->get_field_name( "conditions[page_children][$rule_index]" ) ); ?>" value="has" <?php checked( $rule['has_children'], true ); ?> />
<?php echo esc_html_x( 'Include children', 'Checkbox on Widget Visibility if children of the selected page should be included in the visibility rule.', 'jetpack' ); ?>
</label>
</span>
Expand Down Expand Up @@ -447,7 +542,7 @@ class="
<label>
<input
type="checkbox"
name="conditions[match_all]"
name="<?php echo esc_attr( $widget->get_field_name( 'conditions[match_all]' ) ); ?>"
value="1"
class="conditions-match-all"
<?php checked( $conditions['match_all'], '1' ); ?> />
Expand All @@ -463,35 +558,34 @@ class="conditions-match-all"
/**
* On an AJAX update of the widget settings, process the display conditions.
*
* @param array $instance The current instance's settings.
* @param array $new_instance New settings for this instance as input by the user.
* @param array $old_instance Old settings for this instance.
* @return array Modified settings.
*/
public static function widget_update( $instance, $new_instance, $old_instance ) {
if ( empty( $_POST['conditions'] ) ) {
return $instance;
}

$conditions = array();
$conditions['action'] = $_POST['conditions']['action'];
$conditions['match_all'] = ( isset( $_POST['conditions']['match_all'] ) ? '1' : '0' );
$conditions['rules'] = array();
$conditions['action'] = isset( $new_instance['conditions']['action'] ) ? $new_instance['conditions']['action'] : null;
$conditions['match_all'] = isset( $new_instance['conditions']['match_all'] ) ? '1' : '0';
$conditions['rules'] = isset( $new_instance['conditions']['rules'] ) ? $new_instance['conditions']['rules'] : array();

foreach ( $_POST['conditions']['rules_major'] as $index => $major_rule ) {
if ( ! $major_rule ) {
continue;
}
if ( isset( $new_instance['conditions']['rules_major'] ) ) {
foreach ( $new_instance['conditions']['rules_major'] as $index => $major_rule ) {
if ( ! $major_rule ) {
continue;
}

$conditions['rules'][] = array(
'major' => $major_rule,
'minor' => isset( $_POST['conditions']['rules_minor'][ $index ] ) ? $_POST['conditions']['rules_minor'][ $index ] : '',
'has_children' => isset( $_POST['conditions']['page_children'][ $index ] ) ? true : false,
);
$conditions['rules'][] = array(
'major' => $major_rule,
'minor' => isset( $new_instance['conditions']['rules_minor'][ $index ] ) ? $new_instance['conditions']['rules_minor'][ $index ] : '',
'has_children' => isset( $new_instance['conditions']['page_children'][ $index ] ) ? true : false,
);
}
}

if ( ! empty( $conditions['rules'] ) ) {
$instance['conditions'] = $conditions;
} else {
} elseif ( empty( $new_instance['conditions']['rules'] ) ) {
unset( $instance['conditions'] );
}

Expand Down Expand Up @@ -611,6 +705,12 @@ public static function filter_widget( $instance ) {
return $instance;
}

// Don't filter widgets from the REST API when it's called via the widgets admin page - otherwise they could get
// filtered out and become impossible to edit.
if ( strpos( wp_get_raw_referer(), '/wp-admin/widgets.php' ) && false !== strpos( $_SERVER['REQUEST_URI'], '/wp-json/' ) ) {
return $instance;
}

// Store the results of all in-page condition lookups so that multiple widgets with
// the same visibility conditions don't result in duplicate DB queries.
static $condition_result_cache = array();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
}
.widget-conditional {
margin-bottom: 12px;
margin-top: 10px;
}
.widget-conditional .conditions{
margin-bottom: 12px;
Expand Down Expand Up @@ -68,12 +69,18 @@
text-indent: -9999px;
z-index: 1;
}

.wp-block-legacy-widget__edit-form .widget-conditional .condition-control a {
top: 20px;
}

.widget-conditional .condition-control a:before {
position: absolute;
text-indent: 0;
top: 0;
left: 0;
}
.wp-block-legacy-widget__edit-form .widget-inside.widget-inside .widget-conditional .condition-control .delete-condition,
.widget-conditional .condition-control .delete-condition {
left: 0;
color: #f11;
Expand All @@ -82,6 +89,26 @@
right: 0;
}

.wp-block-legacy-widget__edit-form .widget-inside.widget-inside .widget-conditional .widget-conditional-inner a.dashicons {
font-family: dashicons;
}

.wp-block-legacy-widget__edit-form .widget-inside.widget-inside .widget-conditional-inner select {
display:initial;
width:auto;
background-color: #fff;
}

.wp-block-legacy-widget__edit-form .widget-inside.widget-inside .widget-conditional .widget-conditional-inner select:disabled {
color: #a7aaad;
border-color: #dcdcde;
background-color: #f6f7f7;
}

.editor-styles-wrapper .wp-block-legacy-widget__edit-form .widget-inside.widget-inside .widget-conditional .alignleft {
margin-left:20px
}

.widget-conditional .condition:last-child .condition-conjunction,
.widget-conditional .condition:last-child .condition-intersection {
display: none;
Expand Down Expand Up @@ -115,3 +142,10 @@
top: 20px;
}
}

.wp-block-legacy-widget__edit-form .widget-inside.widget-inside .widget-conditional-inner {
/*
* fonts of labels are reset to 13px in gutenberg editor for legacy widgets, ensure a consistent look on non-labels
*/
font-size: 13px;
}
Original file line number Diff line number Diff line change
@@ -1,10 +1,17 @@
/* global isRtl, widget_conditions_parent_pages, widget_conditions_data, jQuery */

jQuery( function ( $ ) {
var widgets_shell = $( 'div#widgets-right' );
// Gutenberg 'widgets.php' screen.
var widgets_shell = $( '#widgets-editor' );

if ( ! widgets_shell && ! widgets_shell.length ) {
widgets_shell = $( 'form#customize-controls' );
if ( 0 === widgets_shell.length ) {
// Legacy 'widgets.php' screen + customizer.
widgets_shell = $( 'div#widgets-right' );

// For backwards compatibility
if ( 0 === widgets_shell.length ) {
widgets_shell = $( 'form#customize-controls' );
}
}

function setWidgetMargin( $widget ) {
Expand Down Expand Up @@ -51,8 +58,15 @@ jQuery( function ( $ ) {
}

function moveWidgetVisibilityButton( $widget ) {
var $displayOptionsButton = $widget.find( 'a.display-options' ).first();
$displayOptionsButton.insertBefore( $widget.find( 'input.widget-control-save' ) );
var $displayOptionsButton = $widget.find( 'a.display-options' ).first(),
$relativeWidget = $widget.find( 'input.widget-control-save' );

if ( 0 === $relativeWidget.length ) {
// The save button doesn't exist in gutenberg widget editor, the conditional HTML ought to be displayed
// last inside the widget options, so display the button before that.
$relativeWidget = $widget.find( 'div.widget-conditional' );
}
$displayOptionsButton.insertBefore( $relativeWidget );

// Widgets with no configurable options don't show the Save button's container.
$displayOptionsButton
Expand Down

0 comments on commit 4b3956c

Please sign in to comment.