diff --git a/CHANGELOG.md b/CHANGELOG.md index 246e1c40a..ad125d6a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ item (Added, Changed, Depreciated, Removed, Fixed, Security). - Fixed: Vertical Tabs block now scrolls the selected tab into view on mobile, respecting reduced motion preferences. [MOOSE-333](https://moderntribe.atlassian.net/browse/MOOSE-333) - Fixed: Removed top margin from spacer block - Added: Yoast Duplicate Post plugin v4.5 for easier content duplication in the editor. +- Updated: Dynamic blocks now pass data to PHP controller classes for processing, moving toward a more OOP/MVC approach. - Updated: Login logo styling ## [2025.12] diff --git a/wp-content/plugins/core/src/Components/Abstracts/Abstract_Block_Controller.php b/wp-content/plugins/core/src/Components/Abstracts/Abstract_Block_Controller.php new file mode 100644 index 000000000..ac10543b5 --- /dev/null +++ b/wp-content/plugins/core/src/Components/Abstracts/Abstract_Block_Controller.php @@ -0,0 +1,48 @@ + + */ + protected array $attributes; + protected string $block_classes; + protected string $block_styles; + private Block_Animation_Attributes|false $block_animation_attributes; + private string $block_animation_classes; + private string $block_animation_styles; + + public function __construct( array $args = [] ) { + $this->attributes = $args['attributes'] ?? []; + $this->block_classes = $args['block_classes'] ?? ''; + $this->block_styles = $args['block_styles'] ?? ''; + $this->block_animation_attributes = $this->attributes ? new Block_Animation_Attributes( $this->attributes ) : false; + $this->block_animation_classes = $this->block_animation_attributes ? $this->block_animation_attributes->get_classes() : ''; + $this->block_animation_styles = $this->block_animation_attributes ? $this->block_animation_attributes->get_styles() : ''; + } + + public function get_block_classes(): string { + $classes = $this->block_classes; + + if ( '' !== $this->block_animation_classes ) { + $classes .= ' ' . $this->block_animation_classes; + } + + return $classes; + } + + public function get_block_styles(): string { + $styles = $this->block_styles; + + if ( '' !== $this->block_animation_styles ) { + $styles .= ' ' . $this->block_animation_styles; + } + + return $styles; + } + +} diff --git a/wp-content/plugins/core/src/Components/Abstracts/Abstract_Card_Controller.php b/wp-content/plugins/core/src/Components/Abstracts/Abstract_Card_Controller.php new file mode 100644 index 000000000..dd56fb17e --- /dev/null +++ b/wp-content/plugins/core/src/Components/Abstracts/Abstract_Card_Controller.php @@ -0,0 +1,77 @@ +media_id = isset( $this->attributes['mediaId'] ) ? (int) $this->attributes['mediaId'] : 0; + $this->media_url = $this->attributes['mediaUrl'] ?? ''; + $this->title = $this->attributes['title'] ?? ''; + $this->description = $this->attributes['description'] ?? ''; + $this->link_url = $this->attributes['linkUrl'] ?? ''; + $this->link_opens_in_new_tab = $this->attributes['linkOpensInNewTab'] ?? false; + $this->link_text = $this->attributes['linkText'] ?? ''; + $this->link_a11y_label = $this->attributes['linkA11yLabel'] ?? ''; + } + + public function has_media(): bool { + return 0 !== $this->media_id || '' !== $this->media_url; + } + + public function get_media( string $image_size = 'large' ): string { + if ( 0 !== $this->media_id ) { + return wp_get_attachment_image( $this->media_id, $image_size ); + } + + if ( '' !== $this->media_url ) { + return '' . esc_attr__( 'Block placeholder image', 'tribe' ) . ''; + } + + return ''; + } + + public function get_title(): string { + return $this->title; + } + + public function has_description(): bool { + return '' !== $this->description; + } + + public function get_description(): string { + return $this->description; + } + + public function has_link_url(): bool { + return '' !== $this->link_url; + } + + public function get_link_url(): string { + return $this->link_url; + } + + public function does_link_open_in_new_tab(): bool { + return $this->link_opens_in_new_tab; + } + + public function get_link_text(): string { + return $this->link_text; + } + + public function get_link_a11y_label(): string { + return $this->link_a11y_label; + } + +} diff --git a/wp-content/plugins/core/src/Components/Abstract_Controller.php b/wp-content/plugins/core/src/Components/Abstracts/Abstract_Controller.php similarity index 80% rename from wp-content/plugins/core/src/Components/Abstract_Controller.php rename to wp-content/plugins/core/src/Components/Abstracts/Abstract_Controller.php index ec4453110..ea8112487 100644 --- a/wp-content/plugins/core/src/Components/Abstract_Controller.php +++ b/wp-content/plugins/core/src/Components/Abstracts/Abstract_Controller.php @@ -1,10 +1,7 @@ icon_picker = new Icon_Picker( $this->attributes ); + $this->icon_wrapper_styles = $this->icon_picker->get_icon_wrapper_styles(); + $this->icon_svg = $this->icon_picker->get_svg(); + } + + public function get_icon_wrapper_styles(): string { + return $this->icon_wrapper_styles; + } + + public function has_icon(): bool { + return ! empty( $this->icon_svg ); + } + + public function get_icon_svg(): string { + return $this->icon_svg; + } + +} diff --git a/wp-content/plugins/core/src/Components/Blocks/Image_Card_Controller.php b/wp-content/plugins/core/src/Components/Blocks/Image_Card_Controller.php new file mode 100644 index 000000000..26e3b0b90 --- /dev/null +++ b/wp-content/plugins/core/src/Components/Blocks/Image_Card_Controller.php @@ -0,0 +1,13 @@ +overlay_color = $this->attributes['overlayColor'] ?? '#0000001C'; + $this->overlay_hover_color = $this->attributes['overlayHoverColor'] ?? '#00000033'; + $this->card_uses_dark_theme = $this->attributes['cardUsesDarkTheme'] ?? false; + $this->block_styles .= sprintf( + '--card-image-overlay-color: %s;--card-image-overlay-hover-color: %s;', + $this->overlay_color, + $this->overlay_hover_color + ); + + if ( ! $this->card_uses_dark_theme ) { + return; + } + + $this->block_classes .= ' b-image-overlay-card--dark-theme'; + } + +} diff --git a/wp-content/plugins/core/src/Components/Blocks/Masthead_Search_Controller.php b/wp-content/plugins/core/src/Components/Blocks/Masthead_Search_Controller.php new file mode 100644 index 000000000..94401aed3 --- /dev/null +++ b/wp-content/plugins/core/src/Components/Blocks/Masthead_Search_Controller.php @@ -0,0 +1,63 @@ +search_icon = ''; + $this->search_icon_uri = trailingslashit( get_stylesheet_directory_uri() ) . '/assets/media/icons/search.svg'; + $this->search_icon_path = trailingslashit( get_stylesheet_directory() ) . '/assets/media/icons/search.svg'; + } + + public function get_search_icon(): string { + // If the file doesn't exist or we have already loaded it, return early + if ( ! file_exists( $this->search_icon_path ) || $this->search_icon !== '' ) { + return $this->search_icon; + } + + // Attempt to get the file contents from the file system + $this->search_icon = file_get_contents( $this->search_icon_path ); + + // Fallback to wp_remote_get if file_get_contents fails + if ( $this->search_icon === false ) { + $response = wp_remote_get( $this->search_icon_uri ); + + if ( ! is_wp_error( $response ) ) { + // wp_remote_retrieve_body returns an empty string on failure, so it's fine to end here + $this->search_icon = wp_remote_retrieve_body( $response ); + } + } + + return $this->search_icon; + } + + public function get_toggle_button_a11y_label(): string { + return __( 'Toggle Search Overlay', 'tribe' ); + } + + public function get_form_action(): string { + return home_url(); + } + + public function get_label_text(): string { + return __( 'Search', 'tribe' ); + } + + public function get_input_placeholder(): string { + return __( 'What are you looking for?', 'tribe' ); + } + + public function get_submit_button_text(): string { + return __( 'Search', 'tribe' ); + } + +} diff --git a/wp-content/plugins/core/src/Components/Blocks/Post_Card_Controller.php b/wp-content/plugins/core/src/Components/Blocks/Post_Card_Controller.php new file mode 100644 index 000000000..a8eb0bc66 --- /dev/null +++ b/wp-content/plugins/core/src/Components/Blocks/Post_Card_Controller.php @@ -0,0 +1,28 @@ +set_post( $args['post_id'] ?? 0 ); + + $this->layout = $this->attributes['layout'] ?? 'vertical'; + $this->heading_level = $this->attributes['heading_level'] ?? 'h3'; + $this->block_classes .= " c-post-card--layout-{$this->layout}"; + } + + public function get_heading_level(): string { + return $this->heading_level; + } + +} diff --git a/wp-content/plugins/core/src/Components/Blocks/Related_Posts_Controller.php b/wp-content/plugins/core/src/Components/Blocks/Related_Posts_Controller.php new file mode 100644 index 000000000..18d27ae52 --- /dev/null +++ b/wp-content/plugins/core/src/Components/Blocks/Related_Posts_Controller.php @@ -0,0 +1,89 @@ + + */ + private array $query_args; + + /** + * @var array + */ + private array $chosen_posts; + private int|false $post_id; + private bool $has_automatic_selection; + private int $posts_to_show; + private string $block_layout; + private \WP_Query $query; + + public function __construct( array $args = [] ) { + parent::__construct( $args ); + + $this->attributes = $args['attributes'] ?? []; + $this->post_id = get_the_ID(); + $this->has_automatic_selection = $this->attributes['hasAutomaticSelection'] ?? true; + $this->chosen_posts = $this->attributes['chosenPosts'] ?? []; + $this->posts_to_show = $this->attributes['postsToShow'] ? intval( $this->attributes['postsToShow'] ) : 3; + $this->block_layout = $this->attributes['layout'] ?? 'grid'; + + $this->block_classes .= " b-related-posts--layout-{$this->block_layout}"; + + $this->set_query_args(); + $this->set_query(); + } + + public function should_bail_early(): bool { + return ! $this->has_automatic_selection && empty( $this->chosen_posts ); + } + + public function get_query(): \WP_Query { + return $this->query; + } + + private function set_query_args(): void { + $this->query_args = [ + 'post_type' => Post::NAME, + 'post_status' => 'publish', + ]; + + if ( ! $this->has_automatic_selection ) { + $this->query_args = array_merge( $this->query_args, [ + 'post__in' => array_map( static fn( $post ): int => intval( $post['id'] ), $this->chosen_posts ), + 'orderby' => 'post__in', + ] ); + + return; + } + + $this->query_args = array_merge( $this->query_args, [ + 'posts_per_page' => $this->posts_to_show, + 'post__not_in' => [ $this->post_id ], + ] ); + + $post_terms = get_the_terms( $this->post_id, Category::NAME ); + + if ( empty( $post_terms ) || is_wp_error( $post_terms ) ) { + return; + } + + $term_ids = wp_list_pluck( $post_terms, 'term_id' ); + + $this->query_args['tax_query'][] = [ + 'taxonomy' => Category::NAME, + 'field' => 'term_id', + 'terms' => $term_ids, + ]; + } + + private function set_query(): void { + $this->query = new \WP_Query( $this->query_args ); + } + +} diff --git a/wp-content/plugins/core/src/Components/Blocks/Search_Card_Controller.php b/wp-content/plugins/core/src/Components/Blocks/Search_Card_Controller.php new file mode 100644 index 000000000..799e90ab3 --- /dev/null +++ b/wp-content/plugins/core/src/Components/Blocks/Search_Card_Controller.php @@ -0,0 +1,31 @@ +set_post( $args['post_id'] ?? 0 ); + + $this->post_type = get_post_type( $this->post_id ); + $this->post_type_object = get_post_type_object( $this->post_type ); + } + + public function has_post_type(): bool { + return null !== $this->post_type_object; + } + + public function get_post_type_name(): string { + return $this->post_type_object->labels->singular_name; + } + +} diff --git a/wp-content/plugins/core/src/Blocks/Helpers/Terms_Block.php b/wp-content/plugins/core/src/Components/Blocks/Terms_Block_Controller.php similarity index 59% rename from wp-content/plugins/core/src/Blocks/Helpers/Terms_Block.php rename to wp-content/plugins/core/src/Components/Blocks/Terms_Block_Controller.php index b172ec02e..a89ae7d47 100644 --- a/wp-content/plugins/core/src/Blocks/Helpers/Terms_Block.php +++ b/wp-content/plugins/core/src/Components/Blocks/Terms_Block_Controller.php @@ -1,26 +1,30 @@ taxonomy = $block_attributes['taxonomyToUse'] ?? 'category'; - $this->only_primary_term = $block_attributes['onlyPrimaryTerm'] ?? false; - $this->has_links = $block_attributes['hasLinks'] ?? false; + public function __construct( array $args = [] ) { + parent::__construct( $args ); + + $this->taxonomy = $this->attributes['taxonomyToUse'] ?? Category::NAME; + $this->only_primary_term = $this->attributes['onlyPrimaryTerm'] ?? false; + $this->has_links = $this->attributes['hasLinks'] ?? false; } /** diff --git a/wp-content/plugins/core/src/Components/Traits/Post_Data.php b/wp-content/plugins/core/src/Components/Traits/Post_Data.php new file mode 100644 index 000000000..785645073 --- /dev/null +++ b/wp-content/plugins/core/src/Components/Traits/Post_Data.php @@ -0,0 +1,93 @@ +post_id = $post_id; + $this->image_id = get_post_thumbnail_id( $this->post_id ); + $this->primary_category = $this->get_primary_term( $this->post_id, Category::NAME ); + $this->post_title = get_the_title( $this->post_id ); + $this->author_id = get_post_field( 'post_author', $this->post_id ); + $this->date = get_the_date( 'M j, Y', $this->post_id ); + $this->excerpt = get_the_excerpt( $this->post_id ); + $this->permalink = get_the_permalink( $this->post_id ); + + $this->set_post_author(); + } + + public function has_media(): bool { + return false !== $this->image_id && 0 !== $this->image_id; + } + + public function get_media( string $image_size = 'large' ): string { + return wp_get_attachment_image( $this->image_id, $image_size ); + } + + public function has_primary_category(): bool { + return null !== $this->primary_category; + } + + public function get_primary_category_name(): string { + return $this->has_primary_category() ? $this->primary_category->name : ''; + } + + public function get_post_title(): string { + return $this->post_title; + } + + public function has_post_author(): bool { + return '' !== $this->author; + } + + public function get_post_author(): string { + return $this->author; + } + + public function has_post_date(): bool { + return '' !== $this->date; + } + + public function get_post_date(): string { + return $this->date; + } + + public function has_post_excerpt(): bool { + return '' !== $this->excerpt; + } + + + public function get_post_excerpt(): string { + return $this->excerpt; + } + + public function get_post_permalink(): string { + return $this->permalink; + } + + private function set_post_author(): void { + if ( '' === $this->author_id ) { + $this->author = ''; + + return; + } + + $this->author = get_the_author_meta( 'display_name', (int) $this->author_id ); + } + +} diff --git a/wp-content/plugins/core/src/Templates/Traits/Primary_Term.php b/wp-content/plugins/core/src/Components/Traits/Primary_Term.php similarity index 97% rename from wp-content/plugins/core/src/Templates/Traits/Primary_Term.php rename to wp-content/plugins/core/src/Components/Traits/Primary_Term.php index ff430ba2a..c62a03a26 100644 --- a/wp-content/plugins/core/src/Templates/Traits/Primary_Term.php +++ b/wp-content/plugins/core/src/Components/Traits/Primary_Term.php @@ -1,6 +1,6 @@ get_styles(); -$animation_classes = $animation_attributes->get_classes(); -$icon_picker = new Icon_Picker( $attributes ); -$icon_wrapper_styles = $icon_picker->get_icon_wrapper_styles(); -$icon_svg = $icon_picker->get_svg(); -$classes = 'b-icon-card'; -$title = $attributes['title'] ?? ''; -$description = $attributes['description'] ?? ''; -$link_url = $attributes['linkUrl'] ?? ''; -$link_opens_in_new_tab = $attributes['linkOpensInNewTab'] ?? false; -$link_text = $attributes['linkText'] ?? ''; -$link_a11y_label = $attributes['linkA11yLabel'] ?? ''; - -if ( $animation_classes !== '' ) { - $classes .= ' ' . $animation_classes; -} +$c = Icon_Card_Controller::factory( [ + 'attributes' => $attributes, + 'block_classes' => 'b-icon-card', +] ); ?> -
esc_attr( $classes ), 'style' => $animation_styles ] ); ?>> +
esc_attr( $c->get_block_classes() ), 'style' => $c->get_block_styles() ] ); ?>>
- + has_icon() ) : ?>
-
- +
+ get_icon_svg(); ?>
-

