cache = Cache::for('blocks', WEEK_IN_SECONDS); $this->cache->connect('post')->connect('taxonomy'); add_filter('render_block', [$this, 'render'], 900, 3); add_action('init', [$this, 'registerBlockStyles']); } public function registerBlockStyles():void { do_action('jvbBlockStyles'); //Register extra block styles register_block_style( 'core/navigation', [ 'name'=>'condensed', 'label' => __('Condensed', 'jvb') ] ); register_block_style( 'core/navigation', [ 'name'=>'floating', 'label' => __('Floating', 'jvb') ] ); register_block_style( 'core/navigation', [ 'name'=>'fixed', 'label' => __('Fixed', 'jvb') ] ); register_block_style( 'core/group', [ 'name' =>'callout', 'label' => __('Callout', 'jvb') ] ); register_block_style( 'core/group', [ 'name' =>'callalt', 'label' => __('Callout Alt', 'jvb') ] ); } public function render(string $content, array $block, WP_Block $instance) { $blockName = $this->sanitizeBlockName($block); $method = 'render_'.$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); // } // ); } 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)); // } } 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; } /*********************************** * Blocks **********************************/ /** * Common Blocks */ //For Reference: //core_form //core_form_input //core_form_submission_notification //core_form_submit_button /** * Design blocks */ public function render_core_button(array $block):string { preg_match('/href="([^"]*)"/', $block['innerHTML'], $url); preg_match('/>([^<]*)<\/a>/', $block['innerHTML'], $label); if (empty($url[1]) || empty($label[1])) { return ''; } $icon = ''; if (str_contains($url[1], 'google.com/maps')) { $icon = 'google-logo'; } if (str_contains($url[1], 'maps.apple.com')) { $icon = 'apple-logo'; } if ($icon !== '') { return sprintf( '%s Maps', $this->getClassesAndStyles($block['attrs']), esc_url($url[1]), esc_html($label[1]), jvbIcon($icon) ); } return sprintf( '%s', $this->getClassesAndStyles($block['attrs']), esc_url($url[1]), esc_html($label[1]) ); } public function render_core_buttons(array $block):string { return 'getClassesAndStyles($block['attrs'], ['buttons','row']).'>'. $this->innerBlocks($block).''; } public function render_core_column(array $block):string { $styles = (array_key_exists('attrs', $block) && array_key_exists('width', $block['attrs'])) ? ['flex-basis:'.$block['attrs']['width']] : []; return 'getClassesAndStyles($block['attrs'], ['col'], $styles).'>'. $this->innerBlocks($block).''; } public function render_core_columns(array $block):string { return 'getClassesAndStyles($block['attrs'], ['columns']).'>'. $this->innerBlocks($block).''; } //core_comment_template public function render_core_group(array $block):string { $tag = (array_key_exists('tagName', $block['attrs'])) ? $block['attrs']['tagName'] : 'div'; $classes = ($tag === 'main') ? '' : $this->getClassesAndStyles($block['attrs'], ['group']); return '<'.$tag.$classes.'>'.$this->innerBlocks($block).''; } //core_home_link //core_more //core_nextpage public function render_core_separator(array $block):string { return 'getClassesAndStyles($block['attrs']).'>'; } public function render_core_spacer(array $block):string { return 'getClassesAndStyles($block['attrs'], ['spacer'], ['height:2rem']). ' aria-hidden="true">'; } //core_table_of_contents //core_text_columns /** * Embed Block */ //core_embed /** * Media Blocks */ //core_audio public function render_core_cover(array $block):string { // Extract block attributes $attrs = $block['attrs'] ?? []; $innerContent = $this->innerBlocks($block); $position = 'object-position: center;'; if (array_key_exists('focalPoint', $attrs)) { $x = (array_key_exists('x', $attrs['focalPoint'])) ? ($attrs['focalPoint']['x'] * 100).'%' : 'center'; $y = (array_key_exists('y', $attrs['focalPoint'])) ? ($attrs['focalPoint']['y'] * 100).'%' : 'center'; $position = 'object-position:'.$x.' '.$y.';'; unset($attrs['focalPoint']); } // Check for background type $backgroundType = $attrs['backgroundType'] ?? 'image'; $background = ''; $ID = false; if (array_key_exists('useFeaturedImage', $attrs)) { global $post; $ID = get_post_thumbnail_id($post->ID); } else if (array_key_exists('id', $attrs)) { $ID = (int)$attrs['id']; } if ($backgroundType === 'image' && $ID) { $background .= str_replace('image($ID)); } elseif ($backgroundType === 'video' && isset($attrs['url'])) { $background .= ''; } // Build classes and styles unset($attrs['url']); $classes = $this->getClassesAndStyles($attrs, ['cover row']); return '' . $background . '
' . $innerContent . '
'; } //core_file public function render_core_gallery(array $block):string { return 'getClassesAndStyles($block['attrs'], ['gallery']).'>'. $this->innerBlocks($block,'
  • ', '
  • '). ''; } public function render_core_image(array $block):string { $ID = $this->imageID('', $block); if (!$ID) { return ''; } $title = (get_the_title($ID) !== '') ? ''.get_the_title($ID).'' : ''; $caption = (wp_get_attachment_caption($ID)) ? '
    ' . $title . wp_get_attachment_caption($ID) . '
    ' : '
    ' . $title . '
    '; $size = array_key_exists('sizeSlug', $block['attrs']) ? $block['attrs']['sizeSlug'] : 'large'; return 'getClassesAndStyles($block['attrs']).'>'. $this->imageLink(true, $ID, 'tiny', $size) . $caption.''; } public function render_core_media_text(array $block):string { $ID = $this->imageID('', $block); $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'])) { $classes[] = 'nowrap'; } $content = 'getClassesAndStyles($block['attrs'], $classes).'>'; $content .= (array_key_exists( 'mediaPosition', $block['attrs'] ) && $block['attrs']['mediaPosition'] == 'right') ? '
    '.$inner.'
    '.$imgLink.'
    ' : '
    '.$imgLink.'
    '.$inner.'
    '; $content .= ''; return $content; } //core_video /** * Reusable blocks */ //core_pattern /** * Text Blocks */ //render_core_code //render_core_details //render_core_footnotes //render_core_classic public function render_core_heading(array $block):string { $level = (array_key_exists('level', $block['attrs'])) ? $block['attrs']['level'] : '2'; $content = $this->inside($block); $id = sanitize_title(wp_strip_all_tags($this->stripTagContents('small', $content))); return 'getClassesAndStyles($block['attrs']).'>'. $content. ''; } public function render_core_list(array $block):string { $tag = (array_key_exists('ordered', $block['attrs'])) ? 'ol' : 'ul'; return '<'.$tag.$this->getClassesAndStyles($block['attrs']).'>'.$this->innerBlocks($block).''; } // public function render_core_list_item(array $block):string // { // return 'getClassesAndStyles($block['attrs']).'>'.$this->inside($block).''; // } //render_core_missing public function render_core_paragraph(array $block):string { return 'getClassesAndStyles($block['attrs']).'>'. $this->inside($block, 'p'). '

    '; } public function render_core_quote(array $block): string { $innerHTML = $block['innerHTML']; // Extract cite content first $cite = $this->extractElement($innerHTML, 'cite'); $citeHtml = ($cite === '') ? '' : '— '.$cite.''; // Get the blockquote content $content = $this->inside($block, 'blockquote'); // Remove the cite element from content if it exists if ($cite !== '') { $content = $this->stripTagContents('cite', $content); } return 'getClassesAndStyles($block['attrs']).'>
    '.$content.'
    '. $citeHtml. ''; } public function render_core_pullquote(array $block): string { $innerHTML = $block['innerHTML']; // Extract cite content first $cite = $this->extractElement($innerHTML, 'cite'); $citeHtml = ($cite === '') ? '' : '— '.$cite.''; // Get the blockquote content $content = $this->extractElement($innerHTML, 'blockquote'); // Remove the cite element from content if it exists if ($cite !== '') { $content = $this->stripTagContents('cite', $content); } $content = jvb_filter_content( $content); return 'getClassesAndStyles($block['attrs'], ['pull']).'>'. $content. $citeHtml. ''; } //render_core_table //render_core_verse /** * Theme Blocks */ //core_avatar //core_loginout //core_pattern public function render_core_site_logo(array $block, string $content):string { $open = $close = ''; if (!is_home() && !is_front_page()) { $open = ''; $close = ''; } $img = get_theme_mod('custom_logo'); $img = $this->image($img, 'tiny', 'thumbnail'); $img = str_replace('getClassesAndStyles($block['attrs']), $img); return $open.$img.$close; } //core_site_title_tagline public function render_core_site_title(array $block, string $content):string { $tag = (array_key_exists('level', $block['attrs'])) ? $block['attrs']['level'] : 1; $tag = ($tag == 0) ? 'p' : 'h'.$tag; $open = $close = ''; if (!is_front_page()) { $open = ''; $close = ''; } $class = ($tag === 'p') ? $this->getClassesAndStyles($block['attrs'], ['title']) : $this->getClassesAndStyles($block['attrs']); return '<'.$tag.$class.'>'. $open. get_bloginfo('name'). $close. ''; } /** * Theme Comments Blocks */ //core_avatar //core_comment_author_name //core_comment_content //core_comment_date //core_comment_edit_link //core_comment_reply_link //core_comments //core_comments_pagination //core_comments_pagination_next //core_comments_pagination_number //core_comments_pagination_previous //core_comments_title //core_post_comments_form /** * Theme Navigation Blocks */ public function render_core_navigation(array $block, string $content):string { $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']) && $block['attrs']['overlayMenu'] == 'never') ? '': ''; $class = ($toggle === '') ? $this->getClassesAndStyles($block['attrs'], ['mobile']) : $this->getClassesAndStyles($block['attrs']); $helpmenu = (get_the_title($ID) === 'Main') ? '' : ''; //Allows to add custom items to a menu, based on the menu name $helpmenu = apply_filters('jvbMenuExtraAfter', $helpmenu, get_the_title($ID)); $main = trim(apply_filters('jvbMenuExtra', $this->innerBlocks($block), get_the_title($ID), $block)); $main = str_starts_with($main, ''.$main.''; return ' Skip to Content ' . $toggle . $main. ''.$helpmenu; } public function render_core_navigation_link(array $block):string { 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']; unset($temp['url']); $classes = ($current) ? $this->getClassesAndStyles($temp, ['current']): $this->getClassesAndStyles($temp); $aria = ''; if ($current) { $aria = ' aria-current="page"'; } $linkOpen = $this->build_navigation_link($block['attrs'], $aria); return ''.$linkOpen.$block['attrs']['label'].''; } public function render_core_navigation_submenu(array $block):string { 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']; unset($temp['url']); $classes = ($current) ? $this->getClassesAndStyles($temp, ['has-submenu', 'current']): $this->getClassesAndStyles($temp, ['has-submenu']); $aria = ''; if ($current) { $aria = ' aria-current="page"'; } $id = sanitize_title($block['attrs']['label']); $linkOpen = $this->build_navigation_link($block['attrs'], $aria); $content = ''.$linkOpen.$block['attrs']['label']. ''; return $content; } protected function build_navigation_link(array $attrs, string $aria):string { global $wp; $url =(str_starts_with($attrs['url'],'/')) ? home_url($attrs['url']) : $attrs['url']; $target = $type = $id = $label = $desc = $rel = $title = $kind = ''; foreach ($attrs as $k => $v) { switch ($k) { case 'description': $desc = $v; break; case 'rel': $rel = ' rel="'.$v.'"'; break; case 'title': $title = ' title="'.$v.'"'; break; case 'kind': $kind = $v; break; case 'type': $type = $v; break; case 'opensInNewTab': $target = ' target="'.$v.'"'; break; } } return ''; } /** * Theme Query Blocks */ //core_post_author //core_post_author_biography //core_post_author_name public function render_core_post_content(array $block, string $content = ''):string { $tag = (array_key_exists('tagName', $block['attrs'])) ? $block['attrs']['tagName'] : 'main'; if ($content == '') { if(is_singular()) { global $post; $block['innerBlocks'] = parse_blocks($post->post_content); $result = $this->innerBlocks($block); }else { $result = ''; } } else { $result = $this->inside($block, $tag, $content); } return apply_filters('jvb_post_content_output', $result, $block); } //core_post_date public function render_core_post_date(array $block):string { $postDate = get_the_date('c'); return ''; } //core_post_excerpt public function render_core_post_featured_image(array $block):string { global $post; $ID = get_post_thumbnail_id($post->ID); $aOpen = $aClose = ''; if(!is_single($post->ID)) { $aOpen = ''; $aClose = ''; } return 'getClassesAndStyles($block['attrs']).'>'.$aOpen. apply_filters('jvbCoreFeaturedImage', $this->image($ID), $post->post_type). $aClose.''; } //core_post_navigation_link //core_post_template //core_post_terms public function render_core_post_terms(array $block):string { $terms = get_the_terms(get_the_ID(), $block['attrs']['term']); $out = ''; if ($terms && !is_wp_error($terms)) { $out = '
      '; if (array_key_exists('prefix', $block['attrs'])) { $out .= '
    • '.$block['attrs']['prefix'].'
    • '; } foreach($terms as $term) { $out .= '
    • '; } if (array_key_exists('suffix', $block['attrs'])) { $out .= '
    • '.$block['attrs']['suffix'].'
    • '; } $out .= '
    '; } return $out; } //core_post_time_to_read public function render_core_post_title(array $block):string { $open = $close = ''; 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="'.$block['attrs']['linkTarget'].'"' : ''; $open = ''; $close = ''; } if (is_singular(BASE.'partner')) { $open .= 'edmonton.ink partner: '; } $level = (array_key_exists('attrs', $block) && array_key_exists('level', $block['attrs'])) ? $block['attrs']['level'] : 2; return 'getClassesAndStyles($block['attrs']).'>'. $open.get_the_title().$close. ''; } 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); } $inner = ''; foreach ($block['innerBlocks'] as $innerBlock) { switch ($innerBlock['blockName']) { case 'core/post-template': $inner .= '
    '; if ($loop->have_posts()) { while($loop->have_posts()) { $loop->the_post(); $postType = get_post_type(); $inner .= '
    '.$this->innerBlocks($innerBlock).'
    '; } } $inner .= '
    '; break; } } $tagName = (array_key_exists('tagName', $block['attrs'])) ? $block['attrs']['tagName'] : 'div'; $out = '<'.$tagName.' class="loop">'.$inner.''; if ($inherit) { wp_reset_postdata(); } return $out; } //core_query_no_results //core_query_pagination //core_query_pagination_next //core_query_pagination_numbers //core_query_pagination_previous //core_query_title //core_read_more public function render_core_template_part(array $block, string $content):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; if (($isHeaderTemplate || $isFooterTemplate)) { $tag = $isHeaderTemplate ?: $isFooterTemplate ?: 'div'; $breadcrumbs = $themeSwitch = $afterHeader = $beforeHeader = $footerText= ''; if ($isHeaderTemplate) { $beforeHeader = apply_filters('jvbAboveHeader', $beforeHeader); if ($beforeHeader !== '') { $beforeHeader = ''; } $checked = (is_user_logged_in() && current_user_can('prefers_dark_theme', true)) ? ' checked' : ''; $title = ($checked == '') ? 'Toggle Dark Mode' : 'Toggle Light Mode'; $showThemeSwitch = (bool)apply_filters('jvb_show_theme_switch', true); $themeSwitch = ($showThemeSwitch) ? '' : ''; $breadcrumbs = jvbBuildBreadcrumbs(); $afterHeader = apply_filters('jvbBelowHeader', $afterHeader); if ($afterHeader !== '') { $afterHeader = ''; } $footerText = '
    '; } elseif ($isFooterTemplate) { $beforeHeader = apply_filters('jvbBeforeFooter', ''); if ($beforeHeader !== '') { $beforeHeader = ''; } $footerText = jvbRandomFooterText(); } // jvbDump($beforeHeader,'beforeHeader'); // jvbDump('<'.$tag.$this->getClassesAndStyles($block['attrs']).'>','tag'); // jvbDump($themeSwitch,'themeSwitch'); // jvbDump($this->inside($block, $tag, $content),'inside'); // jvbDump($footerText,'footerText'); // jvbDump($afterHeader, 'afterheader'); // jvbDump($breadcrumbs, 'breadcrumbs'); return $beforeHeader.'<'.$tag.$this->getClassesAndStyles($block['attrs']).'>'. $themeSwitch . $this->inside($block, $tag, $content) . $footerText.''.$afterHeader.$breadcrumbs; } return $content; } //core_term_description /** * Widgets Blocks */ //core_archives //core_calendar //core_categories //core_html //core_latest_comments //core_latest_posts //core_page_list //core_page_list_item //core_rss //core_search //core_shortcode public function render_core_social_link(array $block, string $content):string { $url = $block['attrs']['url']; $service = $block['attrs']['service']; $iconName = ($service === 'bluesky') ? 'butterfly' : $service.'-logo'; $icon = jvbIcon($iconName); if (!$icon) { $icon = jvbIcon('link'); } return '
  • '.$icon.'Find us on '.ucfirst($service).'
  • '; } public function render_core_social_links(array $block, string $content):string { return '
      '.$this->innerBlocks($block).'
    '; } //core_tag_cloud /** * Extra feed block localization */ protected function localize_feedblock():void { wp_localize_script('jvb-feed-view-script', 'feedSettings', [ 'currentUser' => is_user_logged_in() ? [ 'id' => get_current_user_id() ] : null, 'from' => get_the_ID(), ]); } /*********************************** * Helpers **********************************/ 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 { $content = ''; foreach ($block['innerBlocks'] as $b) { $method = 'render_'.$this->sanitizeBlockName($b); $function = BASE.$method; $content .= $before; if (function_exists($function)) { $content .= $function($b, ''); } else if (method_exists($this, $method)) { $content .= $this->$method($b, ''); } else { $content .= render_block($b); } $content .= $after; } return $content; } public function inside(array $block, mixed $tag = false, mixed $o = false):string { if (!$o) { $o = trim($block['innerHTML']); } if (!$tag) { //check to see if there was one dynamically set first $tag = (array_key_exists('tagName', $block['attrs'])) ? $block['attrs']['tagName'] : ''; $tag = ($tag == '') ? str_replace('<', '', strtok($o, '>')) : ''; $tag = (str_contains($tag, ' class')) ? strtok($tag, ' class') : $tag; $tag = trim($tag); } if (!str_starts_with($o, '<'.$tag)) { return $o; } $len = strlen(''); return substr_replace( str_replace( strtok($o, '>').'>', ' ', $o ), '', -$len, $len ); } /** * Extract content from a specific nested element * @param string $html The HTML to parse * @param string $tag The tag name to extract * @return string The content of the first matching element, or empty string */ protected function extractElement(string $html, string $tag): string { if (empty($html)) { return ''; } $dom = new DOMDocument(); // Suppress errors for malformed HTML libxml_use_internal_errors(true); $dom->loadHTML('' . $html, LIBXML_HTML_NOIMPLIED | LIBXML_HTML_NODEFDTD); libxml_clear_errors(); $elements = $dom->getElementsByTagName($tag); if ($elements->length === 0) { return ''; } return trim($elements->item(0)->textContent); } public function imageID(int|string $ID, array $block = []):int|false { if ($ID === '' && !empty($block)) { if (($block['blockName'] === 'core/post-featured-image' || (!array_key_exists('attrs', $block) && !array_key_exists('id', $block['attrs'])))) { $ID = get_post_thumbnail_id(); } else { if (array_key_exists('id', $block['attrs'])) { $ID = $block['attrs']['id']; } elseif (array_key_exists('mediaId', $block['attrs'])) { $ID = $block['attrs']['mediaId']; } } } if (!is_int($ID)) { return false; } return $ID; } public function imageLink( bool $close = false, int $ID = 0, string $start = 'tiny', string $replace = 'large', ?string $postSlug = null ):string { $image = jvbFormatImage($ID, $start, $replace, true, $postSlug); if ($close) { return $image; } $len = strlen(''); return substr_replace($image, '', -$len, $len); } public function image(string $ID = '', string $start = 'tiny', string $replace = 'large'):string { if ($ID == '') { $ID = $this->imageID($ID); } if ($ID === 0 || $ID === false) { return ''; } return jvbFormatImage($ID, $start, $replace, false); } public function sanitizeBlockName(array $block):string { if (!array_key_exists('blockName', $block) || is_null($block['blockName'])) { return ''; } return str_replace( '/', '_', str_replace( '-', '_', $block['blockName'] ) ); } /** * Replaces the outside tag of an HTML string * @param string $content * @param bool|string $tagName * @param string $classes * @param string $styles * * @return string */ public function replaceOutsideTag( string $content, bool|string $tagName = false, string $classes = '', string $styles = '' ):string { if ($tagName && !in_array($tagName, ['div', 'section', 'article', 'aside', 'main'])) { return $content; } $content = explode('>', $content); if ($classes !== '') { if (!str_contains($classes, 'class="')) { $classes = ' class="'.$classes.'"'; } } if ($styles !== '') { if (!str_contains($styles, 'style="')) { $styles = ' style="'.$styles.'"'; } } if ($tagName) { $content[0] = '<'.str_replace( '<', '', str_replace( '>', '', $tagName ) ).$classes.$styles; } else { unset($content[0]); } unset($content[array_key_last($content)]); $out = ''; foreach ($content as $c) { $out .= $c.'>'; } return $out; } protected function getClassesAndStyles( array $attrs, array $classes = [], array $styles = [] ):string { // Get styles and classes from attributes $attr_styles = $this->getInlineStyles($attrs); $attr_classes = $this->getClasses($attrs); if(array_key_exists('slug', $attrs) && $attrs['slug'] === 'footer') { $classes[] = 'col'; } // Merge with passed classes and styles $styles = array_merge($attr_styles, $styles); $classes = array_merge($attr_classes, $classes); // Build attribute strings $class_string = !empty($classes) ? ' class="' . implode(' ', $classes) . '"' : ''; $style_string = !empty($styles) ? ' style="' . implode(';', $styles) . '"' : ''; $return = trim($class_string . $style_string); return ($return=='')? '' : ' '.$return; } /** * @param string $spacing * * @return string */ protected function getPresetSpacing(string $spacing):string { return match ($spacing) { 'var:preset|spacing|20' => 1, 'var:preset|spacing|30' => 2, 'var:preset|spacing|40' => 3, 'var:preset|spacing|50' => 4, 'var:preset|spacing|60' => 5, 'var:preset|spacing|70' => 6, 'var:preset|spacing|80' => 7, default => $spacing, }; } protected function getClasses(array $attrs):array { if (empty($attrs)) { return []; } $classes = []; foreach ($attrs as $key => $value) { $class = $this->getClass($key, $value, $attrs); if (is_array($class)) { $classes = array_merge($classes, $class); } else { $classes[] = $class; } } return array_filter($classes, function ($class) { return $class!=='' && !str_starts_with($class, 'wp'); }); } protected function getClass(string $key, string|bool|array|int $value, array $attrs):string|array { switch ($key) { //Any additional classes the user adds case 'className': return match ($value) { 'is-style-floating' => 'always mobile fixed', 'is-style-fixed' => 'fixed bottom', default => str_replace('is-style-', '', $value), }; case 'contentPosition': $classes = []; $pos = explode(' ', $value); foreach($pos as $p) { switch ($p) { case 'top': $classes[] = 'a-start'; break; case 'right': $classes[] = 'end'; break; case 'bottom': $classes[] = 'a-end'; break; case 'left': $classes[] = 'start'; break; } } return implode(' ', $classes); //Layout attributes case 'layout': $classes = []; $type = 'row'; if (array_key_exists('type', $value)) { $type = 'col'; // if ($value['type'] === 'constrained') { // $classes[] = 'container col'; // } } if (array_key_exists('orientation', $value)) { $type = 'col'; if ($value['orientation'] === 'vertical') { $classes[] = 'col'; if (in_array('row', $classes)) { $index = array_search('row', $classes); unset($classes[$index]); } } }else if (array_key_exists('type', $value) && $value['type'] === 'flex') { $classes[] = 'row'; if (in_array('col', $classes)) { $index = array_search('col', $classes); unset($classes[$index]); } } //jvbDump($type); //jvbDump($value); // $check = [$value, $attrs]; // foreach ($check as $ch) { // // } if (!array_key_exists('justifyContent', $value) && !array_key_exists('contentPosition', $attrs)) { $classes[] = ($type === 'row') ? 'start' : 'a-start'; } if (array_key_exists('justifyContent', $value) && !array_key_exists('contentPosition', $attrs)) { if (in_array($value['justifyContent'], ['left', 'right','space-between'])) { // jvbDump($type); switch ($value['justifyContent']) { case 'right': $classes[] = ($type === 'row') ? 'end' : 'a-end'; break; case 'space-between': $classes[] = 'btw'; break; } } } if (array_key_exists('flexWrap', $value)) { if ($value['flexWrap'] === 'nowrap') { $classes[] = 'nowrap'; } } return implode(' ', $classes); case 'align': return !empty($value) ? 'align-'.$value : ''; case 'verticalAlignment': return !empty($value) ? 'v-align-'.$value : ''; case 'isStackedMobile': return ($value === true) ? 'stack-small' : ''; case 'justifyContent': return !empty($value) ? 'j-'.$value : ''; case 'orientation': return $value==='column' ? 'column' : ''; case 'width': case 'dimRatio': if (is_numeric($value)) { $width = match (true) { $value < 25 => '25', $value < 33 => '33', $value <= 50 => '50', $value < 66 => '66', $value < 75 => '75', default => 'full', }; switch ($key) { case 'width': return 'width-'.$width; case 'dimRatio': return 'overlay-'.$width; } } return ''; //Typography case 'textAlign': return !empty($value) ? 'text-'.$value : ''; case 'dropCap': return $value === true ? 'drop-cap' : ''; //Media case 'hasParallax': return $value === true ? 'bg-parallax' : ''; case 'isRepeated': return $value === true ? 'bg-repeat' : ''; //Style base: case 'style': $classes = []; //Margin and Padding if (array_key_exists('spacing', $value)) { foreach (['margin' => 'm', 'padding'=>'p'] as $search => $c) { if (array_key_exists($search, $value['spacing'])) { $directions = []; // Collect ONLY preset spacing values for classes foreach ($value['spacing'][$search] as $direction => $size) { $presetSize = $this->getPresetSpacing($size); if ($presetSize) { $directions[$direction] = $presetSize; } // Non-preset values are skipped here and handled by inline styles below } if (empty($directions)) { continue; } // Check what directions we have $hasTop = isset($directions['top']); $hasBottom = isset($directions['bottom']); $hasLeft = isset($directions['left']); $hasRight = isset($directions['right']); // Check if axes match $xMatch = $hasLeft && $hasRight && $directions['left'] === $directions['right']; $yMatch = $hasTop && $hasBottom && $directions['top'] === $directions['bottom']; // All 4 directions exist and match → p-3 if ($hasTop && $hasBottom && $hasLeft && $hasRight && count(array_unique($directions)) === 1) { $classes[] = $c . '-' . reset($directions); } // Both axes match → px-3 py-2 elseif ($xMatch && $yMatch) { $classes[] = $c . 'x-' . $directions['left']; $classes[] = $c . 'y-' . $directions['top']; } // Only X axis matches → px-3 (+ individual for top/bottom) elseif ($xMatch) { $classes[] = $c . 'x-' . $directions['left']; if ($hasTop) { $classes[] = $c . 't-' . $directions['top']; } if ($hasBottom) { $classes[] = $c . 'b-' . $directions['bottom']; } } // Only Y axis matches → py-3 (+ individual for left/right) elseif ($yMatch) { $classes[] = $c . 'y-' . $directions['top']; if ($hasLeft) { $classes[] = $c . 'l-' . $directions['left']; } if ($hasRight) { $classes[] = $c . 'r-' . $directions['right']; } } // No matches - individual directions else { foreach ($directions as $direction => $size) { $dir = match($direction) { 'top' => 't', 'bottom' => 'b', 'left' => 'l', 'right' => 'r', default => $direction }; $classes[] = $c . $dir . '-' . $size; } } } } } if (array_key_exists('fontSize', $value)) { if (in_array($value['fontSize'], ['small', 'large', 'extra-large', 'huge'])) { $classes[] = 'font-'.$value['fontSize']; } if (in_array('fontWeight', $value)) { $classes[] = 'text-'.$value['fontWeight']; } if (in_array('textTransform', $value)) { if (in_array($value['textTransform'], ['uppercase', 'capitalize', 'lowercase'])) { $classes[] = $value['textTransform']; } } } return implode(' ', $classes); case 'fontSize': $classes[] = 'font-'.$value; return implode(' ', $classes); case 'isStackedOnMobile': return ($value === true) ? 'stack-small' : ''; case 'width': if (is_numeric($value)) { $width = match (true) { $value < 25 => '25', $value < 33 => '33', $value <= 50 => '50', $value < 66 => '66', $value < 75 => '75', default => 'full', }; switch ($key) { case 'width': return 'width-'.$width; case 'dimRatio': return 'overlay-'.$width; } } return ''; default: $ignore = [ 'useFeaturedImage', 'opacity', 'borderColor', 'backgroundColor', 'textColor', 'minHeight', 'minHeightUnit', 'isDark', 'sizeSlug', 'isUserOverlayColor', 'customOverlayColor', 'dimRatio', 'placeholder', 'alt', 'imageFill', 'mediaSizeSlug', 'isLink', 'kind', 'label', 'type', 'id', 'url', 'label', 'shouldSyncIcon', 'rel', 'opensInNewTab', 'title', 'ref', 'overlayMenu', 'slug', 'theme', 'tagName', 'level', 'ordered', 'area', 'mediaId', 'mediaLink', 'mediaType', 'height', //maybe still need? ]; if (!is_admin() &&!in_array($key, $ignore)) { // TESTING // jvbDump($key, 'getClass'); // jvbDump($attrs); } return ''; } } protected function getInlineStyles(array $attrs):array { if (empty($attrs)) { return []; } $styles = []; foreach ($attrs as $key => $value) { $style = $this->getStyle($key, $value, $attrs); $styles = array_merge($styles, $style); } return $styles; } protected function getStyle(string $key, string|bool|array|int $value, array $attrs):array { $styles = []; switch ($key) { // Font family settings case 'fontFamily': if ($value === 'body') { $styles[] = 'font-family: "Open Sans", system-ui, -apple-system, sans-serif'; } elseif ($value === 'heading') { $styles[] = 'font-family: "Josefin Sans", system-ui, -apple-system, sans-serif'; } elseif (!empty($value)) { $styles[] = 'font-family: '.$value; } break; // Icon color (for icon blocks) case 'iconColorValue': if (!empty($value)) { $styles[] = 'color: '.$value; } break; // Minimum height settings case 'minHeight': if (!empty($value) && isset($attrs['minHeightUnit'])) { $styles[] = 'min-height: '.$value.$attrs['minHeightUnit']; } elseif (!empty($value)) { $styles[] = 'min-height: '.$value.'px'; // Default to px if no unit specified } break; // Background URL (for cover, media blocks) case 'url': if (!empty($value) && str_starts_with($value, 'http')) { $styles[] = 'background-image: url('.$value.')'; } break; // Focal point for background images case 'focalPoint': $x = (array_key_exists('x', $attrs['focalPoint'])) ? $attrs['focalPoint']['x'] * 100 : 'center'; $y = (array_key_exists('y', $attrs['focalPoint'])) ? $attrs['focalPoint']['y'] * 100 : 'center'; $styles[] = 'background-position:'.$x.' '.$y.';'; break; // Complex style object case 'style': // Border styles if (isset($value['border'])) { $border = $value['border']; if (isset($border['radius'])) { $styles[] = 'border-radius: '.$border['radius']; } if (isset($border['width'])) { $styles[] = 'border-width: '.$border['width']; } if (isset($border['style']) && isset($border['width']) && !empty($border['style'])) { $styles[] = 'border-style: '.$border['style']; } if (isset($border['color'])) { $styles[] = 'border-color: '.$border['color']; } } // Color styles if (isset($value['color'])) { $color = $value['color']; if (isset($color['background'])) { $styles[] = 'background-color: '.$color['background']; } if (isset($color['text'])) { $styles[] = 'color: '.$color['text']; } if (isset($color['gradient'])) { $styles[] = 'background: '.$color['gradient']; } } // Layout styles if (isset($value['layout'])) { foreach ($value['layout'] as $layout => $option) { switch ($layout) { case 'selfStretch': if ($option === 'fixed' && isset($value['layout']['selfStretchValue'])) { $styles[] = 'width: '.$value['layout']['selfStretchValue']; } break; } } } // Typography styles if (isset($value['typography'])) { $typography = $value['typography']; if (isset($typography['fontSize'])) { $styles[] = 'font-size: '.$typography['fontSize']; } if (isset($typography['fontWeight'])) { $styles[] = 'font-weight: '.$typography['fontWeight']; } if (isset($typography['textDecoration'])) { $styles[] = 'text-decoration: '.$typography['textDecoration']; } if (isset($typography['textTransform'])) { $styles[] = 'text-transform: '.$typography['textTransform']; } if (isset($typography['letterSpacing'])) { $styles[] = 'letter-spacing: '.$typography['letterSpacing']; } if (isset($typography['lineHeight'])) { $styles[] = 'line-height: '.$typography['lineHeight']; } } // Spacing styles if (isset($value['spacing'])) { $spacing = $value['spacing']; // Don't duplicate margin/padding that's handled by classes // Only add specific CSS values here that wouldn't work well as classes if (isset($spacing['margin'])) { foreach ($spacing['margin'] as $direction => $size) { // If not a preset value, add as inline style if (!str_contains($size, 'var:preset')) { $styles[] = 'margin-'.$direction.': '.$size; } } } if (isset($spacing['padding'])) { foreach ($spacing['padding'] as $direction => $size) { // If not a preset value, add as inline style if (!str_contains($size, 'var:preset')) { $styles[] = 'padding-'.$direction.': '.$size; } } } } break; case 'dimRatio': $ratio = (ceil($value /25) *25); $s = 'background-color: rgba(var(--base-rgb), '; switch ($ratio) { case 0: $s .= 'var(--rgb-subtle-hover));'; break; case 25: $s .= 'var(--rgb-light));'; break; case 50: $s .= 'var(--rgb-medium));'; break; default: $s .= 'var(--rgb-heavy));'; break; } $styles[] = $s; break; // Custom styles (any other attributes that need inline styling) case 'backgroundType': if ($value === 'video' && isset($attrs['backgroundUrl'])) { // Don't set a background image for videos - it will be handled by the video element } elseif (isset($attrs['backgroundUrl'])) { $styles[] = 'background-image: url('.$attrs['backgroundUrl'].')'; } break; case 'backgroundColor': case 'borderColor': case 'textColor': $type = ($key === 'backgroundColor') ? 'background-color:' : (($key === 'borderColor') ? 'border-color:' : 'color:'); $defaults = apply_filters('jvbColours', ['base', 'contrast', 'action', 'secondary']); $continue = true; foreach ($defaults as $default) { if (str_starts_with($value, $default)) { $continue = false; $styles[] = $type.'var(--'.$value.')'; } } if ($continue) { $styles[] = $type.$value; } break; // Any other attributes that need direct styling default: $ignore = [ 'useFeaturedImage', 'opacity', 'textAlign', 'minHeightUnit', 'isDark', 'isUserOverlayColor', 'contentPosition', 'sizeSlug', 'customOverlayColor', 'alt', 'placeholder', 'imageFill', 'mediaSizeSlug', 'isLink', 'kind', 'label', 'type', 'id', 'url', 'label', 'shouldSyncIcon', 'rel', 'opensInNewTab', 'title', 'ref', 'overlayMenu', 'slug', 'theme', 'tagName', 'level', 'ordered', 'area', 'className', 'fontSize', 'layout', 'align', 'mediaId', 'mediaLink', 'mediaType', 'isStackedOnMobile', 'width', 'height', // maybe still need? ]; if (!is_admin() && !in_array($key, $ignore)) { //TESTING // jvbDump($key, 'getStyle'); // jvbDump($attrs); } // No default inline styles break; } return $styles; } public function formatImage(int $ID = 0, string $start = 'tiny', string $replace = 'large'):string { if ($ID === 0) { $ID = $this->imageID($ID); } if ($ID === 0) { return ''; } return jvbFormatImage($ID, $start, $replace); } }