PluginProbe ʕ •ᴥ•ʔ
GenerateBlocks / 1.5.2
GenerateBlocks v1.5.2
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
class-do-css.php 4 years ago class-dynamic-content.php 4 years ago class-enqueue-css.php 4 years ago class-legacy-attributes.php 4 years ago class-plugin-update.php 5 years ago class-query-loop.php 4 years ago class-render-blocks.php 4 years ago class-rest.php 4 years ago class-settings.php 4 years ago dashboard.php 4 years ago defaults.php 4 years ago functions.php 4 years ago general.php 4 years ago generate-css.php 4 years ago
class-dynamic-content.php
1095 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 ( 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 return (
274 is_string( $meta_value ) ||
275 is_integer( $meta_value ) ||
276 is_float( $meta_value )
277 ) ? $meta_value : '';
278 }
279 }
280
281 /**
282 * Get the requested author meta.
283 *
284 * @param array $attributes The block attributes.
285 */
286 public static function get_author_meta( $attributes ) {
287 if ( isset( $attributes['metaFieldName'] ) ) {
288 $id = self::get_source_id( $attributes );
289
290 if ( ! $id ) {
291 return;
292 }
293
294 $author_id = get_post_field( 'post_author', $id );
295
296 if ( ! $author_id ) {
297 return;
298 }
299
300 return self::get_user_data( $author_id, $attributes['metaFieldName'] );
301 }
302 }
303
304 /**
305 * Get the number of comments.
306 *
307 * @param array $attributes The block attributes.
308 */
309 public static function get_comments_number( $attributes ) {
310 $id = self::get_source_id( $attributes );
311
312 if ( ! $id ) {
313 return;
314 }
315
316 if ( ! isset( $attributes['noCommentsText'] ) ) {
317 $attributes['noCommentsText'] = __( 'No comments', 'generateblocks' );
318 }
319
320 if ( ! post_password_required( $id ) && ( comments_open( $id ) || get_comments_number( $id ) ) ) {
321 if ( '' === $attributes['noCommentsText'] && get_comments_number( $id ) < 1 ) {
322 return $attributes['noCommentsText'];
323 }
324
325 $comments_text = get_comments_number_text(
326 $attributes['noCommentsText'],
327 ! empty( $attributes['singleCommentText'] ) ? $attributes['singleCommentText'] : __( '1 comment', 'generateblocks' ),
328 ! empty( $attributes['multipleCommentsText'] ) ? $attributes['multipleCommentsText'] : __( '% comments', 'generateblocks' )
329 );
330
331 return $comments_text;
332 } else {
333 return $attributes['noCommentsText'];
334 }
335 }
336
337 /**
338 * Get a list of terms.
339 *
340 * @param array $attributes The block attributes.
341 */
342 public static function get_terms( $attributes ) {
343 $id = self::get_source_id( $attributes );
344
345 if ( ! $id ) {
346 return;
347 }
348
349 $is_button = isset( $attributes['isButton'] );
350 $taxonomy = isset( $attributes['termTaxonomy'] ) ? $attributes['termTaxonomy'] : 'category';
351 $terms = get_the_terms( $id, $taxonomy );
352 $link_type = isset( $attributes['dynamicLinkType'] ) ? $attributes['dynamicLinkType'] : '';
353
354 if ( is_wp_error( $terms ) ) {
355 return;
356 }
357
358 $term_items = array();
359
360 foreach ( (array) $terms as $index => $term ) {
361 if ( ! isset( $term->name ) ) {
362 continue;
363 }
364
365 if ( $is_button ) {
366 $term_items[ $index ] = array(
367 'content' => $term->name,
368 'attributes' => array(
369 'class' => 'post-term-item post-term-' . $term->slug,
370 ),
371 );
372 } else {
373 $term_items[ $index ] = sprintf(
374 '<span class="post-term-item term-%2$s">%1$s</span>',
375 $term->name,
376 $term->slug
377 );
378 }
379
380 if ( 'term-archives' === $link_type ) {
381 $term_link = get_term_link( $term, $taxonomy );
382
383 if ( ! is_wp_error( $term_link ) ) {
384 if ( $is_button ) {
385 $term_items[ $index ]['attributes']['href'] = esc_url( get_term_link( $term, $taxonomy ) );
386 } else {
387 $term_items[ $index ] = sprintf(
388 '<span class="post-term-item term-%3$s"><a href="%1$s">%2$s</a></span>',
389 esc_url( get_term_link( $term, $taxonomy ) ),
390 $term->name,
391 $term->slug
392 );
393 }
394 }
395 }
396 }
397
398 if ( empty( $term_items ) ) {
399 return '';
400 }
401
402 $sep = isset( $attributes['termSeparator'] ) ? $attributes['termSeparator'] : ', ';
403 $term_output = $is_button ? $term_items : implode( $sep, $term_items );
404
405 return $term_output;
406 }
407
408 /**
409 * Get the pagination numbers.
410 *
411 * @param array $attributes The block attributes.
412 * @param WP_Block $block Block instance.
413 */
414 public static function get_paginate_links( $attributes, $block ) {
415 $page_key = isset( $block->context['generateblocks/queryId'] ) ? 'query-' . $block->context['generateblocks/queryId'] . '-page' : 'query-page';
416 $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
417 $max_page = isset( $block->context['generateblocks/query']['pages'] ) ? (int) $block->context['generateblocks/query']['pages'] : 0;
418
419 global $wp_query;
420
421 if ( isset( $block->context['generateblocks/inheritQuery'] ) && $block->context['generateblocks/inheritQuery'] ) {
422 // Take into account if we have set a bigger `max page`
423 // than what the query has.
424 $total = ! $max_page || $max_page > $wp_query->max_num_pages ? $wp_query->max_num_pages : $max_page;
425 $paginate_args = array(
426 'prev_next' => false,
427 'total' => $total,
428 );
429 $links = paginate_links( $paginate_args );
430 } else {
431 $block_query = new WP_Query( GenerateBlocks_Query_Loop::get_query_args( $block, $page ) );
432
433 // `paginate_links` works with the global $wp_query, so we have to
434 // temporarily switch it with our custom query.
435 $prev_wp_query = $wp_query;
436 $wp_query = $block_query; // phpcs:ignore -- No way around overwriting core global.
437 $total = ! $max_page || $max_page > $wp_query->max_num_pages ? $wp_query->max_num_pages : $max_page;
438
439 $paginate_args = array(
440 'base' => '%_%',
441 'format' => "?$page_key=%#%",
442 'current' => max( 1, $page ),
443 'total' => $total,
444 'prev_next' => false,
445 );
446
447 if ( 1 !== $page ) {
448 /**
449 * `paginate_links` doesn't use the provided `format` when the page is `1`.
450 * This is great for the main query as it removes the extra query params
451 * making the URL shorter, but in the case of multiple custom queries is
452 * problematic. It results in returning an empty link which ends up with
453 * a link to the current page.
454 *
455 * A way to address this is to add a `fake` query arg with no value that
456 * is the same for all custom queries. This way the link is not empty and
457 * preserves all the other existent query args.
458 *
459 * @see https://developer.wordpress.org/reference/functions/paginate_links/
460 *
461 * The proper fix of this should be in core. Track Ticket:
462 * @see https://core.trac.wordpress.org/ticket/53868
463 *
464 * TODO: After two WP versions (starting from the WP version the core patch landed),
465 * we should remove this and call `paginate_links` with the proper new arg.
466 */
467 $paginate_args['add_args'] = array( 'cst' => '' );
468 }
469
470 // We still need to preserve `paged` query param if exists, as is used
471 // for Queries that inherit from global context.
472 $paged = empty( $_GET['paged'] ) ? null : (int) $_GET['paged']; // phpcs:ignore -- No data processing happening.
473
474 if ( $paged ) {
475 $paginate_args['add_args'] = array( 'paged' => $paged );
476 }
477
478 $links = paginate_links( $paginate_args );
479 $wp_query = $prev_wp_query; // phpcs:ignore -- Restoring core global.
480 wp_reset_postdata(); // Restore original Post Data.
481 }
482
483 $doc = self::load_html( $links );
484
485 if ( ! $doc ) {
486 return;
487 }
488
489 $data = array();
490 $html_nodes = $doc->getElementsByTagName( '*' );
491
492 foreach ( $html_nodes as $index => $node ) {
493 $classes = $node->getAttribute( 'class' ) ? $node->getAttribute( 'class' ) : '';
494
495 if ( $node->getAttribute( 'aria-current' ) ) {
496 $classes = str_replace( 'current', 'gb-button__current', $classes );
497 }
498
499 // phpcs:ignore -- DOMDocument doesn't use snake-case.
500 if ( 'span' === $node->tagName || 'a' === $node->tagName ) {
501 $data[ $index ]['href'] = $node->getAttribute( 'href' ) ? $node->getAttribute( 'href' ) : '';
502 $data[ $index ]['aria-current'] = $node->getAttribute( 'aria-current' ) ? $node->getAttribute( 'aria-current' ) : '';
503 $data[ $index ]['class'] = $classes;
504
505 // phpcs:ignore -- DOMDocument doesn't use snake-case.
506 foreach ( $node->childNodes as $childNode ) {
507 $data[ $index ]['content'] = $doc->saveHTML( $childNode );
508 }
509 }
510 }
511
512 $paginate_links = array_values( $data );
513 $link_items = array();
514
515 foreach ( (array) $paginate_links as $index => $link ) {
516 $link_items[ $index ] = array(
517 'content' => $link['content'],
518 'attributes' => array(
519 'href' => $link['href'],
520 'aria-current' => $link['aria-current'],
521 'class' => $link['class'],
522 ),
523 );
524 }
525
526 if ( empty( $link_items ) ) {
527 return '';
528 }
529
530 return $link_items;
531 }
532
533 /**
534 * Get the dynamic image.
535 *
536 * @param array $attributes The block attributes.
537 * @param WP_Block $block Block instance.
538 */
539 public static function get_dynamic_image( $attributes, $block ) {
540 $id = self::get_dynamic_image_id( $attributes );
541
542 if ( ! $id ) {
543 $id = apply_filters( 'generateblocks_dynamic_image_fallback', '', $attributes, $block );
544
545 // If still empty return.
546 if ( ! $id ) {
547 return;
548 }
549 }
550
551 $classes = array(
552 'gb-image-' . $attributes['uniqueId'],
553 isset( $attributes['className'] ) ? $attributes['className'] : '',
554 );
555
556 if ( ! empty( $attributes['align'] ) ) {
557 $classes[] = 'align' . $attributes['align'];
558 }
559
560 $html_attributes = array(
561 'id' => isset( $attributes['anchor'] ) ? $attributes['anchor'] : null,
562 'class' => implode( ' ', $classes ),
563 );
564
565 $parsed_html_attributes = generateblocks_parse_attr( 'image', $html_attributes, $attributes, $block );
566 $parsed_html_attributes = array_map( 'trim', $parsed_html_attributes );
567 $parsed_html_attributes = array_filter( $parsed_html_attributes );
568
569 if ( ! empty( $attributes['dynamicContentType'] ) ) {
570 if ( 'author-avatar' === $attributes['dynamicContentType'] ) {
571 $author_id = self::get_source_author_id( $attributes );
572 return get_avatar(
573 $author_id,
574 $attributes['width'],
575 '',
576 '',
577 $parsed_html_attributes
578 );
579 }
580 }
581
582 if ( $id && ! is_numeric( $id ) ) {
583 // Our image ID isn't a number - must be a static URL.
584 $html_attributes['src'] = $id;
585
586 return sprintf(
587 '<img %s />',
588 generateblocks_attr( 'image', $html_attributes, $attributes, $block )
589 );
590 }
591
592 $dynamic_image = wp_get_attachment_image(
593 $id,
594 isset( $attributes['sizeSlug'] ) ? $attributes['sizeSlug'] : 'full',
595 false,
596 $parsed_html_attributes
597 );
598
599 if ( ! $dynamic_image ) {
600 return '';
601 }
602
603 return $dynamic_image;
604 }
605
606 /**
607 * Get our source ID.
608 *
609 * @param array $attributes The block attributes.
610 */
611 public static function get_source_id( $attributes ) {
612 $id = get_the_ID();
613
614 if (
615 isset( $attributes['dynamicSource'] ) &&
616 'current-post' !== $attributes['dynamicSource'] &&
617 isset( $attributes['postId'] )
618 ) {
619 $id = absint( $attributes['postId'] );
620 }
621
622 $image_content_types = array( 'caption', 'post-title', 'alt-text', 'image-description' );
623
624 if ( isset( $attributes['dynamicContentType'] ) ) {
625 if ( in_array( $attributes['dynamicContentType'], $image_content_types ) ) {
626 if ( isset( $attributes['dynamicImage'] ) ) {
627 $id = $attributes['dynamicImage'];
628 } elseif (
629 isset( $attributes['postId'] ) &&
630 isset( $attributes['postType'] ) &&
631 'attachment' === $attributes['postType']
632 ) {
633 // Use the saved post ID if we're working with a static image.
634 $id = absint( $attributes['postId'] );
635 }
636 }
637 }
638
639 return $id;
640 }
641
642 /**
643 * Get the source post author id.
644 *
645 * @param array $attributes The block attributes.
646 *
647 * @return int|boolean
648 */
649 public static function get_source_author_id( $attributes ) {
650 $id = self::get_source_id( $attributes );
651
652 if ( ! $id ) {
653 return false;
654 }
655
656 $author_id = get_post_field( 'post_author', $id );
657
658 if ( ! $author_id ) {
659 return false;
660 }
661
662 return $author_id;
663 }
664
665 /**
666 * Get the dynamic image ID.
667 *
668 * @param array $attributes The block attributes.
669 */
670 public static function get_dynamic_image_id( $attributes ) {
671 $id = self::get_source_id( $attributes );
672
673 if ( ! $id ) {
674 return;
675 }
676
677 if ( ! empty( $attributes['dynamicContentType'] ) ) {
678 if ( 'post-meta' === $attributes['dynamicContentType'] ) {
679 $id = self::get_post_meta( $attributes );
680 }
681
682 if ( 'featured-image' === $attributes['dynamicContentType'] ) {
683 $id = get_post_thumbnail_id( $id );
684 }
685 }
686
687 return $id;
688 }
689
690 /**
691 * Get the dynamic background image url.
692 *
693 * @param array $attributes The block attributes.
694 *
695 * @return int|boolean
696 */
697 public static function get_dynamic_background_image_url( $attributes ) {
698 $id = self::get_dynamic_image_id( $attributes );
699
700 if ( ! $id ) {
701 return false;
702 }
703
704 if ( empty( $attributes['dynamicContentType'] ) ) {
705 return;
706 }
707
708 if ( $id && ! is_numeric( $id ) ) {
709 // Our image ID isn't a number - must be a static URL.
710 return $id;
711 }
712
713 $url = wp_get_attachment_image_url(
714 $id,
715 isset( $attributes['bgImageSize'] ) ? $attributes['bgImageSize'] : 'full'
716 );
717
718 return apply_filters(
719 'generateblocks_dynamic_background_image_url',
720 $url,
721 $attributes
722 );
723 }
724
725 /**
726 * Get our dynamic URL.
727 *
728 * @param array $attributes The block attributes.
729 * @param object $block The block object.
730 */
731 public static function get_dynamic_url( $attributes, $block ) {
732 $id = self::get_source_id( $attributes );
733 $author_id = get_post_field( 'post_author', $id );
734 $link_type = isset( $attributes['dynamicLinkType'] ) ? $attributes['dynamicLinkType'] : '';
735 $url = '';
736
737 if ( 'single-post' === $link_type ) {
738 $url = get_permalink( $id );
739 }
740
741 if ( 'single-image' === $link_type ) {
742 if ( ! empty( $attributes['dynamicContentType'] ) ) {
743 $image_id = self::get_dynamic_image_id( $attributes );
744
745 if ( $image_id && ! is_numeric( $image_id ) ) {
746 // Our image ID isn't a number - must be a static URL.
747 return $image_id;
748 }
749 } else {
750 $image_id = ! empty( $attributes['mediaId'] ) ? $attributes['mediaId'] : false;
751 }
752
753 if ( $image_id ) {
754 $url = wp_get_attachment_url( $image_id );
755 } elseif ( ! empty( $attributes['mediaUrl'] ) ) {
756 $url = $attributes['mediaUrl'];
757 }
758 }
759
760 if ( isset( $attributes['linkMetaFieldName'] ) ) {
761 if ( 'post-meta' === $link_type ) {
762 $url = get_post_meta( $id, $attributes['linkMetaFieldName'], true );
763
764 if ( isset( $attributes['linkMetaFieldType'] ) ) {
765 $url = $attributes['linkMetaFieldType'] . $url;
766 }
767 }
768
769 if ( 'author-meta' === $link_type ) {
770 $url = self::get_user_data( $author_id, $attributes['linkMetaFieldName'] );
771
772 if ( isset( $attributes['linkMetaFieldType'] ) ) {
773 $url = $attributes['linkMetaFieldType'] . $url;
774 }
775 }
776 }
777
778 if ( 'author-email' === $link_type ) {
779 $url = self::get_user_data( $author_id, 'user_email' );
780
781 if ( isset( $attributes['linkMetaFieldType'] ) ) {
782 $url = $attributes['linkMetaFieldType'] . $url;
783 }
784 }
785
786 if ( 'author-archives' === $link_type ) {
787 $url = get_author_posts_url( $author_id );
788 }
789
790 if ( 'comments-area' === $link_type ) {
791 $url = get_comments_link( $id );
792 }
793
794 if ( 'pagination-next' === $link_type ) {
795 $page_key = isset( $block->context['generateblocks/queryId'] ) ? 'query-' . $block->context['generateblocks/queryId'] . '-page' : 'query-page';
796 $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
797 $max_page = isset( $block->context['generateblocks/query']['pages'] ) ? (int) $block->context['generateblocks/query']['pages'] : 0;
798
799 if ( isset( $block->context['generateblocks/inheritQuery'] ) && $block->context['generateblocks/inheritQuery'] ) {
800 global $wp_query, $paged;
801
802 if ( ! $max_page || $max_page > $wp_query->max_num_pages ) {
803 $max_page = $wp_query->max_num_pages;
804 }
805
806 if ( ! $paged ) {
807 $paged = 1; // phpcs:ignore -- Need to overrite global here.
808 }
809
810 $nextpage = (int) $paged + 1;
811
812 if ( $nextpage <= $max_page ) {
813 $url = next_posts( $max_page, false );
814 }
815 } elseif ( ! $max_page || $max_page > $page ) {
816 $custom_query = new WP_Query( GenerateBlocks_Query_Loop::get_query_args( $block, $page ) );
817 $custom_query_max_pages = (int) $custom_query->max_num_pages;
818
819 if ( $custom_query_max_pages && $custom_query_max_pages !== $page ) {
820 $url = esc_url( add_query_arg( $page_key, $page + 1 ) );
821 }
822
823 wp_reset_postdata(); // Restore original Post Data.
824 }
825 }
826
827 if ( 'pagination-prev' === $link_type ) {
828 $page_key = isset( $block->context['generateblocks/queryId'] ) ? 'query-' . $block->context['generateblocks/queryId'] . '-page' : 'query-page';
829 $page = empty( $_GET[ $page_key ] ) ? 1 : (int) $_GET[ $page_key ]; // phpcs:ignore -- No data processing happening.
830
831 if ( isset( $block->context['generateblocks/inheritQuery'] ) && $block->context['generateblocks/inheritQuery'] ) {
832 global $paged;
833
834 if ( $paged > 1 ) {
835 $url = previous_posts( false );
836 }
837 } elseif ( 1 !== $page ) {
838 $url = esc_url( add_query_arg( $page_key, $page - 1 ) );
839 }
840 }
841
842 return apply_filters(
843 'generateblocks_dynamic_url_output',
844 $url,
845 $attributes,
846 $block
847 );
848 }
849
850 /**
851 * Get user data.
852 *
853 * @param int $author_id The ID of the user.
854 * @param string|void $field The field to look up.
855 */
856 public static function get_user_data( $author_id, $field ) {
857 if ( ! $author_id ) {
858 return;
859 }
860
861 $data = get_user_meta( $author_id, $field, true );
862
863 if ( ! $data ) {
864 $user_data_names = array(
865 'user_nicename',
866 'user_email',
867 'display_name',
868 );
869
870 if ( in_array( $field, $user_data_names ) ) {
871 $user_data = get_userdata( $author_id );
872
873 if ( $user_data ) {
874 switch ( $field ) {
875 case 'user_nicename':
876 $data = $user_data->user_nicename;
877 break;
878
879 case 'user_email':
880 $data = $user_data->user_email;
881 break;
882
883 case 'display_name':
884 $data = $user_data->display_name;
885 break;
886 }
887 }
888 }
889 }
890
891 return $data;
892 }
893
894 /**
895 * Run HTML through DOMDocument so we can use parts of it
896 * when needed.
897 *
898 * @param string $content The content to run through DOMDocument.
899 */
900 public static function load_html( $content ) {
901 if ( ! class_exists( 'DOMDocument' ) ) {
902 return;
903 }
904
905 $doc = new DOMDocument();
906
907 // Enable user error handling for the HTML parsing. HTML5 elements aren't
908 // supported (as of PHP 7.4) and There's no way to guarantee that the markup
909 // is valid anyway, so we're just going to ignore all errors in parsing.
910 // Nested heading elements will still be parsed.
911 // The lack of HTML5 support is a libxml2 issue:
912 // https://bugzilla.gnome.org/show_bug.cgi?id=761534.
913 libxml_use_internal_errors( true );
914
915 // Parse the post content into an HTML document.
916 // Ensure UTF-8 encoding.
917 // https://stackoverflow.com/a/37834812.
918 $doc->loadHTML(
919 sprintf(
920 '<html><head><meta http-equiv="Content-Type" content="text/html; charset=%s"></head><body>%s</body></html>',
921 esc_attr( get_bloginfo( 'charset' ) ),
922 $content
923 )
924 );
925
926 // We're done parsing, so we can disable user error handling. This also
927 // clears any existing errors, which helps avoid a memory leak.
928 libxml_use_internal_errors( false );
929
930 return $doc;
931 }
932
933 /**
934 * Extracts the icon element from our content.
935 * This is useful when using icons in dynamic blocks.
936 *
937 * @param string $content The content to search through.
938 */
939 public static function get_icon_html( $content ) {
940 $doc = self::load_html( $content );
941
942 if ( ! $doc ) {
943 return;
944 }
945
946 $icon_html = '';
947 $html_nodes = $doc->getElementsByTagName( 'span' );
948
949 foreach ( $html_nodes as $node ) {
950 if ( 'gb-icon' === $node->getAttribute( 'class' ) ) {
951 $icon_html = $doc->saveHTML( $node );
952 }
953 }
954
955 return $icon_html;
956 }
957
958 /**
959 * Get the dynamic image caption.
960 *
961 * @param array $attributes The block attributes.
962 * @param object $block The block object.
963 */
964 public static function get_image_caption( $attributes, $block ) {
965 $id = self::get_source_id( $attributes );
966
967 if ( ! $id ) {
968 return;
969 }
970
971 return wp_get_attachment_caption( $id );
972 }
973
974 /**
975 * Get the image alt text.
976 *
977 * @param array $attributes The block attributes.
978 * @param object $block The block object.
979 */
980 public static function get_image_alt_text( $attributes, $block ) {
981 $id = self::get_source_id( $attributes );
982
983 if ( ! $id ) {
984 return '';
985 }
986
987 return get_post_meta( $id, '_wp_attachment_image_alt', true );
988 }
989
990 /**
991 * Get the image description.
992 *
993 * @param array $attributes The block attributes.
994 * @param object $block The block object.
995 */
996 public static function get_image_description( $attributes, $block ) {
997 $id = self::get_source_id( $attributes );
998
999 if ( ! $id ) {
1000 return '';
1001 }
1002
1003 $media = get_post( $id );
1004
1005 return isset( $media ) ? $media->post_content : '';
1006 }
1007
1008 /**
1009 * Extracts the static content the user has entered.
1010 * This is useful when using dynamic links with static content.
1011 *
1012 * @param string $content The content to search through.
1013 */
1014 public static function get_static_content( $content ) {
1015 $doc = self::load_html( $content );
1016
1017 if ( ! $doc ) {
1018 return;
1019 }
1020
1021 $static_content = '';
1022 $html_nodes = $doc->getElementsByTagName( '*' );
1023
1024 foreach ( $html_nodes as $node ) {
1025 if (
1026 strpos( $node->getAttribute( 'class' ), 'gb-button-text' ) !== false ||
1027 strpos( $node->getAttribute( 'class' ), 'gb-headline-text' ) !== false ||
1028 strpos( $node->getAttribute( 'class' ), 'gb-block-image' ) !== false
1029 ) {
1030 // phpcs:ignore -- DOMDocument doesn't use snake-case.
1031 foreach ( $node->childNodes as $childNode ) {
1032 $static_content .= $doc->saveHTML( $childNode );
1033 }
1034 }
1035 }
1036
1037 return $static_content;
1038 }
1039
1040 /**
1041 * Set our dynamic background image.
1042 *
1043 * @param string $url Existing background image URL.
1044 * @param array $settings Block settings.
1045 */
1046 public function set_dynamic_background_image( $url, $settings ) {
1047 if ( $settings['useDynamicData'] && '' !== $settings['dynamicContentType'] ) {
1048 $dynamic_image_url = self::get_dynamic_background_image_url( $settings );
1049
1050 if ( $dynamic_image_url ) {
1051 $url = $dynamic_image_url;
1052 }
1053 }
1054
1055 return $url;
1056 }
1057
1058 /**
1059 * Add defaults for our Query settings.
1060 *
1061 * @param array $defaults Block defaults.
1062 */
1063 public function add_block_defaults( $defaults ) {
1064 $defaults['container']['useDynamicData'] = false;
1065 $defaults['container']['dynamicContentType'] = '';
1066 $defaults['container']['dynamicLinkType'] = '';
1067
1068 return $defaults;
1069 }
1070
1071 /**
1072 * Update button count depending on dynamic content.
1073 *
1074 * @param int $button_count How many buttons the block container has.
1075 * @param array $attributes The block attributes.
1076 * @param object $block The block data.
1077 */
1078 public function update_button_count( $button_count, $attributes, $block ) {
1079 $inner_blocks = $block->parsed_block['innerBlocks'];
1080
1081 foreach ( (array) $inner_blocks as $inner_block ) {
1082 $block_attributes = $inner_block['attrs'];
1083
1084 // Remove button from count if it has no dynamic content.
1085 if ( ! empty( $block_attributes['dynamicContentType'] ) && ! self::get_content( $block_attributes, $block ) ) {
1086 $button_count--;
1087 }
1088 }
1089
1090 return $button_count;
1091 }
1092 }
1093
1094 GenerateBlocks_Dynamic_Content::get_instance();
1095