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

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