Plugin Directory

Changeset 3789508


Ignore:
Timestamp:
11/07/2026 12:09:08 PM (less than one hour ago)
Author:
theblockswp
Message:

Version 2.0.1: link fix after Websites Import, theme compatibility, breadcrumbs

Location:
theme-builder-for-elementor
Files:
30 edited
1 copied

Legend:

Unmodified
Added
Removed
  • theme-builder-for-elementor/tags/2.0.1/assets/css/frontend.css

    r3783780 r3789508  
    88.tbfe-location-404,
    99.tbfe-location-content { display: block; width: 100%; }
     10
     11/* Themes style their own #site-header, #site-footer, #content and main (flex rows, paddings, floats of a sidebar
     12   layout); the elements of the plugin carry the same ids, so these rules beat the theme's. Position and background
     13   stay free for the sticky and transparent header. */
     14#site-header.tbfe-location,
     15#site-footer.tbfe-location { display: block; float: none; clear: both; box-sizing: border-box; width: 100%; max-width: none; height: auto; min-height: 0; margin: 0; padding: 0; border: 0; }
     16
     17#content.tbfe-location,
     18main.tbfe-location { float: none; clear: both; box-sizing: border-box; width: 100%; max-width: none; }
    1019
    1120.tbfe-container,
  • theme-builder-for-elementor/tags/2.0.1/assets/css/frontend.min.css

    r3783780 r3789508  
    1 .tbfe-location-404,.tbfe-location-archive,.tbfe-location-content,.tbfe-location-footer,.tbfe-location-header,.tbfe-location-single{display:block;width:100%}.tbfe-archive--boxed,.tbfe-container{box-sizing:border-box;width:100%;max-width:var(--tbfe-container, 1140px);margin-right:auto;margin-left:auto;padding-right:20px;padding-left:20px}.tbfe-archive{display:grid;grid-template-columns:minmax(0,1fr);gap:40px;padding-top:40px;padding-bottom:40px}.tbfe-archive--full{padding-right:20px;padding-left:20px}.tbfe-archive--sidebar{grid-template-columns:minmax(0,3fr) minmax(0,1fr)}.tbfe-archive__posts{display:flex;flex-direction:column;gap:30px;min-width:0}.tbfe-archive__item{margin:0}.tbfe-archive__sidebar{min-width:0}@media (max-width:767px){.tbfe-archive--sidebar{grid-template-columns:minmax(0,1fr)}}.tbfe-password{padding-top:40px;padding-bottom:40px}.tbfe-header--sticky,.tbfe-header--transparent{z-index:990;transition:background-color .3s ease,box-shadow .3s ease,transform .3s ease}.tbfe-header--sticky{position:sticky;top:var(--wp-admin--admin-bar--height, 0px)}.tbfe-header--transparent{position:absolute;top:var(--wp-admin--admin-bar--height, 0px);right:0;left:0;background-color:transparent}.tbfe-header--transparent.tbfe-header--sticky{position:fixed}.tbfe-header--sticky.is-scrolled{background-color:var(--tbfe-scrolled-bg, transparent)}.tbfe-header--shadow.is-scrolled{box-shadow:0 6px 24px rgba(15,23,42,.1)}.tbfe-header--hide.is-hidden{transform:translateY(-110%)}.tbfe-header--shrink .elementor-element-populated,.tbfe-header--shrink .elementor>.e-con,.tbfe-header--shrink .elementor>.elementor-section,.tbfe-header--shrink .tbfe-site-logo img{transition:padding .3s ease,min-height .3s ease,max-height .3s ease}.tbfe-header--shrink.is-scrolled .elementor>.e-con,.tbfe-header--shrink.is-scrolled .elementor>.elementor-section{padding-top:4px!important;padding-bottom:4px!important}.tbfe-header--shrink.is-scrolled .elementor-element-populated{padding-top:2px!important;padding-bottom:2px!important}.tbfe-header--shrink.is-scrolled .elementor-container{min-height:0!important}.tbfe-header--shrink.is-scrolled .tbfe-site-logo img{width:auto!important;max-height:var(--tbfe-shrink-logo, 36px)}@media (max-width:600px){.tbfe-header--sticky,.tbfe-header--transparent{top:0}}@media (max-width:1024px){.tbfe-header--transparent.tbfe-transparent-desktop.tbfe-header--sticky{position:sticky;top:0}.tbfe-header--transparent.tbfe-transparent-desktop,.tbfe-location-header.tbfe-header--sticky.tbfe-sticky-desktop{position:relative;top:auto}.tbfe-location-header.tbfe-header--transparent.tbfe-sticky-desktop:not(.tbfe-transparent-desktop){position:absolute;top:0}}@media (max-width:767px){.tbfe-header--transparent.tbfe-transparent-desktop-tablet.tbfe-header--sticky{position:sticky;top:0}.tbfe-header--transparent.tbfe-transparent-desktop-tablet,.tbfe-location-header.tbfe-header--sticky.tbfe-sticky-desktop-tablet{position:relative;top:auto}.tbfe-location-header.tbfe-header--transparent.tbfe-sticky-desktop-tablet.tbfe-transparent-all{position:absolute;top:0}}
     1.tbfe-location-404,.tbfe-location-archive,.tbfe-location-content,.tbfe-location-footer,.tbfe-location-header,.tbfe-location-single{display:block;width:100%}#site-footer.tbfe-location,#site-header.tbfe-location{display:block;height:auto;min-height:0;margin:0;padding:0;border:0}#content.tbfe-location,#site-footer.tbfe-location,#site-header.tbfe-location,main.tbfe-location{float:none;clear:both;box-sizing:border-box;width:100%;max-width:none}.tbfe-archive--boxed,.tbfe-container{box-sizing:border-box;width:100%;max-width:var(--tbfe-container, 1140px);margin-right:auto;margin-left:auto;padding-right:20px;padding-left:20px}.tbfe-archive{display:grid;grid-template-columns:minmax(0,1fr);gap:40px;padding-top:40px;padding-bottom:40px}.tbfe-archive--full{padding-right:20px;padding-left:20px}.tbfe-archive--sidebar{grid-template-columns:minmax(0,3fr) minmax(0,1fr)}.tbfe-archive__posts{display:flex;flex-direction:column;gap:30px;min-width:0}.tbfe-archive__item{margin:0}.tbfe-archive__sidebar{min-width:0}@media (max-width:767px){.tbfe-archive--sidebar{grid-template-columns:minmax(0,1fr)}}.tbfe-password{padding-top:40px;padding-bottom:40px}.tbfe-header--sticky,.tbfe-header--transparent{z-index:990;transition:background-color .3s ease,box-shadow .3s ease,transform .3s ease}.tbfe-header--sticky{position:sticky;top:var(--wp-admin--admin-bar--height, 0px)}.tbfe-header--transparent{position:absolute;top:var(--wp-admin--admin-bar--height, 0px);right:0;left:0;background-color:transparent}.tbfe-header--transparent.tbfe-header--sticky{position:fixed}.tbfe-header--sticky.is-scrolled{background-color:var(--tbfe-scrolled-bg, transparent)}.tbfe-header--shadow.is-scrolled{box-shadow:0 6px 24px rgba(15,23,42,.1)}.tbfe-header--hide.is-hidden{transform:translateY(-110%)}.tbfe-header--shrink .elementor-element-populated,.tbfe-header--shrink .elementor>.e-con,.tbfe-header--shrink .elementor>.elementor-section,.tbfe-header--shrink .tbfe-site-logo img{transition:padding .3s ease,min-height .3s ease,max-height .3s ease}.tbfe-header--shrink.is-scrolled .elementor>.e-con,.tbfe-header--shrink.is-scrolled .elementor>.elementor-section{padding-top:4px!important;padding-bottom:4px!important}.tbfe-header--shrink.is-scrolled .elementor-element-populated{padding-top:2px!important;padding-bottom:2px!important}.tbfe-header--shrink.is-scrolled .elementor-container{min-height:0!important}.tbfe-header--shrink.is-scrolled .tbfe-site-logo img{width:auto!important;max-height:var(--tbfe-shrink-logo, 36px)}@media (max-width:600px){.tbfe-header--sticky,.tbfe-header--transparent{top:0}}@media (max-width:1024px){.tbfe-header--transparent.tbfe-transparent-desktop.tbfe-header--sticky{position:sticky;top:0}.tbfe-header--transparent.tbfe-transparent-desktop,.tbfe-location-header.tbfe-header--sticky.tbfe-sticky-desktop{position:relative;top:auto}.tbfe-location-header.tbfe-header--transparent.tbfe-sticky-desktop:not(.tbfe-transparent-desktop){position:absolute;top:0}}@media (max-width:767px){.tbfe-header--transparent.tbfe-transparent-desktop-tablet.tbfe-header--sticky{position:sticky;top:0}.tbfe-header--transparent.tbfe-transparent-desktop-tablet,.tbfe-location-header.tbfe-header--sticky.tbfe-sticky-desktop-tablet{position:relative;top:auto}.tbfe-location-header.tbfe-header--transparent.tbfe-sticky-desktop-tablet.tbfe-transparent-all{position:absolute;top:0}}
  • theme-builder-for-elementor/tags/2.0.1/includes/conditions/class-resolver.php

    r3783780 r3789508  
    132132                }
    133133
     134                // Products and the shop of WooCommerce: All singular / All archives of a blog Single or Archive template do
     135                // not count there. The blog layout would take the add to cart and the shop loop away; the theme's shop (or the
     136                // product templates of PRO) stay, unless a condition chooses them, like Products.
     137                $shop = in_array( $location, array( 'single', 'archive' ), true ) && function_exists( 'is_woocommerce' ) && is_woocommerce();
     138
    134139                $best       = 0;
    135140                $best_score = -1;
    136141                foreach ( $index[ $location ] as $id => $rules ) {
     142                        if ( $shop && in_array( Template::type( $id ), array( 'single', 'archive' ), true ) ) {
     143                                $rules = array_filter(
     144                                        $rules,
     145                                        static function ( $rule ) {
     146                                                return 'exclude' === $rule['type'] || ! in_array( $rule['name'], array( 'singular', 'archive' ), true );
     147                                        }
     148                                );
     149                        }
    137150                        $score = self::rules_score( $rules );
    138151                        if ( $score > $best_score ) {
  • theme-builder-for-elementor/tags/2.0.1/includes/elementor/widgets/class-breadcrumbs.php

    r3783780 r3789508  
    162162        }
    163163
     164        /**
     165         * Breadcrumbs of Yoast SEO or Rank Math, only when their breadcrumbs are switched on.
     166         * Empty string otherwise, so the built-in trail is used.
     167         */
     168        private function seo_plugin_trail() {
     169                if ( function_exists( 'yoast_breadcrumb' ) ) {
     170                        $enabled = current_theme_supports( 'yoast-seo-breadcrumbs' )
     171                                || ( class_exists( 'WPSEO_Options' ) && \WPSEO_Options::get( 'breadcrumbs-enable', false ) );
     172                        if ( $enabled ) {
     173                                $html = (string) yoast_breadcrumb( '', '', false );
     174                                if ( '' !== trim( wp_strip_all_tags( $html ) ) ) {
     175                                        return $html;
     176                                }
     177                        }
     178                }
     179                if ( function_exists( 'rank_math_get_breadcrumbs' ) ) {
     180                        $enabled = method_exists( '\RankMath\Helper', 'is_breadcrumbs_enabled' )
     181                                ? \RankMath\Helper::is_breadcrumbs_enabled()
     182                                : current_theme_supports( 'rank-math-breadcrumbs' );
     183                        if ( $enabled ) {
     184                                $html = (string) rank_math_get_breadcrumbs();
     185                                if ( '' !== trim( wp_strip_all_tags( $html ) ) ) {
     186                                        return $html;
     187                                }
     188                        }
     189                }
     190                return '';
     191        }
     192
    164193        protected function render() {
    165194                $settings = $this->get_settings_for_display();
    166195
    167196                if ( 'auto' === $settings['source'] && ! Context::is_template_preview() ) {
    168                         if ( function_exists( 'yoast_breadcrumb' ) ) {
    169                                 yoast_breadcrumb( '<nav class="tbfe-breadcrumbs" aria-label="' . esc_attr__( 'Breadcrumbs', 'theme-builder-for-elementor' ) . '">', '</nav>' );
    170                                 return;
    171                         }
    172                         if ( function_exists( 'rank_math_the_breadcrumbs' ) ) {
    173                                 echo '<nav class="tbfe-breadcrumbs" aria-label="' . esc_attr__( 'Breadcrumbs', 'theme-builder-for-elementor' ) . '">';
    174                                 rank_math_the_breadcrumbs();
    175                                 echo '</nav>';
     197                        $seo = $this->seo_plugin_trail();
     198                        if ( '' !== $seo ) {
     199                                echo '<nav class="tbfe-breadcrumbs" aria-label="' . esc_attr__( 'Breadcrumbs', 'theme-builder-for-elementor' ) . '">' . wp_kses_post( $seo ) . '</nav>';
    176200                                return;
    177201                        }
  • theme-builder-for-elementor/tags/2.0.1/includes/elementor/widgets/class-posts.php

    r3783780 r3789508  
    575575                while ( $query->have_posts() ) {
    576576                        $query->the_post();
    577                         echo '<article class="' . esc_attr( implode( ' ', get_post_class( 'tbfe-posts__item' ) ) ) . '">';
     577                        echo '<article class="' . esc_attr( implode( ' ', Renderer::post_class( 'tbfe-posts__item' ) ) ) . '">';
    578578                        if ( $template ) {
    579579                                Renderer::render_template( $template );
  • theme-builder-for-elementor/tags/2.0.1/includes/library/class-library.php

    r3783780 r3789508  
    475475                }
    476476
    477                 $posts = array_merge( wp_list_pluck( $pages, 'id' ), $templates );
    478                 foreach ( $posts as $post_id ) {
    479                         $document = \Elementor\Plugin::$instance->documents->get( (int) $post_id, false );
     477                $posts = array_map( 'absint', array_merge( wp_list_pluck( $pages, 'id' ), $templates ) );
     478                $next  = 0;
     479                while ( $next < count( $posts ) ) {
     480                        $post_id  = $posts[ $next++ ];
     481                        $document = \Elementor\Plugin::$instance->documents->get( $post_id, false );
    480482                        if ( ! $document ) {
    481483                                continue;
    482484                        }
    483485                        $elements = $document->get_elements_data();
    484                         $changed  = false;
     486                        // Templates imported with the content (slides of a Slider, Section templates, Post Cards) get their links
     487                        // updated too.
     488                        foreach ( References::ids( $elements ) as $ref ) {
     489                                if ( ! in_array( $ref, $posts, true ) && Template::is_template( $ref ) && get_post_meta( $ref, '_tbfe_library_item', true ) ) {
     490                                        $posts[] = $ref;
     491                                }
     492                        }
     493                        $changed = false;
    485494                        $elements = self::walk(
    486495                                $elements,
  • theme-builder-for-elementor/tags/2.0.1/includes/library/class-references.php

    r3783780 r3789508  
    4949                }
    5050                return $elements;
     51        }
     52
     53        /**
     54         * Templates of this site that Elementor content refers to (Post Card, Section Template, slides of a Slider).
     55         *
     56         * @return int[]
     57         */
     58        public static function ids( array $elements ) {
     59                $ids = array();
     60                foreach ( $elements as $element ) {
     61                        if ( ! is_array( $element ) ) {
     62                                continue;
     63                        }
     64                        $type     = isset( $element['widgetType'] ) ? $element['widgetType'] : '';
     65                        $settings = isset( $element['settings'] ) && is_array( $element['settings'] ) ? $element['settings'] : array();
     66                        if ( 'tbfe-posts' === $type && ! empty( $settings['card_template'] ) ) {
     67                                $ids[] = absint( $settings['card_template'] );
     68                        } elseif ( 'tbfe-template' === $type && ! empty( $settings['template_id'] ) ) {
     69                                $ids[] = absint( $settings['template_id'] );
     70                        } elseif ( 'tbfe-slider' === $type && ! empty( $settings['slides'] ) && is_array( $settings['slides'] ) ) {
     71                                foreach ( $settings['slides'] as $slide ) {
     72                                        if ( ! empty( $slide['template_id'] ) ) {
     73                                                $ids[] = absint( $slide['template_id'] );
     74                                        }
     75                                }
     76                        }
     77                        if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) {
     78                                $ids = array_merge( $ids, self::ids( $element['elements'] ) );
     79                        }
     80                }
     81                return array_values( array_unique( array_filter( $ids ) ) );
    5182        }
    5283
  • theme-builder-for-elementor/tags/2.0.1/includes/render/class-renderer.php

    r3783780 r3789508  
    160160         * @param int $id Template ID.
    161161         */
     162        /**
     163         * Classes of the post element in the plugin's layouts: get_post_class() without the post_class filters of the
     164         * theme. Themes add their own card and grid classes there (Astra: ast-article-post, ast-grid-common-col) and
     165         * style them as boxes, which put a theme box around the cards of the Posts widget. Plugins (WooCommerce) stay.
     166         *
     167         * @param string $class Own class.
     168         * @return string[]
     169         */
     170        public static function post_class( $class = '' ) {
     171                global $wp_filter;
     172                $removed = array();
     173                if ( isset( $wp_filter['post_class'] ) ) {
     174                        $dirs = array_unique( array( wp_normalize_path( get_template_directory() ) . '/', wp_normalize_path( get_stylesheet_directory() ) . '/' ) );
     175                        foreach ( $wp_filter['post_class']->callbacks as $priority => $callbacks ) {
     176                                foreach ( $callbacks as $callback ) {
     177                                        $file = self::callback_file( $callback['function'] );
     178                                        foreach ( $dirs as $dir ) {
     179                                                if ( $file && 0 === strpos( $file, $dir ) ) {
     180                                                        $removed[] = array( $callback['function'], $priority, $callback['accepted_args'] );
     181                                                        break;
     182                                                }
     183                                        }
     184                                }
     185                        }
     186                }
     187                foreach ( $removed as $filter ) {
     188                        remove_filter( 'post_class', $filter[0], $filter[1] );
     189                }
     190                $classes = get_post_class( $class );
     191                foreach ( $removed as $filter ) {
     192                        add_filter( 'post_class', $filter[0], $filter[1], $filter[2] );
     193                }
     194                return $classes;
     195        }
     196
     197        /**
     198         * File that defines a callback ('' when unknown).
     199         */
     200        private static function callback_file( $callback ) {
     201                try {
     202                        if ( is_string( $callback ) && false !== strpos( $callback, '::' ) ) {
     203                                $callback = explode( '::', $callback, 2 );
     204                        }
     205                        if ( is_array( $callback ) && 2 === count( $callback ) ) {
     206                                $reflection = new \ReflectionMethod( $callback[0], $callback[1] );
     207                        } elseif ( $callback instanceof \Closure || ( is_string( $callback ) && function_exists( $callback ) ) ) {
     208                                $reflection = new \ReflectionFunction( $callback );
     209                        } else {
     210                                return '';
     211                        }
     212                        return wp_normalize_path( (string) $reflection->getFileName() );
     213                } catch ( \ReflectionException $e ) {
     214                        return '';
     215                }
     216        }
     217
    162218        public static function render_template( $id ) {
    163219                $id = (int) $id;
  • theme-builder-for-elementor/tags/2.0.1/includes/render/class-theme-support.php

    r3783780 r3789508  
    44 *
    55 * Classic themes: on get_header / get_footer the plugin prints its own header (with wp_head) or footer (with
    6  * wp_footer) and loads the theme's header.php / footer.php into a buffer that is thrown away.
     6 * wp_footer) and loads the theme's header.php / footer.php into a buffer that is thrown away. On pages the theme
     7 * renders itself, the content wrappers that header.php leaves open are printed again after the plugin's header and
     8 * closed before its footer, so the theme's content keeps its container.
    79 * Block themes: the header and footer template parts are replaced, and the plugin's page templates print the whole
    810 * document themselves.
    … …  
    1921class Theme_Support {
    2022
     23        /** @var bool The page is rendered by a page template of the plugin (page_start()). */
     24        private static $plugin_page = false;
     25
     26        /** @var array[] Content wrappers of the theme reopened after the plugin's header: tag, html. */
     27        private static $wrappers = array();
     28
    2129        public static function init() {
    2230                add_action( 'get_header', array( __CLASS__, 'replace_header' ), 99 );
    … …  
    3341                        return;
    3442                }
     43                self::remove_theme_part( 'header' );
    3544                require TBFE_PATH . 'templates/parts/header.php';
    3645
    … …  
    4453                ob_start();
    4554                locate_template( $templates, true );
    46                 ob_end_clean();
     55                $html = ob_get_clean();
     56
     57                // Pages the theme renders itself (no page template of the plugin): its content wrappers stay.
     58                if ( ! self::$plugin_page ) {
     59                        self::$wrappers = self::content_wrappers( $html );
     60                        foreach ( self::$wrappers as $wrapper ) {
     61                                echo $wrapper['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- opening tag printed by the theme.
     62                        }
     63                }
    4764        }
    4865
    … …  
    5168                        return;
    5269                }
     70                self::remove_theme_part( 'footer' );
     71                foreach ( array_reverse( self::$wrappers ) as $wrapper ) {
     72                        printf( '</%s>', esc_html( $wrapper['tag'] ) );
     73                }
     74                self::$wrappers = array();
    5375                require TBFE_PATH . 'templates/parts/footer.php';
    5476
    … …  
    6385                locate_template( $templates, true );
    6486                ob_end_clean();
     87        }
     88
     89        /**
     90         * Elements that the theme's header.php leaves open after its header (Astra: #content.site-content >
     91         * .ast-container, Twenty Twenty-One: #content > #primary > main…) and closes in footer.php. Wrappers around the
     92         * theme's header (#page) are left out, the plugin's header is not inside them on any page.
     93         *
     94         * @param string $html Output of header.php.
     95         * @return array[] tag, html (opening tag), outermost first.
     96         */
     97        public static function content_wrappers( $html ) {
     98                if ( ! class_exists( 'WP_HTML_Tag_Processor' ) || false === stripos( $html, '<body' ) ) {
     99                        return array();
     100                }
     101                $void      = array( 'AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'LINK', 'META', 'SOURCE', 'TRACK', 'WBR' );
     102                $processor = new \WP_HTML_Tag_Processor( $html );
     103                $stack     = array();
     104                $in_body   = false;
     105                $boundary  = null; // Depth of the parent of the theme's header.
     106
     107                while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
     108                        $tag = $processor->get_tag();
     109                        if ( ! $in_body ) {
     110                                $in_body = 'BODY' === $tag && ! $processor->is_tag_closer();
     111                                continue;
     112                        }
     113                        if ( $processor->is_tag_closer() ) {
     114                                // Closes the last element with that tag (and the ones left open inside it, like svg paths).
     115                                for ( $i = count( $stack ) - 1; $i >= 0 && $stack[ $i ]['tag'] !== $tag; $i-- ) {
     116                                }
     117                                if ( $i < 0 ) {
     118                                        continue;
     119                                }
     120                                $element = $stack[ $i ];
     121                                $stack   = array_slice( $stack, 0, $i );
     122                                if ( $element['header'] && ( null === $boundary || $i < $boundary ) ) {
     123                                        $boundary = $i;
     124                                }
     125                                continue;
     126                        }
     127                        if ( in_array( $tag, $void, true ) || in_array( $tag, array( 'BODY', 'HTML' ), true ) ) {
     128                                continue;
     129                        }
     130
     131                        $attributes = '';
     132                        foreach ( (array) $processor->get_attribute_names_with_prefix( '' ) as $name ) {
     133                                $value       = $processor->get_attribute( $name );
     134                                $attributes .= true === $value ? ' ' . $name : sprintf( ' %s="%s"', $name, esc_attr( $value ) );
     135                        }
     136                        $id      = (string) $processor->get_attribute( 'id' );
     137                        $class   = ' ' . $processor->get_attribute( 'class' ) . ' ';
     138                        $stack[] = array(
     139                                'tag'    => $tag,
     140                                'html'   => sprintf( '<%s%s>', strtolower( $tag ), $attributes ),
     141                                'header' => 'HEADER' === $tag || in_array( $id, array( 'masthead', 'site-header', 'header' ), true ) || false !== strpos( $class, ' site-header ' ),
     142                        );
     143                }
     144
     145                $wrappers = array_slice( $stack, (int) $boundary );
     146                foreach ( $wrappers as &$wrapper ) {
     147                        $wrapper['tag'] = strtolower( $wrapper['tag'] );
     148                        unset( $wrapper['header'] );
     149                }
     150                unset( $wrapper );
     151
     152                /**
     153                 * Filters the content wrappers of the theme reopened after the plugin's header on pages the theme renders.
     154                 *
     155                 * @param array[] $wrappers array( array( 'tag' => 'div', 'html' => '<div id="content">' ), … ).
     156                 * @param string  $html     Output of the theme's header.php.
     157                 */
     158                return (array) apply_filters( 'tbfe/theme_support/content_wrappers', $wrappers, $html );
     159        }
     160
     161        /**
     162         * Classic themes that print their header on wp_body_open or their footer on wp_footer (not in header.php /
     163         * footer.php), like the Envo themes: those callbacks are removed while the plugin's header or footer is shown
     164         * and on canvas pages.
     165         */
     166        public static function remove_theme_part( $location ) {
     167                $prefix = str_replace( '-', '_', get_template() );
     168                $parts  = array(
     169                        'header' => array( array( 'wp_body_open', $prefix . '_body_header_code' ) ),
     170                        'footer' => array( array( 'wp_footer', $prefix . '_wp_footer_code' ) ),
     171                );
     172
     173                /**
     174                 * Filters the callbacks of the theme that print its header or footer outside header.php / footer.php.
     175                 *
     176                 * @param array  $callbacks array( array( hook, callback ), … ).
     177                 * @param string $location  header or footer.
     178                 */
     179                $callbacks = apply_filters( 'tbfe/theme_support/theme_part_callbacks', $parts[ $location ], $location );
     180
     181                foreach ( (array) $callbacks as $callback ) {
     182                        list( $hook, $function ) = $callback;
     183                        $priority                = has_action( $hook, $function );
     184                        if ( false !== $priority ) {
     185                                remove_action( $hook, $function, $priority );
     186                        }
     187                }
    65188        }
    66189
    … …  
    123246         */
    124247        public static function page_start() {
     248                self::$plugin_page = true;
    125249                if ( ! self::is_block_theme() ) {
    126250                        get_header();
  • theme-builder-for-elementor/tags/2.0.1/readme.txt

    r3783817 r3789508  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 2.0.0
     7Stable tag: 2.0.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    139139== Changelog ==
    140140
     141= 2.0.1 =
     142* Fix: after a Websites Import the buttons in sliders, section templates and post cards link to your site, not to the demo site.
     143* Fix: the Breadcrumbs widget uses the breadcrumbs of Yoast SEO or Rank Math only when they are switched on there, otherwise its own trail.
     144* Fix: Single and Archive templates for the blog are no longer used for WooCommerce products, the shop and product categories.
     145* Fix: themes that print their own header or footer through hooks (e.g. Envo One) no longer show it next to the header or footer of the plugin.
     146* Fix: header, footer and content of the plugin are no longer changed by theme styles (e.g. Royal Elementor Kit, PopularFX).
     147* Fix: on pages the theme renders, its content container is kept with the header and footer of the plugin.
     148* Fix: post cards of the Posts widget are no longer boxed again by the theme (e.g. Astra).
     149
    141150= 2.0.0 =
    142151* New: completely rewritten plugin with a new administration.
  • theme-builder-for-elementor/tags/2.0.1/templates/archive.php

    r3783780 r3789508  
    3737                                                the_post();
    3838                                                ?>
    39                                                 <article id="post-<?php the_ID(); ?>" <?php post_class( 'tbfe-archive__item' ); ?>>
     39                                                <article id="post-<?php the_ID(); ?>" class="<?php echo esc_attr( implode( ' ', Renderer::post_class( 'tbfe-archive__item' ) ) ); ?>">
    4040                                                        <?php Renderer::render_template( $tbfe_id ); ?>
    4141                                                </article>
  • theme-builder-for-elementor/tags/2.0.1/templates/canvas.php

    r3783780 r3789508  
    88
    99defined( 'ABSPATH' ) || exit;
     10
     11// No header and footer of the theme either.
     12TBFE\Render\Theme_Support::remove_theme_part( 'header' );
     13TBFE\Render\Theme_Support::remove_theme_part( 'footer' );
    1014
    1115require __DIR__ . '/parts/document-start.php';
  • theme-builder-for-elementor/tags/2.0.1/templates/full-width.php

    r3783780 r3789508  
    77 */
    88
     9use TBFE\Render\Renderer;
    910use TBFE\Render\Theme_Support;
    1011
    … …  
    1819                the_post();
    1920                ?>
    20                 <div <?php post_class( 'tbfe-page' ); ?>>
     21                <div class="<?php echo esc_attr( implode( ' ', Renderer::post_class( 'tbfe-page' ) ) ); ?>">
    2122                        <?php the_content(); ?>
    2223                </div>
  • theme-builder-for-elementor/tags/2.0.1/templates/single.php

    r3783780 r3789508  
    2222                }
    2323                ?>
    24                 <article id="post-<?php the_ID(); ?>" <?php post_class( 'tbfe-single' ); ?>>
     24                <article id="post-<?php the_ID(); ?>" class="<?php echo esc_attr( implode( ' ', Renderer::post_class( 'tbfe-single' ) ) ); ?>">
    2525                        <?php Renderer::render_location( 'single' ); ?>
    2626                </article>
  • theme-builder-for-elementor/tags/2.0.1/theme-builder-for-elementor.php

    r3783780 r3789508  
    44 * Plugin URI:        https://blocks-wp.com/theme-builder-for-elementor/
    55 * Description:       Build the header, footer, single posts and pages, archives and the 404 page of any theme with the free Elementor.
    6  * Version:           2.0.0
     6 * Version:           2.0.1
    77 * Requires at least: 6.5
    88 * Requires PHP:      7.4
    … …  
    2020defined( 'ABSPATH' ) || exit;
    2121
    22 define( 'TBFE_VERSION', '2.0.0' );
     22define( 'TBFE_VERSION', '2.0.1' );
    2323define( 'TBFE_FILE', __FILE__ );
    2424define( 'TBFE_PATH', plugin_dir_path( __FILE__ ) );
  • theme-builder-for-elementor/trunk/assets/css/frontend.css

    r3783780 r3789508  
    88.tbfe-location-404,
    99.tbfe-location-content { display: block; width: 100%; }
     10
     11/* Themes style their own #site-header, #site-footer, #content and main (flex rows, paddings, floats of a sidebar
     12   layout); the elements of the plugin carry the same ids, so these rules beat the theme's. Position and background
     13   stay free for the sticky and transparent header. */
     14#site-header.tbfe-location,
     15#site-footer.tbfe-location { display: block; float: none; clear: both; box-sizing: border-box; width: 100%; max-width: none; height: auto; min-height: 0; margin: 0; padding: 0; border: 0; }
     16
     17#content.tbfe-location,
     18main.tbfe-location { float: none; clear: both; box-sizing: border-box; width: 100%; max-width: none; }
    1019
    1120.tbfe-container,
  • theme-builder-for-elementor/trunk/assets/css/frontend.min.css

    r3783780 r3789508  
    1 .tbfe-location-404,.tbfe-location-archive,.tbfe-location-content,.tbfe-location-footer,.tbfe-location-header,.tbfe-location-single{display:block;width:100%}.tbfe-archive--boxed,.tbfe-container{box-sizing:border-box;width:100%;max-width:var(--tbfe-container, 1140px);margin-right:auto;margin-left:auto;padding-right:20px;padding-left:20px}.tbfe-archive{display:grid;grid-template-columns:minmax(0,1fr);gap:40px;padding-top:40px;padding-bottom:40px}.tbfe-archive--full{padding-right:20px;padding-left:20px}.tbfe-archive--sidebar{grid-template-columns:minmax(0,3fr) minmax(0,1fr)}.tbfe-archive__posts{display:flex;flex-direction:column;gap:30px;min-width:0}.tbfe-archive__item{margin:0}.tbfe-archive__sidebar{min-width:0}@media (max-width:767px){.tbfe-archive--sidebar{grid-template-columns:minmax(0,1fr)}}.tbfe-password{padding-top:40px;padding-bottom:40px}.tbfe-header--sticky,.tbfe-header--transparent{z-index:990;transition:background-color .3s ease,box-shadow .3s ease,transform .3s ease}.tbfe-header--sticky{position:sticky;top:var(--wp-admin--admin-bar--height, 0px)}.tbfe-header--transparent{position:absolute;top:var(--wp-admin--admin-bar--height, 0px);right:0;left:0;background-color:transparent}.tbfe-header--transparent.tbfe-header--sticky{position:fixed}.tbfe-header--sticky.is-scrolled{background-color:var(--tbfe-scrolled-bg, transparent)}.tbfe-header--shadow.is-scrolled{box-shadow:0 6px 24px rgba(15,23,42,.1)}.tbfe-header--hide.is-hidden{transform:translateY(-110%)}.tbfe-header--shrink .elementor-element-populated,.tbfe-header--shrink .elementor>.e-con,.tbfe-header--shrink .elementor>.elementor-section,.tbfe-header--shrink .tbfe-site-logo img{transition:padding .3s ease,min-height .3s ease,max-height .3s ease}.tbfe-header--shrink.is-scrolled .elementor>.e-con,.tbfe-header--shrink.is-scrolled .elementor>.elementor-section{padding-top:4px!important;padding-bottom:4px!important}.tbfe-header--shrink.is-scrolled .elementor-element-populated{padding-top:2px!important;padding-bottom:2px!important}.tbfe-header--shrink.is-scrolled .elementor-container{min-height:0!important}.tbfe-header--shrink.is-scrolled .tbfe-site-logo img{width:auto!important;max-height:var(--tbfe-shrink-logo, 36px)}@media (max-width:600px){.tbfe-header--sticky,.tbfe-header--transparent{top:0}}@media (max-width:1024px){.tbfe-header--transparent.tbfe-transparent-desktop.tbfe-header--sticky{position:sticky;top:0}.tbfe-header--transparent.tbfe-transparent-desktop,.tbfe-location-header.tbfe-header--sticky.tbfe-sticky-desktop{position:relative;top:auto}.tbfe-location-header.tbfe-header--transparent.tbfe-sticky-desktop:not(.tbfe-transparent-desktop){position:absolute;top:0}}@media (max-width:767px){.tbfe-header--transparent.tbfe-transparent-desktop-tablet.tbfe-header--sticky{position:sticky;top:0}.tbfe-header--transparent.tbfe-transparent-desktop-tablet,.tbfe-location-header.tbfe-header--sticky.tbfe-sticky-desktop-tablet{position:relative;top:auto}.tbfe-location-header.tbfe-header--transparent.tbfe-sticky-desktop-tablet.tbfe-transparent-all{position:absolute;top:0}}
     1.tbfe-location-404,.tbfe-location-archive,.tbfe-location-content,.tbfe-location-footer,.tbfe-location-header,.tbfe-location-single{display:block;width:100%}#site-footer.tbfe-location,#site-header.tbfe-location{display:block;height:auto;min-height:0;margin:0;padding:0;border:0}#content.tbfe-location,#site-footer.tbfe-location,#site-header.tbfe-location,main.tbfe-location{float:none;clear:both;box-sizing:border-box;width:100%;max-width:none}.tbfe-archive--boxed,.tbfe-container{box-sizing:border-box;width:100%;max-width:var(--tbfe-container, 1140px);margin-right:auto;margin-left:auto;padding-right:20px;padding-left:20px}.tbfe-archive{display:grid;grid-template-columns:minmax(0,1fr);gap:40px;padding-top:40px;padding-bottom:40px}.tbfe-archive--full{padding-right:20px;padding-left:20px}.tbfe-archive--sidebar{grid-template-columns:minmax(0,3fr) minmax(0,1fr)}.tbfe-archive__posts{display:flex;flex-direction:column;gap:30px;min-width:0}.tbfe-archive__item{margin:0}.tbfe-archive__sidebar{min-width:0}@media (max-width:767px){.tbfe-archive--sidebar{grid-template-columns:minmax(0,1fr)}}.tbfe-password{padding-top:40px;padding-bottom:40px}.tbfe-header--sticky,.tbfe-header--transparent{z-index:990;transition:background-color .3s ease,box-shadow .3s ease,transform .3s ease}.tbfe-header--sticky{position:sticky;top:var(--wp-admin--admin-bar--height, 0px)}.tbfe-header--transparent{position:absolute;top:var(--wp-admin--admin-bar--height, 0px);right:0;left:0;background-color:transparent}.tbfe-header--transparent.tbfe-header--sticky{position:fixed}.tbfe-header--sticky.is-scrolled{background-color:var(--tbfe-scrolled-bg, transparent)}.tbfe-header--shadow.is-scrolled{box-shadow:0 6px 24px rgba(15,23,42,.1)}.tbfe-header--hide.is-hidden{transform:translateY(-110%)}.tbfe-header--shrink .elementor-element-populated,.tbfe-header--shrink .elementor>.e-con,.tbfe-header--shrink .elementor>.elementor-section,.tbfe-header--shrink .tbfe-site-logo img{transition:padding .3s ease,min-height .3s ease,max-height .3s ease}.tbfe-header--shrink.is-scrolled .elementor>.e-con,.tbfe-header--shrink.is-scrolled .elementor>.elementor-section{padding-top:4px!important;padding-bottom:4px!important}.tbfe-header--shrink.is-scrolled .elementor-element-populated{padding-top:2px!important;padding-bottom:2px!important}.tbfe-header--shrink.is-scrolled .elementor-container{min-height:0!important}.tbfe-header--shrink.is-scrolled .tbfe-site-logo img{width:auto!important;max-height:var(--tbfe-shrink-logo, 36px)}@media (max-width:600px){.tbfe-header--sticky,.tbfe-header--transparent{top:0}}@media (max-width:1024px){.tbfe-header--transparent.tbfe-transparent-desktop.tbfe-header--sticky{position:sticky;top:0}.tbfe-header--transparent.tbfe-transparent-desktop,.tbfe-location-header.tbfe-header--sticky.tbfe-sticky-desktop{position:relative;top:auto}.tbfe-location-header.tbfe-header--transparent.tbfe-sticky-desktop:not(.tbfe-transparent-desktop){position:absolute;top:0}}@media (max-width:767px){.tbfe-header--transparent.tbfe-transparent-desktop-tablet.tbfe-header--sticky{position:sticky;top:0}.tbfe-header--transparent.tbfe-transparent-desktop-tablet,.tbfe-location-header.tbfe-header--sticky.tbfe-sticky-desktop-tablet{position:relative;top:auto}.tbfe-location-header.tbfe-header--transparent.tbfe-sticky-desktop-tablet.tbfe-transparent-all{position:absolute;top:0}}
  • theme-builder-for-elementor/trunk/includes/conditions/class-resolver.php

    r3783780 r3789508  
    132132                }
    133133
     134                // Products and the shop of WooCommerce: All singular / All archives of a blog Single or Archive template do
     135                // not count there. The blog layout would take the add to cart and the shop loop away; the theme's shop (or the
     136                // product templates of PRO) stay, unless a condition chooses them, like Products.
     137                $shop = in_array( $location, array( 'single', 'archive' ), true ) && function_exists( 'is_woocommerce' ) && is_woocommerce();
     138
    134139                $best       = 0;
    135140                $best_score = -1;
    136141                foreach ( $index[ $location ] as $id => $rules ) {
     142                        if ( $shop && in_array( Template::type( $id ), array( 'single', 'archive' ), true ) ) {
     143                                $rules = array_filter(
     144                                        $rules,
     145                                        static function ( $rule ) {
     146                                                return 'exclude' === $rule['type'] || ! in_array( $rule['name'], array( 'singular', 'archive' ), true );
     147                                        }
     148                                );
     149                        }
    137150                        $score = self::rules_score( $rules );
    138151                        if ( $score > $best_score ) {
  • theme-builder-for-elementor/trunk/includes/elementor/widgets/class-breadcrumbs.php

    r3783780 r3789508  
    162162        }
    163163
     164        /**
     165         * Breadcrumbs of Yoast SEO or Rank Math, only when their breadcrumbs are switched on.
     166         * Empty string otherwise, so the built-in trail is used.
     167         */
     168        private function seo_plugin_trail() {
     169                if ( function_exists( 'yoast_breadcrumb' ) ) {
     170                        $enabled = current_theme_supports( 'yoast-seo-breadcrumbs' )
     171                                || ( class_exists( 'WPSEO_Options' ) && \WPSEO_Options::get( 'breadcrumbs-enable', false ) );
     172                        if ( $enabled ) {
     173                                $html = (string) yoast_breadcrumb( '', '', false );
     174                                if ( '' !== trim( wp_strip_all_tags( $html ) ) ) {
     175                                        return $html;
     176                                }
     177                        }
     178                }
     179                if ( function_exists( 'rank_math_get_breadcrumbs' ) ) {
     180                        $enabled = method_exists( '\RankMath\Helper', 'is_breadcrumbs_enabled' )
     181                                ? \RankMath\Helper::is_breadcrumbs_enabled()
     182                                : current_theme_supports( 'rank-math-breadcrumbs' );
     183                        if ( $enabled ) {
     184                                $html = (string) rank_math_get_breadcrumbs();
     185                                if ( '' !== trim( wp_strip_all_tags( $html ) ) ) {
     186                                        return $html;
     187                                }
     188                        }
     189                }
     190                return '';
     191        }
     192
    164193        protected function render() {
    165194                $settings = $this->get_settings_for_display();
    166195
    167196                if ( 'auto' === $settings['source'] && ! Context::is_template_preview() ) {
    168                         if ( function_exists( 'yoast_breadcrumb' ) ) {
    169                                 yoast_breadcrumb( '<nav class="tbfe-breadcrumbs" aria-label="' . esc_attr__( 'Breadcrumbs', 'theme-builder-for-elementor' ) . '">', '</nav>' );
    170                                 return;
    171                         }
    172                         if ( function_exists( 'rank_math_the_breadcrumbs' ) ) {
    173                                 echo '<nav class="tbfe-breadcrumbs" aria-label="' . esc_attr__( 'Breadcrumbs', 'theme-builder-for-elementor' ) . '">';
    174                                 rank_math_the_breadcrumbs();
    175                                 echo '</nav>';
     197                        $seo = $this->seo_plugin_trail();
     198                        if ( '' !== $seo ) {
     199                                echo '<nav class="tbfe-breadcrumbs" aria-label="' . esc_attr__( 'Breadcrumbs', 'theme-builder-for-elementor' ) . '">' . wp_kses_post( $seo ) . '</nav>';
    176200                                return;
    177201                        }
  • theme-builder-for-elementor/trunk/includes/elementor/widgets/class-posts.php

    r3783780 r3789508  
    575575                while ( $query->have_posts() ) {
    576576                        $query->the_post();
    577                         echo '<article class="' . esc_attr( implode( ' ', get_post_class( 'tbfe-posts__item' ) ) ) . '">';
     577                        echo '<article class="' . esc_attr( implode( ' ', Renderer::post_class( 'tbfe-posts__item' ) ) ) . '">';
    578578                        if ( $template ) {
    579579                                Renderer::render_template( $template );
  • theme-builder-for-elementor/trunk/includes/library/class-library.php

    r3783780 r3789508  
    475475                }
    476476
    477                 $posts = array_merge( wp_list_pluck( $pages, 'id' ), $templates );
    478                 foreach ( $posts as $post_id ) {
    479                         $document = \Elementor\Plugin::$instance->documents->get( (int) $post_id, false );
     477                $posts = array_map( 'absint', array_merge( wp_list_pluck( $pages, 'id' ), $templates ) );
     478                $next  = 0;
     479                while ( $next < count( $posts ) ) {
     480                        $post_id  = $posts[ $next++ ];
     481                        $document = \Elementor\Plugin::$instance->documents->get( $post_id, false );
    480482                        if ( ! $document ) {
    481483                                continue;
    482484                        }
    483485                        $elements = $document->get_elements_data();
    484                         $changed  = false;
     486                        // Templates imported with the content (slides of a Slider, Section templates, Post Cards) get their links
     487                        // updated too.
     488                        foreach ( References::ids( $elements ) as $ref ) {
     489                                if ( ! in_array( $ref, $posts, true ) && Template::is_template( $ref ) && get_post_meta( $ref, '_tbfe_library_item', true ) ) {
     490                                        $posts[] = $ref;
     491                                }
     492                        }
     493                        $changed = false;
    485494                        $elements = self::walk(
    486495                                $elements,
  • theme-builder-for-elementor/trunk/includes/library/class-references.php

    r3783780 r3789508  
    4949                }
    5050                return $elements;
     51        }
     52
     53        /**
     54         * Templates of this site that Elementor content refers to (Post Card, Section Template, slides of a Slider).
     55         *
     56         * @return int[]
     57         */
     58        public static function ids( array $elements ) {
     59                $ids = array();
     60                foreach ( $elements as $element ) {
     61                        if ( ! is_array( $element ) ) {
     62                                continue;
     63                        }
     64                        $type     = isset( $element['widgetType'] ) ? $element['widgetType'] : '';
     65                        $settings = isset( $element['settings'] ) && is_array( $element['settings'] ) ? $element['settings'] : array();
     66                        if ( 'tbfe-posts' === $type && ! empty( $settings['card_template'] ) ) {
     67                                $ids[] = absint( $settings['card_template'] );
     68                        } elseif ( 'tbfe-template' === $type && ! empty( $settings['template_id'] ) ) {
     69                                $ids[] = absint( $settings['template_id'] );
     70                        } elseif ( 'tbfe-slider' === $type && ! empty( $settings['slides'] ) && is_array( $settings['slides'] ) ) {
     71                                foreach ( $settings['slides'] as $slide ) {
     72                                        if ( ! empty( $slide['template_id'] ) ) {
     73                                                $ids[] = absint( $slide['template_id'] );
     74                                        }
     75                                }
     76                        }
     77                        if ( ! empty( $element['elements'] ) && is_array( $element['elements'] ) ) {
     78                                $ids = array_merge( $ids, self::ids( $element['elements'] ) );
     79                        }
     80                }
     81                return array_values( array_unique( array_filter( $ids ) ) );
    5182        }
    5283
  • theme-builder-for-elementor/trunk/includes/render/class-renderer.php

    r3783780 r3789508  
    160160         * @param int $id Template ID.
    161161         */
     162        /**
     163         * Classes of the post element in the plugin's layouts: get_post_class() without the post_class filters of the
     164         * theme. Themes add their own card and grid classes there (Astra: ast-article-post, ast-grid-common-col) and
     165         * style them as boxes, which put a theme box around the cards of the Posts widget. Plugins (WooCommerce) stay.
     166         *
     167         * @param string $class Own class.
     168         * @return string[]
     169         */
     170        public static function post_class( $class = '' ) {
     171                global $wp_filter;
     172                $removed = array();
     173                if ( isset( $wp_filter['post_class'] ) ) {
     174                        $dirs = array_unique( array( wp_normalize_path( get_template_directory() ) . '/', wp_normalize_path( get_stylesheet_directory() ) . '/' ) );
     175                        foreach ( $wp_filter['post_class']->callbacks as $priority => $callbacks ) {
     176                                foreach ( $callbacks as $callback ) {
     177                                        $file = self::callback_file( $callback['function'] );
     178                                        foreach ( $dirs as $dir ) {
     179                                                if ( $file && 0 === strpos( $file, $dir ) ) {
     180                                                        $removed[] = array( $callback['function'], $priority, $callback['accepted_args'] );
     181                                                        break;
     182                                                }
     183                                        }
     184                                }
     185                        }
     186                }
     187                foreach ( $removed as $filter ) {
     188                        remove_filter( 'post_class', $filter[0], $filter[1] );
     189                }
     190                $classes = get_post_class( $class );
     191                foreach ( $removed as $filter ) {
     192                        add_filter( 'post_class', $filter[0], $filter[1], $filter[2] );
     193                }
     194                return $classes;
     195        }
     196
     197        /**
     198         * File that defines a callback ('' when unknown).
     199         */
     200        private static function callback_file( $callback ) {
     201                try {
     202                        if ( is_string( $callback ) && false !== strpos( $callback, '::' ) ) {
     203                                $callback = explode( '::', $callback, 2 );
     204                        }
     205                        if ( is_array( $callback ) && 2 === count( $callback ) ) {
     206                                $reflection = new \ReflectionMethod( $callback[0], $callback[1] );
     207                        } elseif ( $callback instanceof \Closure || ( is_string( $callback ) && function_exists( $callback ) ) ) {
     208                                $reflection = new \ReflectionFunction( $callback );
     209                        } else {
     210                                return '';
     211                        }
     212                        return wp_normalize_path( (string) $reflection->getFileName() );
     213                } catch ( \ReflectionException $e ) {
     214                        return '';
     215                }
     216        }
     217
    162218        public static function render_template( $id ) {
    163219                $id = (int) $id;
  • theme-builder-for-elementor/trunk/includes/render/class-theme-support.php

    r3783780 r3789508  
    44 *
    55 * Classic themes: on get_header / get_footer the plugin prints its own header (with wp_head) or footer (with
    6  * wp_footer) and loads the theme's header.php / footer.php into a buffer that is thrown away.
     6 * wp_footer) and loads the theme's header.php / footer.php into a buffer that is thrown away. On pages the theme
     7 * renders itself, the content wrappers that header.php leaves open are printed again after the plugin's header and
     8 * closed before its footer, so the theme's content keeps its container.
    79 * Block themes: the header and footer template parts are replaced, and the plugin's page templates print the whole
    810 * document themselves.
    … …  
    1921class Theme_Support {
    2022
     23        /** @var bool The page is rendered by a page template of the plugin (page_start()). */
     24        private static $plugin_page = false;
     25
     26        /** @var array[] Content wrappers of the theme reopened after the plugin's header: tag, html. */
     27        private static $wrappers = array();
     28
    2129        public static function init() {
    2230                add_action( 'get_header', array( __CLASS__, 'replace_header' ), 99 );
    … …  
    3341                        return;
    3442                }
     43                self::remove_theme_part( 'header' );
    3544                require TBFE_PATH . 'templates/parts/header.php';
    3645
    … …  
    4453                ob_start();
    4554                locate_template( $templates, true );
    46                 ob_end_clean();
     55                $html = ob_get_clean();
     56
     57                // Pages the theme renders itself (no page template of the plugin): its content wrappers stay.
     58                if ( ! self::$plugin_page ) {
     59                        self::$wrappers = self::content_wrappers( $html );
     60                        foreach ( self::$wrappers as $wrapper ) {
     61                                echo $wrapper['html']; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped -- opening tag printed by the theme.
     62                        }
     63                }
    4764        }
    4865
    … …  
    5168                        return;
    5269                }
     70                self::remove_theme_part( 'footer' );
     71                foreach ( array_reverse( self::$wrappers ) as $wrapper ) {
     72                        printf( '</%s>', esc_html( $wrapper['tag'] ) );
     73                }
     74                self::$wrappers = array();
    5375                require TBFE_PATH . 'templates/parts/footer.php';
    5476
    … …  
    6385                locate_template( $templates, true );
    6486                ob_end_clean();
     87        }
     88
     89        /**
     90         * Elements that the theme's header.php leaves open after its header (Astra: #content.site-content >
     91         * .ast-container, Twenty Twenty-One: #content > #primary > main…) and closes in footer.php. Wrappers around the
     92         * theme's header (#page) are left out, the plugin's header is not inside them on any page.
     93         *
     94         * @param string $html Output of header.php.
     95         * @return array[] tag, html (opening tag), outermost first.
     96         */
     97        public static function content_wrappers( $html ) {
     98                if ( ! class_exists( 'WP_HTML_Tag_Processor' ) || false === stripos( $html, '<body' ) ) {
     99                        return array();
     100                }
     101                $void      = array( 'AREA', 'BASE', 'BR', 'COL', 'EMBED', 'HR', 'IMG', 'INPUT', 'LINK', 'META', 'SOURCE', 'TRACK', 'WBR' );
     102                $processor = new \WP_HTML_Tag_Processor( $html );
     103                $stack     = array();
     104                $in_body   = false;
     105                $boundary  = null; // Depth of the parent of the theme's header.
     106
     107                while ( $processor->next_tag( array( 'tag_closers' => 'visit' ) ) ) {
     108                        $tag = $processor->get_tag();
     109                        if ( ! $in_body ) {
     110                                $in_body = 'BODY' === $tag && ! $processor->is_tag_closer();
     111                                continue;
     112                        }
     113                        if ( $processor->is_tag_closer() ) {
     114                                // Closes the last element with that tag (and the ones left open inside it, like svg paths).
     115                                for ( $i = count( $stack ) - 1; $i >= 0 && $stack[ $i ]['tag'] !== $tag; $i-- ) {
     116                                }
     117                                if ( $i < 0 ) {
     118                                        continue;
     119                                }
     120                                $element = $stack[ $i ];
     121                                $stack   = array_slice( $stack, 0, $i );
     122                                if ( $element['header'] && ( null === $boundary || $i < $boundary ) ) {
     123                                        $boundary = $i;
     124                                }
     125                                continue;
     126                        }
     127                        if ( in_array( $tag, $void, true ) || in_array( $tag, array( 'BODY', 'HTML' ), true ) ) {
     128                                continue;
     129                        }
     130
     131                        $attributes = '';
     132                        foreach ( (array) $processor->get_attribute_names_with_prefix( '' ) as $name ) {
     133                                $value       = $processor->get_attribute( $name );
     134                                $attributes .= true === $value ? ' ' . $name : sprintf( ' %s="%s"', $name, esc_attr( $value ) );
     135                        }
     136                        $id      = (string) $processor->get_attribute( 'id' );
     137                        $class   = ' ' . $processor->get_attribute( 'class' ) . ' ';
     138                        $stack[] = array(
     139                                'tag'    => $tag,
     140                                'html'   => sprintf( '<%s%s>', strtolower( $tag ), $attributes ),
     141                                'header' => 'HEADER' === $tag || in_array( $id, array( 'masthead', 'site-header', 'header' ), true ) || false !== strpos( $class, ' site-header ' ),
     142                        );
     143                }
     144
     145                $wrappers = array_slice( $stack, (int) $boundary );
     146                foreach ( $wrappers as &$wrapper ) {
     147                        $wrapper['tag'] = strtolower( $wrapper['tag'] );
     148                        unset( $wrapper['header'] );
     149                }
     150                unset( $wrapper );
     151
     152                /**
     153                 * Filters the content wrappers of the theme reopened after the plugin's header on pages the theme renders.
     154                 *
     155                 * @param array[] $wrappers array( array( 'tag' => 'div', 'html' => '<div id="content">' ), … ).
     156                 * @param string  $html     Output of the theme's header.php.
     157                 */
     158                return (array) apply_filters( 'tbfe/theme_support/content_wrappers', $wrappers, $html );
     159        }
     160
     161        /**
     162         * Classic themes that print their header on wp_body_open or their footer on wp_footer (not in header.php /
     163         * footer.php), like the Envo themes: those callbacks are removed while the plugin's header or footer is shown
     164         * and on canvas pages.
     165         */
     166        public static function remove_theme_part( $location ) {
     167                $prefix = str_replace( '-', '_', get_template() );
     168                $parts  = array(
     169                        'header' => array( array( 'wp_body_open', $prefix . '_body_header_code' ) ),
     170                        'footer' => array( array( 'wp_footer', $prefix . '_wp_footer_code' ) ),
     171                );
     172
     173                /**
     174                 * Filters the callbacks of the theme that print its header or footer outside header.php / footer.php.
     175                 *
     176                 * @param array  $callbacks array( array( hook, callback ), … ).
     177                 * @param string $location  header or footer.
     178                 */
     179                $callbacks = apply_filters( 'tbfe/theme_support/theme_part_callbacks', $parts[ $location ], $location );
     180
     181                foreach ( (array) $callbacks as $callback ) {
     182                        list( $hook, $function ) = $callback;
     183                        $priority                = has_action( $hook, $function );
     184                        if ( false !== $priority ) {
     185                                remove_action( $hook, $function, $priority );
     186                        }
     187                }
    65188        }
    66189
    … …  
    123246         */
    124247        public static function page_start() {
     248                self::$plugin_page = true;
    125249                if ( ! self::is_block_theme() ) {
    126250                        get_header();
  • theme-builder-for-elementor/trunk/readme.txt

    r3783817 r3789508  
    55Tested up to: 7.1
    66Requires PHP: 7.4
    7 Stable tag: 2.0.0
     7Stable tag: 2.0.1
    88License: GPLv2 or later
    99License URI: https://www.gnu.org/licenses/gpl-2.0.html
    … …  
    139139== Changelog ==
    140140
     141= 2.0.1 =
     142* Fix: after a Websites Import the buttons in sliders, section templates and post cards link to your site, not to the demo site.
     143* Fix: the Breadcrumbs widget uses the breadcrumbs of Yoast SEO or Rank Math only when they are switched on there, otherwise its own trail.
     144* Fix: Single and Archive templates for the blog are no longer used for WooCommerce products, the shop and product categories.
     145* Fix: themes that print their own header or footer through hooks (e.g. Envo One) no longer show it next to the header or footer of the plugin.
     146* Fix: header, footer and content of the plugin are no longer changed by theme styles (e.g. Royal Elementor Kit, PopularFX).
     147* Fix: on pages the theme renders, its content container is kept with the header and footer of the plugin.
     148* Fix: post cards of the Posts widget are no longer boxed again by the theme (e.g. Astra).
     149
    141150= 2.0.0 =
    142151* New: completely rewritten plugin with a new administration.
  • theme-builder-for-elementor/trunk/templates/archive.php

    r3783780 r3789508  
    3737                                                the_post();
    3838                                                ?>
    39                                                 <article id="post-<?php the_ID(); ?>" <?php post_class( 'tbfe-archive__item' ); ?>>
     39                                                <article id="post-<?php the_ID(); ?>" class="<?php echo esc_attr( implode( ' ', Renderer::post_class( 'tbfe-archive__item' ) ) ); ?>">
    4040                                                        <?php Renderer::render_template( $tbfe_id ); ?>
    4141                                                </article>
  • theme-builder-for-elementor/trunk/templates/canvas.php

    r3783780 r3789508  
    88
    99defined( 'ABSPATH' ) || exit;
     10
     11// No header and footer of the theme either.
     12TBFE\Render\Theme_Support::remove_theme_part( 'header' );
     13TBFE\Render\Theme_Support::remove_theme_part( 'footer' );
    1014
    1115require __DIR__ . '/parts/document-start.php';
  • theme-builder-for-elementor/trunk/templates/full-width.php

    r3783780 r3789508  
    77 */
    88
     9use TBFE\Render\Renderer;
    910use TBFE\Render\Theme_Support;
    1011
    … …  
    1819                the_post();
    1920                ?>
    20                 <div <?php post_class( 'tbfe-page' ); ?>>
     21                <div class="<?php echo esc_attr( implode( ' ', Renderer::post_class( 'tbfe-page' ) ) ); ?>">
    2122                        <?php the_content(); ?>
    2223                </div>
  • theme-builder-for-elementor/trunk/templates/single.php

    r3783780 r3789508  
    2222                }
    2323                ?>
    24                 <article id="post-<?php the_ID(); ?>" <?php post_class( 'tbfe-single' ); ?>>
     24                <article id="post-<?php the_ID(); ?>" class="<?php echo esc_attr( implode( ' ', Renderer::post_class( 'tbfe-single' ) ) ); ?>">
    2525                        <?php Renderer::render_location( 'single' ); ?>
    2626                </article>
  • theme-builder-for-elementor/trunk/theme-builder-for-elementor.php

    r3783780 r3789508  
    44 * Plugin URI:        https://blocks-wp.com/theme-builder-for-elementor/
    55 * Description:       Build the header, footer, single posts and pages, archives and the 404 page of any theme with the free Elementor.
    6  * Version:           2.0.0
     6 * Version:           2.0.1
    77 * Requires at least: 6.5
    88 * Requires PHP:      7.4
    … …  
    2020defined( 'ABSPATH' ) || exit;
    2121
    22 define( 'TBFE_VERSION', '2.0.0' );
     22define( 'TBFE_VERSION', '2.0.1' );
    2323define( 'TBFE_FILE', __FILE__ );
    2424define( 'TBFE_PATH', plugin_dir_path( __FILE__ ) );
Note: See TracChangeset for help on using the changeset viewer.