From 3d25ad7de4fdbb3eba2cde2a62422e6be5d79a0a Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 25 Aug 2026 22:57:22 +0000
Subject: [PATCH] =It's been a minute. I went through the Routes and associated Managers and ensured they played together well. Reworked the Cache.php, and I think I caught all the references. Moving away from JVBase() root class, to individuall classes that are Instanced. Double checked for the ABSPATH check in every file, and added index.php to every directory that didn't already have it. Also still working on the MenuBlock to work with WooCommerce for now for Northeh
---
inc/blocks/CustomBlocks.php | 3704 ++++++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 2,786 insertions(+), 918 deletions(-)
diff --git a/inc/blocks/CustomBlocks.php b/inc/blocks/CustomBlocks.php
index f0c0f9d..2612fbd 100644
--- a/inc/blocks/CustomBlocks.php
+++ b/inc/blocks/CustomBlocks.php
@@ -1,9 +1,12 @@
<?php
namespace JVBase\blocks;
-use DateTime;
use DOMDocument;
+use JVBase\Instance;
use JVBase\managers\Cache;
+use JVBase\managers\Login;
+use JVBase\managers\SEO\BreadcrumbManager;
+use JVBase\utility\Image;
use WP_Block;
use WP_Query;
@@ -13,12 +16,26 @@
class CustomBlocks
{
+ use Instance;
protected Cache $cache;
- public function __construct()
+
+ protected static ?WP_Query $currentLoop = null;
+ protected static ?int $currentQueryId = null;
+ protected static array $counters = [];
+ protected static ?WP_Query $originalQuery = null;
+ protected array $ignore = ['align','alt','area','aspectRatio','backgroundColor','borderColor','buttonText','buttonPosition','buttonUseIcon','categories','className','columns','contentPosition','customOverlayColor','datetime','dimRatio','displayAsDropdown','displayAuthor','displayFeaturedImage','displayPostContent','displayPostContentRadio','displayPostDate','excerptLength','featuredImageAlign','fontSize','gradient','hasFixedLayout','hasParallax','height','iconColor','iconColorValue','iconColorValue','iconBackgroundColor','iconBackgroundColorValue','id','imageFill','isDark','isLink','isObjectPosition','isRepeated','isSearchFieldHidden','isStackedOnMobile','isUserOverlayColor','kind','label','largestFontSize','layout','lightbox','linkDestination','linkTo','level','mediaId','mediaLink','mediaPosition','mediaSizeSlug','mediaType','mediaWidth','metadata','minHeight','minHeightUnit','opacity','opensInNewTab','order','orderBy','ordered','overlayColor','overlayMenu','placeholder','postLayout','postsToShow','query', 'queryId','ref','rel','scale','shouldSyncIcon','showContent','showEmpty','showHierarchy','showLabel','showLabels','showOnlyTopLevel','showPostCounts','showTagCounts','size','sizeSlug','slug','smallestFontSize','tagName','taxonomy','term','textAlign','textColor','theme','title','type','url','useFeaturedImage','verticalAlignment','width','widthUnit',];
+
+ //For custom style output for nested links, etc
+ protected static array $pendingStyles = [];
+ protected static array $pendingClass = [];
+ protected static bool $renderGallery = false;
+
+ private function __construct()
{
- $this->cache = Cache::for('blocks', WEEK_IN_SECONDS);
- $this->cache->connect('post')->connect('taxonomy');
- add_filter('render_block', [$this, 'render'], 900, 3);
+// $this->cache = Cache::for('blocks', null,WEEK_IN_SECONDS);
+
+ add_filter('pre_render_block', [$this, 'prerender'], 10, 3);
+ add_filter('render_block', [$this, 'render'], 10, 2);
add_action('init', [$this, 'registerBlockStyles']);
}
@@ -62,52 +79,117 @@
'label' => __('Callout Alt', 'jvb')
]
);
+ register_block_style(
+ 'core/separator',
+ [
+ 'name' =>'logo',
+ 'label' => __('With Logo', 'jvb')
+ ]
+ );
+ register_block_style(
+ 'core/gallery',
+ [
+ 'name' =>'hexagon',
+ 'label' => __('Hexagonal', 'jvb')
+ ]
+ );
+ register_block_style(
+ 'core/gallery',
+ [
+ 'name' =>'diamond',
+ 'label' => __('Diamond', 'jvb')
+ ]
+ );
+ register_block_style(
+ 'core/gallery',
+ [
+ 'name' =>'cut',
+ 'label' => __('Cut Corners', 'jvb')
+ ]
+ );
}
-
- public function render(string $content, array $block, WP_Block $instance)
- {
+ protected function checkMethods(?string $content, array $block, ?WP_Block $parent = null, bool $isPrerender = false):?string
+ {
$blockName = $this->sanitizeBlockName($block);
- $method = 'render_'.$blockName;
+ if (str_starts_with($blockName, 'woocommerce')) {
+ return $content;
+ }
+
+ $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 = [
+ $content = $this->$method($block, $content, $parent);
+ return $isPrerender ? $this->maybeOutputCustomStyles().$content : $content;
+ } elseif (JVB_TESTING && !empty($blockName)) {
+ if (!str_starts_with($block['blockName'],'jvb/') && !in_array($block['blockName'], $this->getIgnore($isPrerender))) {
+ jvbDump('No method found for '.print_r($block['blockName'], true));
+ }
+ }
+ return $content;
+ }
+ protected function getIgnore(bool $isPrerender):array
+ {
+ //Ignore for both
+ $base = [
'core/null',
- 'core/post-title',
+ 'core/pattern',
'core/list-item',
- 'core/site-title',
- 'jvb/forms'
+ 'core/post-content',
+ 'jvb/drawer-menu',
+ 'jvb/summary',
+ 'jvb/feed',
+ 'jvb/forms',
+ 'jvb/gmbreviews',
+ 'jvb/timeline',
+ 'jvb/video',
+ 'jvb/faq',
+ 'jvb/glossary'
];
-// if (!in_array($block['blockName'], $ignore)) {
-// jvbDump('No method found for '.print_r($block['blockName'], true));
-// }
+ if ($isPrerender) {
+ $base = array_merge($base, [
+ 'core/query-pagination',
+ 'core/query-pagination-previous',
+ 'core/query-pagination-next',
+ 'core/query-pagination-numbers',
+ 'core/query',
+ 'core/calendar',
+ 'core/archives',
+ ]);
+ } else {
+ $base = array_merge($base, [
+
+ ]);
+ }
+ return $base;
}
- 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');
+ 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
+ {
+ switch ($block['blockName']) {
+ case 'jvb/feed':
+ $this->localize_feedblock();
+ break;
+ case 'jvb/forms':
+ wp_enqueue_style('jvb-form');
+ break;
+ case 'jvb/timeline':
+ wp_enqueue_script('jvb-gallery');
+ add_action('wp_footer', 'jvbRenderGallery');
+ break;
}
- return $content;
+
+
+
+ return $this->checkMethods($content, $block);
}
/***********************************
@@ -126,8 +208,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);
@@ -136,77 +220,135 @@
}
$icon = '';
if (str_contains($url[1], 'google.com/maps')) {
- $icon = 'google-logo';
+ $icon = jvbIcon('google-logo');
}
if (str_contains($url[1], 'maps.apple.com')) {
- $icon = 'apple-logo';
+ $icon = jvbIcon('apple-logo');
}
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)
+ $icon
);
}
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
+ public function prerender_core_buttons(array $block, ?string $content, ?WP_Block $parent):?string
{
- return '<ul'.$this->getClassesAndStyles($block['attrs'], ['buttons','row']).'>'.
- $this->innerBlocks($block).'</ul>';
+// jvbDump($block, 'buttons');
+// jvbDump($parent, 'Parent');
+ return sprintf(
+ '<ul%s>%s</ul>',
+ $this->getClassesAndStyles($block['attrs']??[], ['buttons','row']),
+ $this->innerBlocks($block)
+ );
}
- public function render_core_column(array $block):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->innerBlocks($block).'</div>';
+ return sprintf(
+ '<div%s>%s</div>',
+ $this->getClassesAndStyles($block['attrs']??[], ['col'], $styles),
+ $this->innerBlocks($block)
+ );
}
- public function render_core_columns(array $block):string
+ public function prerender_core_columns(array $block, ?string $content, ?WP_Block $parent):?string
{
- return '<section'.
- $this->getClassesAndStyles($block['attrs'], ['columns']).'>'.
- $this->innerBlocks($block).'</section>';
+// jvbDump($block, 'columns');
+ $attrs = $block['attrs']??[];
+ $tagName = array_key_exists('tagName', $attrs) ? $attrs['tagName'] : 'section';
+
+ $classes = ['row', 'nowrap'];
+ if (!array_key_exists('isStackedOnMobile', $attrs) || $attrs['isStackedOnMobile'] === true){
+ $classes[] = 'stack-small';
+ }
+ return sprintf(
+ '<%s%s>%s</%s>',
+ $tagName,
+ $this->getClassesAndStyles($attrs, $classes),
+ $this->innerBlocks($block).'</section>',
+ $tagName
+ );
}
//core_comment_template
- public function render_core_group(array $block):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->innerBlocks($block).'</'.$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 prerender_core_nextpage(array $block, ?string $content, ?WP_Block $parent):?string
+ {
- public function render_core_separator(array $block):string
+ return str_replace('</a>', '</a></li>',str_replace('<a', '<li><a', wp_link_pages([
+ 'before' => '<nav class="pagination x-btw"><ul>',
+ 'after' => '</ul></nav>',
+ 'nextpagelink' => __('<span>Next </span>'.jvbIcon('caret-circle-right'), 'jvb'),
+ 'previouspagelink' => __(jvbIcon('caret-circle-left').'<span> Previous</span>', 'jvb'),
+ 'next_or_number'=> 'next',
+ 'echo' => false
+ ])));
+ }
+
+ 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');
+ $attrs = $block['attrs']??[];
+ $logo = '';
+ if (array_key_exists('className', $attrs) && $attrs['className'] === 'is-style-logo'){
+ $logo = apply_filters('jvbSeparatorLogo', 'logo');
+ if (!empty($logo)) {
+ $logo = jvbIcon($logo);
+ }
+ }
+ return sprintf(
+ '<hr%s>',
+ $this->getClassesAndStyles($attrs),
+// $logo
+ );
}
- 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']).
- ' aria-hidden="true"></div>';
+
+// jvbDump($block, 'spsacer');
+// jvbDump($parent, 'Parent');
+ return sprintf(
+ '<div%s aria-hidden="true"></div>',
+ $this->getClassesAndStyles($block['attrs']??[], ['spacer'], ['height:2rem'])
+ );
}
//core_table_of_contents
//core_text_columns
@@ -219,21 +361,31 @@
* Media Blocks
*/
//core_audio
- public function render_core_cover(array $block):string
+ public function prerender_core_audio(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block,'audio');
+ $attrs = $block['attrs']??[];
+ $inside = $this->inside($block);
+ return sprintf('<figure%s>%s</figure>',
+ $this->getClassesAndStyles($attrs),
+ $inside
+ );
+ }
+ 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'] ?? [];
+ $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']);
+ $attrs['isObjectPosition'] = true;
}
+
// Check for background type
$backgroundType = $attrs['backgroundType'] ?? 'image';
$background = '';
@@ -246,40 +398,108 @@
$ID = (int)$attrs['id'];
}
- if ($backgroundType === 'image' && $ID) {
+
+ $doImage = true;
+ if ($this->checkAttrs('hasParallax', $attrs) || $this->checkAttrs('isRepeated', $attrs)) {
+ $doImage = false;
+ $attrs['style']['background']['backgroundImage']['id'] = $ID;
+ }
+
+ if ($doImage && $backgroundType === 'image' && $ID) {
$background .= str_replace('<img', '<img style="'.$position.'"', $this->image($ID));
} elseif ($backgroundType === 'video' && isset($attrs['url'])) {
$background .= '<video style="'.$position.'"autoplay muted loop playsinline src="' . esc_url($attrs['url']) . '"></video>';
}
+ $overlay = '';
+ if (!isset($attrs['style']['color']['duotone']) && (array_key_exists('overlayColor', $attrs) || array_key_exists('dimRatio', $attrs))) {
+ $tmp = [];
+ if (array_key_exists('overlayColor', $attrs)) {
+ $tmp['overlayColor'] = $attrs['overlayColor'];
+ }
+ if (array_key_exists('dimRatio', $attrs)) {
+ $tmp['dimRatio'] = $attrs['dimRatio'];
+ }
+ unset($attrs['overlayColor']);
+ unset($attrs['dimRatio']);
+ $overlay = sprintf(
+ '<div class="overlay"%s></div>',
+ $this->buildStylesString($tmp)
+ );
+ }
+
// Build classes and styles
unset($attrs['url']);
$classes = $this->getClassesAndStyles($attrs, ['cover row']);
- return '<section' . $classes . '>' .
- $background .
- '<div class="content">' .
- $innerContent .
- '</div></section>';
+
+ return sprintf('<section%s>%s%s<div class="content">%s</div></section>',
+ $classes,
+ $overlay,
+ $background,
+ $innerContent
+ );
}
//core_file
+ public function prerender_core_file(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+ $attrs = $block['attrs']??[];
- public function render_core_gallery(array $block):string
+ $showButton = !array_key_exists('showDownloadButton', $attrs);
+ preg_match('/>([^<]*)<\/a>/', $block['innerHTML'], $label);
+ $label = $label[1]??'';
+ $button = $showButton ?
+ sprintf(
+ ' <a class="btn chip" href="%s">%s<span>Download</span></a>',
+ $attrs['href'],
+ jvbIcon('cloud-arrow-down')
+ ) :
+ '';
+
+ $aOpen = $showButton ? '' :
+ sprintf(
+ '<a href="%s">',
+ $attrs['href']
+ );
+ $aClose = $showButton ? '' : '</a>';
+ return sprintf(
+ '<p%s>%s%s%s%s</p>',
+ $this->getClassesAndStyles($attrs, ['file']),
+ $aOpen,
+ $showButton ? $label : 'Download: '.$label,
+ $aClose,
+ $button
+ );
+ }
+
+ public function prerender_core_gallery(array $block, ?string $content, ?WP_Block $parent):?string
{
- return '<ul'.$this->getClassesAndStyles($block['attrs'], ['gallery']).'>'.
- $this->innerBlocks($block,'<li>', '</li>').
- '</ul>';
+// jvbDump($block, 'gallery');
+ $attrs = $block['attrs']??[];
+// jvbDump($parent, 'Parent');
+ static::$renderGallery = true;
+ return sprintf(
+ '<ul%s>%s</ul>',
+ $this->getClassesAndStyles($attrs, ['gallery']),
+ $this->innerBlocks($block,'<li>', '</li>')
+ );
}
- 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 '';
}
+ $attrs = $block['attrs']??[];
+
+ static::$renderGallery = true;
+
$title = (get_the_title($ID) !== '') ? '<b>'.get_the_title($ID).'</b>' : '';
$caption = (wp_get_attachment_caption($ID)) ?
'<figcaption>' .
@@ -287,40 +507,111 @@
wp_get_attachment_caption($ID) .
'</figcaption>' :
'<figcaption>' . $title . '</figcaption>';
- $size = array_key_exists('sizeSlug', $block['attrs']) ? $block['attrs']['sizeSlug'] : 'large';
- return '<figure'.
- $this->getClassesAndStyles($block['attrs']).'>'.
- $this->imageLink(true, $ID, 'tiny', $size) .
- $caption.'</figure>';
+ $size = $attrs['sizeSlug'] ?? 'large';
+ $img = $this->imageLink(true, $ID, 'tiny', $size);
+
+ $aspectRatio = $attrs['aspectRatio']??false;
+ if ($aspectRatio) {
+ $img = str_replace('<img', sprintf(
+ '<img style="aspect-ratio:%s;"',
+ $aspectRatio
+ ), $img);
+ }
+
+ return sprintf(
+ '<figure%s>%s%s</figure>',
+ $this->getClassesAndStyles($block['attrs']??[]),
+ $img,
+ $caption,
+ );
}
- public function render_core_media_text(array $block):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);
+ $attrs = $block['attrs']??[];
- $size = array_key_exists('mediaSizeSlug', $block['attrs']) ? $block['attrs']['mediaSizeSlug'] : 'large';
+ $size = array_key_exists('mediaSizeSlug', $attrs) ? $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';
+ $classes = ['media-text', 'row', 'nowrap'];
+ if (!array_key_exists('isStackedOnMobile', $attrs) || $attrs['isStackedOnMobile'] === true) {
+ $classes[] = 'stack-small';
}
- $content = '<div'.$this->getClassesAndStyles($block['attrs'], $classes).'>';
- $content .= (array_key_exists(
- 'mediaPosition',
- $block['attrs']
- ) && $block['attrs']['mediaPosition'] == 'right') ?
- '<div>'.$inner.'</div><figure>'.$imgLink.'</figure>' :
- '<figure>'.$imgLink.'</figure><div>'.$inner.'</div>';
- $content .= '</div>';
- return $content;
+
+ $figClasses = [];
+ if (isset($attrs['mediaWidth'])) {
+ $figClasses[] = 'width:'.$attrs['mediaWidth'].'%';
+ }
+ if (isset($attrs['imageFill']) && $attrs['imageFill'] === true) {
+ $figClasses[] = 'object-fit: cover';
+ }
+ if (array_key_exists('focalPoint', $attrs)) {
+ $attrs['isObjectPosition'] = true;
+ $style = $this->getFocalPointStyle($attrs['focalPoint'], $attrs);
+ $style .= ';object-fit:none;';
+ $imgLink = str_replace('<img', sprintf(
+ '<img style="%s"',
+ $style
+ ), $imgLink);
+ unset($attrs['focalPoint']);
+ }
+
+
+ $figClasses = empty($figClasses) ? '' : ' style="'.implode(';',$figClasses).'"';
+
+
+ $inside = array_key_exists('mediaPosition', $attrs) && $attrs['mediaPosition'] === 'right'
+ ? sprintf(
+ '<div>%s</div><figure%s>%s</figure>',
+ $inner,$figClasses, $imgLink
+ ) : sprintf(
+ '<figure%s>%s</figure><div>%s</div>',
+ $figClasses,$imgLink, $inner
+ );
+
+ return sprintf(
+ '<div%s>%s</div>',
+ $this->getClassesAndStyles($attrs, $classes),
+ $inside
+ );
}
//core_video
+ public function prerender_core_video(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'video');
+// jvbDump($parent, 'Parent');
+
+ $attrs = $block['attrs']??[];
+//
+// $ID = $attrs['id']??false;
+// if (!$ID) {
+// return '';
+// }
+// $caption = wp_get_attachment_caption($ID);
+// $title = get_the_title($ID);
+//
+// $figCaption = sprintf(
+// '<figcaption><b>%s</b>%s</figcaption>',
+// $title,
+// $caption
+// );
+//
+// $video = Video::get($ID);
+
+ $inside = $this->inside($block);
+ return sprintf('<figure%s>%s</figure>',
+ $this->getClassesAndStyles($attrs),
+ $inside
+ );
+ }
/**
* Reusable blocks
@@ -330,40 +621,93 @@
/**
* Text Blocks
*/
- //render_core_code
- //render_core_details
- //render_core_footnotes
- //render_core_classic
- public function render_core_heading(array $block):string
+ public function prerender_core_code(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'code');
+ $attrs = $block['attrs']??[];
+ $content = $this->inside($block);
+
+ return str_replace('<code', sprintf(
+ '<code%s',
+ $this->getClassesAndStyles($attrs),
+ ), $content);
+ }
+ public function prerender_core_details(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'details');
+ $attrs = $block['attrs']??[];
+ $isOpen = $this->checkAttrs('showContent', $attrs);
+ $summary = $this->extractElement($block['innerHTML'], 'summary');
+ $inside = $this->innerBlocks($block);
+
+ return sprintf(
+ '<details%s%s><summary>%s</summary>%s</details>',
+ $this->getClassesAndStyles($attrs),
+ $isOpen ? ' open' : '',
+ $summary,
+ $inside
+ );
+ }
+ //prerender_core_footnotes
+// public function prerender_core_footnotes(array $block, ?string $content, ?WP_Block $parent):?string
+// {
+// jvbDump($block, 'footnotes');
+//
+// return null;
+// }
+ //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';
+// jvbDump($block, 'heading');
+ $attrs = $block['attrs']??[];
+ $level = $attrs['level'] ?? '2';
$content = $this->inside($block);
$id = sanitize_title(wp_strip_all_tags($this->stripTagContents('small', $content)));
- return '<h'.$level.' id="'.$id.'"'.$this->getClassesAndStyles($block['attrs']).'>'.
- $content.
- '</h'.$level.'>';
+ return sprintf(
+ '<h%s id="%s"%s>%s</h%s>',
+ $level,
+ $id,
+ $this->getClassesAndStyles($attrs),
+ $content,
+ $level,
+ );
}
- public function render_core_list(array $block):string
+ public function prerender_core_list(array $block, ?string $content, ?WP_Block $parent):?string
{
- $tag = (array_key_exists('ordered', $block['attrs'])) ? 'ol' : 'ul';
- return '<'.$tag.$this->getClassesAndStyles($block['attrs']).'>'.$this->innerBlocks($block).'</'.$tag.'>';
+// jvbDump($block, 'list');
+// jvbDump($parent, 'Parent');
+ $tag = (array_key_exists('ordered', $block['attrs']??[])) ? 'ol' : 'ul';
+ return sprintf(
+ '<%s%s>%s</%s>',
+ $tag,
+ $this->getClassesAndStyles($block['attrs']??[]),
+ $this->innerBlocks($block),
+ $tag
+ );
}
-// 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').
- '</p>';
+// jvbDump($block, 'paragraph');
+// jvbDump($parent, 'Parent');
+ $inside = $this->inside($block);
+ return empty($inside) ? '' : sprintf(
+ '<p%s>%s</p>',
+ $this->getClassesAndStyles($block['attrs']??[]),
+ $inside
+ );
}
- 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
@@ -371,20 +715,25 @@
$citeHtml = ($cite === '') ? '' : '<cite>— '.$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']).'>
- <div class="content">'.$content.'</div>'.
- $citeHtml.
- '</blockquote>';
+ return sprintf(
+ '<blockquote%s>%s%s%s</blockquote>',
+ $this->getClassesAndStyles($block['attrs']??[]),
+ jvbIcon('quotes',['style' => 'fill']),
+ $content,
+ $citeHtml
+ );
}
- 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
@@ -400,56 +749,153 @@
}
$content = jvb_filter_content( $content);
- return '<blockquote'.$this->getClassesAndStyles($block['attrs'], ['pull']).'>'.
- $content.
- $citeHtml.
- '</blockquote>';
+ return sprintf(
+ '<blockquote%s>%s%s</blockquote>',
+ $this->getClassesAndStyles($block['attrs']??[], ['pull']),
+ $content,
+ $citeHtml
+ );
}
- //render_core_table
- //render_core_verse
+ public function prerender_core_table(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'table');
+ $attrs = $block['attrs']??[];
+ $figAttrs = [
+ 'align' => $attrs['align']??''
+ ];
+ unset($attrs['align']);
+
+ $inside = $this->inside($block); // inside the figure
+ $parts = explode('<figcaption', $inside); // inside the table
+ $table = $parts[0];
+ $table = str_replace(strtok($table, '>'),sprintf(
+ '<table%s',
+ $this->getClassesAndStyles($attrs)
+ ), $table);
+ $caption = '';
+ if (array_key_exists(1, $parts)) {
+ $caption = str_replace(strtok($parts[1], '>'), '<figcaption', $parts[1]);
+ }
+
+
+ return sprintf(
+ '<figure%s>%s%s</figure>',
+ $this->buildClassesString($figAttrs),
+ $table,
+ $caption
+ );
+ }
+ public function prerender_core_preformatted(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'verse');
+ $attrs = $block['attrs']??[];
+ return sprintf(
+ '<pre%s>%s</pre>',
+ $this->getClassesAndStyles($attrs),
+ $this->inside($block)
+ );
+ }
+ public function prerender_core_verse(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'verse');
+ $attrs = $block['attrs']??[];
+ return sprintf(
+ '<pre%s>%s</pre>',
+ $this->getClassesAndStyles($attrs),
+ $this->inside($block)
+ );
+ }
/**
* 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']) {
+ Login::getInstance()->setAction($action);
+ return Login::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 = null):?string
{
+// jvbDump($block, 'site logo');
+// jvbDump($parent, 'Parent');
+ $attrs = $block['attrs']??[];
$open = $close = '';
- if (!is_home() && !is_front_page()) {
- $open = '<a href="'.get_home_url().'" rel="home">';
+ if ((!is_home() && !is_front_page()) && (!array_key_exists('isLink', $attrs) || $attrs['isLink'] === true)) {
+ $open = '<a href="'.get_home_url().'" rel="home" class="logo">';
$close = '</a>';
}
$img = get_theme_mod('custom_logo');
- $img = $this->image($img, 'tiny', 'thumbnail');
- $img = str_replace('<img', '<img'.$this->getClassesAndStyles($block['attrs']), $img);
+ $img = sprintf(
+ '<figure%s>%s</figure>',
+ $this->getClassesAndStyles($attrs, ['logo']),
+ $this->image($img, 'tiny', 'thumbnail')
+ );
return $open.$img.$close;
}
- //core_site_title_tagline
+ public function prerender_core_site_tagline(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+ $tagline = get_bloginfo('description');
- public function render_core_site_title(array $block, string $content):string
+ return empty($tagline) ? '' : sprintf(
+ '<p%s>%s</p>',
+ $this->getClassesAndStyles($block['attrs']??[], ['tagline']),
+ $tagline
+ );
+ }
+
+ 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');
+ $attrs = $block['attrs']??[];
+ $tag = (array_key_exists('level', $attrs)) ? $attrs['level'] : 1;
$tag = ($tag == 0) ? 'p' : 'h'.$tag;
$open = $close = '';
- if (!is_front_page()) {
- $open = '<a href="' . get_home_url() . '" rel="home">';
+ if (!is_front_page() && (!array_key_exists('isLink', $attrs) || $attrs['isLink'] === true)) {
+ $open = sprintf(
+ '<a href="%s" rel="home">',
+ get_home_url()
+ );
$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.'>'.
- $open.
- get_bloginfo('name').
- $close.
- '</'.$tag.'>';
+ return sprintf(
+ '<%s%s>%s%s%s</%s>',
+ $tag,
+ $class,
+ $open,
+ get_bloginfo('name'),
+ $close,
+ $tag
+ );
}
/**
@@ -472,93 +918,144 @@
/**
* 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');
+// jvbDump($block, 'navigation');
+ $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);
}
+ $attrs = $block['attrs']??[];
- $toggle = (array_key_exists('overlayMenu', $block['attrs'])
- && $block['attrs']['overlayMenu'] == 'never') ?
- '':
- '<button class="toggle main"
- data-action="toggle-menu"
- aria-label="Open Menu"
- aria-controls="navigation-' .$ID. '"
- aria-expanded="false">'.
- jvbIcon('list', ['title'=>'Toggle Menu']).
- jvbIcon('x', ['title'=>'Toggle Menu']).
- '</button>';
- $class = ($toggle === '') ?
- $this->getClassesAndStyles($block['attrs'], ['mobile']) :
- $this->getClassesAndStyles($block['attrs']);
- $helpmenu = (get_the_title($ID) === 'Main') ?
- '<nav><ul>'.jvbNotificationMenu().jvbHelpMenu().'</ul></nav>' :
- '';
+ $toggle = '';
+ $classes = [];
+ if (!array_key_exists('overlayMenu', $attrs) || $attrs['overlayMenu'] !== 'never') {
+ $toggle = sprintf(
+ '<button class="toggle main"
+ data-action="toggle-menu"
+ aria-label="Open Menu"
+ aria-controls="navigation-%d"
+ aria-expanded="false">%s%s</button>',
+ $ID,
+ jvbIcon('list'),
+ jvbIcon('x')
+ );
+ $classes[] = 'mobile';
+ if (array_key_exists('overlayMenu', $attrs) && $attrs['overlayMenu'] === 'always') {
+ $classes[] = 'always';
+ }
+ }
+ if (!array_key_exists('layout', $attrs)) {
+ $classes[] = 'left';
+ $classes[] = 'row';
+ }
+ $class = $this->getClassesAndStyles($attrs, $classes);
+
+ $helpmenu = '';
+ $title = get_the_title($ID);
+ $isMain = false;
+ if ($title === 'Main') {
+ $isMain = true;
+ $helpmenu = sprintf(
+ '<nav><ul>%s%s</ul></nav>',
+ jvbNotificationMenu(),
+ jvbHelpMenu()
+ );
+ }
+
//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));
+ $helpmenu = apply_filters('jvbMenuExtraAfter', $helpmenu, $title, $ID);
+ $main = trim(apply_filters('jvbMenuExtra', $this->innerBlocks($block), $title, $block));
- $main = str_starts_with($main, '<ul') ? $main : '<ul>'.$main.'</ul>';
+ $main = str_starts_with($main, '<ul') ? $main : sprintf('<ul>%s</ul>',$main);
- return '<nav'.$class.' id="navigation-' . $ID . '"aria-label="Navigation">
- <span class="screen-reader-text">
+ $skipToContent = $isMain ? '<span class="screen-reader-text">
<a href="#content">Skip to Content</a>
- </span>' .
- $toggle .
- $main.
- '</nav>'.$helpmenu;
+ </span>' : '';
+ return sprintf(
+ '<nav%s id="navigation-%d"aria-label="Navigation">
+ %s%s%s</nav>%s',
+ $class,
+ $ID,
+ $skipToContent,
+ $toggle,
+ $main,
+ $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;
- $url = (str_starts_with($block['attrs']['url'],'/')) ?
- home_url($block['attrs']['url']) :
- $block['attrs']['url'];
+ if (!array_key_exists('attrs', $block)) {
+ return '';
+ }
+ $attrs = $block['attrs']??[];
+ $url = (str_starts_with($attrs['url'],'/')) ?
+ home_url($attrs['url']) :
+ $attrs['url'];
$current = (home_url($wp->request.'/') == $url);
- $temp = $block['attrs'];
- unset($temp['url']);
+ $attrs['url'] = $url;
$classes = ($current) ?
- $this->getClassesAndStyles($temp, ['current']):
- $this->getClassesAndStyles($temp);
+ $this->getClassesAndStyles($attrs, ['current']):
+ $this->getClassesAndStyles($attrs);
$aria = '';
if ($current) {
$aria = ' aria-current="page"';
}
- $linkOpen = $this->build_navigation_link($block['attrs'], $aria);
+ $linkOpen = $this->buildNavigationLink($attrs, $aria);
- return '<li'.$classes.'>'.$linkOpen.$block['attrs']['label'].'</a></li>';
+ return sprintf(
+ '<li%s>%s%s</a></li>',
+ $classes,
+ $linkOpen,
+ $block['attrs']['label']
+ );
}
- public function render_core_navigation_submenu(array $block):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'];
- unset($temp['url']);
+ $attrs = $block['attrs']??[];
+ $attrs['url'] = $url;
$classes = ($current) ?
- $this->getClassesAndStyles($temp, ['has-submenu', 'current']):
- $this->getClassesAndStyles($temp, ['has-submenu']);
+ $this->getClassesAndStyles($attrs, ['has-submenu', 'current']):
+ $this->getClassesAndStyles($attrs, ['has-submenu']);
$aria = '';
if ($current) {
$aria = ' aria-current="page"';
}
$id = sanitize_title($block['attrs']['label']);
- $linkOpen = $this->build_navigation_link($block['attrs'], $aria);
- $content = '<li'.$classes.'>'.$linkOpen.$block['attrs']['label'].
- '</a><button class="toggle" data-action="toggle-submenu" title="Toggle Submenu" aria-label="Open '.$block['attrs']['label'].' Submenu" aria-expanded="false" aria-controls="'.$id.'-submenu">'.
- jvbIcon('caret-down', ['title'=>'Toggle Submenu']).
- '</button><ul class="submenu" id='.$id.'-submenu">';
+ $linkOpen = $this->buildNavigationLink($attrs, $aria);
+ $content = sprintf(
+ '<li%s>%s%s</a>
+ <button class="toggle" data-action="toggle-submenu" title="Toggle Submenu" aria-label="Open %s Submenu" aria-expanded="false" aria-controls="%s-submenu">
+ %s
+ </button>
+ <ul class="submenu" id=%s-submenu">',
+ $classes,
+ $linkOpen,
+ $attrs['label'],
+ $attrs['label'],
+ $id,
+ jvbIcon('caret-down', ['title'=>'Toggle Submenu']),
+ $id
+ );
$content .= $this->innerBlocks($block);
$content .= '</ul></li>';
@@ -566,9 +1063,8 @@
return $content;
}
- protected function build_navigation_link(array $attrs, string $aria):string
+ protected function buildNavigationLink(array $attrs, string $aria):string
{
- global $wp;
$url =(str_starts_with($attrs['url'],'/')) ?
home_url($attrs['url']) :
$attrs['url'];
@@ -596,276 +1092,908 @@
break;
}
}
- return '<a href="'.$url.'"'.$aria.$rel.$target.$title.'>';
+ return sprintf(
+ '<a href="%s"%s%s%s%s>',
+ $url,
+ $aria,
+ $rel,
+ $target,
+ $title
+ );
}
/**
* 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'])) ?
+ public function prerender_core_post_author(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+ $attrs = $block['attrs'] ?? [];
+
+ $size = 96;
+ if (array_key_exists('avatarSize',$attrs) && is_int($attrs['avatarSize'])) {
+ $size = $attrs['avatarSize'];
+ }
+ $byline = $aOpen = $aClose = $avatar = $bio = '';
+ global $post;
+ $user = get_userdata($post->post_author);
+
+ if (!array_key_exists('showAvatar', $attrs) || $this->checkAttrs('showAvatar', $attrs)){
+ $avatar = get_avatar($post->post_author, $size);
+ }
+ if (!array_key_exists('showBio', $attrs) || $this->checkAttrs('showBio', $attrs)) {
+ $bio = wpautop($user->description);
+ }
+
+ $target = '';
+ if (array_key_exists('linkTarget', $attrs) && $attrs['linkTarget']=== '_blank') {
+ $target = ' target="_blank"';
+ }
+
+ if ($this->checkAttrs('isLink', $attrs)) {
+ $aOpen = sprintf(
+ '<a href="%s"%s>',
+ get_author_posts_url($post->post_author),
+ $target
+ );
+ $aClose = '</a>';
+ }
+
+ if (array_key_exists('byline', $attrs)) {
+ $byline = sprintf(
+ '<small>%s</small> — ',
+ $attrs['byline']
+ );
+ }
+
+ $name = $user->display_name;
+
+
+ return sprintf(
+ '<div%s>%s%s%s<p>%s%s%s%s</p>%s</div>',
+ $this->getClassesAndStyles($attrs, ['row','nowrap']),
+ $aOpen,
+ $avatar,
+ $aClose,
+ $aOpen,
+ $byline,
+ $name,
+ $aClose,
+ $bio
+ );
+ }
+ //core_post_author_biography
+
+ public function prerender_core_post_author_name(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+ $attrs = $block['attrs']??[];
+ global $post;
+ $aOpen = $aClose = '';
+ if ($this->checkAttrs('isLink', $attrs)) {
+
+ $aOpen = sprintf(
+ '<a href="%s" rel="author">',
+ get_author_posts_url($post->post_author)
+ );
+ $aClose = '</a>';
+ }
+
+ $author = get_userdata($post->post_author);
+ return sprintf(
+ '<p%s>%s%s%s</p>',
+ $this->getClassesAndStyles($attrs, ['author']),
+ $aOpen,
+ $author->display_name,
+ $aClose
+ );
+ }
+ 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']??[])) ?
$block['attrs']['tagName'] :
'main';
if ($content == '') {
if(is_singular()) {
- global $post;
+ global $post, $page;
- $block['innerBlocks'] = parse_blocks($post->post_content);
+ $pages = explode('<!--nextpage-->', $post->post_content);
+ $currentContent = $pages[max(0, $page - 1)] ?? $pages[0];
+
+
+ if ($page > 1 && !str_contains($currentContent, '<!--nextpage-->')) {
+ $currentContent = str_replace('<!-- /wp:nextpage -->','', $currentContent);
+ $currentContent .= '
+ <!-- wp:nextpage -->
+ <!--nextpage-->
+ <!-- /wp:nextpage -->';
+ }
+
+ $block['innerBlocks'] = parse_blocks($currentContent);
$result = $this->innerBlocks($block);
}else {
$result = '';
}
} else {
- $result = $this->inside($block, $tag, $content);
+ $result = $this->innerBlocks($block);
}
+ if(static::$renderGallery) {
+ add_action('wp_footer', 'jvbRenderGallery');
+ }
+
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
{
- $postDate = get_the_date('c');
- return '<time datetime="'.$postDate.'" itemprop="datePublished"'.$this->getClassesAndStyles($block['attrs']).'>'.get_the_date().'</time>';
+
+// jvbDump($block, 'post date');
+// return null;
+ $attrs = $block['attrs']??[];
+ $postDate = null;
+ $itemProp = 'datePublished';
+ $format = array_key_exists('format', $attrs) ? $attrs['format'] : 'M d, Y';
+ $dateFormat = null;
+ if (array_key_exists('displayType', $attrs)) {
+ switch ($attrs['displayType']) {
+ case 'displayType':
+ $postDate = get_post_modified_time('c');
+ $dateFormat = get_post_modified_time($format);
+ $itemProp = 'dateModified';
+ break;
+
+
+ }
+ }
+ $postDate = is_null($postDate) ? get_the_date('c') : $postDate;
+ $dateFormat = is_null($dateFormat) ? get_the_date($format) : $dateFormat;
+
+
+
+ $aOpen = $aClose = '';
+ if ($this->checkAttrs('isLink', $attrs) && !is_singular()) {
+ $aOpen = sprintf(
+ '<a href="%s">',
+ get_the_permalink()
+ );
+ $aClose = '</a>';
+ }
+// jvbDump($parent, 'Parent');
+
+ return sprintf(
+ '<time datetime="%s" itemprop="%s"%s>%s%s%s</time>',
+ $postDate,
+ $itemProp,
+ $this->getClassesAndStyles($attrs),
+ $aOpen,
+ $dateFormat,
+ $aClose
+ );
}
//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
+ {
+ $attrs = $block['attrs']??[];
+
+ $moreText = array_key_exists('moreText', $attrs) ? $attrs['moreText'] : 'Read more '.jvbIcon('arrow-circle-right');
+ $showMoreOnNewLine = !array_key_exists('showMoreOnNewLine', $attrs) || $this->checkAttrs('showMoreOnNewLine', $attrs);
+// jvbDump($block);
+// jvbDump($showMoreOnNewLine);
+
+ $excerpt = array_filter(explode('<p>',wpautop(get_the_excerpt())));
+ $classes = $this->getClassesAndStyles($attrs);
+ $excerpt = array_map(function ($line) use ($classes) {
+ return sprintf(
+ '<p%s>%s',
+ $classes,
+ $line
+ );
+ }, $excerpt);
+
+ if (!empty($moreText)) {
+ if ($showMoreOnNewLine) {
+ $excerpt[] = sprintf(
+ '<p%s><a href="%s" class="read-more">%s</a></p>',
+ $classes,
+ get_the_permalink(),
+ $moreText
+ );
+ } else {
+ $last = array_key_last($excerpt);
+ $excerpt[$last] = str_replace('</p>', sprintf('<a href="%s" class="read-more">%s</a>',
+ get_the_permalink(),
+ $moreText), $excerpt[$last]);
+ }
+ }
+ return implode('',$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;
+ $attrs = $block['attrs']??[];
$ID = get_post_thumbnail_id($post->ID);
$aOpen = $aClose = '';
- if(!is_single($post->ID)) {
+ if(!is_single($post->ID) && $this->checkAttrs('isLink', $attrs)) {
$aOpen = '<a href="'.get_the_permalink($post->ID).'">';
$aClose = '</a>';
}
- return '<figure'.$this->getClassesAndStyles($block['attrs']).'>'.$aOpen.
- apply_filters('jvbCoreFeaturedImage', $this->image($ID), $post->post_type).
- $aClose.'</figure>';
+ $img = apply_filters('jvbCoreFeaturedImage', '', $post->post_type, $attrs);
+
+ if (empty($img)) {
+ $img = $this->image($ID);
+ }
+ $aspectRatio = $attrs['aspectRatio']??false;
+ if ($aspectRatio) {
+ $img = str_replace('<img', sprintf(
+ '<img style="aspect-ratio:%s;"',
+ $aspectRatio
+ ), $img);
+ }
+
+
+ return !empty($img) ? sprintf(
+ '<figure%s>%s%s%s</figure>',
+ $this->getClassesAndStyles($attrs),
+ $aOpen,
+ $img,
+ $aClose,
+ ):'';
}
//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 prerender_core_post_template(array $block, ?string $content):?string
+ {
+ $inner = '';
+
+ if (!static::$currentLoop) {
+
+ if (JVB_TESTING) {
+ jvbDump('No loop stored');
+ }
+ return $content;
+ }
+ if (static::$currentLoop->have_posts()) {
+ while (static::$currentLoop->have_posts()) {
+ static::$currentLoop->the_post();
+
+ $inner .= sprintf(
+ '<li>%s</li>',
+ $this->innerBlocks($block, '','',$block)
+ );
+ }
+ }
+
+ return sprintf(
+ '<ul%s>%s</ul>',
+ $this->getClassesAndStyles($block['attrs']??[], ['loop']),
+ $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']);
+ $attrs = $block['attrs']??[];
$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($attrs, ['term-list', 'row', 'left'])
+ );
+ if (array_key_exists('prefix', $attrs)) {
+ $out .= sprintf(
+ '<li class="prefix">%s</li>',
+ $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>';
+ if (array_key_exists('suffix', $attrs)) {
+ $out .= sprintf(
+ '<li class="suffix">%s</li>',
+ $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($parent, 'Parent');
$open = $close = '';
- if (array_key_exists('isLink', $block['attrs'])) {
- $rel = (array_key_exists('rel', $block['attrs'])) ?
+ $attrs = $block['attrs']??[];
+ if ($this->checkAttrs('isLink', $attrs)) {
+ $rel = (array_key_exists('rel', $attrs)) ?
' rel="'.$block['attrs']['rel'].'"' :
'';
- $target = (array_key_exists('linkTarget', $block['attrs'])) ?
+ $target = (array_key_exists('linkTarget', $attrs)) ?
' target="'.$block['attrs']['linkTarget'].'"' :
'';
$open = '<a href="' . get_the_permalink() . '"' . $rel . $target . '>';
$close = '</a>';
}
- if (is_singular(BASE.'partner')) {
- $open .= '<small>edmonton.ink partner:</small> ';
- }
- $level = (array_key_exists('attrs', $block) &&
- array_key_exists('level', $block['attrs'])) ?
- $block['attrs']['level'] :
- 2;
- return '<h'.$level.$this->getClassesAndStyles($block['attrs']).'>'.
- $open.get_the_title().$close.
- '</h'.$level.'>';
- }
- public function render_core_query(array $block, string $content):string
+ $level = $attrs['level']??2;
+
+ $title = (!static::$currentLoop && !is_singular())
+ ? get_the_title(get_queried_object_id())
+ : get_the_title();
+
+ return sprintf(
+ '<h%s%s>%s%s%s</h%s>',
+ $level,
+ $this->getClassesAndStyles($attrs),
+ $open,
+ $title,
+ $close,
+ $level
+ );
+ }
+ public function prerender_core_query(array $block, ?string $content):?string
{
- $queryID = $block['attrs']['queryId'];
- $args = [];
- $inherit = $block['attrs']['inherit']??false;
+ global $wp_query;
+ $inherit = $block['attrs']['inherit'] ?? false;
+
if ($inherit) {
- global $wp_query;
- $loop = $wp_query;
+ static::$currentLoop = $wp_query;
} else {
- foreach ($block['attrs']['query'] as $key => $value) {
- if (empty($value)) {
- continue;
- }
+ static::$currentLoop = new WP_Query($this->buildQueryArgs($block['attrs']));
+ }
+ static::$currentQueryId = $block['attrs']['queryId'] ?? null;
+
+ static::$originalQuery = $wp_query;
+
+ $inside = $this->innerBlocks($block);
+
+ if (str_contains($inside, 'loop')) {
+ $classes = $this->getClassesAndStyles($block['attrs'] ?? [], ['loop']);
+ $classes = str_replace(' class="', '', $classes);
+ $classes = strtok($classes, '"');
+ $inside = str_replace('loop', $classes, $inside);
+ }
+
+
+ static::$currentQueryId = null;
+ static::$currentLoop = null;
+
+ wp_reset_postdata();
+ return $inside;
+ }
+// public function render_core_query(array $block, string $content): string
+// {
+// $inside = $this->innerBlocks($block);
+// if (str_contains($inside, 'loop')) {
+// $classes = $this->getClassesAndStyles($block['attrs']??[], ['loop']);
+// $classes = str_replace(' class="', '', $classes);
+// $classes = strtok($classes, '"');
+//
+// $inside = str_replace('loop', $classes, $inside);
+// }
+// return $inside;
+// }
+ protected function buildQueryArgs(array $attrs): array
+ {
+ $queryID = $attrs['queryId'] ?? null;
+ $args = [
+ 'post_status' => 'publish',
+ ];
+ foreach (($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;
+ case 'postType': $args['post_type'] = $value; break;
+ case 'perPage': $args['posts_per_page'] = $value; break;
+ case 'orderBy': $args['orderby'] = $value; break;
+ case 'sticky':
+ match ($value) {
+ 'ignore' => $args['ignore_sticky_posts'] = true,
+ 'exclude' => $args['post__not_in'] = get_option('sticky_posts'),
+ 'only' => $args['post__in'] = get_option('sticky_posts'),
+ default => null
+ };
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';
- }
- }
+ $taxQuery = array_map(fn($tax, $terms) => [
+ 'taxonomy' => $tax, 'terms' => $terms
+ ], array_keys($value), $value);
+ if (count($taxQuery) > 1) $taxQuery['relation'] = 'OR';
+ $args['tax_query'] = $taxQuery;
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;
-
+ case 'search': $args['s'] = $value; break;
+ default: $args[$key] = $value; break;
}
}
- //Add in any args from the query string
- $search = 'query-'.$queryID;
+
+ // Handle pagination from query string
+ $search = 'q-' . $queryID.'-';
foreach ($_GET as $key => $value) {
- if (str_contains($key, $search)) {
- $key = str_replace($search, '', $key);
- if ($key === 'page') {
- $args['paged'] = (int)$value;
- }
+ if (str_contains($key, $search) && str_replace($search, '', $key) === 'page') {
+ $args['paged'] = (int)$value;
}
}
- $loop = new WP_Query($args);
+ return $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;
-
- }
+ protected function buildPaginationUrl(int $page): string
+ {
+ $param = 'q-' . static::$currentQueryId . '-page';
+ $url = remove_query_arg($param);
+ return $page > 1 ? add_query_arg($param, $page, $url) : $url;
}
-
-
- $tagName = (array_key_exists('tagName', $block['attrs'])) ? $block['attrs']['tagName'] : 'div';
- $out = '<'.$tagName.' class="loop">'.$inner.'</'.$tagName.'>';
- if ($inherit) {
- wp_reset_postdata();
+ protected function getCurrentPage(): int
+ {
+ $param = 'q-' . static::$currentQueryId . '-page';
+ return isset($_GET[$param]) ? (int)$_GET[$param] : 1;
}
- return $out;
- }
+// public function render_core_query(array $block, string $content): string
+// {
+//
+//// $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();
+//// }
+//
+// $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
+ public function prerender_core_query_no_results(array $block, ?string $content):?string
+ {
+ if (!static::$currentLoop || static::$currentLoop->have_posts()) {
+ return '';
+ }
+
+ $inside = $this->innerBlocks($block);
+ return empty($inside) ? '' : sprintf(
+ '<div%s>%s</div>',
+ $this->getClassesAndStyles($block['attrs']??[], ['no-results']),
+ $inside
+ );
+ }
//core_query_pagination
+ public function prerender_core_query_pagination(array $block, ?string $content):?string
+ {
+ return sprintf(
+ '<nav%s>%s</nav>',
+ $this->getClassesAndStyles($block['attrs']??[], ['pagination', 'condensed','btw']),
+ $this->innerBlocks($block)
+ );
+ }
+
//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;
+ public function prerender_core_query_pagination_next(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+ if (!static::$currentLoop) return '';
+ $currentPage = $this->getCurrentPage();
+ $maxPages = static::$currentLoop->max_num_pages;
- if (($isHeaderTemplate || $isFooterTemplate)) {
+ if ($currentPage >= $maxPages) return '';
- $tag = $isHeaderTemplate ?: $isFooterTemplate ?: 'div';
-
- $breadcrumbs = $themeSwitch = $afterHeader = $beforeHeader = $footerText= '';
- if ($isHeaderTemplate) {
-
- $beforeHeader = apply_filters('jvbAboveHeader', $beforeHeader);
- if ($beforeHeader !== '') {
- $beforeHeader = '<aside class="pre-header">'.$beforeHeader.'</aside>';
- }
- $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) ? '<label title="'.$title.'" id="theme-switch" class="toggle-switch" for="theme-switcher">
- <input class="theme-switch row" id="theme-switcher" name="theme-switcher" type="checkbox"'.$checked.' data-setting="theme" data-theme role="switch" name="dark-mode" aria-label="Toggle dark mode"><span class="slider">'.
- jvbIcon('sun-dim', ['title'=> 'Light Mode']).
- jvbIcon('moon', ['title'=>'Dark Mode']).
- '</span></label>' : '';
- $breadcrumbs = jvbBuildBreadcrumbs();
- $afterHeader = apply_filters('jvbBelowHeader', $afterHeader);
-
- if ($afterHeader !== '') {
- $afterHeader = '<aside class="sub-header">'.$afterHeader.'</aside>';
- }
- $footerText = '<div class="scroll-progress"><div class="bar"></div>
-</div>';
- } elseif ($isFooterTemplate) {
- $beforeHeader = apply_filters('jvbBeforeFooter', '');
- if ($beforeHeader !== '') {
- $beforeHeader = '<section class="pre-footer">'.$beforeHeader.'</section>';
- }
- $footerText = jvbRandomFooterText();
+ $nextLabel = $rArrow = '';
+ $type = get_post_type_object(get_post_type())->label;
+ if ($parent) {
+ $attrs = $parent->attributes;
+ if (array_key_exists('paginationArrow', $attrs)){
+ $rArrow = match($attrs['paginationArrow']) {
+ 'chevron' => jvbIcon('caret-circle-right'),
+ default => jvbIcon('arrow-circle-right')
+ };
}
-// 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');
+ if (!array_key_exists('showLabel', $attrs) || $attrs['showLabel'] === true) {
- return $beforeHeader.'<'.$tag.$this->getClassesAndStyles($block['attrs']).'>'.
- $themeSwitch .
- $this->inside($block, $tag, $content) .
- $footerText.'</'.$tag.'>'.$afterHeader.$breadcrumbs;
- }
+ $nextLabel = 'Next '.$type;
+ }
+ } else {
+ $rArrow = jvbIcon('caret-circle-right');
+ }
- return $content;
+ $aOpen = sprintf(
+ '<a class="nav next" href="%s" title="Next %s">',
+ $this->buildPaginationUrl($currentPage + 1),
+ $type
+ );
+ $aClose = '</a>';
+ return sprintf(
+ '%s%s%s%s',
+ $aOpen,
+ $nextLabel,
+ $rArrow,
+ $aClose
+ );
+ }
+ //core_query_pagination_numbers
+ public function prerender_core_query_pagination_numbers(array $block, ?string $content):?string
+ {
+ if (!static::$currentLoop) return '';
+ $currentPage = $this->getCurrentPage();
+ $maxPages = (int)static::$currentLoop->max_num_pages;
+
+ $attrs = $block['attrs']??[];
+ if ($maxPages <= 1) return '';
+
+ $midSize = $attrs['midSize'] ?? 2;
+ $endSize = 1;
+
+
+ $items = '';
+ $gap = false;
+ for ($i = 1; $i <= $maxPages; $i++) {
+ if (($i <= min($endSize + 1, $maxPages)) ||
+ ($i >= max(1, $currentPage - $midSize) && $i <= min($maxPages, $currentPage + $midSize)) ||
+ ($i >= max(1, $maxPages - $endSize) && $i <= $maxPages)) {
+ $gap = true;
+ $items .= ($i === $currentPage)
+ ? sprintf('<li aria-current="page" class="current">%d</li>', $i)
+ : sprintf('<li><a href="%s">%d</a></li>', $this->buildPaginationUrl($i), $i);
+ } elseif ($gap) {
+ $gap = false;
+ $items .= sprintf(
+ '<li class="dots"><span>%s</span></li>',
+ jvbIcon('dots-three')
+ );
+ }
+ }
+
+ return sprintf('<ul%s>%s</ul>',
+ $this->getClassesAndStyles($attrs, ['row', 'nowrap']),
+ $items
+ );
+ }
+ //core_query_pagination_previous
+
+ public function prerender_core_query_pagination_previous(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+
+ if (!static::$currentLoop) return '';
+
+ $currentPage = $this->getCurrentPage();
+ $maxPages = static::$currentLoop->max_num_pages;
+
+ if ($currentPage <= 1) return '';
+
+ $nextLabel = $rArrow = '';
+ $type = get_post_type_object(get_post_type())->label;
+ if ($parent) {
+ $attrs = $parent->attributes;
+ if (array_key_exists('paginationArrow', $attrs)){
+ $rArrow = match($attrs['paginationArrow']) {
+ 'chevron' => jvbIcon('caret-circle-left'),
+ default => jvbIcon('arrow-circle-left')
+ };
+ }
+ if (!array_key_exists('showLabel', $attrs) || $attrs['showLabel'] === true) {
+
+ $nextLabel = 'Previous '.$type;
+ }
+ } else {
+ $rArrow = jvbIcon('caret-circle-left');
+ }
+
+ $aOpen = sprintf(
+ '<a class="nav prev" href="%s" title="Previous %s">',
+ $this->buildPaginationUrl($currentPage - 1),
+ $type
+ );
+ $aClose = '</a>';
+ return sprintf(
+ '%s%s%s%s',
+ $aOpen,
+ $nextLabel,
+ $rArrow,
+ $aClose
+ );
+ }
+
+ public function prerender_core_query_title(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'query title');
+ $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 prerender_core_template_part(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'template part');
+// jvbDump($parent, 'Parent');
+
+
+ $slug = $block['attrs']['slug'] ?? null;
+ $theme = $block['attrs']['theme'] ?? get_stylesheet();
+ $tag = $block['attrs']['tagName'] ?? 'div';
+ if (!$slug) {
+ return $content;
+ }
+
+ // Try to get the template part post (customized via FSE)
+ $template_part = get_block_template( "$theme//$slug", 'wp_template_part' );
+
+ if ( $template_part && ! empty( $template_part->content ) ) {
+ $block['innerBlocks'] = parse_blocks( $template_part->content );
+
+ $before = $themeSwitch = $after = $beforeClose = '';
+ switch ($tag) {
+ case 'header':
+ $before = apply_filters('jvbAboveHeader', '');
+ if (!empty($before)) {
+ $before = sprintf(
+ '<aside class="pre header row x-btw">%s</aside>',
+ $before
+ );
+ }
+ $themeSwitch = jvbDarkModeToggle();
+
+ $after = apply_filters('jvbBelowHeader', $after);
+ if (!empty($after)) {
+ $after = sprintf(
+ '<aside class="sub header row x-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;
+ }
+
+ return sprintf(
+ '%s<%s%s>%s%s%s</%s>%s',
+ $before,
+ $tag,
+ $this->getClassesAndStyles($block['attrs']??[]),
+ $themeSwitch,
+ $this->innerBlocks($block),
+ $beforeClose,
+ $tag,
+ $after
+ );
+
+ }
+ 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 x-btw">'.$beforeHeader.'</aside>';
+// }
+// $themeSwitch = jvbDarkModeToggle();
+// $breadcrumbs = BreadcrumbManager::getInstance()->renderNavigation();
+// $afterHeader = apply_filters('jvbBelowHeader', $afterHeader);
+//
+// if ($afterHeader !== '') {
+// $afterHeader = '<aside class="sub header row x-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
@@ -873,32 +2001,426 @@
* Widgets Blocks
*/
//core_archives
+ public function render_core_archives(array $block, string $content):string
+ {
+// jvbDump($block, 'archives');
+ $attrs = $block['attrs']??[];
+ $isDropdown = $this->checkAttrs('displayAsDropdown', $attrs);
+
+ $replace = strtok($content,'>').'>';
+ $content = str_replace($replace, '', $content);
+
+ if ($isDropdown) {
+ $content = sprintf(
+ '<div%s>%s',
+ $this->getClassesAndStyles($attrs, ['archive dropdown']),
+ $content
+ );
+ } else {
+ $content = sprintf(
+ '<ul%s>%s',
+ $this->getClassesAndStyles($attrs, ['archive-list']),
+ $content
+ );
+ }
+
+ return $content;
+ }
//core_calendar
+ public function render_core_calendar(array $block, string $content):string
+ {
+ $content = $this->inside($block, false, $content);
+ $replace = strtok($content, '>').'>';
+ $content = str_replace($replace, '', $content);
+ return sprintf(
+ '<table%s>%s',
+ $this->getClassesAndStyles($block['attrs']??[], ['calendar']),
+ $content
+ );
+ }
//core_categories
+ public function prerender_core_categories(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+ $attrs = $block['attrs']??[];
+ $args = [
+ 'taxonomy' => 'category',
+ 'hide_empty' => !$this->checkAttrs('showEmpty', $attrs)
+ ];
+
+ $showHierarchy = $this->checkAttrs('showHierarchy', $attrs);
+ if ($this->checkAttrs('showOnlyTopLevel', $attrs) || $showHierarchy){
+ $args['parent'] = 0;
+ }
+
+ $terms = $this->getTerms($args, $showHierarchy);
+ if (!$terms){
+ return '';
+ }
+
+
+ $showPostCounts = $this->checkAttrs('showPostCounts', $attrs);
+ $isDropdown = $this->checkAttrs('displayAsDropdown', $attrs);
+
+ if ($isDropdown) {
+ $this->counter('core_categories');
+ }
+
+ $tax = get_taxonomy($args['taxonomy']);
+ $taxonomyName = $tax->label??'Categories';
+ $taxonomySingular = $tax->labels->singular_name??'Category';
+ $inner = $this->buildTermList($terms, $taxonomyName, $taxonomySingular, $isDropdown, $showPostCounts);
+ if ($isDropdown) {
+ return sprintf(
+ '<div%s>%s</div>',
+ $this->getClassesAndStyles($attrs, ['taxonomy-dropdown']),
+ $inner
+ );
+ }
+ return sprintf(
+ '<ul%s>%s</ul>',
+ $this->getClassesAndStyles($attrs, ['taxonomy-list', jvbNoBase($args['taxonomy'])]),
+ $inner
+ );
+ }
+ public function getTerms(array $args, bool $showHierarchy = false):array|false
+ {
+ $terms = get_terms($args);
+ if (!$terms || is_wp_error($terms)) {
+ return false;
+ }
+ $terms = array_map(function ($term) {
+ return (array) $term;
+ }, $terms);
+
+ if ($showHierarchy) {
+ $terms = array_map(function ($term) use ($args) {
+ $args['parent'] = $term['term_id'];
+ $children = $this->getTerms($args, true);
+ $term['children'] = $children?:[];
+ return $term;
+ }, $terms);
+ }
+
+ return $terms;
+ }
+ protected function buildTermList(array $terms, string $taxonomyName, string $taxonomySingular, bool $isDropdown, bool $showPostCounts, bool $isOpening = true, int $level = 0):string
+ {
+ $out = '';
+ if ($isOpening) {
+ $out = $isDropdown ?
+ sprintf(
+ '<label for="taxonomy-select-%s">%s</label>
+ <select name="%s_name" id="taxonomy-select-%s"><option value="">Select %s</option>',
+ static::$counters['core_categories'],
+ $taxonomyName,
+ str_replace('-', '_',sanitize_title(strtolower($taxonomyName))),
+ static::$counters['core_categories'],
+ $taxonomyName
+ ) :
+ '';
+ } elseif (!$isDropdown) {
+ $out .= '<ul>';
+ }
+
+
+ $prefix = '';
+ if ($isDropdown) {
+ $base = ' ';
+ for ($i = 1; $i <= $level; $i++) {
+ $prefix .= $base;
+ }
+ $prefix .= empty($prefix) ? '' : '- ';
+ }
+
+ $theTerms = array_map(function ($term) use ($taxonomyName, $taxonomySingular, $isDropdown, $showPostCounts, $prefix, $level) {
+ if ($isDropdown) {
+ return sprintf(
+ '<option value="%s">%s%s%s</option>%s',
+ $term['slug'],
+ $prefix,
+ $term['name'],
+ $showPostCounts ? ' ('.$term['count'].')' : '',
+ empty($term['children']??[]) ? '' : $this->buildTermList($term['children'], $taxonomyName, $taxonomySingular, $isDropdown, $showPostCounts, false, $level+1)
+ );
+ }
+ return sprintf(
+ '<li><a href="%s">%s%s</a>%s</li>',
+ get_term_link($term['term_id']),
+ $term['name'],
+ $showPostCounts ? ' <span class="count">'.$term['count'].'</span>' : '',
+ empty($term['children']??[]) ? '' : $this->buildTermList($term['children'], $taxonomyName, $taxonomySingular, $isDropdown, $showPostCounts, false, $level+1)
+ );
+ }, $terms);
+
+ $out .= implode('', $theTerms);
+
+ if ($isOpening) {
+ $out .= $isDropdown ?
+ '</select>' :
+ '';
+ } else if (!$isDropdown) {
+ $out .= '</ul>';
+ }
+
+
+ return $out;
+ }
//core_html
//core_latest_comments
//core_latest_posts
+ public function prerender_core_latest_posts(array $block, ?string $content, ?WP_Block $parent):?string {
+ $attrs = $block['attrs']??[];
+// jvbDump($block, 'latest posts');
+
+ $args = [];
+ $title = 'Latest Posts';
+ $args['order'] = array_key_exists('order', $attrs) ? strtoupper($attrs['order']) : 'DESC';
+ $args['orderby'] = array_key_exists('orderBy', $attrs) ? $attrs['orderBy'] : 'date';
+ $args['posts_per_page'] = array_key_exists('postsToShow', $attrs) ? $attrs['postsToShow'] : 5;
+
+ if (array_key_exists('categories', $attrs)) {
+ $list = jvbCommaList(array_column($attrs['categories'], 'name'));
+ $args['tax_query'] = [];
+ $args['tax_query'][] = [
+ 'taxonomy' => 'category',
+ 'terms' => array_column($attrs['categories'], 'id')
+ ];
+ $title .= ' in '.$list;
+ }
+
+ $posts = new WP_Query($args);
+
+ if (!$posts->have_posts()) {
+ return '';
+ }
+ $posts = array_map(function ($post) use ($attrs) {
+ $img = $this->checkAttrs('displayFeaturedImage', $attrs)
+ ? $this->image(get_post_thumbnail_id($post->ID), 'tiny', 'thumbnail')
+ : '';
+
+ $author = $this->checkAttrs('displayAuthor', $attrs)
+ ? sprintf(
+ '<a href="%s">%s</a>',
+ get_author_posts_url($post->post_author),
+ get_userdata($post->post_author)->display_name
+ )
+ : '';
+
+ $date = $this->checkAttrs('displayPostDate', $attrs)
+ ? sprintf(
+ '<time datetime="%s">%s</time>',
+ date('Y-m-d', strtotime($post->post_date)),
+ date_i18n('M j, Y', strtotime($post->post_date))
+ )
+ : '';
+ $authorDate = $author;
+ if (!empty($authorDate) && !empty($date)) {
+ $authorDate .= ' | '.$date;
+ } else if (!empty($date)) {
+ $authorDate = $date;
+ }
+
+ $excerpt = '';
+ if ($this->checkAttrs('displayPostContent', $attrs)) {
+ if (array_key_exists('excerptLength', $attrs)) {
+ $excerpt = wp_trim_words(get_the_content($post->ID), $attrs['excerptLength'], '...');
+ } else {
+ $excerpt = get_the_excerpt($post->ID);
+ }
+ }
+ if (!empty($excerpt)) {
+ $excerpt = wpautop($excerpt);
+ }
+
+ return sprintf(
+ '<li>%s<p><a href="%s">%s</a>%s</p>%s</li>',
+ $img,
+ get_the_permalink($post->ID),
+ $post->post_title,
+ !empty($authorDate) ? ' <small>— '.$authorDate.'</small>' : '',
+ $excerpt
+ );
+ }, $posts->posts);
+
+ wp_reset_postdata();
+ return sprintf(
+ '<ul%s>%s</ul>',
+// $title,
+ $this->getClassesAndStyles($attrs, ['post-list']),
+ implode('', $posts)
+ );
+ }
//core_page_list
- //core_page_list_item
- //core_rss
+ public function prerender_core_page_list(array $block, ?string $content, ?WP_Block $parent):?string{
+ $attrs = $block['attrs']??[];
+ $parent = array_key_exists('parentPageID', $attrs) ? $attrs['parentPageID'] : 0;
+ $pages = new WP_Query([
+ 'post_type' => 'page',
+ 'posts_per_page' => -1,
+ 'parent' => $parent
+ ]);
+
+ if (!$pages->have_posts()) {
+ return '';
+ }
+ $inside = [];
+ foreach($pages->posts as $page) {
+ $inside[] = sprintf(
+ '<li><a href="%s">%s</a>',
+ get_the_permalink($page->ID),
+ $page->post_title
+ );
+ }
+ wp_reset_postdata();
+ return sprintf(
+ '<ul%s>%s</ul>',
+ $this->getClassesAndStyles($attrs, ['page-list']),
+ implode('',$inside)
+ );
+ }
+ //core_page_list_item (doesn't seem to be a thing)
+// public function prerender_core_page_list_item(array $block, ?string $content, ?WP_Block $parent):?string{
+// return $content;
+// }
+ //core_
+// public function prerender_core_rss(array $block, ?string $content, ?WP_Block $parent):?string
+// {
+// jvbDump($block, 'rss');
+// return $content;
+// }
//core_search
- //core_shortcode
- public function render_core_social_link(array $block, string $content):string
+ public function prerender_core_search(array $block, ?string $content, ?WP_Block $parent):?string
{
- $url = $block['attrs']['url'];
- $service = $block['attrs']['service'];
+// jvbDump($block, 'search');
+ $attrs = $block['attrs']??[];
+ $label = array_key_exists('label', $attrs) && !empty($attrs['label']) ? $attrs['label'] : '';
+ if (array_key_exists('showLabel', $attrs) && $attrs['showLabel'] === false) {
+ $label = '';
+ }
+ $placeholder = array_key_exists('placeholder', $attrs) ? $attrs['placeholder'] : 'Search...';
+
+ $buttonText = array_key_exists('buttonText', $attrs) && !empty($attrs['buttonText']) ? $attrs['buttonText'] : '';
+
+ $isInside = array_key_exists('buttonPosition', $attrs) && $attrs['buttonPosition'] === 'button-inside';
+
+ $hideInput = $this->checkAttrs('isSearchFieldHidden', $attrs) || (array_key_exists('buttonPosition', $attrs) && $attrs['buttonPosition'] === 'button-only');
+
+ return str_replace('<div class="search-container row left nowrap"', sprintf(
+ '<div%s',
+ $this->getClassesAndStyles($attrs, ['search-container', 'row', 'left', 'nowrap'])
+ ), jvbSearch($placeholder, uniqid(), $label, $buttonText, $isInside, $hideInput));
+ }
+ //core_shortcode
+ public function prerender_core_social_link(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'social link');
+// jvbDump($parent, 'Parent');
+ $parentAttrs = false;
+ if ($parent) {
+ $parentAttrs = $parent->attributes;
+ }
+ $attrs = $block['attrs']??[];
+ $url = $attrs['url']??'';
+ $service = $attrs['service']?:'';
$iconName = ($service === 'bluesky') ? 'butterfly' : $service.'-logo';
$icon = jvbIcon($iconName);
if (!$icon) {
$icon = jvbIcon('link');
}
- 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>';
+ $serviceName = $this->getServiceName($service);
+ $label = $parentAttrs && (!array_key_exists('className', $parentAttrs) || !str_contains($parentAttrs['className'], 'logos-only'))
+ ? sprintf(
+ '<span>%s</span>',
+ $serviceName
+ )
+ : sprintf(
+ '<span class="screen-reader-text">Find us on %s</span>',
+ $serviceName
+ );
+ $pillShaped = $parentAttrs && (array_key_exists('className', $parentAttrs) && str_contains($parentAttrs['className'], 'pill-shape'))
+ ? 'style="border-radius:var(--radius-outer);"'
+ : '';
+ return sprintf(
+ '<li><a href="%s" target="_blank" rel="nofollow" title="Find us on %s"%s>%s%s</a></li>',
+ $url,
+ $serviceName,
+ $pillShaped,
+ $icon,
+ $label
+ );
}
- public function render_core_social_links(array $block, string $content):string
+ private function getServiceName(string $service) {
+ return match($service){
+ 'wordpress' => 'WordPress',
+ default => ucfirst($service)
+ };
+ }
+ public function prerender_core_social_links(array $block, ?string $content, ?WP_Block $parent):?string
{
- return '<ul class="socials">'.$this->innerBlocks($block).'</ul>';
+// jvbDump($block['attrs']??[], 'social links');
+// jvbDump($parent, 'Parent');
+
+ return sprintf(
+ '<ul%s>%s</ul>',
+ $this->getClassesAndStyles($block['attrs']??[], ['socials']),
+ $this->innerBlocks($block, '','',$block)
+ );
}
//core_tag_cloud
+ public function prerender_core_tag_cloud(array $block, ?string $content, ?WP_Block $parent):?string
+ {
+// jvbDump($block, 'tag cloud');
+ $attrs = $block['attrs']??[];
+ $taxonomy = (array_key_exists('taxonomy', $attrs) && !empty($attrs['taxonomy']))
+ ? $attrs['taxonomy']
+ : 'post_tag';
+ $showCounts = $this->checkAttrs('showTagCounts', $attrs);
+
+ $terms = get_terms([
+ 'taxonomy' => $taxonomy,
+ 'hide_empty' => true,
+ ]);
+
+ if (!$terms || is_wp_error($terms)) {
+ return '';
+ }
+
+ $inside = '';
+
+ foreach ($terms as $term) {
+ $url = get_term_link($term->term_id, $taxonomy);
+ $count = $showCounts ?
+ sprintf(
+ '<span class="count">%d</span>',
+ $term->count
+ ) :
+ '';
+ $size = match(true) {
+ $term->count <= 2 => 'small',
+ $term->count <= 5 => 'x-small',
+ $term->count <= 10 => 'medium',
+ $term->count <= 15 => 'x-medium',
+ $term->count <= 20 => 'large',
+ $term->count <= 25 => 'x-large',
+ $term->count <= 30 => 'xx-large',
+ $term->count > 30 => 'xxx-large',
+ };
+ $fontSize = 'font-size: var(--txt-'.$size.');';
+ $inside .= sprintf(
+ '<li class="%s");"><a href="%s" rel="tag">%s%s</a></li>',
+ $size,
+// $fontSize,
+ $url,
+ $term->name,
+ $count
+ );
+ }
+ return sprintf(
+ '<ul%s>%s</ul>',
+ $this->getClassesAndStyles($attrs, ['term-list','cloud', jvbNoBase($taxonomy)]),
+ $inside
+ );
+ }
/**
@@ -918,62 +2440,51 @@
/***********************************
* 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 = '', ?array $parent = null):string
{
+ if ($parent) {
+ $parent = new WP_Block($parent);
+ }
$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;
+ $rendered = $parent
+ ? $this->checkMethods(null, $b, $parent, true)
+ : render_block($b);
+
+ $content .= sprintf('%s%s%s',
+ $before,
+ $rendered,
+ $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;
+ public function inside(array $block, mixed $tag = false, mixed $o = false): string
+ {
+ $html = $o ?: trim($block['innerHTML']);
+
+ if (empty($html)) {
+ return '';
}
- $len = strlen('</'.$tag.'>');
+ if (preg_match('/^<(\w+)[^>]*>(.*)<\/\1>$/s', $html, $matches)) {
+ if ($tag && strtolower($matches[1]) !== strtolower($tag)) {
+ return $html;
+ }
+ return trim($matches[2]);
+ }
- return substr_replace(
- str_replace(
- strtok($o, '>').'>',
- ' ',
- $o
- ),
- '',
- -$len,
- $len
- );
- }
+ return $html;
+ }
/**
* Extract content from a specific nested element
@@ -981,7 +2492,7 @@
* @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
+ public function extractElement(string $html, string $tag): string
{
if (empty($html)) {
return '';
@@ -1009,7 +2520,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'];
@@ -1115,30 +2626,64 @@
return $out;
}
- protected function getClassesAndStyles(
+ public 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 = trim($this->buildClassesString($attrs, $classes) . $this->buildStylesString($attrs,$styles) . $this->buildDataset($attrs));
return ($return=='')? '' : ' '.$return;
}
+ protected function buildStylesString(array $attrs, array $custom = []):string
+ {
+ $attr_styles = $this->getInlineStyles($attrs);
+ $styles = array_merge($attr_styles, $custom);
+
+ $styles = array_map(function ($property, $value) {
+ return sprintf('%s:%s', $property, $value);
+ }, array_keys($styles), $styles);
+
+ return !empty($styles) ? ' style="' . implode(';', $styles) . '"' : '';
+ }
+
+ protected function buildClassesString(array $attrs, array $custom = []):string
+ {
+ $attr_classes = $this->getClasses($attrs);
+ if(array_key_exists('slug', $attrs) && $attrs['slug'] === 'footer') {
+ $attr_classes[] = 'col';
+ }
+
+ // Merge with passed classes and styles
+ $classes = array_merge($attr_classes, $custom);
+
+ if (!empty(static::$pendingClass)) {
+ $classes = array_merge($classes, static::$pendingClass);
+ static::$pendingClass = [];
+ }
+ $classes = array_unique($classes);
+
+ // Build attribute strings
+ return !empty($classes) ? ' class="' . implode(' ', $classes) . '"' : '';
+ }
+
+ protected function buildDataset(array $attrs):string
+ {
+ $data = $this->getDataset($attrs);
+ $data_string = '';
+ if (!empty($data)) {
+ foreach ($data as $d => $v) {
+ if ($d === 'bg-small') {
+ $data_string .= ' data-bg-img';
+ }
+ $data_string .= sprintf(
+ ' data-%s="%s"',
+ $d,
+ $v
+ );
+ }
+ }
+ return $data_string;
+ }
/**
* @param string $spacing
*
@@ -1166,133 +2711,60 @@
$classes = [];
foreach ($attrs as $key => $value) {
$class = $this->getClass($key, $value, $attrs);
- if (is_array($class)) {
- $classes = array_merge($classes, $class);
- } else {
- $classes[] = $class;
- }
+ if (is_string($class)) {
+ $class = explode(' ', $class);
+ }
+ $classes = array_merge($classes, $class);
}
- return array_filter($classes, function ($class) {
+ return array_unique(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
{
+ //TODO: gradient
switch ($key) {
//Any additional classes the user adds
case 'className':
return match ($value) {
'is-style-floating' => 'always mobile fixed',
'is-style-fixed' => 'fixed bottom',
+ 'is-style-default' => '',
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);
+ return $this->getContentPosition($value);
+ case 'term':
+ case 'taxonomy':
+ return jvbNoBase($value);
//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);
+ return $this->getLayout($value, $attrs);
case 'align':
return !empty($value) ? 'align-'.$value : '';
case 'verticalAlignment':
- return !empty($value) ? 'v-align-'.$value : '';
- case 'isStackedMobile':
+ switch ($value) {
+ case 'bottom':
+ $value = 'btm';
+ break;
+ case 'center':
+ $value = 'y-mid';
+ default:
+ }
+ return !empty($value) ? $value : '';
+ case 'isStackedOnMobile':
return ($value === true) ? 'stack-small' : '';
case 'justifyContent':
return !empty($value) ? 'j-'.$value : '';
case 'orientation':
return $value==='column' ? 'column' : '';
case 'width':
+ return $this->getWidth($value);
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 '';
-
+ return $this->getDimRatio($value, $attrs);
+ case 'overlayColor':
+ return $value;
+ break;
//Typography
case 'textAlign':
return !empty($value) ? 'text-'.$value : '';
@@ -1301,177 +2773,307 @@
//Media
case 'hasParallax':
- return $value === true ? 'bg-parallax' : '';
+ return $value === true ? 'bg-fixed' : '';
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);
+ return $this->getPresetStyles($value);
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;
- }
+ case 'postLayout':
+ $classes[] = 'item-grid';
+ if (isset($attrs['columns']) && $attrs['columns']!== 3){
+ $classes[] = sprintf(
+ 'split-%d',
+ $attrs['columns']
+ );
}
- return '';
+ return $classes;
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)) {
+ if (JVB_TESTING && !is_admin() &&!in_array($key, $this->ignore)) {
// TESTING
-// jvbDump($key, 'getClass');
-// jvbDump($attrs);
+ jvbDump($attrs, '[getClass] '.$key);
}
return '';
}
}
+ /*** CLASS HELPERS ***/
+ private function getContentPosition(string $value):string
+ {
+ $classes = [];
+ $pos = explode(' ', $value);
+ foreach($pos as $p) {
+ switch ($p) {
+ case 'top':
+ $classes[] = 'top';
+ break;
+ case 'right':
+ $classes[] = 'right';
+ break;
+ case 'bottom':
+ $classes[] = 'btm';
+ break;
+ case 'left':
+ $classes[] = 'left';
+ break;
+ }
+ }
+ return implode(' ', $classes);
+ }
+
+ private function getLayout(array $value, array $attrs):array
+ {
+// jvbDump($value, 'getLayout');
+ $classes = [];
+
+ $type = 'row';
+ $isRow = true;
+
+ //Determine type
+ if ((array_key_exists('type', $value) && !in_array($value['type'], ['flex', 'grid', 'default'])) ||
+ (array_key_exists('orientation', $value) && $value['orientation'] === 'vertical')) {
+ $type = 'col';
+ $isRow = false;
+ } elseif (array_key_exists('type', $value) && $value['type'] === 'grid') {
+ $type = 'item-grid';
+ $isRow = false;
+ if (array_key_exists('columnCount', $value) && $value['columnCount']!== 3) {
+ $classes[] = sprintf(
+ 'split-%s',
+ $value['columnCount']
+ );
+ }
+ }
+
+ if (array_key_exists('justifyContent', $value) && !array_key_exists('contentPosition', $attrs)) {
+ switch ($value['justifyContent']) {
+ case 'right':
+ $classes[] = 'right';
+ break;
+ case 'center':
+ $classes[] = 'x-mid';
+ break;
+ case 'space-between':
+ $classes[] = 'x-btw';
+ break;
+ case 'left':
+ $classes[] = 'left';
+ break;
+ case 'space-evenly':
+ $classes[] = 'x-even';
+ break;
+ case 'space-around':
+ $classes[] = 'x-around';
+ break;
+ case 'stretch':
+ $classes[] = 'stretch';
+ }
+ } else {
+ $classes[] = 'left';
+ }
+
+ if (array_key_exists('verticalAlignment', $value)) {
+ switch ($value['verticalAlignment']) {
+ case 'bottom':
+ $classes[] = 'btm';
+ break;
+ case 'top':
+ $classes[] = 'top';
+ break;
+ case 'center':
+ $classes[] = 'y-mid';
+ break;
+ case 'space-between':
+ $classes[] = 'y-btw';
+ break;
+ case 'space-around':
+ $classes[] = 'y-around';
+ break;
+ case 'space-even':
+ $classes[] = 'y-even';
+ }
+ }
+
+
+
+ if (array_key_exists('flexWrap', $value)) {
+ if ($value['flexWrap'] === 'nowrap') {
+ $classes[] = 'nowrap';
+ }
+ }
+
+ $classes[] = $type;
+ return $classes;
+ }
+
+ private function getWidth(string $value):string
+ {
+
+ $value = str_replace('%', '', $value);
+
+ if (str_contains($value, 'px') ||
+ str_contains($value, 'em') ||
+ str_contains($value, 'rem') ||
+ str_contains($value, 'vw') ||
+ str_contains($value, 'vh')) {
+ return '';
+ }
+
+ return sprintf(
+ 'width-%d',
+ match (true) {
+ $value <= 25 => '25',
+ $value <= 33 => '33',
+ $value <= 50 => '50',
+ $value <= 66 => '66',
+ $value <= 75 => '75',
+ default => 'full',
+ }
+ );
+ }
+ private function getDimRatio(string $value, array $attrs):string
+ {
+ if (array_key_exists('overlayColor', $attrs)) {
+ return '';
+ }
+ if (is_numeric($value)) {
+ return sprintf(
+ 'op-%d',
+ match (true) {
+ $value <= 14 => '1',
+ $value <= 28 => '2',
+ $value <= 42 => '3',
+ $value <= 56 => '45',
+ $value <= 70 => '4',
+ $value <= 84 => '5',
+ default => '6',
+ }
+ );
+ }
+ return '';
+ }
+
+ private function getPresetStyles(array $value):string
+ {
+ $classes = [];
+ //Margin and Padding
+ if (array_key_exists('spacing', $value)) {
+ $classes = array_merge($classes, $this->buildSpacingClasses($value));
+ }
+ if (array_key_exists('color', $value)) {
+ if (array_key_exists('duotone', $value['color'])) {
+ $preset = explode('|', $value['color']['duotone']);
+ $preset = $preset[array_key_last($preset)];
+ $preset = $this->getColor($preset, false);
+
+ if (str_contains($preset, '-')) {
+ $preset = explode('-', $preset);
+ } else {
+ $preset = [$preset];
+ }
+ $classes[] = 'duotone';
+ foreach ($preset as $p) {
+ $classes[] = $p;
+ }
+ }
+ }
+
+ 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);
+ }
+ private function buildSpacingClasses(array $value):array
+ {
+ $classes = [];
+ 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;
+ }
+ }
+ }
+ }
+ return $classes;
+ }
protected function getInlineStyles(array $attrs):array
{
@@ -1481,258 +3083,83 @@
$styles = [];
foreach ($attrs as $key => $value) {
$style = $this->getStyle($key, $value, $attrs);
+
$styles = array_merge($styles, $style);
+ $styles = array_unique($styles);
}
return $styles;
}
- protected function getStyle(string $key, string|bool|array|int $value, array $attrs):array
+ protected function getStyle(string $key, string|bool|array|int $value, array $attrs):array|string
{
$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;
+ case 'size':
+ return $this->getIconSizeStyle($value);
+
+ case 'fontFamily':
+ return $this->getFontFamilyStyle($value);
// Icon color (for icon blocks)
case 'iconColorValue':
- if (!empty($value)) {
- $styles[] = 'color: '.$value;
- }
- break;
+ return $this->getColorStyle($value);
// 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;
+ return $this->getMinHeightStyle($value, $attrs);
// Background URL (for cover, media blocks)
case 'url':
if (!empty($value) && str_starts_with($value, 'http')) {
- $styles[] = 'background-image: url('.$value.')';
+ return ['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;
+ return $this->getFocalPointStyle($value, $attrs);
// Complex style object
case 'style':
- // Border styles
- if (isset($value['border'])) {
- $border = $value['border'];
+ return $this->extractStyles($value, $attrs);
- 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;
+ return $this->getDimRatioStyle($value, $attrs);
// 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'].')';
+ return ['background-image' => 'url('.$attrs['backgroundUrl'].')'];
}
break;
+ case 'width':
+ if (str_contains($value, 'px') ||
+ str_contains($value, 'em') ||
+ str_contains($value, 'rem') ||
+ str_contains($value, 'vw') ||
+ str_contains($value, 'vh')) {
+ return ['width' => $value];
+ }
+ 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.')';
- }
+ $type = str_replace('Color', '-color', $key);
+ $type = str_replace('text-', '', $type);
+ if ($key === 'borderColor' && isset($attrs['border']['width'])) {
+ break;
}
- if ($continue) {
- $styles[] = $type.$value;
- }
- break;
+ return $this->getColorStyle($value, $type);
+
// 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)) {
+ if (JVB_TESTING && !is_admin() && !in_array($key, $this->ignore)) {
//TESTING
-// jvbDump($key, 'getStyle');
-// jvbDump($attrs);
+ jvbDump($attrs, '[getStyle] '.$key);
}
// No default inline styles
break;
@@ -1740,6 +3167,419 @@
return $styles;
}
+ private function getIconSizeStyle(string $value):array
+ {
+ $values = explode(' ', $value);
+ $styles = [];
+ foreach ($values as $v) {
+ switch ($value) {
+ case 'has-small-icon-size':
+ $styles['--w'] = 'var(--txt-x-small)';
+ break;
+ case 'has-large-icon-size':
+ $styles['--w'] = 'var(--txt-large)';
+ break;
+ case 'has-huge-icon-size':
+ $styles['--w'] = 'var(--txt-xx-large)';
+ break;
+ default:
+ if (JVB_TESTING) {
+ jvbDump($value, 'No preset found for size: '.print_r($value, true));
+ }
+ }
+ }
+ return $styles;
+ }
+ private function getFontFamilyStyle(string $value):array
+ {
+ return match($value) {
+ 'body' => ['font-family' => 'var(--body)'],
+ 'heading' => ['font-family' => 'var(--heading)'],
+ default => ['font-family' => $value]
+ };
+ }
+ private function getColorStyle(string $value, ?string $type = 'color'):array
+ {
+ if (!in_array($type, ['color', 'background','background-color','border-color'])) {
+ $type = null;
+ }
+ if (!$type) {
+ return [];
+ }
+ return [
+ $type => $this->getColor($value)
+ ];
+ }
+ private function getMinHeightStyle(string $value, array $attrs):array
+ {
+ $out = [];
+ if (!empty($value)) {
+ if (isset($attrs['minHeightUnit'])) {
+ $out['min-height'] = sprintf('%s%s', $value, $attrs['minHeightUnit']);
+
+ } else {
+ $out['min-height'] = sprintf(
+ '%spx',
+ $value
+ );
+ }
+ }
+ return $out;
+ }
+
+ private function getFocalPointStyle(array $value, array $attrs):array
+ {
+ $x = array_key_exists('x', $value) ? ($value['x'] * 100).'%' : 'center';
+ $y = array_key_exists('y', $value) ? ($value['y'] * 100).'%' : 'center';
+
+ $y = $x === $y ? '' : ' '.$y;
+
+ $key = array_key_exists('isObjectPosition', $attrs) ? 'object-position' : 'background-position';
+ return [
+ $key => sprintf(
+ '%s%s',
+ $x,
+ $y
+ )];
+ }
+
+ private function extractStyles(array $value, array $attrs):array
+ {
+ $styles = [];
+ foreach ($value as $k => $v) {
+ switch ($k) {
+ case 'border':
+ $styles = array_merge($styles, $this->getBorderStyle($v, $attrs));
+ break;
+
+ case 'color':
+ if (isset($v['background'])) {
+ $styles['background-color'] = $this->getColor($v['background']);
+ }
+ if (isset($v['text'])) {
+ $styles['color'] = $this->getColor($v['text']);
+ }
+ if (isset($v['gradient'])) {
+
+ if (JVB_TESTING) {
+ jvbDump($v, 'Gradient');
+ }
+ }
+ break;
+
+ case 'layout':
+ $styles = array_merge($styles, $this->getLayoutStyle($v, $attrs));
+ break;
+
+ case 'typography':
+ $styles = array_merge($styles, $this->getTypographyStyle($v, $attrs));
+ break;
+
+ case 'spacing':
+ if (isset($v['blockGap'])) {
+ if (is_array($v['blockGap'])) {
+ $inner = [];
+ foreach ($v['blockGap'] as $gap) {
+ $inner[] = sprintf(
+ 'var(--sp%s)',
+ $this->getPresetSpacing($gap)
+ );
+ }
+ if (!empty($inner)) {
+ $styles['--gap'] = sprintf(
+ '%s',
+ implode(' ', $inner)
+ );
+ }
+ } else {
+ $styles['--gap'] = 'var(--sp'.$this->getPresetSpacing($v['blockGap']).')';
+ }
+ }
+
+ // 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($v['margin'])) {
+ foreach ($v['margin'] as $direction => $size) {
+ if (!str_contains($size, 'var:preset')) {
+ $styles['margin-'.$direction] = $size;
+ }
+ }
+ }
+
+ if (isset($v['padding'])) {
+ foreach($v['padding'] as $dir => $size) {
+ if (!str_contains($size, 'var:preset')) {
+ $styles['padding-'.$dir] = $size;
+ }
+ }
+ }
+ break;
+
+ case 'background':
+ if (array_key_exists('backgroundImage', $v)) {
+ $data = Image::getData($v['backgroundImage']['id']);
+ if (!empty($data) && array_key_exists('tiny', $data)) {
+ $styles['background-image'] = sprintf(
+ 'url(%s)',
+ $data['tiny']
+ );
+ }
+
+ }
+ break;
+
+ case 'dimensions':
+ foreach ($v as $sk => $sv) {
+ if ($sk === 'minHeight') {
+ $styles['min-height'] = $sv;
+ } else {
+ if (JVB_TESTING) {
+ jvbDump('No config set for dimension '.$sk.': '.print_r($sv, true));
+ }
+ }
+ }
+
+
+ break;
+
+ case 'elements':
+ if (!empty($v)) {
+ // Generate a unique class tied to this block instance
+ $uid = 'b-'.substr(md5(serialize($attrs)), 0, 8);
+ $this->extractElementStyles($v, $uid);
+ // We need the uid added as a class — store it for getClassesAndStyles to pick up
+ static::$pendingClass[] = $uid;
+ }
+ break;
+
+ default:
+ if (JVB_TESTING) {
+ jvbDump($v,'No config set for '.$k.': ');
+ }
+
+ }
+ }
+
+ return $styles;
+ }
+ private function getBorderStyle(array $border, array $attrs):array
+ {
+ $styles = [];
+
+ if (isset($border['radius'])) {
+ $styles['border-radius'] = $border['radius'];
+ }
+
+ if (isset($border['width']) && (isset($attrs['borderColor']) || isset($border['color']))) {
+ $st = $border['style'] ?? 'solid';
+ $color = $border['color']??$attrs['borderColor'];
+ $styles['border'] = sprintf(
+ '%s %s %s',
+ $border['width'],
+ $st,
+ $this->getColor($color)
+ );
+ } else {
+ if (isset($border['color'])) {
+ $styles['border-color'] = $border['color'];
+ }
+ if (isset($border['width'])) {
+ $styles['border-width'] = $border['width'];
+ }
+ if (isset($border['style'])) {
+ $styles['border-style'] = $border['style'];
+ }
+ }
+ if (JVB_TESTING) {
+ unset($border['radius']);
+ unset($border['width']);
+ unset($border['style']);
+ unset($border['color']);
+ if (!empty($border)) {
+ jvbDump($border, '[getBorderStyle] Leftover styles:');
+ }
+ }
+
+ return $styles;
+ }
+
+ private function getLayoutStyle(array $layout, array $attrs):array
+ {
+ $styles = [];
+// jvbDump($layout);
+
+ foreach ($layout as $l => $option) {
+ switch ($l) {
+ case 'selfStretch':
+ if ($option === 'fixed' && isset($layout['selfStretchValue'])) {
+ $styles['width'] = $layout['selfStretchValue'];
+ } elseif ($option === 'fill') {
+ $styles['flex'] = 1;
+ }
+ break;
+ default:
+ $ignore = [
+ 'selfStretchValue',
+ 'flexSize',
+ ];
+ if (JVB_TESTING && !in_array($l, $ignore)) {
+ jvbDump($l, 'No layout style set for: ');
+ }
+// case 'type':
+// if ($option === 'grid' && $value['layout']['columnCount'] !== 3) {
+// $styles[] = sprintf(
+// 'grid-template-columns: repeat(1fr, %s)',
+// $value['layout']['columnCount']
+// );
+// }
+// break;
+ }
+ }
+ return $styles;
+ }
+
+ private function getTypographyStyle(array $typography, array $attrs):array
+ {
+ $styles = [];
+ foreach ($typography as $property => $value) {
+ switch ($property) {
+ case 'fontSize':
+ $styles['font-size'] = $value;
+ break;
+ case 'fontWeight':
+ $styles['font-weight'] = $value;
+ break;
+ case 'textDecoration':
+ $styles['text-decoration'] = $value;
+ break;
+ case 'textTransform':
+ $styles['text-transform'] = $value;
+ break;
+ case 'letterSpacing':
+ $styles['letter-spacing'] = $value;
+ break;
+ case 'lineHeight':
+ $styles['line-height'] = $value;
+ break;
+ case 'fontStyle':
+ $styles['font-style'] = $value;
+ break;
+ case 'writingMode':
+ $styles['writing-mode'] = $value;
+ break;
+ case 'textAlign':
+ $styles['text-align'] = $value;
+ break;
+ default:
+ if (JVB_TESTING) {
+ jvbDump($value,'[getTypographyStyle] No property set for '.$property.': ');
+ }
+ }
+ }
+
+ return $styles;
+ }
+ private function extractElementStyles(array $elements, string $uid):void
+ {
+ foreach ($elements as $element => $states) {
+ $selector = match($element) {
+ 'link' => "a",
+ 'heading' => "h1,h2,h3,h4,h5,h6",
+ 'button' => "button,.button",
+ default => $element,
+ };
+
+ $selectors = explode(',',$selector);
+ foreach ($states as $state => $rules) {
+ $css = [];
+
+ $fullSelector = array_map(function($sel) use ($uid, $state) {
+ return str_starts_with($state, ':')
+ ? ".{$uid} {$sel}{$state}"
+ : ".{$uid} {$sel}";
+ }, $selectors);
+ $fullSelector = implode(',', $fullSelector);
+
+
+ if (JVB_TESTING) {
+ jvbDump($state, 'state');
+ jvbDump($rules, 'rules');
+ }
+
+ if (isset($rules['color']['text']) || isset($rules['text'])) {
+ $css['color'] = $this->getColor($rules['color']['text'] ?? $rules['text']);
+ }
+ if (isset($rules['color']['background']) || isset($rules['background'])) {
+ $css['background-color'] = $this->getColor($rules['color']['background']??$rules['background']);
+ }
+ //clean out possible empty values
+ $css = array_filter($css);
+
+ $css = array_map(function ($property, $value) {
+ return $property.': '.$value;
+ }, array_keys($css), $css);
+
+ if (!empty($css)) {
+ static::$pendingStyles[] = $fullSelector.' { '.implode('; ', $css).' }';
+ }
+ }
+ }
+ }
+
+ public function maybeOutputCustomStyles(): string
+ {
+ if (empty(static::$pendingStyles)) return '';
+ $out = '<style>'.implode(' ', static::$pendingStyles).'</style>';
+ static::$pendingStyles = [];
+ return $out;
+ }
+
+ private function getDimRatioStyle(int $value, array $attrs):array
+ {
+ //TODO: This likely isn't working correctly
+// jvbDump($value, 'dimRatio');
+// jvbDump($attrs, 'dimRatio attrs');
+ $ratio = [];
+
+ $s = array_key_exists('overlayColor', $attrs) ? 'var(--'.$attrs['overlayColor'].')' : 'var(--base)';
+ $s = 'rgba('.$s.', ';
+ if ($value <= 14) {
+ $s .= 'var(--op-1))';
+ } elseif ($value <= 28) {
+ $s .= 'var(--op-2))';
+ } elseif ($value <= 42) {
+ $s .= 'var(--op-3))';
+ } elseif ($value <= 56) {
+ $s .= 'var(--op-45))';
+ } elseif ($value <= 70) {
+ $s .= 'var(--op-4))';
+ } elseif ($value <= 84) {
+ $s .= 'var(--op-5))';
+ } else {
+ $s .= 'var(--op-6))';
+ }
+
+ return ['background-color' => $s];
+ }
+
+ protected function getDataset(array $attrs):array
+ {
+ $dataset = [];
+ if (array_key_exists('style', $attrs)) {
+ if (array_key_exists('background', $attrs['style'])){
+ if (array_key_exists('backgroundImage', $attrs['style']['background'])) {
+ $id = $attrs['style']['background']['backgroundImage']['id']??false;
+ if ($id) {
+ $data = Image::getData($id);
+ $dataset['bg-small'] = $data['small'];
+ $dataset['bg-med'] = $data['medium'];
+ $dataset['bg-large'] = $data['large'];
+ }
+ }
+ }
+ }
+ return $dataset;
+ }
public function formatImage(int $ID = 0, string $start = 'tiny', string $replace = 'large'):string
{
@@ -1752,4 +3592,32 @@
return jvbFormatImage($ID, $start, $replace);
}
+ protected function checkAttrs(string $test, array $attrs):bool
+ {
+ return array_key_exists($test, $attrs) && $attrs[$test]===true;
+ }
+
+ protected function getColor(string $value, bool $prefix = true):string
+ {
+ $defaults = apply_filters('jvbColours', ['base', 'contrast', 'action', 'secondary']);
+ foreach ($defaults as $default) {
+ if (str_starts_with($value, $default)) {
+ return $prefix ? 'var(--'.$value.')' : $value;
+ }
+ }
+ //We removed the presets
+ if (str_contains($value, 'var:preset')) {
+ return '';
+ }
+ return $value;
+ }
+
+ protected function counter(string $key):void
+ {
+ if (!array_key_exists($key, static::$counters)) {
+ static::$counters[$key] = 1;
+ } else {
+ static::$counters[$key]++;
+ }
+ }
}
--
Gitblit v1.10.0