Jake Vanderwerf
2026-05-15 894ec8a6f2ac62edbac7b3b6a88e3666f335c673
=Refactor of CustomBlocks.php to move a majority of the logic to the pre_render_field instead of render_field, and adding support for more blocks
98 files deleted
5 files modified
13960 ■■■■■ changed files
cleanup2.php 97 ●●●●● patch | view | raw | blame | history
inc/blocks/CustomBlocks.php 890 ●●●●● patch | view | raw | blame | history
inc/managers/DashboardManager.php 12 ●●●● patch | view | raw | blame | history
inc/managers/LoginManager.php 97 ●●●● patch | view | raw | blame | history
inc/managers/SEO/BreadcrumbManager.php 13 ●●●●● patch | view | raw | blame | history
inc/managers/SEO/render/Thing/Place/AdministrativeArea/State.php 2 ●●● patch | view | raw | blame | history
src/drawer-menu/block.json 27 ●●●●● patch | view | raw | blame | history
src/drawer-menu/edit.js 33 ●●●●● patch | view | raw | blame | history
src/drawer-menu/editor.scss patch | view | raw | blame | history
src/drawer-menu/index.js 10 ●●●●● patch | view | raw | blame | history
src/drawer-menu/index.php patch | view | raw | blame | history
src/drawer-menu/render.php 38 ●●●●● patch | view | raw | blame | history
src/drawer-menu/save.js 3 ●●●●● patch | view | raw | blame | history
src/drawer-menu/style.scss 88 ●●●●● patch | view | raw | blame | history
src/drawer-menu/view.js 1 ●●●● patch | view | raw | blame | history
src/faq/block.json 34 ●●●●● patch | view | raw | blame | history
src/faq/edit.js 145 ●●●●● patch | view | raw | blame | history
src/faq/editor.scss 99 ●●●●● patch | view | raw | blame | history
src/faq/index.js 11 ●●●●● patch | view | raw | blame | history
src/faq/index.php patch | view | raw | blame | history
src/faq/render.php patch | view | raw | blame | history
src/faq/style.scss 72 ●●●●● patch | view | raw | blame | history
src/faq/view.js 84 ●●●●● patch | view | raw | blame | history
src/feed/block.json 57 ●●●●● patch | view | raw | blame | history
src/feed/edit.js 256 ●●●●● patch | view | raw | blame | history
src/feed/editor.scss 128 ●●●●● patch | view | raw | blame | history
src/feed/index.js 39 ●●●●● patch | view | raw | blame | history
src/feed/index.php 2 ●●●●● patch | view | raw | blame | history
src/feed/render.php 4 ●●●● patch | view | raw | blame | history
src/feed/save.js 3 ●●●●● patch | view | raw | blame | history
src/feed/style.scss 956 ●●●●● patch | view | raw | blame | history
src/feed/view.js 747 ●●●●● patch | view | raw | blame | history
src/feed/viewOld.js 770 ●●●●● patch | view | raw | blame | history
src/fields/block.json 25 ●●●●● patch | view | raw | blame | history
src/fields/edit.js 29 ●●●●● patch | view | raw | blame | history
src/fields/editor.scss 20 ●●●●● patch | view | raw | blame | history
src/fields/index.js 39 ●●●●● patch | view | raw | blame | history
src/fields/index.php patch | view | raw | blame | history
src/fields/render.php 320 ●●●●● patch | view | raw | blame | history
src/fields/save.js 3 ●●●●● patch | view | raw | blame | history
src/fields/style.scss 20 ●●●●● patch | view | raw | blame | history
src/fields/view.js 1 ●●●● patch | view | raw | blame | history
src/forms/block.json 47 ●●●●● patch | view | raw | blame | history
src/forms/edit.js 319 ●●●●● patch | view | raw | blame | history
src/forms/editor.scss patch | view | raw | blame | history
src/forms/index.js 40 ●●●●● patch | view | raw | blame | history
src/forms/index.php patch | view | raw | blame | history
src/forms/render.php 55 ●●●●● patch | view | raw | blame | history
src/forms/save.js 23 ●●●●● patch | view | raw | blame | history
src/forms/style.scss 5572 ●●●●● patch | view | raw | blame | history
src/forms/view.js 112 ●●●●● patch | view | raw | blame | history
src/glossary/block.json 24 ●●●●● patch | view | raw | blame | history
src/glossary/edit.js 38 ●●●●● patch | view | raw | blame | history
src/glossary/editor.scss patch | view | raw | blame | history
src/glossary/index.js 33 ●●●●● patch | view | raw | blame | history
src/glossary/index.php patch | view | raw | blame | history
src/glossary/render.php 8 ●●●●● patch | view | raw | blame | history
src/glossary/style.scss 109 ●●●●● patch | view | raw | blame | history
src/glossary/view.js 184 ●●●●● patch | view | raw | blame | history
src/gmbreviews/block.json 68 ●●●●● patch | view | raw | blame | history
src/gmbreviews/edit.js 69 ●●●●● patch | view | raw | blame | history
src/gmbreviews/editor.scss patch | view | raw | blame | history
src/gmbreviews/index.js 11 ●●●●● patch | view | raw | blame | history
src/gmbreviews/index.php patch | view | raw | blame | history
src/gmbreviews/render.php 207 ●●●●● patch | view | raw | blame | history
src/gmbreviews/style.scss 122 ●●●●● patch | view | raw | blame | history
src/gmbreviews/view.js patch | view | raw | blame | history
src/index.php 3 ●●●●● patch | view | raw | blame | history
src/menu/block.json 24 ●●●●● patch | view | raw | blame | history
src/menu/edit.js 38 ●●●●● patch | view | raw | blame | history
src/menu/editor.scss patch | view | raw | blame | history
src/menu/index.js 33 ●●●●● patch | view | raw | blame | history
src/menu/index.php patch | view | raw | blame | history
src/menu/render.php 8 ●●●●● patch | view | raw | blame | history
src/menu/style.scss patch | view | raw | blame | history
src/menu/view.js 43 ●●●●● patch | view | raw | blame | history
src/summary/block.json 32 ●●●●● patch | view | raw | blame | history
src/summary/edit.js 29 ●●●●● patch | view | raw | blame | history
src/summary/editor.scss 20 ●●●●● patch | view | raw | blame | history
src/summary/index.js 39 ●●●●● patch | view | raw | blame | history
src/summary/index.php patch | view | raw | blame | history
src/summary/render.php 320 ●●●●● patch | view | raw | blame | history
src/summary/save.js 3 ●●●●● patch | view | raw | blame | history
src/summary/style.scss 20 ●●●●● patch | view | raw | blame | history
src/summary/view.js 1 ●●●● patch | view | raw | blame | history
src/timeline/block.json 23 ●●●●● patch | view | raw | blame | history
src/timeline/edit.js 38 ●●●●● patch | view | raw | blame | history
src/timeline/editor.scss patch | view | raw | blame | history
src/timeline/index.js 33 ●●●●● patch | view | raw | blame | history
src/timeline/index.php patch | view | raw | blame | history
src/timeline/render.php 8 ●●●●● patch | view | raw | blame | history
src/timeline/style.scss 135 ●●●●● patch | view | raw | blame | history
src/timeline/view.js patch | view | raw | blame | history
src/video/block.json 79 ●●●●● patch | view | raw | blame | history
src/video/edit.js 276 ●●●●● patch | view | raw | blame | history
src/video/editor.scss 141 ●●●●● patch | view | raw | blame | history
src/video/index.js 21 ●●●●● patch | view | raw | blame | history
src/video/index.php patch | view | raw | blame | history
src/video/render.php 1 ●●●● patch | view | raw | blame | history
src/video/save.js 23 ●●●●● patch | view | raw | blame | history
src/video/style.scss 178 ●●●●● patch | view | raw | blame | history
src/video/view.js 47 ●●●●● patch | view | raw | blame | history
wp-config.php 128 ●●●●● patch | view | raw | blame | history
cleanup2.php
File was deleted
inc/blocks/CustomBlocks.php
@@ -4,6 +4,7 @@
use DateTime;
use DOMDocument;
use JVBase\managers\Cache;
use JVBase\managers\LoginManager;
use JVBase\managers\SEO\BreadcrumbManager;
use WP_Block;
use WP_Query;
@@ -15,12 +16,14 @@
class CustomBlocks
{
    protected Cache $cache;
    protected array $shouldRender = ['core/query'];
    public function __construct()
    {
        $this->cache = Cache::for('blocks', WEEK_IN_SECONDS);
        $this->cache->connect('post')->connect('taxonomy');
        $this->cache->flush();
        add_filter('render_block', [$this, 'render'], 900, 2);
        add_filter('pre_render_block', [$this, 'prerender'], 10, 3);
        add_filter('render_block', [$this, 'render'], 10, 2);
        add_action('init', [$this, 'registerBlockStyles']);
    }
@@ -65,51 +68,59 @@
            ]
        );
    }
    public function render(string $content, array $block)
    {
    protected function checkMethods(?string $content, array $block, ?WP_Block $parent = null, bool $isPrerender = false):?string
    {
        $blockName = $this->sanitizeBlockName($block);
        $method = 'render_'.$blockName;
        $base = ($isPrerender) ? 'prerender_' : 'render_';
        $method = $base.$blockName;
        $function = BASE.$method;
        if (function_exists($function)) {
            return $function($block, $content);
//          return $this->cache->remember(
//              get_the_ID(),
//              function () use ($function, $block, $content) {
//                  return $function($block, $content);
//              }
//          );
            return $function($block, $content, $parent);
        } else if (method_exists($this, $method)) {
            return $this->$method($block, $content);
//
//          return $this->cache->remember(
//              get_the_ID(),
//              function () use ($method, $block, $content) {
//                  return $this->$method($block, $content);
//              }
//          );
        } else if (!empty($block['blockName'])){
            //TESTING
            $ignore = [
                'core/null',
                'core/post-title',
                'core/list-item',
                'core/site-title',
                'jvb/forms'
            ];
//          if (!in_array($block['blockName'], $ignore)) {
//              jvbDump('No method found for '.print_r($block['blockName'], true));
//          }
            return $this->$method($block, $content, $parent);
        } elseif (!empty($blockName) && JVB_TESTING) {
            if (!in_array($block['blockName'], $this->getIgnore($isPrerender))) {
                jvbDump('No method found for '.print_r($block['blockName'], true));
            }
        }
        if ($block['blockName'] === 'jvb/feed') {
            // Enqueue the feed block script (it will automatically load dependencies)
            $this->localize_feedblock();
        }
        return $content;
    }
        protected function getIgnore(bool $isPrerender):array
        {
            //Ignore for both
            $base = [
                'core/null'
            ];
            if ($isPrerender) {
                $base = array_merge($base, [
                ]);
            } else {
                $base = array_merge($base, [
                ]);
            }
            return $base;
        }
    public function prerender(?string $content, array $block, ?WP_Block $parent = null):?string
    {
        $result = $this->checkMethods($content, $block, $parent, true);
        return $result;
    }
    public function render(string $content, array $block):string
    {
        if ($block['blockName'] === 'jvb/feed') {
            // Enqueue the feed block script (it will automatically load dependencies)
            $this->localize_feedblock();
        }
        if ($block['blockName'] === 'jvb/forms') {
            wp_enqueue_style('jvb-form');
        }
        return $content;
        return $this->checkMethods($content, $block);
    }
    /***********************************
@@ -128,8 +139,10 @@
     */
    public function render_core_button(array $block):string
    public function prerender_core_button(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'Button');
//      jvbDump($parent, 'Parent');
        preg_match('/href="([^"]*)"/', $block['innerHTML'], $url);
        preg_match('/>([^<]*)<\/a>/', $block['innerHTML'], $label);
@@ -147,7 +160,7 @@
        if ($icon !== '') {
            return sprintf(
                '<li%s><a href="%s" title="Find Us On %s">%s Maps</a></li>',
                $this->getClassesAndStyles($block['attrs']),
                $this->getClassesAndStyles($block['attrs']??[]),
                esc_url($url[1]),
                esc_html($label[1]),
                jvbIcon($icon)
@@ -156,58 +169,79 @@
        return sprintf(
            '<li%s><a href="%s">%s</a></li>',
            $this->getClassesAndStyles($block['attrs']),
            $this->getClassesAndStyles($block['attrs']??[]),
            esc_url($url[1]),
            esc_html($label[1])
        );
    }
    public function render_core_buttons(array $block, string $content):string
    public function prerender_core_buttons(array $block, ?string $content, ?WP_Block $parent):?string
    {
        return '<ul'.$this->getClassesAndStyles($block['attrs'], ['buttons','row']).'>'.
               $this->inside($block, false, $content).'</ul>';
//      jvbDump($block, 'buttons');
//      jvbDump($parent, 'Parent');
        return '<ul'.$this->getClassesAndStyles($block['attrs']??[], ['buttons','row']).'>'.
               $this->innerBlocks($block).'</ul>';
    }
    public function render_core_column(array $block, string $content):string
    public function prerender_core_column(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'column');
//      jvbDump($parent, 'Parent');
        $styles = (array_key_exists('attrs', $block) &&
                   array_key_exists('width', $block['attrs'])) ?
            ['flex-basis:'.$block['attrs']['width']]
            : [];
        return '<div'.
               $this->getClassesAndStyles($block['attrs'], ['col'], $styles).'>'.
               $this->inside($block, false, $content).'</div>';
               $this->getClassesAndStyles($block['attrs']??[], ['col'], $styles).'>'.
               $this->innerBlocks($block).'</div>';
    }
    public function render_core_columns(array $block, string $content):string
    public function prerender_core_columns(array $block, ?string $content, ?WP_Block $parent):?string
    {
        return '<section'.
               $this->getClassesAndStyles($block['attrs'], ['columns']).'>'.
               $this->inside($block, false, $content).'</section>';
        $tagName = array_key_exists('tagName', $block['attrs']) ? $block['attrs']['tagName'] : 'section';
        return sprintf(
            '<%s%s>%s</%s>',
            $tagName,
            $this->getClassesAndStyles($block['attrs']??[], ['row nowrap']),
            $this->innerBlocks($block).'</section>',
            $tagName
        );
    }
    //core_comment_template
    public function render_core_group(array $block, string $content):string
    public function prerender_core_group(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $tag = (array_key_exists('tagName', $block['attrs'])) ? $block['attrs']['tagName'] : 'div';
//      jvbDump($block, 'group');
//      jvbDump($parent, 'Parent');
        $tag = (array_key_exists('tagName', $block['attrs']??[])) ? $block['attrs']['tagName'] : 'div';
        $classes = ($tag === 'main') ?
            '' :
            $this->getClassesAndStyles($block['attrs'], ['group']);
        return '<'.$tag.$classes.'>'.$this->inside($block, false, $content).'</'.$tag.'>';
        return sprintf(
            '<%s%s>%s</%s>',
            $tag,
            $tag === 'main' ? '' : $this->getClassesAndStyles($block['attrs']??[], ['group']),
            $this->innerBlocks($block),
            $tag
        );
    }
    //core_home_link
    //core_more
    //core_nextpage
    public function render_core_separator(array $block):string
    public function prerender_core_separator(array $block, ?string $content, ?WP_Block $parent):?string
    {
        return '<hr'.$this->getClassesAndStyles($block['attrs']).'>';
//      jvbDump($block, 'separator');
//      jvbDump($parent, 'Parent');
        return '<hr'.$this->getClassesAndStyles($block['attrs']??[]).'>';
    }
    public function render_core_spacer(array $block):string
    public function prerender_core_spacer(array $block, ?string $content, ?WP_Block $parent):?string
    {
        return '<div'.$this->getClassesAndStyles($block['attrs'], ['spacer'], ['height:2rem']).
//      jvbDump($block, 'spsacer');
//      jvbDump($parent, 'Parent');
        return '<div'.$this->getClassesAndStyles($block['attrs']??[], ['spacer'], ['height:2rem']).
               ' aria-hidden="true"></div>';
    }
    //core_table_of_contents
@@ -221,12 +255,13 @@
     * Media Blocks
     */
    //core_audio
    public function render_core_cover(array $block, string $content):string
    public function prerender_core_cover(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'cover');
//      jvbDump($parent, 'Parent');
        // Extract block attributes
        $attrs = $block['attrs'] ?? [];
        $innerContent = $this->inside($block, false, $content);
        $attrs = $block['attrs'] ?:[];
        $innerContent = $this->innerBlocks($block);
        $position = 'object-position: center;';
        if (array_key_exists('focalPoint', $attrs)) {
@@ -268,15 +303,19 @@
    //core_file
    public function render_core_gallery(array $block, string $content):string
    public function prerender_core_gallery(array $block, ?string $content, ?WP_Block $parent):?string
    {
        return '<ul'.$this->getClassesAndStyles($block['attrs'], ['gallery']).'>'.
//      jvbDump($block, 'gallery');
//      jvbDump($parent, 'Parent');
        return '<ul'.$this->getClassesAndStyles($block['attrs']??[], ['gallery']).'>'.
               $this->innerBlocks($block,'<li>', '</li>').
               '</ul>';
    }
    public function render_core_image(array $block):string
    public function prerender_core_image(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'image');
//      jvbDump($parent, 'Parent');
        $ID = $this->imageID('', $block);
        if (!$ID) {
            return '';
@@ -289,32 +328,34 @@
                wp_get_attachment_caption($ID) .
            '</figcaption>' :
            '<figcaption>' . $title . '</figcaption>';
        $size = array_key_exists('sizeSlug', $block['attrs']) ? $block['attrs']['sizeSlug'] : 'large';
        $size = array_key_exists('sizeSlug', $block['attrs']??[]) ? $block['attrs']['sizeSlug'] : 'large';
        return '<figure'.
               $this->getClassesAndStyles($block['attrs']).'>'.
               $this->getClassesAndStyles($block['attrs']??[]).'>'.
               $this->imageLink(true, $ID, 'tiny', $size) .
               $caption.'</figure>';
    }
    public function render_core_media_text(array $block, string $content):string
    public function prerender_core_media_text(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'media text');
//      jvbDump($parent, 'Parent');
        $ID = $this->imageID('', $block);
        $size = array_key_exists('mediaSizeSlug', $block['attrs']) ? $block['attrs']['mediaSizeSlug'] : 'large';
        $size = array_key_exists('mediaSizeSlug', $block['attrs']??[]) ? $block['attrs']['mediaSizeSlug'] : 'large';
        $imgLink = ($ID) ? $this->imageLink(true, $ID, 'tiny', $size) : '';
        $inner = $this->innerBlocks($block);
        $classes = ['media-text', 'row'];
        if (array_key_exists('isStackedOnMobile', $block['attrs'])) {
        if (array_key_exists('isStackedOnMobile', $block['attrs']??[])) {
            $classes[] = 'nowrap';
        }
        $content = '<div'.$this->getClassesAndStyles($block['attrs'], $classes).'>';
        $content = '<div'.$this->getClassesAndStyles($block['attrs']??[], $classes).'>';
        $content .= (array_key_exists(
            'mediaPosition',
            $block['attrs']
            $block['attrs']??[]
        ) && $block['attrs']['mediaPosition'] == 'right') ?
            '<div>'.$inner.'</div><figure>'.$imgLink.'</figure>' :
            '<figure>'.$imgLink.'</figure><div>'.$inner.'</div>';
@@ -332,41 +373,49 @@
    /**
     * Text Blocks
    */
    //render_core_code
    //render_core_details
    //render_core_footnotes
    //render_core_classic
    public function render_core_heading(array $block):string
    //prerender_core_code
    //prerender_core_details
    //prerender_core_footnotes
    //prerender_core_classic
    public function prerender_core_heading(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $level = (array_key_exists('level', $block['attrs'])) ? $block['attrs']['level'] : '2';
        $content = $this->inside($block);
//      jvbDump($block, 'heading');
//      jvbDump($parent, 'Parent');
        $level = (array_key_exists('level', $block['attrs']??[])) ? $block['attrs']['level'] : '2';
        $content = $this->innerBlocks($block);
        $id = sanitize_title(wp_strip_all_tags($this->stripTagContents('small', $content)));
        return '<h'.$level.' id="'.$id.'"'.$this->getClassesAndStyles($block['attrs']).'>'.
        return '<h'.$level.' id="'.$id.'"'.$this->getClassesAndStyles($block['attrs']??[]).'>'.
               $content.
               '</h'.$level.'>';
    }
    public function render_core_list(array $block, string $content):string
    public function prerender_core_list(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $tag = (array_key_exists('ordered', $block['attrs'])) ? 'ol' : 'ul';
        $output = '<'.$tag.$this->getClassesAndStyles($block['attrs']).'>'.$this->inside($block, false, $content).'</'.$tag.'>';
//      jvbDump($block, 'list');
//      jvbDump($parent, 'Parent');
        $tag = (array_key_exists('ordered', $block['attrs']??[])) ? 'ol' : 'ul';
        $output = '<'.$tag.$this->getClassesAndStyles($block['attrs']??[]).'>'.$this->innerBlocks($block).'</'.$tag.'>';
        return $output;
    }
//  public function render_core_list_item(array $block):string
//  public function prerender_core_list_item(array $block):string
//  {
//      return '<li'.$this->getClassesAndStyles($block['attrs']).'>'.$this->inside($block).'</li>';
//  }
    //render_core_missing
    //prerender_core_missing
    public function render_core_paragraph(array $block):string
    public function prerender_core_paragraph(array $block, ?string $content, ?WP_Block $parent):?string
    {
        return '<p'.$this->getClassesAndStyles($block['attrs']).'>'.
               $this->inside($block, 'p').
//      jvbDump($block, 'paragraph');
//      jvbDump($parent, 'Parent');
        return '<p'.$this->getClassesAndStyles($block['attrs']??[]).'>'.
               $this->innerBlocks($block).
               '</p>';
    }
    public function render_core_quote(array $block): string
    public function prerender_core_quote(array $block, ?string $content, ?WP_Block $parent): ?string
    {
//      jvbDump($block, 'quote');
//      jvbDump($parent, 'Parent');
        $innerHTML = $block['innerHTML'];
        // Extract cite content first
@@ -374,20 +423,22 @@
        $citeHtml = ($cite === '') ? '' : '<cite>—&emsp;'.$cite.'</cite>';
        // Get the blockquote content
        $content = $this->inside($block, 'blockquote');
        $content = $this->innerBlocks($block);
        // Remove the cite element from content if it exists
        if ($cite !== '') {
            $content = $this->stripTagContents('cite', $content);
        }
        return '<blockquote'.$this->getClassesAndStyles($block['attrs']).'>
        return '<blockquote'.$this->getClassesAndStyles($block['attrs']??[]).'>
        <div class="content">'.$content.'</div>'.
            $citeHtml.
            '</blockquote>';
    }
    public function render_core_pullquote(array $block): string
    public function prerender_core_pullquote(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'pullquote');
//      jvbDump($parent, 'Parent');
        $innerHTML = $block['innerHTML'];
        // Extract cite content first
@@ -403,23 +454,48 @@
        }
        $content = jvb_filter_content( $content);
        return '<blockquote'.$this->getClassesAndStyles($block['attrs'], ['pull']).'>'.
        return '<blockquote'.$this->getClassesAndStyles($block['attrs']??[], ['pull']).'>'.
            $content.
            $citeHtml.
            '</blockquote>';
    }
    //render_core_table
    //render_core_verse
    //prerender_core_table
    //prerender_core_verse
    /**
     * Theme Blocks
     */
    //core_avatar
    //core_loginout
    public function prerender_core_loginout(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $action = is_user_logged_in() ? 'logout' : 'login';
        $attrs = $block['attrs'];
        $redirect = '';
        if (array_key_exists('redirectToCurrent', $attrs) && $attrs['redirectToCurrent']) {
            global $wp;
            $redirect = get_home_url(null, $wp->request);
        }
        if (array_key_exists('displayLoginAsForm', $attrs) && $attrs['displayLoginAsForm']) {
            LoginManager::getInstance()->setAction($action);
            return LoginManager::getInstance()->renderLoginForm($action, $redirect, '<h2>Login</h2>');
        }
        return sprintf(
            '<a href="%s"%s>%s</a>',
            wp_login_url($redirect),
            $this->getClassesAndStyles($attrs),
            $action === 'login' ? jvbIcon('sign-in').'<span>Log in</span>' : jvbIcon('sign-out').'<span>Logout</span>'
        );
    }
    //core_pattern
    public function render_core_site_logo(array $block, string $content):string
    public function prerender_core_site_logo(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'site logo');
//      jvbDump($parent, 'Parent');
        $open = $close = '';
        if (!is_home() && !is_front_page()) {
@@ -428,14 +504,16 @@
        }
        $img = get_theme_mod('custom_logo');
        $img = $this->image($img, 'tiny', 'thumbnail');
        $img = str_replace('<img', '<img'.$this->getClassesAndStyles($block['attrs']), $img);
        $img = str_replace('<img', '<img'.$this->getClassesAndStyles($block['attrs']??[]), $img);
        return $open.$img.$close;
    }
    //core_site_title_tagline
    public function render_core_site_title(array $block, string $content):string
    public function prerender_core_site_title(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $tag = (array_key_exists('level', $block['attrs'])) ? $block['attrs']['level'] : 1;
//      jvbDump($block, 'site title');
//      jvbDump($parent, 'Parent');
        $tag = (array_key_exists('level', $block['attrs']??[])) ? $block['attrs']['level'] : 1;
        $tag = ($tag == 0) ? 'p' : 'h'.$tag;
        $open = $close = '';
@@ -444,8 +522,8 @@
            $close = '</a>';
        }
        $class = ($tag === 'p') ?
            $this->getClassesAndStyles($block['attrs'], ['title']) :
            $this->getClassesAndStyles($block['attrs']);
            $this->getClassesAndStyles($block['attrs']??[], ['title']) :
            $this->getClassesAndStyles($block['attrs']??[]);
        return '<'.$tag.$class.'>'.