+

get_title() ); ?>

- + has_description() ) : ?>
- + get_description() ) ); ?>
- + has_link_url() ) : ?>
- - class="b-icon-card__link-overlay" aria-label=""> + has_link_url() ) : ?> + does_link_open_in_new_tab() ? ' target="_blank" rel="noopener noreferrer"' : ''; ?> class="b-icon-card__link-overlay" aria-label="get_link_a11y_label() ); ?>">
diff --git a/wp-content/themes/core/blocks/tribe/image-card/render.php b/wp-content/themes/core/blocks/tribe/image-card/render.php index 7fdd08965..b1558e3d8 100644 --- a/wp-content/themes/core/blocks/tribe/image-card/render.php +++ b/wp-content/themes/core/blocks/tribe/image-card/render.php @@ -1,58 +1,44 @@ get_classes() !== '' ) { - $classes .= ' ' . $animation_attributes->get_classes(); -} +$c = Image_Card_Controller::factory( [ + 'attributes' => $attributes, + 'block_classes' => 'b-image-card', +] ); ?> -
esc_attr( $classes ), 'style' => $animation_attributes->get_styles() ] ); ?>> +
esc_attr( $c->get_block_classes() ), 'style' => $c->get_block_styles() ] ); ?>>
- -
- -
- + has_media() ) : ?>
- <?php echo esc_attr__( 'Block placeholder image', 'tribe' ); ?> + get_media() ); ?>
-

