From 32ce5c542c90be348895328078e392dde2052957 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 04 Sep 2026 19:15:16 +0000
Subject: [PATCH] =Working at adding defined fields to a dedicated panel in the gutenberg editor. Registrar now has AddCustomFields.php, which currently only supports post fields. Added a @wordpress/plugins base _meta/fields in /build and /src that successfully creates a panel. Next steps: determine sections and fields, group fields by section (each section to a panel), and save output

---
 inc/blocks/MenuBlock.php |  307 +++++++++++++++++++++++++++++++++++++++++++++++---
 1 files changed, 284 insertions(+), 23 deletions(-)

diff --git a/inc/blocks/MenuBlock.php b/inc/blocks/MenuBlock.php
index 11a61a3..b1d7347 100644
--- a/inc/blocks/MenuBlock.php
+++ b/inc/blocks/MenuBlock.php
@@ -1,14 +1,12 @@
 <?php
 namespace JVBase\blocks;
 
-use JVBase\base\Options;
 use JVBase\base\Site;
 use JVBase\managers\Cache;
-use JVBase\forms\TaxonomySelector;
-use JVBase\meta\Form;
 use JVBase\meta\Meta;
-use JVBase\meta\Render;
+use JVBase\registrar\Registrar;
 use WP_Block;
+use WP_Post;
 use WP_Query;
 
 if (!defined('ABSPATH')) {
@@ -32,15 +30,29 @@
 
     public function __construct()
     {
-        $this->cache = Cache::for('menu', WEEK_IN_SECONDS)->connect('post');
+        $this->cache = Cache::for('menu', 'post', WEEK_IN_SECONDS);
 		if (Site::hasIntegration('square')) {
 			$this->base = '_square_';
 		} else if (Site::hasIntegration('helcim')) {
 			$this->base = '_helcim_';
 		}
         add_action('init', [ $this, 'registerBlock' ]);
+		add_action('save_post', [$this, 'flushCache'], 10, 2);
+		add_action('wp_footer', [$this, 'renderGallery']);
     }
 
+	public function flushCache(int $postID, WP_Post $post):void
+	{
+		if (defined('DOING_AUTOSAVE') && DOING_AUTOSAVE) return;
+		if (wp_is_post_revision($postID) || wp_is_post_autosave($postID)) return;
+
+		$postType = $post->post_type;
+		if ($postType !== 'product' && !in_array($postType, Registrar::getFoodMenuTypes())) {
+			return;
+		}
+		error_log('Flushing food menu block cache');
+		$this->cache->flush();
+	}
     public function registerBlock():void
     {
         register_block_type($this->path, [
@@ -50,9 +62,12 @@
 
 	protected function buildParams():void
 	{
-		if (is_post_type_archive(BASE.'menu_item')) {
+//		if (is_post_type_archive(BASE.'menu_item') ||
+		if (is_post_type_archive('product') ||
+			(function_exists('is_shop') && is_shop())) {
 			$this->params = ['all'];
-		} elseif (is_tax(BASE.'section')) {
+//		} elseif (is_tax(BASE.'section')) {
+		} elseif (is_tax('product_cat')) {
 			$this->params = [];
 		} else {
 			$this->params = [];
@@ -66,10 +81,8 @@
 			return '';
 		}
 		jvbInlineStyles('forms');
-		$key = $this->cache->generateKey($this->params);
-		$this->cache->flush();
 		return $this->cache->remember(
-			$key,
+			$this->params,
 			function() {
 				ob_start();
 				$this->renderBlock();
@@ -85,6 +98,23 @@
         $this->renderOnThisPage();
     }
 
+	protected function termHasPosts(int $termID):bool
+	{
+		$query = new WP_Query([
+			'post_type'	=> 'product',
+			'post_status'	=> 'publish',
+			'posts_per_page'	=> 1,
+			'fields'	=> 'ids',
+			'tax_query' => [
+				[
+					'taxonomy' => 'product_cat',
+					'terms'	=> $termID
+				]
+			]
+		]);
+		return $query->have_posts();
+	}
+
     protected function renderHeader():void
     {
 		$title = 'Our Menu';
@@ -95,11 +125,13 @@
         ?>
         <header id="top">
             <h1><?=$title?></h1>
-			<nav class="menu"><ul>
+        </header>
+		<nav class="menu"><ul>
 				<?php
 				$sections = $this->getSections();
 				foreach ($sections as $section) {
-					if (!jvbTermHasPosts($section['id'], BASE.'section')) {
+//					if (!jvbTermHasPosts($section['id'], BASE.'section')) {
+					if (!$this->termHasPosts($section['id'])) {
 						continue;
 					}
 					?>
@@ -108,18 +140,13 @@
 				}
 				?>
 			</ul></nav>
-        </header>
         <?php
     }
 
 	protected function getSections():array
 	{
 		if (!$this->sections) {
-			$sections = Options::get('menu_section_order');
-			if (!is_array($sections)) {
-				$sections = [];
-			}
-			$this->sections = $sections;
+			$this->sections = aneb_menu_sections();
 		}
 
 		return $this->sections;
@@ -129,13 +156,18 @@
     {
 		$sections = $this->getSections();
 		foreach ($sections as $section) {
+			if($section['name'] === 'Uncategorized') {
+				continue;
+			}
 			$items = new WP_Query([
-				'post_type' => BASE.'menu_item',
+				'post_type' => 'product',
+//				'post_type' => BASE.'menu_item',
 				'order' => 'ASC',
 				'posts_per_page' => -1,
 				'tax_query' => [
 					[
-						'taxonomy' => BASE.'section',
+						'taxonomy' => 'product_cat',
+//						'taxonomy' => BASE.'section',
 						'terms' => (int) $section['id'],
 					]
 				],
@@ -162,7 +194,227 @@
 		}
     }
 
-	protected function renderMenuItem(int $ID, string $slug, string $postType = 'menu_item') {
+	/**
+	 * Renders from WooCommerce product
+	 * @param int $ID
+	 * @param string $slug
+	 * @param string $postType
+	 * @return void
+	 */
+	protected function renderMenuItem(int $ID, string $slug, string $postType = 'product'):void {
+		$meta = Meta::forPost($ID);
+
+		$fields = $meta->getAll();
+
+
+
+		$excerpt = get_the_excerpt($ID);
+		$isVariable = !empty($fields['variations']??[]);
+
+
+		$product = wc_get_product($ID);
+		$isFeatured = $product->is_featured();
+		$isOnSale = $product->is_on_sale();
+		$isPurchasable = $product->is_purchasable() && $product->is_in_stock();
+
+		$priceRange = $this->getPrice($fields, $isOnSale, $isPurchasable);
+
+		if ($isVariable) {
+			$prices = array_map('floatval', array_column($fields['variations'], 'price'));
+			$min = min($prices);
+			$max = max($prices);
+			$priceRange = $min===$max ? jvbCurrency($min) : jvbCurrency($min) .' - '.jvbCurrency($max);
+		}
+
+//		if (in_array($fields['post_title'],  ['Chopped Brisket Sandwich', 'Breakfast Poutine'])) {
+//			jvbDump ($fields);
+//		}
+
+		$gallery = '';
+		if (!empty($fields['gallery_image_ids'])) {
+			$gallery = implode(array_map(function($id) use ($fields){
+				$id = absint($id);
+				$caption = wp_get_attachment_caption($id);
+				return sprintf(
+					'<figure>%s<figcaption>%s</figcaption></figure>',
+					jvbFormatImage($id,'tiny','medium',true,'menu'),
+					empty($caption) ? $fields['post_title'] : $caption
+				);
+			}, $fields['gallery_image_ids']));
+		}
+
+		$caption = '';
+		if (!empty($fields['image_id'])) {
+			$caption = wp_get_attachment_caption((int)$fields['image_id']);
+			$caption = empty($caption) ? $fields['post_title'] : $caption;
+		}
+
+
+		$out = sprintf(
+	'<div id="%s" class="menu-item%s" data-section="%s" data-label="%s" data-id="%s">
+				<div class="tags abs top left row">%s%s</div>
+				<div class="header row x-btw">
+					<figure>%s<figcaption>%s</figcaption></figure>
+					<div class="text row x-btw">
+						<h3>%s</h3>
+						<p class="price">%s</p>
+					</div>
+				</div>
+				<div class="gallery row left%s">%s</div>
+				<div class="description">%s</div>
+				<div class="info col right">
+				%s',
+			sanitize_title($fields['post_title']),
+			$isVariable ? ' variable' : '',
+			$slug,
+			$fields['post_title'],
+			$ID,
+			$isOnSale ? '<span class="sale row left top">'.jvbIcon('tag-chevron').'Sale!</span>' : '',
+			$isFeatured ? '<span class="featured row top">'.jvbIcon('star').'We Recommend</span>' : '',
+			!empty($fields['image_id']) ? jvbFormatImage((int)$fields['image_id'],'tiny','medium',true,'menu') : '',
+			$caption,
+			$fields['post_title'],
+			$priceRange,
+			empty($gallery) ? '' : ' has',
+			$gallery,
+			$excerpt,
+			$this->getSaleText($fields, $isPurchasable)
+		);
+
+		if (!empty($fields['variations']??[])) {
+			foreach ($fields['variations'] as $index => $row) {
+				$v = wc_get_product($row['id']);
+				$isOnSale = $v->is_on_sale();
+				$isPurchasable = $v->is_purchasable() && $v->is_in_stock();
+				$price = $this->getPrice($row, $isOnSale, $isPurchasable);
+
+
+				$title = str_replace($fields['post_title'].' - ','',$row['post_title']);
+				$out .= sprintf(
+				'<div class="row right top nowrap">
+						%s
+						<figure>%s<figcaption>%s</figcaption></figure>
+						<div>
+							<h4>%s — %s</h4>
+							%s
+						</div><a %shref="?add-to-cart=%d&variation_id=%d"
+   data-quantity="1"
+   data-product_id="%d"
+   data-variation_id="%d"
+   class="btn chip add_to_cart_button ajax_add_to_cart add-to-cart">
+   %s
+   <span>Add to Cart</span>
+</a>%s', //Note: we had 3 %s%s%s before the last </div> last time. This was to add optional ingredients or preparation time duration, not default in WooCommerce
+				$isOnSale ? '<span class="abs top right">'.jvbIcon('tag-chevron').'Sale!</span>' : '',
+						!empty($row['image_id']) ? jvbFormatImage($row['image_id'], 'tiny', 'medium') : '',
+						empty($row['post_title']) ? $fields['post_title'] : $row['post_title'],
+						empty($title) ? $fields['post_title'] : $title,
+						$price,
+						empty($row['description']) ? '' : wpautop($row['description']),
+						$isPurchasable ? '' : 'class="disabled" ',
+						$ID,
+						$row['id'],
+						$ID,
+						$row['id'],
+						jvbIcon('plus-square'),
+						$this->getSaleText($row, $isPurchasable)
+				);
+
+//				$out .= Form::render(
+//					$row['id'],
+//					'',
+//					[
+//						'type'	=> 'quantity',
+//						'label'	=> $row['title'],
+//						'min'	=> 0,
+//						'max'	=> 30,
+//						'data'	=> [
+//							'price'	=> $row['price'],
+//							'id'	=> $row['id'],
+//							'sku'	=> $row['sku'],
+//							'name'	=> $row['title'],
+//							'add-to-cart'	=> ''
+//						],
+//						'add'	=> 'Add 1 '.$row['title'].' to cart',
+//						'remove'	=> 'Remove 1 '.$row['title']. ' from cart',
+//					]
+//				);
+				$out .= '</div>';
+			}
+		} else {
+			$isPurchasable = $product->is_purchasable() && $product->is_in_stock();
+			$out .= sprintf(
+				'<a href="?add-to-cart=%d"
+				   data-quantity="1"
+				   data-product_id="%d"
+				   class="%sbtn chip add_to_cart_button ajax_add_to_cart add-to-cart">
+				   %s
+   					<span>Add to Cart</span>
+				</a>',
+				$ID,
+				$ID,
+				$isPurchasable ? '' : 'disabled ',
+				jvbIcon('plus-square')
+			);
+//			$out .= Form::render(
+//				$ID,
+//				'',
+//				[
+//					'type'	=> 'quantity',
+//					'label'	=> 'Add to Order',
+//					'min'	=> 0,
+//					'max'	=> 30,
+//					'step'	=> 1,
+//					'data'	=> [
+//						'price'	=> $fields['_price'],
+//						'id'	=> $ID,
+//						'name'	=> $fields['post_title'],
+//						'step'	=> 1,
+//						'add-to-cart'	=> '',
+//					],
+//					'add'	=> 'Add 1 '.$fields['post_title'].' to cart',
+//					'remove'	=> 'Remove 1 '.$fields['title']. ' from cart',
+//				]
+//			);
+		}
+
+		$out .= '</div></div>';
+
+		echo $out;
+
+	}
+
+	protected function getPrice(array $item, bool $isOnSale, bool $isPurchasable):string
+	{
+		$price = jvbCurrency($item['regular_price']);
+		$price = $isOnSale ? '<s> '.$price.' </s> '.jvbCurrency($item['sale_price']) : $price;
+
+		return $price;
+	}
+
+	protected function getSaleText(array $item, bool $isPurchasable):string
+	{
+		if (empty($item['date_on_sale_to']) && $isPurchasable) {
+			return '';
+		}
+
+		$out = sprintf(
+			'<span class="sale-until">%sOn sale until <time datetime="%s">%s</time>.</span>',
+			jvbIcon('tag-chevron'),
+			$item['date_on_sale_to']->format('c'),
+			$item['date_on_sale_to']->format('l, F j, Y')
+		);
+
+		if (!$isPurchasable) {
+			$out .= sprintf(
+				'<span class="out-of-stock">%sTemporarily Out of Stock</span>',
+				jvbIcon('warning-circle')
+			);
+		}
+		return $out;
+	}
+	/**
+	protected function renderMenuItemOLD(int $ID, string $slug, string $postType = 'menu_item') {
 		$meta = Meta::forPost($ID);
 		$fields = $meta->getAll();
 		$values = $meta->getAll([
@@ -188,8 +440,10 @@
 					$prices[] = (float) $row[$this->base.'price'];
 				}
 			}
-			if (!empty($prices)) {
+			if (!empty($prices) && (min($prices) !== max($prices))) {
 				$priceRange = jvbCurrency(min($prices)).' - '.jvbCurrency(max($prices));
+			} elseif (!empty($prices)) {
+				$priceRange = jvbCurrency(max($prices));
 			}
 		}
 
@@ -215,6 +469,7 @@
 				if (empty($variations)) {
 					echo Form::render(
 						$ID.'|cart_quantity',
+						'',
 						[
 							'type'	=> 'quantity',
 							'label'	=> 'Add to Order',
@@ -275,7 +530,8 @@
 
 		</div>
 		<?php
-	}
+	} **/
+
 
 
     protected function renderOnThisPage():void
@@ -285,4 +541,9 @@
         }
         echo jvbOnThisPage(array_keys($this->details));
     }
+
+	public function renderGallery():void
+	{
+		jvbRenderGallery();
+	}
 }

--
Gitblit v1.10.0