@@ -475,15 +553,17 @@
    /**
     * Theme Navigation Blocks
     */
    public function render_core_navigation(array $block, string $content):string
    public function prerender_core_navigation(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $ID = (array_key_exists('ref', $block['attrs'])) ? $block['attrs']['ref'] : false;
//      jvbDump($block, 'navigation');
//      jvbDump($parent, 'Parent');
        $ID = (array_key_exists('ref', $block['attrs']??[])) ? $block['attrs']['ref'] : false;
        if (empty($block['innerBlocks']) && $ID && get_post($ID)) {
            $block['innerBlocks'] = parse_blocks(get_post($ID)->post_content);
        }
        $toggle = (array_key_exists('overlayMenu', $block['attrs'])
        $toggle = (array_key_exists('overlayMenu', $block['attrs']??[])
                   && $block['attrs']['overlayMenu'] == 'never') ?
            '':
            '<button class="toggle main"
@@ -495,8 +575,8 @@
            jvbIcon('x', ['title'=>'Toggle Menu']).
        '</button>';
        $class = ($toggle === '') ?
            $this->getClassesAndStyles($block['attrs'], ['mobile']) :
            $this->getClassesAndStyles($block['attrs']);
            $this->getClassesAndStyles($block['attrs']??[], ['mobile']) :
            $this->getClassesAndStyles($block['attrs']??[]);
        $helpmenu = (get_the_title($ID) === 'Main') ?
            '<nav><ul>'.jvbNotificationMenu().jvbHelpMenu().'</ul></nav>' :
            '';
@@ -517,14 +597,19 @@
           '</nav>'.$helpmenu;
    }
    public function render_core_navigation_link(array $block):string
    public function prerender_core_navigation_link(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'navigation link');
//      jvbDump($parent, 'Parent');
        global $wp;
        if (!array_key_exists('attrs', $block)) {
            return '';
        }
        $url = (str_starts_with($block['attrs']['url'],'/')) ?
            home_url($block['attrs']['url']) :
            $block['attrs']['url'];
        $current = (home_url($wp->request.'/') == $url);
        $temp = $block['attrs'];
        $temp = $block['attrs']??[];
        unset($temp['url']);
        $classes = ($current) ?
            $this->getClassesAndStyles($temp, ['current']):
@@ -533,21 +618,23 @@
        if ($current) {
            $aria = ' aria-current="page"';
        }
        $linkOpen = $this->build_navigation_link($block['attrs'], $aria);
        $linkOpen = $this->build_navigation_link($block['attrs']??[], $aria);
        return '<li'.$classes.'>'.$linkOpen.$block['attrs']['label'].'</a></li>';
    }
    public function render_core_navigation_submenu(array $block, string $content):string
    public function prerender_core_navigation_submenu(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'navigation submenu');
