PluginProbe
WebberZone Top 10 — Popular Posts / 4.5.2
WebberZone Top 10 — Popular Posts v4.5.2
4.5.2 4.5.1 4.5.0 4.4.3 4.4.2 4.4.1 4.4.0 4.3.4 4.3.3 4.3.2 4.3.1 4.3.0 trunk 1.0 1.0.1 1.1 1.2 1.3 1.4 1.4.1 1.5 1.5.1 1.5.2 1.5.3 1.6 All 118 releases
top-10 / includes / frontend / blocks / class-blocks.php

class-blocks.php in WebberZone Top 10 — Popular Posts 4.5.2, at includes/frontend/blocks/class-blocks.php

531 lines 15.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2 /**
3 * Blocks class.
4 *
5 * @package WebberZone\Top_Ten\Frontend\Blocks
6 */
7
8 namespace WebberZone\Top_Ten\Frontend\Blocks;
9
10 use WebberZone\Top_Ten\Admin\Settings;
11 use WebberZone\Top_Ten\Counter;
12 use WebberZone\Top_Ten\Frontend\Styles_Handler;
13 use WebberZone\Top_Ten\Util\Hook_Registry;
14
15 if ( ! defined( 'WPINC' ) ) {
16 die;
17 }
18
19 /**
20 * Blocks class.
21 *
22 * @since 3.3.0
23 */
24 class Blocks {
25
26 /**
27 * Request-local cache for post counts rendered by the Post Count block.
28 *
29 * @var array<string, int|string>
30 */
31 private static $post_count_cache = array();
32
33 /**
34 * Register blocks with WordPress.
35 */
36 public function __construct() {
37 Hook_Registry::add_action( 'init', array( $this, 'register_blocks' ) );
38 Hook_Registry::add_action( 'enqueue_block_editor_assets', array( $this, 'enqueue_block_editor_assets' ) );
39 Hook_Registry::add_filter( 'block_editor_rest_api_preload_paths', array( $this, 'add_custom_preload_paths' ) );
40 }
41
42 /**
43 * Add custom preload paths for the REST API.
44 *
45 * @since 4.5.0
46 *
47 * @param array $preload_paths Existing preload paths.
48 * @return array Modified preload paths.
49 */
50 public static function add_custom_preload_paths( $preload_paths ) {
51 $preload_paths[] = '/wp/v2/top-10/v1/counter';
52
53 return $preload_paths;
54 }
55
56 /**
57 * Registers the block using the metadata loaded from the `block.json` file.
58 * Behind the scenes, it registers also all assets so they can be enqueued
59 * through the block editor in the corresponding context.
60 *
61 * @since 3.1.0
62 */
63 public function register_blocks() {
64 // Define an array of blocks with their paths and optional render callbacks.
65 $blocks = array(
66 'popular-posts' => array(
67 'path' => __DIR__ . '/build/popular-posts/',
68 'render_callback' => array( $this, 'render_block_popular_posts' ),
69 ),
70 'post-count' => array(
71 'path' => __DIR__ . '/build/post-count/',
72 'render_callback' => array( $this, 'render_block_post_count' ),
73 ),
74 );
75 $default_blocks = $blocks;
76
77 /**
78 * Filters the blocks registered by the plugin.
79 *
80 * @since 4.0.0
81 *
82 * @param array $blocks Array of blocks registered by the plugin.
83 */
84 $blocks = apply_filters( 'tptn_register_blocks', $blocks );
85 $manifest = __DIR__ . '/build/blocks-manifest.php';
86
87 if ( function_exists( 'wp_register_block_types_from_metadata_collection' ) && file_exists( $manifest ) ) {
88 wp_register_block_types_from_metadata_collection( __DIR__ . '/build', $manifest );
89
90 // Register any blocks added or changed by the filter that fall outside the manifest.
91 foreach ( $blocks as $block_name => $block_data ) {
92 if ( isset( $default_blocks[ $block_name ] ) && $default_blocks[ $block_name ] === $block_data ) {
93 continue;
94 }
95
96 $args = array();
97 if ( isset( $block_data['render_callback'] ) ) {
98 $args['render_callback'] = $block_data['render_callback'];
99 }
100
101 register_block_type( $block_data['path'], $args );
102 }
103
104 return;
105 }
106
107 // Loop through each block and register it.
108 foreach ( $blocks as $block_name => $block_data ) {
109 $args = array();
110
111 // If a render callback is provided, add it to the args.
112 if ( isset( $block_data['render_callback'] ) ) {
113 $args['render_callback'] = $block_data['render_callback'];
114 }
115
116 register_block_type( $block_data['path'], $args );
117 }
118 }
119
120 /**
121 * Renders the `top-10/popular-posts` block on server.
122 *
123 * @since 3.0.0
124 * @param array $attributes The block attributes.
125 *
126 * @return string Returns the post content with popular posts added.
127 */
128 public static function render_block_popular_posts( $attributes ) {
129
130 $attributes['extra_class'] = isset( $attributes['className'] ) ? esc_attr( $attributes['className'] ) : '';
131
132 $defaults = array_merge(
133 \tptn_get_settings_with_defaults(),
134 array(
135 'is_block' => 1,
136 )
137 );
138
139 $arguments = wp_parse_args( $attributes, $defaults );
140
141 if ( isset( $attributes['other_attributes'] ) ) {
142 $arguments = wp_parse_args( $attributes['other_attributes'], $arguments );
143 }
144
145 /**
146 * Filters arguments passed to get_tptn for the block.
147 *
148 * @since 3.0.0
149 *
150 * @param array $arguments Top 10 block options array.
151 * @param array $attributes Block attributes array.
152 */
153 $arguments = apply_filters( 'tptn_block_options', $arguments, $attributes );
154
155 // Enqueue the stylesheet for the selected style for this block.
156 $style_array = Styles_Handler::get_style( $arguments['tptn_styles'] );
157
158 if ( ! empty( $style_array['name'] ) ) {
159 $style = $style_array['name'];
160 $extra_css = $style_array['extra_css'];
161 $is_rtl = is_rtl();
162 $handle = "tptn-style-{$style}";
163
164 $already_enqueued = wp_style_is( $handle, 'enqueued' );
165
166 wp_register_style(
167 $handle,
168 plugins_url( Styles_Handler::get_stylesheet_path( $style, $is_rtl ), TOP_TEN_PLUGIN_FILE ),
169 array(),
170 TOP_TEN_VERSION
171 );
172 wp_enqueue_style( $handle );
173
174 if ( ! $already_enqueued && ! empty( $extra_css ) ) {
175 wp_add_inline_style( $handle, $extra_css );
176 }
177 }
178
179 return \WebberZone\Top_Ten\Frontend\Display::pop_posts( $arguments );
180 }
181
182 /**
183 * Renders the `core/post-title` block on the server.
184 *
185 * @since 4.0.0
186 *
187 * @param array $attributes Block attributes.
188 * @param string $content Block default content.
189 * @param \WP_Block $block Block instance.
190 *
191 * @return string Returns the post count.
192 */
193 public static function render_block_post_count( $attributes, $content, $block ) {
194 if ( ! isset( $block->context['postId'] ) ) {
195 return '';
196 }
197 $output = '';
198 $args = array();
199
200 $post_id = absint( $block->context['postId'] );
201 $counter_type = isset( $attributes['counter'] ) ? $attributes['counter'] : 'total';
202 $blog_id = isset( $attributes['blogId'] ) ? absint( $attributes['blogId'] ) : get_current_blog_id();
203 $from_date = isset( $attributes['fromDate'] ) ? $attributes['fromDate'] : '';
204 $to_date = isset( $attributes['toDate'] ) ? $attributes['toDate'] : '';
205 $text_before = isset( $attributes['textBefore'] ) ? $attributes['textBefore'] : '';
206 $text_after = isset( $attributes['textAfter'] ) ? $attributes['textAfter'] : '';
207 $text_advanced = isset( $attributes['textAdvanced'] ) ? $attributes['textAdvanced'] : '';
208 $advanced_mode = isset( $attributes['advancedMode'] ) ? $attributes['advancedMode'] : false;
209 $svg_code = isset( $attributes['svgCode'] ) ? $attributes['svgCode'] : '';
210 $svg_icon_size = isset( $attributes['svgIconSize'] ) ? $attributes['svgIconSize'] : '1';
211 $svg_icon_size_unit = isset( $attributes['svgIconSizeUnit'] ) ? $attributes['svgIconSizeUnit'] : 'em';
212 $svg_padding_values = isset( $attributes['svgPaddingValues'] ) ? $attributes['svgPaddingValues'] : array( 0, 0, 0, 0 );
213 $svg_padding_units = isset( $attributes['svgPaddingUnits'] ) ? $attributes['svgPaddingUnits'] : array( 'px', 'px', 'px', 'px' );
214 $svg_icon_location = isset( $attributes['svgIconLocation'] ) ? $attributes['svgIconLocation'] : 'before';
215
216 $args['format_number'] = isset( $attributes['numberFormat'] ) ? $attributes['numberFormat'] : false;
217
218 if ( ! empty( $from_date ) ) {
219 $args['from_date'] = $from_date;
220 }
221 if ( ! empty( $to_date ) ) {
222 $args['to_date'] = $to_date;
223 }
224
225 $classes = array();
226 $classes[] = 'wp-block-tptn-post-count';
227 $classes[] = 'tptn-post-count';
228
229 if ( isset( $attributes['textAlign'] ) ) {
230 $classes[] = 'has-text-align-' . $attributes['textAlign'];
231 }
232 if ( isset( $attributes['style']['elements']['link']['color']['text'] ) ) {
233 $classes[] = 'has-link-color';
234 }
235
236 $counter = null;
237 if ( ! $advanced_mode || empty( $text_advanced ) ) {
238 $counter = self::get_cached_post_count( $post_id, $counter_type, $blog_id, $args );
239 $output = sprintf(
240 '%1$s%2$s%3$s',
241 wp_kses_post( (string) $text_before ),
242 esc_html( (string) $counter ),
243 wp_kses_post( (string) $text_after )
244 );
245 } else {
246 $classes[] = 'tptn-advanced-mode';
247 $output = $text_advanced;
248
249 foreach ( array( 'total', 'daily', 'overall' ) as $type ) {
250 if ( false !== strpos( $text_advanced, "%{$type}count%" ) ) {
251 $count = self::get_cached_post_count( $post_id, $type, $blog_id, $args );
252
253 $output = str_replace(
254 "%{$type}count%",
255 (string) $count,
256 $output
257 );
258 }
259 }
260 }
261
262 $output = sprintf(
263 '<span class="tptn-post-count-text">%s</span>',
264 $output
265 );
266
267 // Add the icon to the output.
268 $icon = '';
269 if ( ! empty( $svg_code ) ) {
270 $padding_style = sprintf(
271 'padding:%s%s %s%s %s%s %s%s;',
272 esc_attr( $svg_padding_values[0] ),
273 esc_attr( $svg_padding_units[0] ),
274 esc_attr( $svg_padding_values[1] ),
275 esc_attr( $svg_padding_units[1] ),
276 esc_attr( $svg_padding_values[2] ),
277 esc_attr( $svg_padding_units[2] ),
278 esc_attr( $svg_padding_values[3] ),
279 esc_attr( $svg_padding_units[3] )
280 );
281
282 $svg_style = sprintf(
283 'width: %1$s%2$s; height: %1$s%2$s; %3$s',
284 esc_attr( $svg_icon_size ),
285 esc_attr( $svg_icon_size_unit ),
286 $padding_style
287 );
288
289 $svg_style = esc_attr( $svg_style );
290
291 if ( preg_match( '/<svg[^>]*style="([^"]*)"/i', $svg_code, $matches ) ) {
292 $existing_style = $matches[1];
293 $new_style = $svg_style . $existing_style;
294
295 $svg_code_with_style = preg_replace( '/(<svg[^>]*style=")[^"]*(")/i', '${1}' . $new_style . '${2}', $svg_code, 1 );
296 } else {
297 $svg_code_with_style = preg_replace( '/<svg /', '<svg style="' . $svg_style . '" ', $svg_code, 1 );
298 }
299
300 $icon = sprintf(
301 '<span class="tptn-post-count-icon">%1$s</span>',
302 wp_kses( $svg_code_with_style, self::get_allowed_svg_tags() )
303 );
304 }
305 if ( ! empty( $icon ) ) {
306 if ( 'before' === $svg_icon_location ) {
307 $output = $icon . $output;
308 } else {
309 $output = $output . $icon;
310 }
311 }
312
313 $wrapper_attributes = get_block_wrapper_attributes( array( 'class' => implode( ' ', $classes ) ) );
314
315 return sprintf(
316 '<span %1$s>%2$s</span>',
317 $wrapper_attributes,
318 wp_kses( $output, self::get_allowed_svg_tags() )
319 );
320 }
321
322 /**
323 * Get a request-cached post count.
324 *
325 * @param int $post_id Post ID.
326 * @param string $counter Counter type.
327 * @param int $blog_id Blog ID.
328 * @param array $args Additional counter arguments.
329 * @return int|string Post count.
330 */
331 private static function get_cached_post_count( $post_id, $counter, $blog_id, $args ) {
332 $cache_key = md5( (string) wp_json_encode( array( $post_id, $counter, $blog_id, $args ) ) );
333
334 if ( ! array_key_exists( $cache_key, self::$post_count_cache ) ) {
335 self::$post_count_cache[ $cache_key ] = Counter::get_post_count_only( $post_id, $counter, $blog_id, $args );
336 }
337
338 return self::$post_count_cache[ $cache_key ];
339 }
340
341 /**
342 * Get allowed SVG tags for wp_kses.
343 *
344 * @since 4.0.0
345 *
346 * @return array Allowed SVG tags and attributes.
347 */
348 private static function get_allowed_svg_tags() {
349 $allowed_tags = wp_kses_allowed_html( 'post' );
350
351 $svg_tags = array(
352 'svg' => array(
353 'class' => true,
354 'aria-hidden' => true,
355 'aria-labelledby' => true,
356 'role' => true,
357 'xmlns' => true,
358 'width' => true,
359 'height' => true,
360 'fill' => true,
361 'id' => true,
362 'data-name' => true,
363 'viewbox' => true,
364 'style' => true,
365 ),
366 'g' => array(
367 'fill' => true,
368 'stroke-width' => true,
369 'stroke-linecap' => true,
370 'stroke-linejoin' => true,
371 'id' => true,
372 ),
373 'defs' => array(),
374 'style' => array(
375 'type' => true,
376 ),
377 'line' => array(
378 'class' => true,
379 'x1' => true,
380 'y1' => true,
381 'x2' => true,
382 'y2' => true,
383 'fill' => true,
384 'stroke' => true,
385 'stroke-width' => true,
386 'stroke-miterlimit' => true,
387 ),
388 'path' => array(
389 'class' => true,
390 'd' => true,
391 'fill' => true,
392 'stroke' => true,
393 'stroke-linecap' => true,
394 'stroke-linejoin' => true,
395 'stroke-width' => true,
396 ),
397 'polygon' => array(
398 'class' => true,
399 'points' => true,
400 'fill' => true,
401 'stroke' => true,
402 'stroke-width' => true,
403 'stroke-linecap' => true,
404 'stroke-linejoin' => true,
405 'stroke-miterlimit' => true,
406 ),
407 'polyline' => array(
408 'class' => true,
409 'points' => true,
410 'fill' => true,
411 'stroke' => true,
412 'stroke-width' => true,
413 'stroke-linecap' => true,
414 'stroke-linejoin' => true,
415 'stroke-miterlimit' => true,
416 ),
417 'circle' => array(
418 'class' => true,
419 'cx' => true,
420 'cy' => true,
421 'r' => true,
422 'fill' => true,
423 'stroke' => true,
424 'stroke-width' => true,
425 ),
426 'rect' => array(
427 'class' => true,
428 'x' => true,
429 'y' => true,
430 'width' => true,
431 'height' => true,
432 'rx' => true,
433 'ry' => true,
434 'fill' => true,
435 'stroke' => true,
436 'stroke-width' => true,
437 ),
438 'ellipse' => array(
439 'class' => true,
440 'cx' => true,
441 'cy' => true,
442 'rx' => true,
443 'ry' => true,
444 'fill' => true,
445 'stroke' => true,
446 'stroke-width' => true,
447 ),
448 'use' => array(
449 'class' => true,
450 'xlink:href' => true,
451 ),
452 'text' => array(
453 'class' => true,
454 'x' => true,
455 'y' => true,
456 'dy' => true,
457 'text-anchor' => true,
458 'fill' => true,
459 ),
460 'tspan' => array(
461 'class' => true,
462 'x' => true,
463 'y' => true,
464 'dy' => true,
465 'text-anchor' => true,
466 'fill' => true,
467 ),
468 'symbol' => array(
469 'id' => true,
470 'viewBox' => true,
471 'preserveAspectRatio' => true,
472 ),
473 'clipPath' => array(
474 'id' => true,
475 ),
476 'mask' => array(
477 'id' => true,
478 ),
479 'image' => array(
480 'x' => true,
481 'y' => true,
482 'width' => true,
483 'height' => true,
484 'xlink:href' => true,
485 ),
486 'foreignObject' => array(
487 'x' => true,
488 'y' => true,
489 'width' => true,
490 'height' => true,
491 ),
492 );
493
494 return array_merge( $allowed_tags, $svg_tags );
495 }
496
497 /**
498 * Enqueue scripts and styles for the block editor.
499 *
500 * @since 3.1.0
501 */
502 public static function enqueue_block_editor_assets() {
503
504 $styles = Settings::get_styles();
505 $file_prefix = ( defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ) ? '' : '.min';
506
507 foreach ( $styles as $style ) {
508
509 $style_array = Styles_Handler::get_style( $style['id'] );
510
511 if ( ! empty( $style_array['name'] ) ) {
512 $style = $style_array['name'];
513 $extra_css = $style_array['extra_css'];
514
515 $base_path = 'includes/frontend/css/';
516 if ( false !== strpos( $style, '-pro' ) ) {
517 $base_path = 'includes/pro/frontend/css/';
518 }
519
520 wp_enqueue_style(
521 "popular-posts-block-editor-{$style}",
522 plugins_url( "{$base_path}{$style}{$file_prefix}.css", TOP_TEN_PLUGIN_FILE ),
523 array( 'wp-edit-blocks' ),
524 TOP_TEN_VERSION
525 );
526 wp_add_inline_style( "popular-posts-block-editor-{$style}", $extra_css );
527 }
528 }
529 }
530 }
531