+

get_title() ); ?>

- + has_description() ) : ?>
- + get_description() ) ); ?>
- + has_description() ) : ?>
- - class="b-image-card__link-overlay" aria-label=""> + has_link_url() ) : ?> + does_link_open_in_new_tab() ? ' target="_blank" rel="noopener noreferrer"' : ''; ?> class="b-image-card__link-overlay" aria-label="get_link_a11y_label() ); ?>">
diff --git a/wp-content/themes/core/blocks/tribe/image-overlay-card/render.php b/wp-content/themes/core/blocks/tribe/image-overlay-card/render.php index 4a6aee1e8..1e3cdd62e 100644 --- a/wp-content/themes/core/blocks/tribe/image-overlay-card/render.php +++ b/wp-content/themes/core/blocks/tribe/image-overlay-card/render.php @@ -1,64 +1,35 @@ get_styles(); -$animation_classes = $animation_attributes->get_classes(); -$media_id = $attributes['mediaId'] ? (int) $attributes['mediaId'] : 0; // this returns a float by default so we need to cast it to int -$media_url = $attributes['mediaUrl'] ?? ''; -$overlay_color = $attributes['overlayColor'] ?? '#0000001C'; -$overlay_hover_color = $attributes['overlayHoverColor'] ?? '#00000033'; -$card_uses_dark_theme = $attributes['cardUsesDarkTheme']; -$title = $attributes['title'] ?? ''; -$link_url = $attributes['linkUrl'] ?? ''; -$link_opens_in_new_tab = $attributes['linkOpensInNewTab'] ?? false; -$link_text = $attributes['linkText'] ?? ''; -$link_a11y_label = $attributes['linkA11yLabel'] ?? ''; - -// add overlay color as CSS custom property -$styles = $animation_styles; -$styles .= "--card-image-overlay-color: {$overlay_color};"; -$styles .= "--card-image-overlay-hover-color: {$overlay_hover_color};"; - -// add dark theme class if applicable -if ( $card_uses_dark_theme ) { - $classes .= ' b-image-overlay-card--dark-theme'; -} - -// add animation attribute classes -if ( $animation_classes !== '' ) { - $classes .= ' ' . $animation_classes; -} +$c = Image_Overlay_Card_Controller::factory( [ + 'attributes' => $attributes, + 'block_classes' => 'b-image-overlay-card', +] ); ?> -
esc_attr( $classes ), 'style' => esc_attr( $styles ) ] ); ?>> - -
- -
- + diff --git a/wp-content/themes/core/blocks/tribe/masthead-search/render.php b/wp-content/themes/core/blocks/tribe/masthead-search/render.php index 01e59f95b..2617bbc0c 100644 --- a/wp-content/themes/core/blocks/tribe/masthead-search/render.php +++ b/wp-content/themes/core/blocks/tribe/masthead-search/render.php @@ -1,34 +1,26 @@ $attributes, + 'block_classes' => 'masthead-search', +] ); ?> -