PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 1.8.0
GenerateBlocks v1.8.0
trunk 1.0 1.0.1 1.0.2 1.1.0 1.1.1 1.1.2 1.2.0 1.3.0 1.3.1 1.3.2 1.3.3 1.3.4 1.3.5 1.4.0 1.4.1 1.4.2 1.4.3 1.4.4 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.6.0 1.7.0 1.7.1 1.7.2 1.7.3 1.8.0 1.8.1 1.8.2 1.8.3 1.9.0 1.9.1 2.0.0 2.0.1 2.0.2 2.1.0 2.1.1 2.1.2 2.2.0 2.2.1 2.3.0
generateblocks / includes / class-dynamic-content.php
generateblocks / includes Last commit date
blocks 2 years ago class-do-css.php 2 years ago class-dynamic-content.php 2 years ago class-enqueue-css.php 3 years ago class-legacy-attributes.php 4 years ago class-map-deprecated-attributes.php 2 years ago class-plugin-update.php 5 years ago class-query-loop.php 3 years ago class-render-blocks.php 3 years ago class-rest.php 2 years ago class-settings.php 4 years ago dashboard.php 3 years ago defaults.php 2 years ago functions.php 2 years ago general.php 2 years ago
class-dynamic-content.php
1157 lines
1 <?php
2 /**
3 * Handles option changes on plugin updates.
4 *
5 * @package GenerateBlocks
6 */
7
8 if ( ! defined( 'ABSPATH' ) ) {
9 exit; // Exit if accessed directly.
10 }
11
12 /**
13 * Process option updates if necessary.
14 */
15 class GenerateBlocks_Dynamic_Content {
16 /**
17 * Class instance.
18 *
19 * @access private
20 * @var $instance Class instance.
21 */
22 private static $instance;
23
24 /**
25 * For the excerpt we need to keep track of ids to prevent infinite loops.
26 *
27 * @var array $source_ids The current post id.
28 */
29 private static $source_ids = [];
30
31 /**
32 * Initiator
33 */
34 public static function get_instance() {
35 if ( ! isset( self::$instance ) ) {
36 self::$instance = new self();
37 }
38 return self::$instance;
39 }
40
41 /**
42 * Constructor.
43 */
44 public function __construct() {
45 add_filter( 'generateblocks_defaults', array( $this, 'add_block_defaults' ) );
46 add_filter( 'generateblocks_background_image_url', array( $this, 'set_dynamic_background_image' ), 10, 2 );
47 add_filter( 'generateblocks_button_count', array( $this, 'update_button_count' ), 10, 3 );
48 }
49
50 /**
51 * Get the requested dynamic content.
52 *
53 * @param array $attributes The block attributes.
54 * @param WP_Block $block Block instance.
55 */
56 public static function get_content( $attributes, $block ) {
57 $content = '';
58
59 switch ( $attributes['dynamicContentType'] ) {
60 case 'post-title':
61 $content = self::get_post_title( $attributes );
62 break;
63
64 case 'post-excerpt':
65 $content = self::get_post_excerpt( $attributes );
66 // Once we have the excerpt content we are safe to clear the source ids.
67 // By doing so we avoid empty content for subsequent calls.
68 self::$source_ids = [];
69 break;
70
71 case 'post-date':
72 $content = self::get_post_date( $attributes );
73 break;
74
75 case 'post-meta':
76 $content = self::get_post_meta( $attributes );
77 break;
78
79 case 'comments-number':
80 $content = self::get_comments_number( $attributes );
81 break;
82
83 case 'terms':
84 $content = self::get_terms( $attributes );
85 break;
86
87 case 'author-meta':
88 $content = self::get_author_meta( $attributes );
89 break;
90
91 case 'author-email':
92 $content = self::get_user_data( self::get_source_author_id( $attributes ), 'user_email' );
93 break;
94
95 case 'author-name':
96 $content = self::get_user_data( self::get_source_author_id( $attributes ), 'display_name' );
97 break;
98
99 case 'author-nickname':
100 $content = self::get_user_data( self::get_source_author_id( $attributes ), 'nickname' );
101 break;
102
103 case 'author-first-name':
104 $content = self::get_user_data( self::get_source_author_id( $attributes ), 'first_name' );
105 break;
106
107 case 'author-last-name':
108 $content = self::get_user_data( self::get_source_author_id( $attributes ), 'last_name' );
109 break;
110
111 case 'pagination-numbers':
112 $content = self::get_paginate_links( $attributes, $block );
113 break;
114
115 case 'featured-image':
116 $content = self::get_dynamic_image( $attributes, $block );
117 break;
118
119 case 'caption':
120 $content = self::get_image_caption( $attributes, $block );
121 break;
122
123 case 'alt-text':
124 $content = self::get_image_alt_text( $attributes, $block );
125 break;
126
127 case 'image-description':
128 $content = self::get_image_description( $attributes, $block );
129 break;
130 }
131
132 return apply_filters(
133 'generateblocks_dynamic_content_output',
134 $content,
135 $attributes,
136 $block
137 );
138 }
139
140 /**
141 * Get the requested post title.
142 *
143 * @param array $attributes The block attributes.
144 */
145 public static function get_post_title( $attributes ) {
146 return get_the_title( self::get_source_id( $attributes ) );
147 }
148
149 /**
150 * Get the post excerpt
151 *
152 * @param array $attributes The block attributes.
153 *
154 * @return string
155 */
156 public static function get_post_excerpt( $attributes ) {
157 $source_id = self::get_source_id( $attributes );
158 $unique_id = $attributes['uniqueId'];
159
160 // This prevents endless loops by not rendering excerpts within themselves.
161 if ( ! $source_id || ( isset( self::$source_ids[ $unique_id ] ) && $source_id === self::$source_ids[ $unique_id ] ) ) {
162 return '';
163 }
164
165 self::$source_ids[ $unique_id ] = $source_id;
166
167 $filter_excerpt_length = function( $length ) use ( $attributes ) {
168 return isset( $attributes['excerptLength'] ) ? $attributes['excerptLength'] : $length;
169 };
170
171 add_filter(
172 'excerpt_length',
173 $filter_excerpt_length,
174 100
175 );
176
177 if ( isset( $attributes['useDefaultMoreLink'] ) && ! $attributes['useDefaultMoreLink'] ) {
178 $filter_more_text = function() use ( $attributes ) {
179 if ( empty( $attributes['customMoreLinkText'] ) ) {
180 return ' ...';
181 }
182
183 return apply_filters(
184 'generateblocks_dynamic_excerpt_more_link',
185 sprintf(
186 ' ... <a class="gb-dynamic-read-more" href="%1$s" aria-label="%3$s">%2$s</a>',
187 esc_url( get_permalink( get_the_ID() ) ),
188 wp_kses_post( $attributes['customMoreLinkText'] ),
189 sprintf(
190 /* translators: Aria-label describing the read more button */
191 _x( 'More on %s', 'more on post title', 'gp-premium' ),
192 the_title_attribute( 'echo=0' )
193 )
194 )
195 );
196 };
197
198 add_filter(
199 'excerpt_more',
200 $filter_more_text,
201 100
202 );
203 }
204
205 $excerpt = get_the_excerpt( $source_id );
206
207 if ( isset( $filter_excerpt_length ) ) {
208 remove_filter(
209 'excerpt_length',
210 $filter_excerpt_length,
211 100
212 );
213 }
214
215 if ( isset( $filter_more_text ) ) {
216 remove_filter(
217 'excerpt_more',
218 $filter_more_text,
219 100
220 );
221 }
222
223 return $excerpt;
224 }
225
226 /**
227 * Get the requested post date.
228 *
229 * @param array $attributes The block attributes.
230 */
231 public static function get_post_date( $attributes ) {
232 $id = self::get_source_id( $attributes );
233
234 if ( ! $id ) {
235 return;
236 }
237
238 $updated_time = get_the_modified_time( 'U', $id );
239 $published_time = get_the_time( 'U', $id ) + 1800;
240
241 $post_date = sprintf(
242 '<time class="entry-date published" datetime="%1$s">%2$s</time>',
243 esc_attr( get_the_date( 'c', $id ) ),
244 esc_html( get_the_date( '', $id ) )
245 );
246
247 $is_updated_date = isset( $attributes['dateType'] ) && 'updated' === $attributes['dateType'];
248
249 if ( ! empty( $attributes['dateReplacePublished'] ) || $is_updated_date ) {
250 if ( $updated_time > $published_time ) {
251 $post_date = sprintf(
252 '<time class="entry-date updated-date" datetime="%1$s">%2$s</time>',
253 esc_attr( get_the_modified_date( 'c', $id ) ),
254 esc_html( get_the_modified_date( '', $id ) )
255 );
256 } elseif ( $is_updated_date ) {
257 // If we're showing the updated date but no updated date exists, don't display anything.
258 return '';
259 }
260 }
261
262 return $post_date;
263 }
264
265 /**
266 * Get the requested post meta.
267 *
268 * @param array $attributes The block attributes.
269 */
270 public static function get_post_meta( $attributes ) {
271 if ( isset( $attributes['metaFieldName'] ) ) {
272 $meta_value = get_post_meta( self::get_source_id( $attributes ), $attributes['metaFieldName'], true );
273
274 $value = (
275 is_string( $meta_value ) ||
276 is_integer( $meta_value ) ||
277 is_float( $meta_value )
278 ) ? $meta_value : '';
279
280 return apply_filters(
281 'generateblocks_dynamic_content_post_meta',
282 $value,
283 self::get_source_id( $attributes ),
284 $attributes
285 );
286 }
287 }
288
289 /**
290 * Get the requested author meta.
291 *
292 * @param array $attributes The block attributes.
293 */
294 public static function get_author_meta( $attributes ) {
295 if ( isset( $attributes['metaFieldName'] ) ) {
296 $id = self::get_source_id( $attributes );
297
298 if ( ! $id ) {
299 return;
300 }
301
302 $author_id = get_post_field( 'post_author', $id );
303
304 if ( ! $author_id ) {
305 return;
306 }
307
308 $value = self::get_user_data( $author_id, $attributes['metaFieldName'] );
309
310 return apply_filters(
311 'generateblocks_dynamic_content_author_meta',
312 $value,
313 $author_id,
314 $attributes
315 );
316 }
317 }
318
319 /**
320 * Get the number of comments.
321 *
322 * @param array $attributes The block attributes.
323 */
324 public static function get_comments_number( $attributes ) {
325 $id = self::get_source_id( $attributes );
326
327 if ( ! $id ) {
328 return;
329 }
330
331 if ( ! isset( $attributes['noCommentsText'] ) ) {
332 $attributes['noCommentsText'] = __( 'No comments', 'generateblocks' );
333 }
334
335 if ( ! post_password_required( $id ) && ( comments_open( $id ) || get_comments_number( $id ) ) ) {
336 if ( '' === $attributes['noCommentsText'] && get_comments_number( $id ) < 1 ) {
337 return $attributes['noCommentsText'];
338 }
339
340 $comments_text = get_comments_number_text(
341 $attributes['noCommentsText'],
342 ! empty( $attributes['singleCommentText'] ) ? $attributes['singleCommentText'] : __( '1 comment', 'generateblocks' ),
343 ! empty( $attributes['multipleCommentsText'] ) ? $attributes['multipleCommentsText'] : __( '% comments', 'generateblocks' )
344 );
345
346 return $comments_text;
347 } else {
348 return $attributes['noCommentsText'];
349 }
350 }
351
352 /**
353 * Get a list of terms.
354 *
355 * @param array $attributes The block attributes.
356 */
357 public static function get_terms( $attributes ) {
358 $id = self::get_source_id( $attributes );
359
360 if ( ! $id ) {
361 return;
362 }
363
364 $is_button = isset( $attributes['isButton'] );
365 $taxonomy = isset( $attributes['termTaxonomy'] ) ? $attributes['termTaxonomy'] : 'category';
366 $terms = get_the_terms( $id, $taxonomy );
367 $link_type = isset( $attributes['dynamicLinkType'] ) ? $attributes['dynamicLinkType'] : '';
368
369 if ( is_wp_error( $terms ) ) {
370 return;
371 }
372
373 $term_items = array();
374
375 foreach ( (array) $terms as $index => $term ) {
376 if ( ! isset( $term->name ) ) {
377 continue;
378 }
379
380 if ( $is_button ) {
381 $term_items[ $index ] = array(
382 'content' => $term->name,
383 'attributes' => array(
384 'class' => 'post-term-item post-term-' . $term->slug,
385 ),
386 );
387 } else {
388 $term_items[ $index ] = sprintf(
389 '<span class="post-term-item term-%2$s">%1$s</span>',
390 $term->name,
391 $term->slug
392 );
393 }
394
395 if ( 'term-archives' === $link_type ) {
396 $term_link = get_term_link( $term, $taxonomy );
397
398 if ( ! is_wp_error( $term_link ) ) {
399 if ( $is_button ) {
400 $term_items[ $index ]['attributes']['href'] = esc_url( get_term_link( $term, $taxonomy ) );
401 } else {
402 $term_items[ $index ] = sprintf(
403 '<span class="post-term-item term-%3$s"><a href="%1$s">%2$s</a></span>',
404 esc_url( get_term_link( $term, $taxonomy ) ),
405 $term->name,
406 $term->slug
407 );
408 }
409 }
410 }
411 }
412
413 if ( empty( $term_items ) ) {
414 return '';
415 }
416
417 $sep = isset( $attributes['termSeparator'] ) ? $attributes['termSeparator'] : ', ';
418 $term_output = $is_button ? $term_items : implode( $sep, $term_items );
419
420 return $term_output;
421 }
422
423 /**
424 * Get the pagination numbers.
425 *
426 * @param array $attributes The block attributes.
427 * @param WP_Block $block Block instance.
428 */
429 public static function get_paginate_links( $attributes, $block ) {
430 $page_key = isset( $block->context['generateblocks/queryId'] ) ? 'query-' . $block->context['generateblocks/queryId'] . '-page' : 'query-page';
431 $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
432 $max_page = isset( $block->context['generateblocks/query']['pages'] ) ? (int) $block->context['generateblocks/query']['pages'] : 0;
433
434 global $wp_query;
435
436 if ( isset( $block->context['generateblocks/inheritQuery'] ) && $block->context['generateblocks/inheritQuery'] ) {
437 // Take into account if we have set a bigger `max page`
438 // than what the query has.
439 $total = ! $max_page || $max_page > $wp_query->max_num_pages ? $wp_query->max_num_pages : $max_page;
440 $paginate_args = array(
441 'prev_next' => false,
442 'total' => $total,
443 );
444 $links = paginate_links( $paginate_args );
445 } else {
446 $query_args = apply_filters(
447 'generateblocks_query_loop_args',
448 GenerateBlocks_Query_Loop::get_query_args( $block, $page ),
449 $attributes,
450 $block
451 );
452
453 $block_query = new WP_Query( $query_args );
454
455 // `paginate_links` works with the global $wp_query, so we have to
456 // temporarily switch it with our custom query.
457 $prev_wp_query = $wp_query;
458 $wp_query = $block_query; // phpcs:ignore -- No way around overwriting core global.
459 $total = ! $max_page || $max_page > $wp_query->max_num_pages ? $wp_query->max_num_pages : $max_page;
460
461 $paginate_args = array(
462 'base' => '%_%',
463 'format' => "?$page_key=%#%",
464 'current' => max( 1, $page ),
465 'total' => $total,
466 'prev_next' => false,
467 );
468
469 if ( 1 !== $page ) {
470 /**
471 * `paginate_links` doesn't use the provided `format` when the page is `1`.
472 * This is great for the main query as it removes the extra query params
473 * making the URL shorter, but in the case of multiple custom queries is
474 * problematic. It results in returning an empty link which ends up with
475 * a link to the current page.
476 *
477 * A way to address this is to add a `fake` query arg with no value that
478 * is the same for all custom queries. This way the link is not empty and
479 * preserves all the other existent query args.
480 *
481 * @see https://developer.wordpress.org/reference/functions/paginate_links/
482 *
483 * The proper fix of this should be in core. Track Ticket:
484 * @see https://core.trac.wordpress.org/ticket/53868
485 *
486 * TODO: After two WP versions (starting from the WP version the core patch landed),
487 * we should remove this and call `paginate_links` with the proper new arg.
488 */
489 $paginate_args['add_args'] = array( 'cst' => '' );
490 }
491
492 // We still need to preserve `paged` query param if exists, as is used
493 // for Queries that inherit from global context.
494 $paged = empty( $_GET['paged'] ) ? null : (int) $_GET['paged']; // phpcs:ignore -- No data processing happening.
495
496 if ( $paged ) {
497 $paginate_args['add_args'] = array( 'paged' => $paged );
498 }
499
500 $links = paginate_links( $paginate_args );
501 $wp_query = $prev_wp_query; // phpcs:ignore -- Restoring core global.
502 wp_reset_postdata(); // Restore original Post Data.
503 }
504
505 $doc = self::load_html( $links );
506
507 if ( ! $doc ) {
508 return;
509 }
510
511 $data = array();
512 $html_nodes = $doc->getElementsByTagName( '*' );
513
514 foreach ( $html_nodes as $index => $node ) {
515 $classes = $node->getAttribute( 'class' ) ? $node->getAttribute( 'class' ) : '';
516
517 if ( $node->getAttribute( 'aria-current' ) ) {
518 $classes = str_replace( 'current', 'gb-block-is-current', $classes );
519 }
520
521 // phpcs:ignore -- DOMDocument doesn't use snake-case.
522 if ( 'span' === $node->tagName || 'a' === $node->tagName ) {
523 $data[ $index ]['href'] = $node->getAttribute( 'href' ) ? $node->getAttribute( 'href' ) : '';
524 $data[ $index ]['aria-current'] = $node->getAttribute( 'aria-current' ) ? $node->getAttribute( 'aria-current' ) : '';
525 $data[ $index ]['class'] = $classes;
526
527 // phpcs:ignore -- DOMDocument doesn't use snake-case.
528 foreach ( $node->childNodes as $childNode ) {
529 $data[ $index ]['content'] = $doc->saveHTML( $childNode );
530 }
531 }
532 }
533
534 $paginate_links = array_values( $data );
535 $link_items = array();
536
537 foreach ( (array) $paginate_links as $index => $link ) {
538 $link_items[ $index ] = array(
539 'content' => $link['content'],
540 'attributes' => array(
541 'href' => $link['href'],
542 'aria-current' => $link['aria-current'],
543 'class' => $link['class'],
544 ),
545 );
546 }
547
548 if ( empty( $link_items ) ) {
549 return '';
550 }
551
552 return $link_items;
553 }
554
555 /**
556 * Get the dynamic image.
557 *
558 * @param array $attributes The block attributes.
559 * @param WP_Block $block Block instance.
560 */
561 public static function get_dynamic_image( $attributes, $block ) {
562 $id = self::get_dynamic_image_id( $attributes );
563
564 if ( ! $id ) {
565 $id = apply_filters( 'generateblocks_dynamic_image_fallback', '', $attributes, $block );
566
567 // If still empty return.
568 if ( ! $id ) {
569 return;
570 }
571 }
572
573 $classes = array(
574 'gb-image-' . $attributes['uniqueId'],
575 isset( $attributes['className'] ) ? $attributes['className'] : '',
576 );
577
578 if ( ! empty( $attributes['align'] ) ) {
579 $classes[] = 'align' . $attributes['align'];
580 }
581
582 $html_attributes = array(
583 'id' => isset( $attributes['anchor'] ) ? $attributes['anchor'] : null,
584 'class' => implode( ' ', $classes ),
585 );
586
587 $parsed_html_attributes = generateblocks_parse_attr( 'image', $html_attributes, $attributes, $block );
588 $parsed_html_attributes = array_map( 'trim', $parsed_html_attributes );
589 $parsed_html_attributes = array_filter( $parsed_html_attributes );
590
591 if ( ! empty( $attributes['dynamicContentType'] ) ) {
592 if ( 'author-avatar' === $attributes['dynamicContentType'] ) {
593 $author_id = self::get_source_author_id( $attributes );
594 return get_avatar(
595 $author_id,
596 $attributes['width'],
597 '',
598 '',
599 $parsed_html_attributes
600 );
601 }
602 }
603
604 if ( $id && ! is_numeric( $id ) ) {
605 // Our image ID isn't a number - must be a static URL.
606 $html_attributes['src'] = $id;
607
608 return sprintf(
609 '<img %s />',
610 generateblocks_attr( 'image', $html_attributes, $attributes, $block )
611 );
612 }
613
614 $dynamic_image = wp_get_attachment_image(
615 $id,
616 isset( $attributes['sizeSlug'] ) ? $attributes['sizeSlug'] : 'full',
617 false,
618 $parsed_html_attributes
619 );
620
621 if ( ! $dynamic_image ) {
622 return '';
623 }
624
625 return $dynamic_image;
626 }
627
628 /**
629 * Get our source ID.
630 *
631 * @param array $attributes The block attributes.
632 */
633 public static function get_source_id( $attributes ) {
634 $id = get_the_ID();
635
636 if (
637 isset( $attributes['dynamicSource'] ) &&
638 'current-post' !== $attributes['dynamicSource'] &&
639 isset( $attributes['postId'] )
640 ) {
641 $id = absint( $attributes['postId'] );
642 }
643
644 $image_content_types = array( 'caption', 'post-title', 'alt-text', 'image-description' );
645
646 if ( isset( $attributes['dynamicContentType'] ) ) {
647 if ( in_array( $attributes['dynamicContentType'], $image_content_types ) ) {
648 if ( isset( $attributes['dynamicImage'] ) ) {
649 $id = $attributes['dynamicImage'];
650 } elseif (
651 isset( $attributes['postId'] ) &&
652 isset( $attributes['postType'] ) &&
653 'attachment' === $attributes['postType']
654 ) {
655 // Use the saved post ID if we're working with a static image.
656 $id = absint( $attributes['postId'] );
657 }
658 }
659 }
660
661 return apply_filters(
662 'generateblocks_dynamic_source_id',
663 $id,
664 $attributes
665 );
666 }
667
668 /**
669 * Get the source post author id.
670 *
671 * @param array $attributes The block attributes.
672 *
673 * @return int|boolean
674 */
675 public static function get_source_author_id( $attributes ) {
676 $id = self::get_source_id( $attributes );
677
678 if ( ! $id ) {
679 return false;
680 }
681
682 $author_id = get_post_field( 'post_author', $id );
683
684 if ( ! $author_id ) {
685 return false;
686 }
687
688 return $author_id;
689 }
690
691 /**
692 * Get the dynamic image ID.
693 *
694 * @param array $attributes The block attributes.
695 */
696 public static function get_dynamic_image_id( $attributes ) {
697 $id = self::get_source_id( $attributes );
698
699 if ( ! $id ) {
700 return;
701 }
702
703 if ( ! empty( $attributes['dynamicContentType'] ) ) {
704 if ( 'post-meta' === $attributes['dynamicContentType'] ) {
705 $id = self::get_post_meta( $attributes );
706 }
707
708 if ( 'featured-image' === $attributes['dynamicContentType'] ) {
709 $id = get_post_thumbnail_id( $id );
710 }
711 }
712
713 return $id;
714 }
715
716 /**
717 * Get the dynamic background image url.
718 *
719 * @param array $attributes The block attributes.
720 *
721 * @return int|boolean
722 */
723 public static function get_dynamic_background_image_url( $attributes ) {
724 $id = self::get_dynamic_image_id( $attributes );
725
726 if ( ! $id ) {
727 return false;
728 }
729
730 if ( empty( $attributes['dynamicContentType'] ) ) {
731 return;
732 }
733
734 if ( $id && ! is_numeric( $id ) ) {
735 // Our image ID isn't a number - must be a static URL.
736 return $id;
737 }
738
739 $url = wp_get_attachment_image_url(
740 $id,
741 isset( $attributes['bgImageSize'] ) ? $attributes['bgImageSize'] : 'full'
742 );
743
744 return apply_filters(
745 'generateblocks_dynamic_background_image_url',
746 $url,
747 $attributes
748 );
749 }
750
751 /**
752 * Get our dynamic URL.
753 *
754 * @param array $attributes The block attributes.
755 * @param object $block The block object.
756 */
757 public static function get_dynamic_url( $attributes, $block ) {
758 $id = self::get_source_id( $attributes );
759 $author_id = get_post_field( 'post_author', $id );
760 $link_type = isset( $attributes['dynamicLinkType'] ) ? $attributes['dynamicLinkType'] : '';
761 $url = '';
762
763 if ( 'single-post' === $link_type ) {
764 $url = get_permalink( $id );
765 }
766
767 if ( 'single-image' === $link_type ) {
768 if ( ! empty( $attributes['dynamicContentType'] ) ) {
769 $image_id = self::get_dynamic_image_id( $attributes );
770
771 if ( $image_id && ! is_numeric( $image_id ) ) {
772 // Our image ID isn't a number - must be a static URL.
773 return $image_id;
774 }
775 } else {
776 $image_id = ! empty( $attributes['mediaId'] ) ? $attributes['mediaId'] : false;
777 }
778
779 if ( $image_id ) {
780 $url = wp_get_attachment_url( $image_id );
781 } elseif ( ! empty( $attributes['mediaUrl'] ) ) {
782 $url = $attributes['mediaUrl'];
783 }
784 }
785
786 if ( isset( $attributes['linkMetaFieldName'] ) ) {
787 if ( 'post-meta' === $link_type ) {
788 $url = get_post_meta( $id, $attributes['linkMetaFieldName'], true );
789
790 $url = apply_filters(
791 'generateblocks_dynamic_url_post_meta',
792 $url,
793 self::get_source_id( $attributes ),
794 $attributes
795 );
796
797 if ( isset( $attributes['linkMetaFieldType'] ) ) {
798 $url = $attributes['linkMetaFieldType'] . $url;
799 }
800 }
801
802 if ( 'author-meta' === $link_type ) {
803 $url = self::get_user_data( $author_id, $attributes['linkMetaFieldName'] );
804
805 $url = apply_filters(
806 'generateblocks_dynamic_url_author_meta',
807 $url,
808 $author_id,
809 $attributes
810 );
811
812 if ( isset( $attributes['linkMetaFieldType'] ) ) {
813 $url = $attributes['linkMetaFieldType'] . $url;
814 }
815 }
816 }
817
818 if ( 'author-email' === $link_type ) {
819 $url = self::get_user_data( $author_id, 'user_email' );
820
821 if ( isset( $attributes['linkMetaFieldType'] ) ) {
822 $url = $attributes['linkMetaFieldType'] . $url;
823 }
824 }
825
826 if ( 'author-archives' === $link_type ) {
827 $url = get_author_posts_url( $author_id );
828 }
829
830 if ( 'comments-area' === $link_type ) {
831 $url = get_comments_link( $id );
832 }
833
834 if ( 'pagination-next' === $link_type ) {
835 $page_key = isset( $block->context['generateblocks/queryId'] ) ? 'query-' . $block->context['generateblocks/queryId'] . '-page' : 'query-page';
836 $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
837 $max_page = isset( $block->context['generateblocks/query']['pages'] ) ? (int) $block->context['generateblocks/query']['pages'] : 0;
838
839 if ( isset( $block->context['generateblocks/inheritQuery'] ) && $block->context['generateblocks/inheritQuery'] ) {
840 global $wp_query, $paged;
841
842 if ( ! $max_page || $max_page > $wp_query->max_num_pages ) {
843 $max_page = $wp_query->max_num_pages;
844 }
845
846 if ( ! $paged ) {
847 $paged = 1; // phpcs:ignore -- Need to overrite global here.
848 }
849
850 $nextpage = (int) $paged + 1;
851
852 if ( $nextpage <= $max_page ) {
853 $url = next_posts( $max_page, false );
854 }
855 } elseif ( ! $max_page || $max_page > $page ) {
856 $query_args = apply_filters(
857 'generateblocks_query_loop_args',
858 GenerateBlocks_Query_Loop::get_query_args( $block, $page ),
859 $attributes,
860 $block
861 );
862
863 $custom_query = new WP_Query( $query_args );
864 $custom_query_max_pages = (int) $custom_query->max_num_pages;
865
866 if ( $custom_query_max_pages && $custom_query_max_pages !== $page ) {
867 $url = esc_url( add_query_arg( $page_key, $page + 1 ) );
868 }
869
870 wp_reset_postdata(); // Restore original Post Data.
871 }
872 }
873
874 if ( 'pagination-prev' === $link_type ) {
875 $page_key = isset( $block->context['generateblocks/queryId'] ) ? 'query-' . $block->context['generateblocks/queryId'] . '-page' : 'query-page';
876 $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
877
878 if ( isset( $block->context['generateblocks/inheritQuery'] ) && $block->context['generateblocks/inheritQuery'] ) {
879 global $paged;
880
881 if ( $paged > 1 ) {
882 $url = previous_posts( false );
883 }
884 } elseif ( 1 !== $page ) {
885 $url = esc_url( add_query_arg( $page_key, $page - 1 ) );
886 }
887 }
888
889 return apply_filters(
890 'generateblocks_dynamic_url_output',
891 $url,
892 $attributes,
893 $block
894 );
895 }
896
897 /**
898 * Get user data.
899 *
900 * @param int $author_id The ID of the user.
901 * @param string|void $field The field to look up.
902 */
903 public static function get_user_data( $author_id, $field ) {
904 if ( ! $author_id ) {
905 return;
906 }
907
908 $data = get_user_meta( $author_id, $field, true );
909
910 if ( ! $data ) {
911 $user_data_names = array(
912 'user_nicename',
913 'user_email',
914 'display_name',
915 );
916
917 if ( in_array( $field, $user_data_names ) ) {
918 $user_data = get_userdata( $author_id );
919
920 if ( $user_data ) {
921 switch ( $field ) {
922 case 'user_nicename':
923 $data = $user_data->user_nicename;
924 break;
925
926 case 'user_email':
927 $data = $user_data->user_email;
928 break;
929
930 case 'display_name':
931 $data = $user_data->display_name;
932 break;
933 }
934 }
935 }
936 }
937
938 return $data;
939 }
940
941 /**
942 * Run HTML through DOMDocument so we can use parts of it
943 * when needed.
944 *
945 * @param string $content The content to run through DOMDocument.
946 */
947 public static function load_html( $content ) {
948 if ( ! class_exists( 'DOMDocument' ) ) {
949 return;
950 }
951
952 $doc = new DOMDocument();
953
954 // Enable user error handling for the HTML parsing. HTML5 elements aren't
955 // supported (as of PHP 7.4) and There's no way to guarantee that the markup
956 // is valid anyway, so we're just going to ignore all errors in parsing.
957 // Nested heading elements will still be parsed.
958 // The lack of HTML5 support is a libxml2 issue:
959 // https://bugzilla.gnome.org/show_bug.cgi?id=761534.
960 libxml_use_internal_errors( true );
961
962 // Parse the post content into an HTML document.
963 // Ensure UTF-8 encoding.
964 // https://stackoverflow.com/a/37834812.
965 $doc->loadHTML(
966 sprintf(
967 '<html><head><meta http-equiv="Content-Type" content="text/html; charset=%s"></head><body>%s</body></html>',
968 esc_attr( get_bloginfo( 'charset' ) ),
969 $content
970 )
971 );
972
973 // We're done parsing, so we can disable user error handling. This also
974 // clears any existing errors, which helps avoid a memory leak.
975 libxml_use_internal_errors( false );
976
977 return $doc;
978 }
979
980 /**
981 * Extracts the icon element from our content.
982 * This is useful when using icons in dynamic blocks.
983 *
984 * @param string $content The content to search through.
985 */
986 public static function get_icon_html( $content ) {
987 $doc = self::load_html( $content );
988
989 if ( ! $doc ) {
990 return;
991 }
992
993 $icon_html = '';
994 $html_nodes = $doc->getElementsByTagName( 'span' );
995
996 foreach ( $html_nodes as $node ) {
997 if ( 'gb-icon' === $node->getAttribute( 'class' ) ) {
998 $icon_html = $doc->saveHTML( $node );
999 }
1000 }
1001
1002 return $icon_html;
1003 }
1004
1005 /**
1006 * Get the dynamic image caption.
1007 *
1008 * @param array $attributes The block attributes.
1009 * @param object $block The block object.
1010 */
1011 public static function get_image_caption( $attributes, $block ) {
1012 $id = self::get_source_id( $attributes );
1013
1014 if ( ! $id ) {
1015 return;
1016 }
1017
1018 return wp_get_attachment_caption( $id );
1019 }
1020
1021 /**
1022 * Get the image alt text.
1023 *
1024 * @param array $attributes The block attributes.
1025 * @param object $block The block object.
1026 */
1027 public static function get_image_alt_text( $attributes, $block ) {
1028 $id = self::get_source_id( $attributes );
1029
1030 if ( ! $id ) {
1031 return '';
1032 }
1033
1034 return get_post_meta( $id, '_wp_attachment_image_alt', true );
1035 }
1036
1037 /**
1038 * Get the image description.
1039 *
1040 * @param array $attributes The block attributes.
1041 * @param object $block The block object.
1042 */
1043 public static function get_image_description( $attributes, $block ) {
1044 $id = self::get_source_id( $attributes );
1045
1046 if ( ! $id ) {
1047 return '';
1048 }
1049
1050 $media = get_post( $id );
1051
1052 return isset( $media ) ? $media->post_content : '';
1053 }
1054
1055 /**
1056 * Extracts the static content the user has entered.
1057 * This is useful when using dynamic links with static content.
1058 *
1059 * @param string $content The content to search through.
1060 */
1061 public static function get_static_content( $content ) {
1062 $doc = self::load_html( $content );
1063
1064 if ( ! $doc ) {
1065 return;
1066 }
1067
1068 $static_content = '';
1069 $html_nodes = $doc->getElementsByTagName( '*' );
1070
1071 foreach ( $html_nodes as $node ) {
1072 $classes = explode( ' ', $node->getAttribute( 'class' ) );
1073
1074 if (
1075 in_array( 'gb-button-text', $classes ) ||
1076 in_array( 'gb-headline-text', $classes ) ||
1077 in_array( 'gb-block-image', $classes )
1078 ) {
1079 // Captions are added dynamically in class-image.php, so we can remove
1080 // the static one here if it exists.
1081 if ( in_array( 'gb-block-image', $classes ) ) {
1082 $figcaptions = $node->getElementsByTagName( 'figcaption' );
1083
1084 if ( ! empty( $figcaptions ) ) {
1085 foreach ( $figcaptions as $figcaption ) {
1086 // phpcs:ignore -- DOMDocument doesn't use snake-case.
1087 $figcaption->parentNode->removeChild( $figcaption );
1088 }
1089 }
1090 }
1091
1092 // phpcs:ignore -- DOMDocument doesn't use snake-case.
1093 foreach ( $node->childNodes as $childNode ) {
1094 $static_content .= $doc->saveHTML( $childNode );
1095 }
1096 }
1097 }
1098
1099 return $static_content;
1100 }
1101
1102 /**
1103 * Set our dynamic background image.
1104 *
1105 * @param string $url Existing background image URL.
1106 * @param array $settings Block settings.
1107 */
1108 public function set_dynamic_background_image( $url, $settings ) {
1109 if ( $settings['useDynamicData'] && '' !== $settings['dynamicContentType'] ) {
1110 $dynamic_image_url = self::get_dynamic_background_image_url( $settings );
1111
1112 if ( $dynamic_image_url ) {
1113 $url = $dynamic_image_url;
1114 }
1115 }
1116
1117 return $url;
1118 }
1119
1120 /**
1121 * Add defaults for our Query settings.
1122 *
1123 * @param array $defaults Block defaults.
1124 */
1125 public function add_block_defaults( $defaults ) {
1126 $defaults['container']['useDynamicData'] = false;
1127 $defaults['container']['dynamicContentType'] = '';
1128 $defaults['container']['dynamicLinkType'] = '';
1129
1130 return $defaults;
1131 }
1132
1133 /**
1134 * Update button count depending on dynamic content.
1135 *
1136 * @param int $button_count How many buttons the block container has.
1137 * @param array $attributes The block attributes.
1138 * @param object $block The block data.
1139 */
1140 public function update_button_count( $button_count, $attributes, $block ) {
1141 $inner_blocks = $block->parsed_block['innerBlocks'];
1142
1143 foreach ( (array) $inner_blocks as $inner_block ) {
1144 $block_attributes = $inner_block['attrs'];
1145
1146 // Remove button from count if it has no dynamic content.
1147 if ( ! empty( $block_attributes['dynamicContentType'] ) && ! self::get_content( $block_attributes, $block ) ) {
1148 $button_count--;
1149 }
1150 }
1151
1152 return $button_count;
1153 }
1154 }
1155
1156 GenerateBlocks_Dynamic_Content::get_instance();
1157