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 | 393 ++++++++++++++++++++++++++++++++++++++++++++++++--------
1 files changed, 337 insertions(+), 56 deletions(-)
diff --git a/inc/blocks/MenuBlock.php b/inc/blocks/MenuBlock.php
index 89b9c60..b1d7347 100644
--- a/inc/blocks/MenuBlock.php
+++ b/inc/blocks/MenuBlock.php
@@ -1,12 +1,12 @@
<?php
namespace JVBase\blocks;
+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')) {
@@ -26,14 +26,34 @@
protected string $detailsTitle;
protected array $details;
protected array|false $sections = false;
+ protected string $base;
public function __construct()
{
- $this->cache = Cache::for('menu', WEEK_IN_SECONDS)->connectTo('post', 'menu_item');
+ $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 registerBlock()
+ 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, [
'render_callback' => [ $this, 'render' ]
@@ -42,10 +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')) {
- jvbDump(get_queried_object());
+// } elseif (is_tax(BASE.'section')) {
+ } elseif (is_tax('product_cat')) {
$this->params = [];
} else {
$this->params = [];
@@ -58,9 +80,9 @@
if (empty($this->params)) {
return '';
}
- $key = $this->cache->generateKey($this->params);
+ jvbInlineStyles('forms');
return $this->cache->remember(
- $key,
+ $this->params,
function() {
ob_start();
$this->renderBlock();
@@ -76,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';
@@ -86,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;
}
?>
@@ -99,19 +140,13 @@
}
?>
</ul></nav>
- </header>
<?php
}
protected function getSections():array
{
if (!$this->sections) {
- $options = Meta::forOptions('options');
- $sections = $options->get('menu_section_order');
- if (!is_array($sections)) {
- $sections = [];
- }
- $this->sections = $sections;
+ $this->sections = aneb_menu_sections();
}
return $this->sections;
@@ -121,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'],
]
],
@@ -154,46 +194,269 @@
}
}
- 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([
'post_title',
- '_square_catalog_id',
- 'price',
- 'product_variations',
+ $this->base.'item_id',
+ $this->base.'price',
+ $this->base.'product_variations',
'max_order',
'min_order',
'step'
]);
- $priceRange = jvbCurrency($values['price']);
- $variations = $values['product_variations'];
+ $priceRange = jvbCurrency($values[$this->base.'price']);
+ $variations = $values[$this->base.'product_variations'];
- if(!empty($variations) && $variations[0]['name'] === '') {
+ if(!empty($variations) && $variations[0][$this->base.'name'] === '') {
$variations = [];
}
if (!empty($variations)) {
$prices = [];
foreach ($variations as $row) {
- if (isset($row['price']) && is_numeric($row['price'])) {
- $prices[] = (float) $row['price'];
+ if (isset($row[$this->base.'price']) && is_numeric($row[$this->base.'price'])) {
+ $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));
}
}
- $max = $values['max_order'];
- $min = $values['min_order'];
- $step = $values['step'];
+ $max = $values['max_order']??'';
+ $min = $values['min_order']??'';
+ $step = $values['step']??'';
$max = ($max === '') ? 50 : $max;
$min = ($min === '') ? 0 : $min;
$step = ($step === '') ? 1 : $step;
?>
- <div class="menu-item<?= !empty($variations) ? ' variable' : '' ?>" data-section="<?=$slug?>">
+ <div class="menu-item<?= !empty($variations) ? ' variable' : '' ?>" data-section="<?=$slug?>" data-label="<?=$values['post_title']?>" data-id="<?=$ID?>">
<div class="header row x-btw">
<h3><?= $values['post_title']?></h3>
<p class="price"><?= $priceRange ?></p>
@@ -201,52 +464,64 @@
<div class="description">
<?= Render::renderFrom($meta, 'post_excerpt')?>
</div>
- <div class="info row right">
+ <div class="info col right">
<?php
if (empty($variations)) {
- Form::renderFrom($meta,
+ echo Form::render(
$ID.'|cart_quantity',
+ '',
[
- 'type' => 'number',
+ 'type' => 'quantity',
'label' => 'Add to Order',
'min' => $min,
'max' => $max,
'step' => $step,
'data' => [
- 'price' => $values['price'],
+ 'price' => $values[$this->base.'price'],
'id' => $ID,
- 'square-catalog-id' => $values['_square_catalog_id'],
+ 'catalog-id' => $values[$this->base.'item_id'],
'name' => $values['post_title'],
'step' => $step,
'add-to-cart' => ''
],
'add' => 'Add 1 '.$values['post_title'].' to cart',
'remove' => 'Remove 1 '.$values['post_title'].' from cart',
- ],
- true);
+ ]);
} else {
foreach ($variations as $index =>$row) {
- jvbDump($index, 'index');
- jvbDump($row, 'row');
- Form::renderFrom($meta,
+ echo sprintf(
+ '<div class="row right nowrap">
+ <div>
+ <h4>%s — %s</h4>
+ %s%s%s
+ </div>',
+ $row[$this->base.'name'],
+ jvbCurrency($row[$this->base.'price']),
+ empty($row[$this->base.'description']) ? '' : wpautop($row[$this->base.'description']),
+ empty($row[$this->base.'ingredients']) ? '' : wpautop('<b>Ingredients:</b>'.$row[$this->base.'ingredients']),
+ empty($row[$this->base.'preparation_time_duration']) ? '' : wpautop('Ready in <b>'.$row[$this->base.'preparation_time_duration'].'</b>'),
+ );
+ echo Form::render(
'quantity-'.$index,
+ '',
[
- 'type' => 'number',
- 'label' => $row['label'],
+ 'type' => 'quantity',
+ 'label' => $row[$this->base.'name'],
'min' => $min,
'max' => $max,
'step' => $step,
'data' => [
- 'price' => $row['price'],
- 'id' => $ID,
- 'square-catalog-id' => $row['_square_catalog_id'],
- 'name' => $row['label'],
+ 'price' => $row[$this->base.'price'],
+ 'id' => $ID.'-'.$row[$this->base.'sku'],
+ 'catalog-id' => $row[$this->base.'item_id'],
+ 'name' => $row[$this->base.'name'],
'add-to-cart' => ''
],
- 'add' => 'Add 1 '.$row['label'].' to cart.',
- 'remove' => 'Remove 1 '.$row['label'].' from cart.',
- ],
- true);
+ 'add' => 'Add 1 '.$row[$this->base.'name'].' to cart.',
+ 'remove' => 'Remove 1 '.$row[$this->base.'name'].' from cart.',
+ ]
+ );
+ echo '</div>';
}
}
?>
@@ -255,7 +530,8 @@
</div>
<?php
- }
+ } **/
+
protected function renderOnThisPage():void
@@ -265,4 +541,9 @@
}
echo jvbOnThisPage(array_keys($this->details));
}
+
+ public function renderGallery():void
+ {
+ jvbRenderGallery();
+ }
}
--
Gitblit v1.10.0