PluginProbe
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More / 2.0.4
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More v2.0.4
trunk 1.0.0 1.0.1 1.0.2 1.0.3 1.1.0 1.1.1 1.1.10 1.1.2 1.1.3 1.1.4 1.1.5 1.1.6 1.1.7 1.1.8 2.0.0 2.0.1 2.0.10 2.0.11 2.0.12 2.0.2 2.0.3 2.0.4 2.0.5 2.0.6 All 47 releases
content-control / classes / Controllers / Frontend / Blocks.php

Blocks.php in Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More 2.0.4, at classes/Controllers/Frontend/Blocks.php

358 lines 9.7 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Frontend general setup.
4 *
5 * @copyright (c) 2023, Code Atlantic LLC.
6 * @package ContentControl
7 */
8
9 namespace ContentControl\Controllers\Frontend;
10
11 use ContentControl\Base\Controller;
12
13 use function ContentControl\protection_is_disabled;
14
15 defined( 'ABSPATH' ) || exit;
16
17 /**
18 * Class Frontend
19 */
20 class Blocks extends Controller {
21
22 /**
23 * Initialize Hooks & Filters
24 */
25 public function init() {
26 if ( is_admin() ) {
27 return;
28 }
29
30 add_action( 'wp_loaded', [ $this, 'register_block_attributes' ], 100 );
31 add_filter( 'pre_render_block', [ $this, 'pre_render_block' ], 10, 3 );
32 add_filter( 'render_block', [ $this, 'render_block' ], 10, 2 );
33 add_filter( 'content_control/should_hide_block', [ $this, 'block_user_rules' ], 10, 2 );
34 add_action( 'wp_print_styles', [ $this, 'print_block_styles' ] );
35 }
36
37 /**
38 * Adds custom attributes to allowed block attributes.
39 */
40 public function register_block_attributes() {
41 $registered_blocks = \WP_Block_Type_Registry::get_instance()->get_all_registered();
42
43 foreach ( $registered_blocks as $name => $block ) {
44 $block->attributes['contentControls'] = [
45 'type' => 'object',
46 'default' => [
47 'enabled' => false,
48 'rules' => [],
49 ],
50 ];
51 }
52 }
53
54 /**
55 * Check if block has controls enabled.
56 *
57 * @param array $block Block to be checked.
58 * @return boolean Whether the block has Controls enabled.
59 */
60 public function has_block_controls( $block ) {
61 if ( ! isset( $block['attrs']['contentControls'] ) ) {
62 return false;
63 }
64
65 $controls = wp_parse_args( $block['attrs']['contentControls'], [
66 'enabled' => false,
67 ] );
68
69 return ! ! $controls['enabled'];
70 }
71
72 /**
73 * Get blocks controls if enabled.
74 *
75 * @param array $block Block to get controls from.
76 * @return array|null Controls if enabled.
77 */
78 public function get_block_controls( $block ) {
79 if ( ! $this->has_block_controls( $block ) ) {
80 return null;
81 }
82
83 return wp_parse_args( $block['attrs']['contentControls'], [
84 'enabled' => false,
85 'rules' => [],
86 ] );
87 }
88
89 /**
90 * Short curcuit block rendering for hidden blocks.
91 *
92 * @param string|null $pre_render The pre-rendered content. Default null.
93 * @param array $parsed_block The block being rendered.
94 * @param WP_Block|null $parent_block If this is a nested block, a reference to the parent block.
95 *
96 * @return string|null
97 */
98 public function pre_render_block( $pre_render, $parsed_block, $parent_block ) {
99 if ( 'core/navigation' === $parsed_block['blockName'] ) {
100 $nav_menu_ref = ! empty( $parsed_block['attrs']['ref'] ) ? $parsed_block['attrs']['ref'] : 0;
101
102 if ( $nav_menu_ref ) {
103 $navigation_post = get_post( $nav_menu_ref );
104 $parsed_blocks = parse_blocks( $navigation_post->post_content );
105 }
106
107 // TODO for some reason, controls applied to core/navigation-link are not being saved, or not appearing in attrs.
108 }
109
110 if ( ! isset( $parsed_block['attrs']['contentControls'] ) ) {
111 return $pre_render;
112 }
113
114 if ( protection_is_disabled() ) {
115 return $pre_render;
116 }
117
118 $controls = wp_parse_args( $parsed_block['attrs']['contentControls'], [
119 'enabled' => false,
120 'rules' => [],
121 ] );
122
123 if ( ! $controls['enabled'] ) {
124 return $pre_render;
125 }
126
127 /**
128 * Filter whether to hide the block.
129 *
130 * @param bool $should_hide Whether the block should be hidden.
131 * @param array $rules Rules to check.
132 * @param array $parsed_block The block being rendered.
133 * @return bool
134 */
135 $should_hide = apply_filters(
136 'content_control/should_hide_block',
137 false,
138 $controls['rules'],
139 $parsed_block
140 );
141
142 return $should_hide ? '' : $pre_render;
143 }
144
145 /**
146 * Check block rules to see if it should be hidden from user.
147 *
148 * @param bool $should_hide Whether the block should be hidden.
149 * @param array $rules Rules to check.
150 * @return bool
151 */
152 public function block_user_rules( $should_hide, $rules ) {
153 $rules = wp_parse_args( $rules, [
154 'user' => null,
155 ] );
156
157 if ( null === $rules['user'] || true === $should_hide ) {
158 return $should_hide;
159 }
160
161 $user_status = ! empty( $rules['user']['userStatus'] ) ? $rules['user']['userStatus'] : false;
162 $role_match = ! empty( $rules['user']['roleMatch'] ) ? $rules['user']['roleMatch'] : 'any';
163 $user_roles = ! empty( $rules['user']['userRoles'] ) ? $rules['user']['userRoles'] : [];
164
165 if ( ! \ContentControl\user_meets_requirements( $user_status, $user_roles, $role_match ) ) {
166 return true;
167 }
168
169 return $should_hide;
170 }
171
172 /**
173 * Get any classes to be added to the outer block element.
174 *
175 * @param array $block Block to get classes for.
176 * @return null|array
177 */
178 public function get_block_control_classes( $block ) {
179 if ( ! $this->has_block_controls( $block ) ) {
180 return null;
181 }
182
183 $classes = [];
184
185 $controls = $this->get_block_controls( $block );
186
187 if ( isset( $controls['rules']['device'] ) ) {
188 $device_rules = wp_parse_args( $controls['rules']['device'], [
189 'hideOn' => [],
190 ] );
191
192 $hide_on = wp_parse_args( $device_rules['hideOn'], [
193 'mobile' => null,
194 'tablet' => null,
195 'desktop' => null,
196 ] );
197
198 foreach ( $hide_on as $device => $hidden ) {
199 if ( $hidden ) {
200 $classes[] = 'cc-hide-on-' . esc_attr( $device );
201 }
202 }
203 }
204
205 /**
206 * Filter the classes to be added to the block.
207 *
208 * @param array $classes Classes to be added.
209 * @param array $block Block to get classes for.
210 *
211 * @return string[]
212 */
213 $classes = apply_filters( 'content_control/get_block_control_classes', $classes, $block );
214
215 return array_unique( $classes );
216 }
217
218 /**
219 * Filter the block attributes, primarily to add classes and control visibility.
220 *
221 * References: https://github.com/WordPress/gutenberg/search?l=PHP&q=%27render_block%27
222 * - https://github.com/WordPress/gutenberg/blob/9aab0c4f60c78d19aae0af3351a2b66f8fa4c162/lib/block-supports/layout.php#L317
223 * - https://github.com/WordPress/gutenberg/blob/e776b4f00f690ce9cf21c027ebf5e7442420d716/lib/block-supports/duotone.php#L503
224 * - https://github.com/WordPress/gutenberg/blob/9aab0c4f60c78d19aae0af3351a2b66f8fa4c162/lib/block-supports/elements.php#L54-L72
225 *
226 * @param string $block_content Blocks rendered html.
227 * @param array $block Array of block properties.
228 *
229 * @return string
230 */
231 public function render_block( $block_content, $block ) {
232 if ( ! $this->has_block_controls( $block ) ) {
233 return $block_content;
234 }
235
236 $classes = $this->get_block_control_classes( $block );
237
238 if ( empty( $classes ) ) {
239 return $block_content;
240 }
241
242 // Enqueue the styles.
243 // wp_enqueue_style( 'content-control-block-styles' );.
244
245 $class_name = implode( ' ', $classes );
246
247 /** Mimicing WP Cores usage in https://github.com/WordPress/wordpress-develop/blob/trunk/src/wp-includes/block-supports/elements.php#L32 */
248 $html_element_matches = [];
249 preg_match( '/<[^>]+>/', $block_content, $html_element_matches, PREG_OFFSET_CAPTURE );
250 $first_element = $html_element_matches[0][0];
251
252 // If the first HTML element has a class attribute just add the new class
253 // as we do on layout and duotone.
254 if ( strpos( $first_element, 'class="' ) !== false || strpos( $first_element, "class='" ) !== false ) {
255 $content = preg_replace(
256 // Matches $1 & $3 is ' or ", $2 are the existing classes.
257 '/class=([\'"])([a-z0-9-_ ]*)([\'"])/',
258 'class=$1$2 ' . $class_name . '$3',
259 $block_content,
260 1
261 );
262 } else {
263 // If the first HTML element has no class attribute we should inject the attribute before the attribute at the end.
264 $first_element_offset = $html_element_matches[0][1];
265 $content = substr_replace( $block_content, ' class="' . $class_name . '"', $first_element_offset + strlen( $first_element ) - 1, 0 );
266 }
267
268 return $content;
269 }
270
271 /**
272 * Print the styles for the block controls.
273 *
274 * @return void
275 */
276 public function print_block_styles() {
277 $media_queries = $this->container->get_option( 'mediaQueries' );
278
279 if ( ! $media_queries ) {
280 ?>
281 <style id="content-control-block-styles">
282 @media (max-width: 480px) {
283 .cc-hide-on-mobile {
284 display: none !important;
285 }
286 }
287 @media (min-width: 481px) and (max-width: 991px) {
288 .cc-hide-on-tablet {
289 display: none !important;
290 }
291 }
292 @media (min-width: 992px) {
293 .cc-hide-on-desktop {
294 display: none !important;
295 }
296 }
297 </style>
298 <?php
299 return;
300 }
301
302 $mobile_breakpoint = isset( $media_queries['mobile'] ) ? $media_queries['mobile']['breakpoint'] : 640;
303 $tablet_breakpoint = isset( $media_queries['tablet'] ) ? $media_queries['tablet']['breakpoint'] : 920;
304 $desktop_breakpoint = isset( $media_queries['desktop'] ) ? $media_queries['desktop']['breakpoint'] : 1440;
305
306 $tablet_start = $mobile_breakpoint + 1;
307 $desktop_start = $tablet_breakpoint + 1;
308
309 $styles[] = <<<CSS
310 @media (max-width: {$mobile_breakpoint}px) {
311 .cc-hide-on-mobile {
312 display: none !important;
313 }
314 }
315 CSS;
316
317 $styles[] = <<<CSS
318 @media (min-width: {$tablet_start}px) and (max-width: {$tablet_breakpoint}px) {
319 .cc-hide-on-tablet {
320 display: none !important;
321 }
322 }
323 CSS;
324
325 $styles[] = <<<CSS
326 @media (min-width: {$desktop_start}px) and (max-width: {$desktop_breakpoint}px) {
327 .cc-hide-on-desktop {
328 display: none !important;
329 }
330 }
331 CSS;
332
333 unset( $media_queries['mobile'], $media_queries['tablet'], $media_queries['desktop'] );
334
335 foreach ( $media_queries as $media_query => $media_query_settings ) {
336 $breakpoint = $media_query_settings['breakpoint'];
337
338 $style = <<<CSS
339 @media (min-width: {$breakpoint}px) {
340 .cc-hide-on-{$media_query} {
341 display: none !important;
342 }
343 }
344 CSS;
345
346 $styles[] = apply_filters( 'content_control/block_styles', $style, $media_query, $breakpoint );
347 }
348
349 $styles = implode( "\n", $styles );
350
351 ?>
352 <style id="content-control-block-styles">
353 <?php echo $styles; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>
354 </style>
355 <?php
356 }
357 }
358