PluginProbe
Jetpack – WP Security, Backup, Speed, & Growth / 16.3-a.1
Jetpack – WP Security, Backup, Speed, & Growth v16.3-a.1
16.3-a.3 16.3-a.1 16.2 16.2-beta 12.0.3 12.1.3 12.2.3 12.3.2 12.4.2 12.5.2 12.6.4 12.7.3 12.8.3 12.9.5 13.0.2 13.1.5 13.2.4 13.3.3 13.4.5 13.5.2 13.6.2 13.7.2 13.8.3 13.9.2 14.0.1 All 504 releases
← All changes | modules/widget-visibility/widget-conditions.php +128 -36 12.2.3 → 16.3-a.1 View file →
@@ -4,10 +4,15 @@
4 4 *
5 5 * @package automattic/jetpack
6 6 */
7 7
8 +use Automattic\Block_Scanner;
8 9 use Automattic\Jetpack\Assets;
9 10
11 +if ( ! defined( 'ABSPATH' ) ) {
12 + exit( 0 );
13 +}
14 +
10 15 /**
11 16 * Hide or show legacy widgets conditionally.
12 17 *
13 18 * This class has two responsiblities - administrating the conditions in which legacy widgets may be hidden or shown
@@ -52,9 +57,9 @@
52 57 return;
53 58 }
54 59
55 60 // API call to *list* the widget types doesn't use editing visibility or display widgets.
56 - if ( isset( $_SERVER['REQUEST_URI'] ) && false !== strpos( $_SERVER['REQUEST_URI'], '/widget-types?' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
61 + if ( isset( $_SERVER['REQUEST_URI'] ) && str_contains( $_SERVER['REQUEST_URI'], '/widget-types?' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
57 62 return;
58 63 }
59 64
60 65 $add_data_assets_to_page = false;
@@ -65,9 +70,9 @@
65 70 // Check to see if using the customizer, but not using the preview. The preview should filter out widgets,
66 71 // the customizer controls in the sidebar should not (so they can be edited).
67 72 // phpcs:ignore WordPress.Security.NonceVerification.Recommended
68 73 $customizer_not_previewer = is_customize_preview() && ! isset( $_GET['customize_changeset_uuid'] );
69 - $using_classic_experience = ( ! function_exists( 'wp_use_widgets_block_editor' ) || ! wp_use_widgets_block_editor() );
74 + $using_classic_experience = ! wp_use_widgets_block_editor();
70 75 if ( $using_classic_experience &&
71 76 ( $customizer_not_previewer || 'widgets.php' === $pagenow ||
72 77 // phpcs:ignore WordPress.Security.NonceVerification.Missing
73 78 ( 'admin-ajax.php' === $pagenow && array_key_exists( 'action', $_POST ) && 'save-widget' === $_POST['action'] )
@@ -90,15 +95,15 @@
90 95 }
91 96
92 97 // Batch API is usually saving but could be anything.
93 98 $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
94 - if ( false !== strpos( $current_url, '/wp-json/batch/v1' ) || 1 === preg_match( '/^\/wp\/v2\/sites\/\d+\/batch\/v1/', $current_url ) ) {
99 + if ( str_contains( $current_url, '/wp-json/batch/v1' ) || 1 === preg_match( '/^\/wp\/v2\/sites\/\d+\/batch\/v1/', $current_url ) ) {
95 100 $handle_widget_updates = true;
96 101 $add_html_to_form = true;
97 102 }
98 103
99 104 // Saving widgets via non-batch API. This isn't used within WordPress but could be used by third parties in theory.
100 - if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] && false !== strpos( $_SERVER['REQUEST_URI'], '/wp/v2/widgets' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
105 + if ( isset( $_SERVER['REQUEST_METHOD'] ) && 'GET' !== $_SERVER['REQUEST_METHOD'] && str_contains( $_SERVER['REQUEST_URI'], '/wp/v2/widgets' ) ) { // phpcs:ignore WordPress.Security.ValidatedSanitizedInput.MissingUnslash, WordPress.Security.ValidatedSanitizedInput.InputNotSanitized
101 106 $handle_widget_updates = true;
102 107 $add_html_to_form = true;
103 108 }
104 109 }
@@ -152,9 +157,9 @@
152 157 * but these blocks use a ServerSideRender dynamic preview, so the php env needs
153 158 * to know about the new attribute, too.
154 159 */
155 160 public static function add_block_attributes_filter() {
156 - $blocks_to_add_visibility_conditions = array(
161 + $blocks = array(
157 162 // These use <ServerSideRender>.
158 163 'core/calendar',
159 164 'core/latest-comments',
160 165 'core/rss',
@@ -163,11 +168,28 @@
163 168 'core/page-list',
164 169 'core/latest-posts',
165 170 'woocommerce/product-categories',
166 171 );
172 + /**
173 + * Filters the list of widget visibility blocks using <ServerSideRender>.
174 + *
175 + * @since 12.4
176 + *
177 + * @module widget-visibility
178 + *
179 + * @param string[] $blocks Array of block names from WordPress core and WooCommerce.
180 + */
181 + $blocks_to_add_visibility_conditions = apply_filters( 'jetpack_widget_visibility_server_side_render_blocks', $blocks );
167 182
168 - $filter_metadata_registration = function ( $settings, $metadata ) use ( $blocks_to_add_visibility_conditions ) {
169 - if ( in_array( $metadata['name'], $blocks_to_add_visibility_conditions, true ) && ! empty( $settings['attributes'] ) ) {
183 + /**
184 + * Block registration filter callback.
185 + *
186 + * @param array $settings Array of arguments for registering a block type.
187 + * @param string $name Block type name including namespace.
188 + * @return array
189 + */
190 + $filter_metadata_registration = function ( $settings, $name ) use ( $blocks_to_add_visibility_conditions ) {
191 + if ( in_array( $name, $blocks_to_add_visibility_conditions, true ) && ! empty( $settings['attributes'] ) ) {
170 192 $settings['attributes']['conditions'] = array(
171 193 'type' => 'object',
172 194 );
173 195 }
@@ -173,9 +195,9 @@
173 195 }
174 196 return $settings;
175 197 };
176 198
177 - add_filter( 'block_type_metadata_settings', $filter_metadata_registration, 10, 2 );
199 + add_filter( 'register_block_type_args', $filter_metadata_registration, 10, 2 );
178 200 }
179 201
180 202 /**
181 203 * Prepare the interface for editing widgets - loading css, javascript & data
@@ -645,11 +667,11 @@
645 667 * @return array Modified settings.
646 668 */
647 669 public static function widget_update( $instance, $new_instance, $old_instance ) {
648 670 $conditions = array();
649 - $conditions['action'] = isset( $new_instance['conditions']['action'] ) ? $new_instance['conditions']['action'] : null;
671 + $conditions['action'] = $new_instance['conditions']['action'] ?? null;
650 672 $conditions['match_all'] = ! empty( $new_instance['conditions']['match_all'] ) ? '1' : '0';
651 - $conditions['rules'] = isset( $new_instance['conditions']['rules'] ) ? $new_instance['conditions']['rules'] : array();
673 + $conditions['rules'] = $new_instance['conditions']['rules'] ?? array();
652 674
653 675 if ( isset( $new_instance['conditions']['rules_major'] ) ) {
654 676 foreach ( $new_instance['conditions']['rules_major'] as $index => $major_rule ) {
655 677 if ( ! $major_rule ) {
@@ -657,10 +679,10 @@
657 679 }
658 680
659 681 $conditions['rules'][] = array(
660 682 'major' => $major_rule,
661 - 'minor' => isset( $new_instance['conditions']['rules_minor'][ $index ] ) ? $new_instance['conditions']['rules_minor'][ $index ] : '',
662 - 'has_children' => isset( $new_instance['conditions']['page_children'][ $index ] ) ? true : false,
683 + 'minor' => $new_instance['conditions']['rules_minor'][ $index ] ?? '',
684 + 'has_children' => isset( $new_instance['conditions']['page_children'][ $index ] ),
663 685 );
664 686 }
665 687 }
666 688
@@ -777,8 +799,42 @@
777 799 return $rule['major'] . ':' . $rule['minor'];
778 800 }
779 801
780 802 /**
803 + * Normalize widget `content` into a string suitable for block scanning.
804 + *
805 + * @since 15.1
806 + *
807 + * @param mixed $content The widget instance 'content' value.
808 + * @return string|false Normalized string content or false if none.
809 + */
810 + private static function normalize_widget_content( $content ) {
811 + if ( empty( $content ) ) {
812 + return false;
813 + }
814 +
815 + if ( is_string( $content ) ) {
816 + return $content;
817 + }
818 +
819 + if ( ! is_array( $content ) ) {
820 + return false;
821 + }
822 +
823 + if ( isset( $content['content'] ) && is_string( $content['content'] ) ) {
824 + return $content['content'];
825 + }
826 +
827 + if ( isset( $content[0] ) && is_array( $content[0] ) && isset( $content[0]['blockName'] ) ) {
828 + // Looks like a parsed blocks array.
829 + return serialize_blocks( $content );
830 + }
831 +
832 + // Unknown array shape: treat as no visibility rules.
833 + return false;
834 + }
835 +
836 + /**
781 837 * Determine whether the widget should be displayed based on conditions set by the user.
782 838 *
783 839 * @param array $instance The widget settings.
784 840 * @return array Settings to display or bool false to hide.
@@ -785,9 +841,9 @@
785 841 */
786 842 public static function filter_widget( $instance ) {
787 843 // Don't filter widgets from the REST API when it's called via the widgets admin page - otherwise they could get
788 844 // filtered out and become impossible to edit.
789 - if ( strpos( wp_get_raw_referer(), '/wp-admin/widgets.php' ) && isset( $_SERVER['REQUEST_URI'] ) && false !== strpos( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), '/wp-json/' ) ) {
845 + if ( strpos( wp_get_raw_referer(), '/wp-admin/widgets.php' ) && isset( $_SERVER['REQUEST_URI'] ) && str_contains( filter_var( wp_unslash( $_SERVER['REQUEST_URI'] ) ), '/wp-json/' ) ) {
790 846 return $instance;
791 847 }
792 848 // WordPress.com specific check - here, referer ends in /rest-proxy/ and doesn't tell us what's requesting.
793 849 $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
@@ -803,23 +859,47 @@
803 859 if ( self::filter_widget_check_conditions( $instance['conditions'] ) ) {
804 860 return $instance;
805 861 }
806 862 return false;
807 - } elseif ( ! empty( $instance['content'] ) && has_blocks( $instance['content'] ) ) {
808 - // Block-Based widgets: We have gutenberg blocks that could have the 'conditions' attribute.
809 - $blocks = parse_blocks( $instance['content'] );
810 - if ( empty( $blocks[0]['attrs']['conditions']['rules'] ) ) {
863 + }
864 +
865 + if ( empty( $instance['content'] ) ) {
866 + return $instance;
867 + }
868 + $content = self::normalize_widget_content( $instance['content'] ?? null );
869 +
870 + if ( false === $content || ! has_blocks( $content ) ) {
871 + // No visibility found.
872 + return $instance;
873 + }
874 +
875 + $scanner = Block_Scanner::create( $content );
876 + if ( ! $scanner ) {
877 + // No Rules: Display widget.
878 + return $instance;
879 + }
880 +
881 + // Find the first block that opens
882 + while ( $scanner->next_delimiter() ) {
883 + if ( ! $scanner->opens_block() ) {
884 + continue;
885 + }
886 +
887 + $attributes = $scanner->allocate_and_return_parsed_attributes();
888 +
889 + if ( ! is_array( $attributes ) || empty( $attributes['conditions']['rules'] ) ) {
811 890 // No Rules: Display widget.
812 891 return $instance;
813 892 }
814 - if ( self::filter_widget_check_conditions( $blocks[0]['attrs']['conditions'] ) ) {
893 +
894 + if ( self::filter_widget_check_conditions( $attributes['conditions'] ) ) {
815 895 // Rules passed checks: Display widget.
816 896 return $instance;
817 897 }
898 +
818 899 // Rules failed checks: Hide widget.
819 900 return false;
820 901 }
821 -
822 902 // No visibility found.
823 903 return $instance;
824 904 }
825 905
@@ -892,11 +972,11 @@
892 972 $condition_result = is_front_page() && ! is_paged();
893 973 }
894 974 break;
895 975 default:
896 - if ( substr( $rule['minor'], 0, 10 ) === 'post_type-' ) {
976 + if ( str_starts_with( $rule['minor'], 'post_type-' ) ) {
897 977 $condition_result = is_singular( substr( $rule['minor'], 10 ) );
898 - } elseif ( substr( $rule['minor'], 0, 18 ) === 'post_type_archive-' ) {
978 + } elseif ( str_starts_with( $rule['minor'], 'post_type_archive-' ) ) {
899 979 $condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
900 980 } elseif ( get_option( 'page_for_posts' ) === $rule['minor'] ) {
901 981 // If $rule['minor'] is a page ID which is also the posts page.
902 982 $condition_result = $wp_query->is_posts_page;
@@ -958,15 +1038,17 @@
958 1038 $condition_result = ! $condition_result;
959 1039 }
960 1040 break;
961 1041 case 'author':
962 - $post = get_post();
963 1042 if ( ! $rule['minor'] && is_author() ) {
964 1043 $condition_result = true;
965 1044 } elseif ( $rule['minor'] && is_author( $rule['minor'] ) ) {
966 1045 $condition_result = true;
967 - } elseif ( is_singular() && $rule['minor'] && $rule['minor'] === $post->post_author ) {
968 - $condition_result = true;
1046 + } elseif ( is_singular() && $rule['minor'] ) {
1047 + $post = get_post();
1048 + if ( $post && $rule['minor'] === $post->post_author ) {
1049 + $condition_result = true;
1050 + }
969 1051 }
970 1052 break;
971 1053 case 'role':
972 1054 if ( is_user_logged_in() ) {
@@ -983,11 +1065,11 @@
983 1065 $condition_result = false;
984 1066 }
985 1067 break;
986 1068 case 'post_type':
987 - if ( substr( $rule['minor'], 0, 10 ) === 'post_type-' ) {
1069 + if ( str_starts_with( $rule['minor'], 'post_type-' ) ) {
988 1070 $condition_result = is_singular( substr( $rule['minor'], 10 ) );
989 - } elseif ( substr( $rule['minor'], 0, 18 ) === 'post_type_archive-' ) {
1071 + } elseif ( str_starts_with( $rule['minor'], 'post_type_archive-' ) ) {
990 1072 $condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
991 1073 }
992 1074 break;
993 1075 case 'taxonomy':
@@ -1112,12 +1194,17 @@
1112 1194 *
1113 1195 * @since 4.7.1
1114 1196 */
1115 1197 public static function migrate_post_type_rules() {
1116 - global $wp_registered_widgets;
1198 + global $wp_widget_factory, $wp_registered_widgets;
1199 + '@phan-var \WP_Widget_Factory $wp_widget_factory';
1117 1200
1118 1201 $sidebars_widgets = get_option( 'sidebars_widgets' );
1119 1202
1203 + if ( ! is_array( $sidebars_widgets ) ) {
1204 + return;
1205 + }
1206 +
1120 1207 // Going through all sidebars and through inactive and orphaned widgets.
1121 1208 foreach ( $sidebars_widgets as $sidebar ) {
1122 1209 if ( ! is_array( $sidebar ) ) {
1123 1210 continue;
@@ -1128,11 +1215,17 @@
1128 1215 if ( empty( $wp_registered_widgets[ $widget ] ) ) {
1129 1216 continue;
1130 1217 }
1131 1218
1132 - $opts = $wp_registered_widgets[ $widget ];
1133 - $instances = get_option( $opts['callback'][0]->option_name );
1219 + $id_base = wp_parse_widget_id( $widget )['id_base'];
1220 + $widget_object = $wp_widget_factory->get_widget_object( $id_base );
1134 1221
1222 + if ( ! $widget_object ) {
1223 + continue;
1224 + }
1225 +
1226 + $instances = get_option( $widget_object->option_name );
1227 +
1135 1228 if ( ! is_array( $instances ) || empty( $instances ) ) {
1136 1229 continue;
1137 1230 }
1138 1231
@@ -1139,10 +1232,10 @@
1139 1232 // Going through each instance of the widget.
1140 1233 foreach ( $instances as $number => $instance ) {
1141 1234 if (
1142 1235 ! is_array( $instance ) ||
1143 - empty( $instance['conditions'] ) ||
1144 - empty( $instance['conditions']['rules'] )
1236 + empty( $instance['conditions']['rules'] ) ||
1237 + ! is_array( $instance['conditions']['rules'] )
1145 1238 ) {
1146 1239 continue;
1147 1240 }
1148 1241
@@ -1156,11 +1249,11 @@
1156 1249
1157 1250 $rule_type = false;
1158 1251
1159 1252 // Post type or type archive rule.
1160 - if ( 0 === strpos( $rule['minor'], 'post_type_archive' ) ) {
1253 + if ( str_starts_with( $rule['minor'], 'post_type_archive' ) ) {
1161 1254 $rule_type = 'post_type_archive';
1162 - } elseif ( 0 === strpos( $rule['minor'], 'post_type' ) ) {
1255 + } elseif ( str_starts_with( $rule['minor'], 'post_type' ) ) {
1163 1256 $rule_type = 'post_type';
1164 1257 }
1165 1258
1166 1259 if ( $rule_type ) {
@@ -1172,13 +1265,12 @@
1172 1265 }
1173 1266 }
1174 1267 }
1175 1268
1176 - update_option( $opts['callback'][0]->option_name, $instances );
1269 + update_option( $widget_object->option_name, $instances );
1177 1270 }
1178 1271 }
1179 1272 }
1180 -
1181 1273 }
1182 1274
1183 1275 add_action( 'init', array( 'Jetpack_Widget_Conditions', 'init' ) );
1184 1276
@@ -1187,9 +1279,9 @@
1187 1279 global $pagenow;
1188 1280 $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
1189 1281 if ( is_customize_preview()
1190 1282 || 'widgets.php' === $pagenow
1191 - || ( false !== strpos( $current_url, '/wp-json/wp/v2/block-renderer' ) )
1283 + || str_contains( $current_url, '/wp-json/wp/v2/block-renderer' )
1192 1284 || 1 === preg_match( '~^/wp/v2/sites/\d+/block-renderer~', $current_url )
1193 1285 ) {
1194 1286 Jetpack_Widget_Conditions::add_block_attributes_filter();
1195 1287 }