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 +114 -32 12.5.2 → 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 }
@@ -174,8 +179,15 @@
174 179 * @param string[] $blocks Array of block names from WordPress core and WooCommerce.
175 180 */
176 181 $blocks_to_add_visibility_conditions = apply_filters( 'jetpack_widget_visibility_server_side_render_blocks', $blocks );
177 182
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 + */
178 190 $filter_metadata_registration = function ( $settings, $name ) use ( $blocks_to_add_visibility_conditions ) {
179 191 if ( in_array( $name, $blocks_to_add_visibility_conditions, true ) && ! empty( $settings['attributes'] ) ) {
180 192 $settings['attributes']['conditions'] = array(
181 193 'type' => 'object',
@@ -655,11 +667,11 @@
655 667 * @return array Modified settings.
656 668 */
657 669 public static function widget_update( $instance, $new_instance, $old_instance ) {
658 670 $conditions = array();
659 - $conditions['action'] = isset( $new_instance['conditions']['action'] ) ? $new_instance['conditions']['action'] : null;
671 + $conditions['action'] = $new_instance['conditions']['action'] ?? null;
660 672 $conditions['match_all'] = ! empty( $new_instance['conditions']['match_all'] ) ? '1' : '0';
661 - $conditions['rules'] = isset( $new_instance['conditions']['rules'] ) ? $new_instance['conditions']['rules'] : array();
673 + $conditions['rules'] = $new_instance['conditions']['rules'] ?? array();
662 674
663 675 if ( isset( $new_instance['conditions']['rules_major'] ) ) {
664 676 foreach ( $new_instance['conditions']['rules_major'] as $index => $major_rule ) {
665 677 if ( ! $major_rule ) {
@@ -667,10 +679,10 @@
667 679 }
668 680
669 681 $conditions['rules'][] = array(
670 682 'major' => $major_rule,
671 - 'minor' => isset( $new_instance['conditions']['rules_minor'][ $index ] ) ? $new_instance['conditions']['rules_minor'][ $index ] : '',
672 - '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 ] ),
673 685 );
674 686 }
675 687 }
676 688
@@ -787,8 +799,42 @@
787 799 return $rule['major'] . ':' . $rule['minor'];
788 800 }
789 801
790 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 + /**
791 837 * Determine whether the widget should be displayed based on conditions set by the user.
792 838 *
793 839 * @param array $instance The widget settings.
794 840 * @return array Settings to display or bool false to hide.
@@ -795,9 +841,9 @@
795 841 */
796 842 public static function filter_widget( $instance ) {
797 843 // Don't filter widgets from the REST API when it's called via the widgets admin page - otherwise they could get
798 844 // filtered out and become impossible to edit.
799 - 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/' ) ) {
800 846 return $instance;
801 847 }
802 848 // WordPress.com specific check - here, referer ends in /rest-proxy/ and doesn't tell us what's requesting.
803 849 $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
@@ -813,23 +859,47 @@
813 859 if ( self::filter_widget_check_conditions( $instance['conditions'] ) ) {
814 860 return $instance;
815 861 }
816 862 return false;
817 - } elseif ( ! empty( $instance['content'] ) && has_blocks( $instance['content'] ) ) {
818 - // Block-Based widgets: We have gutenberg blocks that could have the 'conditions' attribute.
819 - $blocks = parse_blocks( $instance['content'] );
820 - 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'] ) ) {
821 890 // No Rules: Display widget.
822 891 return $instance;
823 892 }
824 - if ( self::filter_widget_check_conditions( $blocks[0]['attrs']['conditions'] ) ) {
893 +
894 + if ( self::filter_widget_check_conditions( $attributes['conditions'] ) ) {
825 895 // Rules passed checks: Display widget.
826 896 return $instance;
827 897 }
898 +
828 899 // Rules failed checks: Hide widget.
829 900 return false;
830 901 }
831 -
832 902 // No visibility found.
833 903 return $instance;
834 904 }
835 905
@@ -902,11 +972,11 @@
902 972 $condition_result = is_front_page() && ! is_paged();
903 973 }
904 974 break;
905 975 default:
906 - if ( substr( $rule['minor'], 0, 10 ) === 'post_type-' ) {
976 + if ( str_starts_with( $rule['minor'], 'post_type-' ) ) {
907 977 $condition_result = is_singular( substr( $rule['minor'], 10 ) );
908 - } elseif ( substr( $rule['minor'], 0, 18 ) === 'post_type_archive-' ) {
978 + } elseif ( str_starts_with( $rule['minor'], 'post_type_archive-' ) ) {
909 979 $condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
910 980 } elseif ( get_option( 'page_for_posts' ) === $rule['minor'] ) {
911 981 // If $rule['minor'] is a page ID which is also the posts page.
912 982 $condition_result = $wp_query->is_posts_page;
@@ -968,15 +1038,17 @@
968 1038 $condition_result = ! $condition_result;
969 1039 }
970 1040 break;
971 1041 case 'author':
972 - $post = get_post();
973 1042 if ( ! $rule['minor'] && is_author() ) {
974 1043 $condition_result = true;
975 1044 } elseif ( $rule['minor'] && is_author( $rule['minor'] ) ) {
976 1045 $condition_result = true;
977 - } elseif ( is_singular() && $rule['minor'] && $rule['minor'] === $post->post_author ) {
978 - $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 + }
979 1051 }
980 1052 break;
981 1053 case 'role':
982 1054 if ( is_user_logged_in() ) {
@@ -993,11 +1065,11 @@
993 1065 $condition_result = false;
994 1066 }
995 1067 break;
996 1068 case 'post_type':
997 - if ( substr( $rule['minor'], 0, 10 ) === 'post_type-' ) {
1069 + if ( str_starts_with( $rule['minor'], 'post_type-' ) ) {
998 1070 $condition_result = is_singular( substr( $rule['minor'], 10 ) );
999 - } elseif ( substr( $rule['minor'], 0, 18 ) === 'post_type_archive-' ) {
1071 + } elseif ( str_starts_with( $rule['minor'], 'post_type_archive-' ) ) {
1000 1072 $condition_result = is_post_type_archive( substr( $rule['minor'], 18 ) );
1001 1073 }
1002 1074 break;
1003 1075 case 'taxonomy':
@@ -1122,12 +1194,17 @@
1122 1194 *
1123 1195 * @since 4.7.1
1124 1196 */
1125 1197 public static function migrate_post_type_rules() {
1126 - global $wp_registered_widgets;
1198 + global $wp_widget_factory, $wp_registered_widgets;
1199 + '@phan-var \WP_Widget_Factory $wp_widget_factory';
1127 1200
1128 1201 $sidebars_widgets = get_option( 'sidebars_widgets' );
1129 1202
1203 + if ( ! is_array( $sidebars_widgets ) ) {
1204 + return;
1205 + }
1206 +
1130 1207 // Going through all sidebars and through inactive and orphaned widgets.
1131 1208 foreach ( $sidebars_widgets as $sidebar ) {
1132 1209 if ( ! is_array( $sidebar ) ) {
1133 1210 continue;
@@ -1138,11 +1215,17 @@
1138 1215 if ( empty( $wp_registered_widgets[ $widget ] ) ) {
1139 1216 continue;
1140 1217 }
1141 1218
1142 - $opts = $wp_registered_widgets[ $widget ];
1143 - $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 );
1144 1221
1222 + if ( ! $widget_object ) {
1223 + continue;
1224 + }
1225 +
1226 + $instances = get_option( $widget_object->option_name );
1227 +
1145 1228 if ( ! is_array( $instances ) || empty( $instances ) ) {
1146 1229 continue;
1147 1230 }
1148 1231
@@ -1149,10 +1232,10 @@
1149 1232 // Going through each instance of the widget.
1150 1233 foreach ( $instances as $number => $instance ) {
1151 1234 if (
1152 1235 ! is_array( $instance ) ||
1153 - empty( $instance['conditions'] ) ||
1154 - empty( $instance['conditions']['rules'] )
1236 + empty( $instance['conditions']['rules'] ) ||
1237 + ! is_array( $instance['conditions']['rules'] )
1155 1238 ) {
1156 1239 continue;
1157 1240 }
1158 1241
@@ -1166,11 +1249,11 @@
1166 1249
1167 1250 $rule_type = false;
1168 1251
1169 1252 // Post type or type archive rule.
1170 - if ( 0 === strpos( $rule['minor'], 'post_type_archive' ) ) {
1253 + if ( str_starts_with( $rule['minor'], 'post_type_archive' ) ) {
1171 1254 $rule_type = 'post_type_archive';
1172 - } elseif ( 0 === strpos( $rule['minor'], 'post_type' ) ) {
1255 + } elseif ( str_starts_with( $rule['minor'], 'post_type' ) ) {
1173 1256 $rule_type = 'post_type';
1174 1257 }
1175 1258
1176 1259 if ( $rule_type ) {
@@ -1182,13 +1265,12 @@
1182 1265 }
1183 1266 }
1184 1267 }
1185 1268
1186 - update_option( $opts['callback'][0]->option_name, $instances );
1269 + update_option( $widget_object->option_name, $instances );
1187 1270 }
1188 1271 }
1189 1272 }
1190 -
1191 1273 }
1192 1274
1193 1275 add_action( 'init', array( 'Jetpack_Widget_Conditions', 'init' ) );
1194 1276
@@ -1197,9 +1279,9 @@
1197 1279 global $pagenow;
1198 1280 $current_url = ! empty( $_SERVER['REQUEST_URI'] ) ? esc_url_raw( wp_unslash( $_SERVER['REQUEST_URI'] ) ) : '';
1199 1281 if ( is_customize_preview()
1200 1282 || 'widgets.php' === $pagenow
1201 - || ( false !== strpos( $current_url, '/wp-json/wp/v2/block-renderer' ) )
1283 + || str_contains( $current_url, '/wp-json/wp/v2/block-renderer' )
1202 1284 || 1 === preg_match( '~^/wp/v2/sites/\d+/block-renderer~', $current_url )
1203 1285 ) {
1204 1286 Jetpack_Widget_Conditions::add_block_attributes_filter();
1205 1287 }