//      jvbDump($parent, 'Parent');
        global $wp;
        $url = (str_starts_with($block['attrs']['url'],'/')) ?
            home_url($block['attrs']['url']) :
            $block['attrs']['url'];
        $current = (home_url($wp->request) == $url);
        $temp = $block['attrs'];
        $temp = $block['attrs']??[];
        unset($temp['url']);
        $classes = ($current) ?
            $this->getClassesAndStyles($temp, ['has-submenu', 'current']):
@@ -609,10 +696,12 @@
    //core_post_author
    //core_post_author_biography
    //core_post_author_name
    public function render_core_post_content(array $block, string $content = ''):string
    public function prerender_core_post_content(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'post content');
//      jvbDump($parent, 'Parent');
        $tag = (array_key_exists('tagName', $block['attrs'])) ?
        $tag = (array_key_exists('tagName', $block['attrs']??[])) ?
            $block['attrs']['tagName'] :
            'main';
@@ -626,20 +715,28 @@
                $result = '';
            }
        } else {
            $result = $this->inside($block, false, $content);
            $result = $this->innerBlocks($block);
        }
        return apply_filters('jvb_post_content_output', $result, $block);
    }
    //core_post_date
    public function render_core_post_date(array $block):string
    public function prerender_core_post_date(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'post date');
