PluginProbe
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More / 2.6.1
Content Control – The Ultimate Content Restriction Plugin! Restrict Content, Create Conditional Blocks & More v2.6.1
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.6.1, at classes/Controllers/Frontend/Blocks.php

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