//      jvbDump($parent, 'Parent');
        $postDate = get_the_date('c');
        return '<time datetime="'.$postDate.'" itemprop="datePublished"'.$this->getClassesAndStyles($block['attrs']).'>'.get_the_date().'</time>';
        return '<time datetime="'.$postDate.'" itemprop="datePublished"'.$this->getClassesAndStyles($block['attrs']??[]).'>'.get_the_date().'</time>';
    }
    //core_post_excerpt
    public function render_core_post_featured_image(array $block):string
    public function prerender_core_post_excerpt(array $block, ?string $content, ?WP_Block $parent):?string
    {
        return wpautop(get_the_excerpt());
    }
    public function prerender_core_post_featured_image(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'featured image');
//      jvbDump($parent, 'Parent');
        global $post;
        $ID = get_post_thumbnail_id($post->ID);
        $aOpen = $aClose = '';
@@ -648,41 +745,129 @@
            $aClose = '</a>';
        }
        return '<figure'.$this->getClassesAndStyles($block['attrs']).'>'.$aOpen.
        return '<figure'.$this->getClassesAndStyles($block['attrs']??[]).'>'.$aOpen.
               apply_filters('jvbCoreFeaturedImage', $this->image($ID), $post->post_type).
                $aClose.'</figure>';
    }
    //core_post_navigation_link
    //core_post_template
    //core_post_terms
    public function render_core_post_terms(array $block):string
    public function prerender_core_post_navigation_link(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $attr = $block['attrs'];
        $isPrevious = $attr['type']==='previous';
        $title = array_key_exists('showTitle', $attr)&&$attr['showTitle'];
        $linkLabel = array_key_exists('linkLabel', $attr)&&$attr['linkLabel'];
        $label = array_key_exists('label', $attr) ? $attr['label'] : '';
        $arrow = '';
        if (array_key_exists('arrow', $attr)) {
            $dir = $isPrevious ? 'left' : 'right';
            $icon = match($attr['arrow']) {
                'arrow' => 'arrow-square-',
                'chevron' => 'caret-circle-'
            };
            if ($icon) {
                $arrow = jvbIcon($icon.$dir);
            }
        }
//      return $content;
        $linkedLabel = $unlinkedLabel = '';
        if (!empty($label)) {
            $linkedLabel = $linkLabel ? $label : '';
            $unlinkedLabel = $linkLabel ? '' : $label;
        }
        if ($title) {
            $linkedLabel .=' %title';
        } elseif (!empty($label)) {
            $linkedLabel = $label;
            $unlinkedLabel = '';
        } else {
            $linkedLabel = $isPrevious ? 'Previous' : 'Next';
            $unlinkedLabel = '';
        }
        $result = $isPrevious ?
            get_previous_post_link(
                $arrow.$unlinkedLabel.' %link',
                $linkedLabel
            ) :
            get_next_post_link(
                '%link '.$unlinkedLabel.$arrow,
                $linkedLabel
            );
        return sprintf('<div%s>%s</div>',
            $this->getClassesAndStyles($attr,['row', 'nowrap']),
            $result
        );
    }
    //core_post_template
    public function render_core_post_template(array $block, string $content):string
    {
        global $wp_query;
        $inner = '';
        $block['innerBlocks'][0]['attrs']['tagName'] = 'li';
        if ($wp_query->have_posts()) {
            while ($wp_query->have_posts()) {
                $wp_query->the_post();
                $inner .= $this->innerBlocks($block);
            }
            wp_reset_postdata();
        }
        return sprintf(
            '<ul class="loop">%s</ul>',
            $inner
        );
    }
    //core_post_terms
    public function prerender_core_post_terms(array $block, ?string $content, ?WP_Block $parent):?string
    {
        if (!array_key_exists('attrs', $block)) {
            return '';
        }
        $terms = get_the_terms(get_the_ID(), $block['attrs']['term']);
        $out = '';
        if ($terms && !is_wp_error($terms)) {
            $out = '<ul class="term-list">';
                if (array_key_exists('prefix', $block['attrs'])) {
                    $out .= '<li>'.$block['attrs']['prefix'].'</li>';
            $out = sprintf(
                '<ul%s>',
                $this->getClassesAndStyles($block['attrs'], ['term-list', 'row', 'start'])
            );
                if (array_key_exists('prefix', $block['attrs']??[])) {
                    $out .= sprintf(
                        '<li class="prefix">%s</li>',
                        $block['attrs']['prefix']
                    );
                }
                foreach($terms as $term) {
                    $out .= '<li><a href="'.get_term_link($term).'" rel="tag">'.html_entity_decode($term->name).'</a></li>';
                    $out .= sprintf(
                        '<li><a href="%s" rel="tag">%s</a></li>',
                        get_term_link($term),
                        html_entity_decode($term->name)
                    );
                }
            if (array_key_exists('suffix', $block['attrs'])) {
                $out .= '<li>'.$block['attrs']['suffix'].'</li>';
                $out .= sprintf(
                    '<li class="suffix">%s</li>',
                    $block['attrs']['suffix']
                );
            }
            $out .= '</ul>';
        }
        return $out;
    }
    //core_post_time_to_read
    public function render_core_post_title(array $block):string
    public function prerender_core_post_title(array $block, ?string $content, ?WP_Block $parent):?string
    {
//      jvbDump($block, 'post content');
//      jvbDump($parent, 'Parent');
        $open = $close = '';
        if (array_key_exists('isLink', $block['attrs'])) {
            $rel = (array_key_exists('rel', $block['attrs'])) ?
        if (array_key_exists('isLink', $block['attrs']??[])) {
            $rel = (array_key_exists('rel', $block['attrs']??[])) ?
                ' rel="'.$block['attrs']['rel'].'"' :
                '';
            $target = (array_key_exists('linkTarget', $block['attrs'])) ?
            $target = (array_key_exists('linkTarget', $block['attrs']??[])) ?
                ' target="'.$block['attrs']['linkTarget'].'"' :
                '';
            $open = '<a href="' . get_the_permalink() . '"' . $rel . $target . '>';
@@ -695,110 +880,114 @@
                  array_key_exists('level', $block['attrs'])) ?
            $block['attrs']['level'] :
            2;
        return '<h'.$level.$this->getClassesAndStyles($block['attrs']).'>'.
        return '<h'.$level.$this->getClassesAndStyles($block['attrs']??[]).'>'.
               $open.get_the_title().$close.
               '</h'.$level.'>';
    }
    public function render_core_query(array $block, string $content):string
    public function render_core_query(array $block, string $content): string
    {
        $queryID = $block['attrs']['queryId'];
        $args = [];
        $inherit = $block['attrs']['inherit']??false;
        if ($inherit) {
            global $wp_query;
            $loop = $wp_query;
        } else {
            foreach ($block['attrs']['query'] as $key => $value) {
                if (empty($value)) {
                    continue;
                }
                switch ($key) {
                    case 'postType':
                        if ($value === BASE.'progress'){
                            $args['post_parent'] = 0;
                        }
                        $args['post_type'] = $value;
                        break;
                    case 'perPage':
                        $args['posts_per_page'] = $value;
                        break;
                    case 'orderBy':
                        $args['orderby'] = $value;
                        break;
                    case 'taxQuery':
                        $taxQuery = [];
                        foreach ($value as $tax => $terms) {
                            $taxQuery[] = [
                                'taxonomy'  => $tax,
                                'terms'     => $terms
                            ];
                        }
                        if (!empty($taxQuery)) {
                            $args['tax_query'] = $taxQuery;
                            if (count($taxQuery) > 1) {
                                $args['tax_query']['relation'] = 'OR';
                            }
                        }
                        break;
                    case 'sticky':
                        if ($value === 'ignore') {
                            $args['ignore_sticky_posts'] = true;
                        } else if ($value === 'exclude'){
                            $args['post__not_in'] = get_option('sticky_posts');
                        } else if ($value === 'only') {
                            $args['include'] = get_option('sticky_posts');
                        }
                        break;
                    case 'search':
                        $args['s'] = $value;
                        break;
                    default:
                        $args[$key] = $value;
                        break;
                }
            }
            //Add in any args from the query string
            $search = 'query-'.$queryID;
            foreach ($_GET as $key => $value) {
                if (str_contains($key, $search)) {
                    $key = str_replace($search, '', $key);
                    if ($key === 'page') {
                        $args['paged'] = (int)$value;
                    }
                }
            }
            $loop = new WP_Query($args);
        }
//      $queryID = $block['attrs']['queryId'] ?? null;
//      $inherit = $block['attrs']['inherit'] ?? false;
//
//      if ($inherit) {
//          global $wp_query;
//          $loop = $wp_query;
//      } else {
//          $args = [];
//          foreach (($block['attrs']['query'] ?? []) as $key => $value) {
//              if (empty($value)) {
//                  continue;
//              }
//              switch ($key) {
//                  case 'postType':
//                      if ($value === BASE.'progress'){
//                          $args['post_parent'] = 0;
//                      }
//                      $args['post_type'] = $value;
//                      break;
//                  case 'perPage':
//                      $args['posts_per_page'] = $value;
//                      break;
//                  case 'orderBy':
//                      $args['orderby'] = $value;
//                      break;
//                  case 'taxQuery':
//                      $taxQuery = [];
//                      foreach ($value as $tax => $terms) {
//                          $taxQuery[] = [
//                              'taxonomy'  => $tax,
//                              'terms'     => $terms
//                          ];
//                      }
//                      if (!empty($taxQuery)) {
//                          $args['tax_query'] = $taxQuery;
//                          if (count($taxQuery) > 1) {
//                              $args['tax_query']['relation'] = 'OR';
//                          }
//                      }
//                      break;
//                  case 'sticky':
//                      if ($value === 'ignore') {
//                          $args['ignore_sticky_posts'] = true;
//                      } else if ($value === 'exclude'){
//                          $args['post__not_in'] = get_option('sticky_posts');
//                      } else if ($value === 'only') {
//                          $args['include'] = get_option('sticky_posts');
//                      }
//                      break;
//                  case 'search':
//                      $args['s'] = $value;
//                      break;
//                  default:
//                      $args[$key] = $value;
//                      break;
//
//              }
//          }
//          $search = 'query-' . $queryID;
//          foreach ($_GET as $key => $value) {
//              if (str_contains($key, $search)) {
//                  $key = str_replace($search, '', $key);
//                  if ($key === 'page') {
//                      $args['paged'] = (int)$value;
//                  }
//              }
//          }
//          $loop = new WP_Query($args);
//      }
//
//      $inner = '';
//      foreach ($block['innerBlocks'] as $innerBlock) {
//          switch ($innerBlock['blockName']) {
//              case 'core/post-template':
//                  $inner .= '<section class="item-grid">';
//                  if ($loop->have_posts()) {
//                      while ($loop->have_posts()) {
//                          $loop->the_post();
//                          $postType = get_post_type();
//                          $inner .= '<div class="item ' . jvbNoBase($postType) . '">' . $this->innerBlocks($innerBlock) . '</div>';
//                      }
//                  }
//                  $inner .= '</section>';
//                  break;
//          }
//      }
//
//      // Reset only after a custom query, not the main query
//      if (!$inherit) {
//          wp_reset_postdata();
//      }
        $inner = '';
        foreach ($block['innerBlocks'] as $innerBlock) {
            switch ($innerBlock['blockName']) {
                case 'core/post-template':
                    $inner .= '<section class="item-grid">';
                    if ($loop->have_posts()) {
                        while($loop->have_posts()) {
                            $loop->the_post();
                            $postType = get_post_type();
                            $inner .= '<div class="item '.jvbNoBase($postType).'">'.$this->innerBlocks($innerBlock).'</div>';
                        }
                    }
                    $inner .= '</section>';
                    break;
            }
        }
        $tagName = (array_key_exists('tagName', $block['attrs'])) ? $block['attrs']['tagName'] : 'div';
        $out =  '<'.$tagName.' class="loop">'.$inner.'</'.$tagName.'>';
        if ($inherit) {
            wp_reset_postdata();
        }
        return $out;
        $tagName = $block['attrs']['tagName'] ?? 'div';
//      return sprintf(
//          '<%s class="loop">%s</%s>',
//          $tagName,
//          $this->innerBlocks($block),
//          $tagName
//      );
        return $this->innerBlocks($block);
    }
    //core_query_no_results
@@ -806,59 +995,153 @@
    //core_query_pagination_next
    //core_query_pagination_numbers
    //core_query_pagination_previous
    //core_query_title
    public function prerender_core_query_title(array $block, ?string $content, ?WP_Block $parent):?string
    {
        jvbDump($block);
        $attr = $block['attrs'];
        $name = '';
        $showPrefix = $attr['showPrefix']??false;
        $obj = get_queried_object();
        if (is_tax()) {
            $name = $showPrefix ? $obj->label.':' : '';
            $name .= $obj->name;
        } else if (is_post_type_archive()) {
            $name = $showPrefix ? 'Archive: ' : '';
            $name .= $obj->label;
        } elseif (is_search()) {
            $name = '<small>Search results for:</small> '.get_search_query();
        } elseif (is_singular()) {
            $name = $obj->post_title;
        }
        $level = array_key_exists('level', $attr) ? 'h'.$attr['level'] : 'h1';
        return sprintf(
            '<%s id="%s">%s</%s>',
            $level,
            sanitize_title($name),
            $name,
            $level
        );
    }
    //core_read_more
    public function render_core_template_part(array $block, string $content):string
    public function prerender_core_template_part(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $isHeaderTemplate = (
            (array_key_exists('slug', $block['attrs']) && str_contains(strtolower($block['attrs']['slug']), 'header')) ||
            (array_key_exists('tagName', $block['attrs']) && str_contains(strtolower($block['attrs']['tagName']), 'header'))
        ) ? 'header' : false;
        $isFooterTemplate = (
            (array_key_exists('slug', $block['attrs']) && str_contains(strtolower($block['attrs']['slug']), 'footer')) ||
            (array_key_exists('tagName', $block['attrs']) && str_contains(strtolower($block['attrs']['tagName']), 'footer'))
        ) ? 'footer' : false;
//      jvbDump($block, 'template part');
//      jvbDump($parent, 'Parent');
        if (($isHeaderTemplate || $isFooterTemplate)) {
            $innerContent = $content;
        $slug  = $block['attrs']['slug'] ?? null;
        $theme = $block['attrs']['theme'] ?? get_stylesheet();
        $tag = $block['attrs']['tagName'] ?? 'div';
        if (!$slug) {
            return $content;
        }
            $tag = $isHeaderTemplate ?: $isFooterTemplate ?: 'div';
        // Try to get the template part post (customized via FSE)
        $template_part = get_block_template( "$theme//$slug", 'wp_template_part' );
            $breadcrumbs = $themeSwitch = $afterHeader = $beforeHeader = $footerText= '';
            if ($isHeaderTemplate) {
        if ( $template_part && ! empty( $template_part->content ) ) {
            $block['innerBlocks'] = parse_blocks( $template_part->content );
                $beforeHeader = apply_filters('jvbAboveHeader', $beforeHeader);
                if ($beforeHeader !== '') {
                    $beforeHeader = '<aside class="pre header row btw">'.$beforeHeader.'</aside>';
                }
                $themeSwitch = jvbDarkModeToggle();
                $breadcrumbs = BreadcrumbManager::getInstance()->renderNavigation();
                $afterHeader = apply_filters('jvbBelowHeader', $afterHeader);
            $before = $themeSwitch = $after = $beforeClose = '';
            switch ($tag) {
                case 'header':
                    $before = apply_filters('jvbAboveHeader', '');
                    if (!empty($before)) {
                        $before = sprintf(
                            '<aside class="pre header row btw">%s</aside>',
                            $before
                        );
                    }
                    $themeSwitch = jvbDarkModeToggle();
                if ($afterHeader !== '') {
                    $afterHeader = '<aside class="sub header row btw">'.$afterHeader.'</aside>';
                }
                $footerText = '<div class="scroll-progress"><div class="bar"></div>
</div>';
            } elseif ($isFooterTemplate) {
                $beforeHeader = apply_filters('jvbBeforeFooter', '');
                if ($beforeHeader !== '') {
                    $beforeHeader = '<aside class="footer">'.$beforeHeader.'</aside>';
                }
                    $footerText = jvbRandomFooterText();
                    $after = apply_filters('jvbBelowHeader', $after);
                    if (!empty($after)) {
                        $after = sprintf(
                            '<aside class="sub header row btw">%s</aside>',
                            $after
                        );
                    }
                    $after .= BreadcrumbManager::getInstance()->renderNavigation();
                    $beforeClose = '<div class="scroll-progress"><div class="bar"></div></div>';
                    break;
                case 'footer':
                    $before = apply_filters('jvbBeforeFooter', $before);
                    if (!empty($before)) {
                        $before = sprintf(
                            '<aside class="pre footer">%s</aside>',
                            $before
                        );
                    }
                    $beforeClose = jvbRandomFooterText();
                    break;
            }
            $content = $beforeHeader.'<'.$tag.$this->getClassesAndStyles($block['attrs']).'>'.
                   $themeSwitch .
                    $this->inside($block, false, $innerContent).
//                 $this->innerBlocks($block).
//                  $innerContent.
                   $footerText.'</'.$tag.'>'.$afterHeader.$breadcrumbs;
        }
            return sprintf(
                '%s<%s%s>%s%s%s</%s>%s',
                $before,
                $tag,
                $this->getClassesAndStyles($block['attrs']??[]),
                $themeSwitch,
                $this->innerBlocks($block),
                $beforeClose,
                $tag,
                $after
            );
        return $content;
        }
        if (JVB_TESTING) {
            jvbDump('Could not create template part block for '.$block['blockName']);
        }
        return $content;
//      $isHeaderTemplate = (
//          (array_key_exists('slug', $block['attrs']??[]) && str_contains(strtolower($block['attrs']['slug']), 'header')) ||
//          (array_key_exists('tagName', $block['attrs']??[]) && str_contains(strtolower($block['attrs']['tagName']), 'header'))
//      ) ? 'header' : false;
//      $isFooterTemplate = (
//          (array_key_exists('slug', $block['attrs']??[]) && str_contains(strtolower($block['attrs']['slug']), 'footer')) ||
//          (array_key_exists('tagName', $block['attrs']??[]) && str_contains(strtolower($block['attrs']['tagName']), 'footer'))
//      ) ? 'footer' : false;
//        if (($isHeaderTemplate || $isFooterTemplate)) {
//          $innerContent = $content;
//
//          $tag = $isHeaderTemplate ?: $isFooterTemplate ?: 'div';
//
//            $breadcrumbs = $themeSwitch = $afterHeader = $beforeHeader = $footerText= '';
//            if ($isHeaderTemplate) {
//
//              $beforeHeader = apply_filters('jvbAboveHeader', $beforeHeader);
//              if ($beforeHeader !== '') {
//                  $beforeHeader = '<aside class="pre header row btw">'.$beforeHeader.'</aside>';
//              }
//                $themeSwitch = jvbDarkModeToggle();
//                $breadcrumbs = BreadcrumbManager::getInstance()->renderNavigation();
//              $afterHeader = apply_filters('jvbBelowHeader', $afterHeader);
//
//              if ($afterHeader !== '') {
//                  $afterHeader = '<aside class="sub header row btw">'.$afterHeader.'</aside>';
//              }
//              $footerText = '<div class="scroll-progress"><div class="bar"></div>
//</div>';
//            } elseif ($isFooterTemplate) {
//              $beforeHeader = apply_filters('jvbBeforeFooter', '');
//              if ($beforeHeader !== '') {
//                  $beforeHeader = '<aside class="footer">'.$beforeHeader.'</aside>';
//              }
//                  $footerText = jvbRandomFooterText();
//          }
//
//            $content = $beforeHeader.'<'.$tag.$this->getClassesAndStyles($block['attrs']??[]).'>'.
//                   $themeSwitch .
//                  $this->innerBlocks($block).
////                   $this->innerBlocks($block).
////                    $innerContent.
//                   $footerText.'</'.$tag.'>'.$afterHeader.$breadcrumbs;
//        }
//
//        return $content;
    }
    //core_term_description
@@ -876,10 +1159,14 @@
    //core_rss
    //core_search
    //core_shortcode
    public function render_core_social_link(array $block, string $content):string
    public function prerender_core_social_link(array $block, ?string $content, ?WP_Block $parent):?string
    {
        $url = $block['attrs']['url'];
        $service = $block['attrs']['service'];
//      jvbDump($block, 'social link');
//      jvbDump($parent, 'Parent');
        $attrs = $block['attrs']??[];
        $url = $attrs['url']??'';
        $service = $attrs['service']?:'';
        $iconName = ($service === 'bluesky') ? 'butterfly' : $service.'-logo';
        $icon = jvbIcon($iconName);
        if (!$icon) {
@@ -887,9 +1174,12 @@
        }
        return '<li><a href="'.$url.'" target="_blank" rel="nofollow" title="Find us on '.ucfirst($service).'">'.$icon.'<span class="screen-reader-text">Find us on '.ucfirst($service).'</span></a></li>';
    }
    public function render_core_social_links(array $block, string $content):string
    public function prerender_core_social_links(array $block, ?string $content, ?WP_Block $parent):?string
    {
        return '<ul class="socials">'.$this->inside($block, false, $content).'</ul>';
//      jvbDump($block, 'social links');
//      jvbDump($parent, 'Parent');
        return '<ul class="socials">'.$this->innerBlocks($block).'</ul>';
    }
    //core_tag_cloud
@@ -911,56 +1201,42 @@
    /***********************************
     * Helpers
     **********************************/
    public function stripTagContents(string $tag, string $content):string
    public function stripTagContents(string $tag, ?string $content):string
    {
        $clean = preg_replace('/<'.$tag.'\b[^>]*>.*?<\/'.$tag.'>/is', '', $content);
        $clean = preg_replace('/\s+/', ' ', $clean);
        return trim($clean);
    }
    public function innerBlocks(array $block, string $before = '', string $after = ''):string
    public function innerBlocks(array $block, string $before = '', string $after = '', bool $prerender = true):string
    {
        $content = '';
        foreach ($block['innerBlocks'] as $b) {
            $content .= $this->render('', $b);
            $content .= sprintf('%s%s%s',
                $before,
                render_block($b),
                $after
            );
        }
        return $content;
    }
    public function inside(array $block, mixed $tag = false, mixed $o = false): string
    {
        if (!$o) {
            $o = trim($block['innerHTML']);
        $html = $o ?: trim($block['innerHTML']);
        if (empty($html)) {
            return '';
        }
        $dom = new \DOMDocument();
        @$dom->loadHTML('<?xml encoding="utf-8"?>' . $o, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD);
        // Find the real outermost element
        $root = null;
        foreach ($dom->childNodes as $node) {
            if ($node->nodeType === XML_ELEMENT_NODE) {
                $root = $node;
                break;
        if (preg_match('/^<(\w+)[^>]*>(.*)<\/\1>$/s', $html, $matches)) {
            if ($tag && strtolower($matches[1]) !== strtolower($tag)) {
                return $html;
            }
            return trim($matches[2]);
        }
        if (!$root) {
            return $o;
        }
        // Only enforce tag match if explicitly provided
        if ($tag && strtolower($root->nodeName) !== strtolower($tag)) {
            return $o;
        }
        $inner = '';
        foreach ($root->childNodes as $child) {
            $inner .= $dom->saveHTML($child);
        }
        return trim($inner);
        return $html;
    }
    /**
@@ -997,7 +1273,7 @@
                 (!array_key_exists('attrs', $block) && !array_key_exists('id', $block['attrs'])))) {
                $ID = get_post_thumbnail_id();
            } else {
                if (array_key_exists('id', $block['attrs'])) {
                if (array_key_exists('id', $block['attrs']??[])) {
                    $ID = $block['attrs']['id'];
                } elseif (array_key_exists('mediaId', $block['attrs'])) {
                    $ID = $block['attrs']['mediaId'];
inc/managers/DashboardManager.php
@@ -27,7 +27,7 @@
    {
        $this->cache = Cache::for('dashboard', WEEK_IN_SECONDS)->connect('user');
        add_action('init', [$this, 'registerDashboard']);
        $this->cache->flush();
        $this->user = wp_get_current_user();
        $this->role = jvbUserRole($this->user->ID);
        $this->userLink = (int)get_user_meta($this->user->ID, BASE.'profile_link', true);
@@ -677,7 +677,10 @@
            if (function_exists($function)) {
                echo $function([],'');
            } else {
                echo JVB()->blocks()->render_core_site_logo([],'');
                echo render_block( [
                    'blockName' => 'core/site-logo',
                    'attrs'     => [],
                ]);
            }
            ?>
@@ -754,8 +757,11 @@
                        $itemMenu = $item->submenu($slug);
                        foreach ($taxonomies as $s) {
                            $taxRegistrar = Registrar::getInstance($s);
                            $itemMenu->addItem($taxRegistrar->getPlural(), $taxRegistrar->getIcon())
                            if ($taxRegistrar) {
                                $itemMenu->addItem($taxRegistrar->getPlural(), $taxRegistrar->getIcon())
                                ->url($this->baseURL.'/'.$s);
                            }
                        }
                    }
                }
inc/managers/LoginManager.php
@@ -23,6 +23,7 @@
    protected array $fields = [];
    protected ?string $action = null;
    protected string $title = '';
    protected static LoginManager $instance;
    // Token handlers registry
    protected array $messageHandlers = [];
@@ -35,8 +36,10 @@
    ];
    private int $max_file_size = 5242880; // 5MB in bytes
    public function __construct()
    {
        self::$instance = $this;
        $this->cache = Cache::for('login');
        $this->cache->flush();
        // Initialize magic link support if enabled
@@ -65,6 +68,10 @@
        add_action('user_register', array($this, 'saveRegistrationFields'), 999, 2);
        add_filter('the_seo_framework_sitemap_exclude_ids', [$this, 'excludeLoginSitemap'], 10, 1);
    }
    public static function getInstance():self
    {
        return self::$instance;
    }
    public function excludeLoginSitemap(array $ids): array
    {
@@ -183,8 +190,13 @@
    protected function setupFields():void
    {
        $this->fields = $this->getFieldsForAction($this->action);
    }
    protected function getFieldsForAction(string $action):array
    {
        $fields = [];
        switch($this->action) {
        switch($action) {
            case 'register':
                $fields = $this->getRegistrationFormFields();
                break;
@@ -255,9 +267,10 @@
                break;
        }
        $this->fields = $fields;
        return $fields;
    }
    /**
     * Ensure login page exists
     */
@@ -540,28 +553,8 @@
            <h1><?=$this->labels['title']?></h1>
            <?= $this->labels['description'] ?>
            <?= $this->renderLoginForm($this->action); ?>
            <form name="<?=$form?>" method="post" data-action="jvb_<?=$this->action?>">
                <?= jvbFormStatus() ?>
                <?php wp_nonce_field('jvb_'.$this->action, '_wpnonce'); ?>
                <input type="hidden" name="action" value="jvb_<?=$this->action?>">
                <input type="hidden" name="redirect_to" value="<?= esc_attr($_GET['redirect_to'] ?? '') ?>">
                <input type="hidden" name="request_id" value="<?= wp_generate_password(16, false) ?>">
                <?= ($this->action === 'magic') ? '<input type="hidden" name="type" value="login">' : '' ?>
                <?php
                do_action('jvb_add_token_inputs', $this->action);
                foreach ($this->fields as $name => $config) {
                    echo Form::render($name, '', $config);
                }
                $this->maybeTurnstile();
                 ?>
                 <div class="row btw nowrap">
                    <button type="submit" class="button button-primary button-large"><?=$this->labels['submit']?></button>
                    <?php $this->maybeMagicLink(); ?>
                </div>
            </form>
            <?php
            if (is_array($this->labels['extra'])) {
@@ -610,6 +603,59 @@
        </div>
        <?php
    }
    public function renderLoginForm(string $action = 'login', string $redirect = '', string $title = ''):string
    {
        ob_start();
        do_action('jvb_add_token_inputs', $this->action);
        $additionalInputs = ob_get_clean();
        $fields = '';
        $theFields = $this->getFieldsForAction($action);
        foreach ($theFields as $name => $config) {
            $fields .= Form::render($name, '', $config);
        }
        ob_start();
        $this->maybeTurnstile();
        $turnstile = ob_get_clean();
        ob_start();
        $this->maybeMagicLink();
        $magicLink = ob_get_clean();
        $redirect = !empty($redirect) ? $redirect : esc_attr($_GET['redirect_to'] ?? '');
        return sprintf(
            '<form name="%sform" method="post" data-action="jvb_%s">
                %s%s%s
                <input type="hidden" name="action" value="jvb_%s">
                <input type="hidden" name="redirect_to" value="%s">
                <input type="hidden" name="request_id" value="%s">
                %s
                %s
                %s
                %s
                 <div class="row btw nowrap">
                    <button type="submit" class="button button-primary button-large">%s</button>
                    %s
                </div>
            </form>',
            $action,
            $action,
            jvbFormStatus(),
            $title,
            wp_nonce_field('jvb_'.$action),
            $action,
            $redirect,
            wp_generate_password(16, false),
            ($action === 'magic') ? '<input type="hidden" name="type" value="login">' : '',
            $additionalInputs,
            $fields,
            $turnstile,
            $this->labels['submit'],
            $magicLink
        );
    }
    protected function renderHeader():void
    {
    ?>
@@ -968,6 +1014,11 @@
    {
    }
    public function setAction(string $action = 'login'):void
    {
        $this->action = $action;
        $this->setup();
    }
}
// Initialize the login manager
inc/managers/SEO/BreadcrumbManager.php
@@ -215,6 +215,7 @@
        $name = jvbNoBase($type);
        $registrar = Registrar::getInstance($name);
        if($registrar && $registrar->hasFeature('show_directory')) {
            $directory = JVB()->directories();
            if ($directory && !empty($directory->directories($name)??[])){
@@ -228,11 +229,15 @@
                'name'  => JVB()->directories()->referAs(true),
                'url'   => get_post_type_archive_link($type)
            ];
        } elseif (is_post_type_archive() && $registrar && $registrar->hasFeature('show_directory')) {
        } elseif ($registrar) {
            $crumbs[] = [
                'name' => $registrar->getConfig('breadcrumbs')['title'] ?? $registrar->getPlural(),
                'url'  => get_post_type_archive_link($type)
                'name'  => $registrar->getConfig('breadcrumbs')['title'] ?? $registrar->getPlural(),
                'url'   => get_post_type_archive_link($type)
            ];
        } else {
            $crumbs[] = [
                'name'  => $obj->label,
                'url'   => get_post_type_archive_link($type)
            ];
        }
inc/managers/SEO/render/Thing/Place/AdministrativeArea/State.php
@@ -1,5 +1,5 @@
<?php
namespace JVBase\inc\managers\SEO\render\Thing\Place\AdministrativeArea;
namespace JVBase\managers\SEO\render\Thing\Place\AdministrativeArea;
use JVBase\managers\SEO\render\Thing\Intangible\ContactPoint\PostalAddress;
use JVBase\managers\SEO\render\Thing\Place\AdministrativeArea\AdministrativeArea;
src/drawer-menu/block.json
File was deleted
src/drawer-menu/edit.js
File was deleted
src/drawer-menu/editor.scss
src/drawer-menu/index.js
File was deleted
src/drawer-menu/index.php
src/drawer-menu/render.php
File was deleted
src/drawer-menu/save.js
File was deleted
src/drawer-menu/style.scss
File was deleted
src/drawer-menu/view.js
File was deleted
src/faq/block.json
File was deleted
src/faq/edit.js
File was deleted
src/faq/editor.scss
File was deleted
src/faq/index.js
File was deleted
src/faq/index.php
src/faq/render.php
src/faq/style.scss
File was deleted
src/faq/view.js
File was deleted
src/feed/block.json
File was deleted
src/feed/edit.js
File was deleted
src/feed/editor.scss
File was deleted
src/feed/index.js
File was deleted
src/feed/index.php
File was deleted
src/feed/render.php
File was deleted
src/feed/save.js
File was deleted
src/feed/style.scss
File was deleted
src/feed/view.js
File was deleted
src/feed/viewOld.js
File was deleted
src/fields/block.json
File was deleted
src/fields/edit.js
File was deleted
src/fields/editor.scss
File was deleted
src/fields/index.js
File was deleted
src/fields/index.php
src/fields/render.php
File was deleted
src/fields/save.js
File was deleted
src/fields/style.scss
File was deleted
src/fields/view.js
File was deleted
src/forms/block.json
File was deleted
src/forms/edit.js
File was deleted
src/forms/editor.scss
src/forms/index.js
File was deleted
src/forms/index.php
src/forms/render.php
File was deleted
src/forms/save.js
File was deleted
src/forms/style.scss
File was deleted
src/forms/view.js
File was deleted
src/glossary/block.json
File was deleted
src/glossary/edit.js
File was deleted
src/glossary/editor.scss
src/glossary/index.js
File was deleted
src/glossary/index.php
src/glossary/render.php
File was deleted
src/glossary/style.scss
File was deleted
src/glossary/view.js
File was deleted
src/gmbreviews/block.json
File was deleted
src/gmbreviews/edit.js
File was deleted
src/gmbreviews/editor.scss
src/gmbreviews/index.js
File was deleted
src/gmbreviews/index.php
src/gmbreviews/render.php
File was deleted
src/gmbreviews/style.scss
File was deleted
src/gmbreviews/view.js
src/index.php
File was deleted
src/menu/block.json
File was deleted
src/menu/edit.js
File was deleted
src/menu/editor.scss
src/menu/index.js
File was deleted
src/menu/index.php
src/menu/render.php
File was deleted
src/menu/style.scss
src/menu/view.js
File was deleted
src/summary/block.json
File was deleted
src/summary/edit.js
File was deleted
src/summary/editor.scss
File was deleted
src/summary/index.js
File was deleted
src/summary/index.php
src/summary/render.php
File was deleted
src/summary/save.js
File was deleted
src/summary/style.scss
File was deleted
src/summary/view.js
File was deleted
src/timeline/block.json
File was deleted
src/timeline/edit.js
File was deleted
src/timeline/editor.scss
src/timeline/index.js
File was deleted
src/timeline/index.php
src/timeline/render.php
File was deleted
src/timeline/style.scss
File was deleted
src/timeline/view.js
src/video/block.json
File was deleted
src/video/edit.js
File was deleted
src/video/editor.scss
File was deleted
src/video/index.js
File was deleted
src/video/index.php
src/video/render.php
File was deleted
src/video/save.js
File was deleted
src/video/style.scss
File was deleted
src/video/view.js
File was deleted
wp-config.php
File was deleted