<?php
|
namespace JVBase\blocks;
|
|
use JVBase\base\Site;
|
use JVBase\managers\Cache;
|
use JVBase\meta\Meta;
|
use JVBase\registrar\Registrar;
|
use WP_Block;
|
use WP_Post;
|
use WP_Query;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
|
class MenuBlock
|
{
|
protected Cache $cache;
|
protected string $config;
|
protected string $type;
|
protected string $path = JVB_DIR . '/build/menu';
|
protected string $image;
|
protected string $header;
|
protected array $params = ['all'];
|
protected string $headerExtra;
|
protected string $detailsTitle;
|
protected array $details;
|
protected array|false $sections = false;
|
protected string $base;
|
|
public function __construct()
|
{
|
$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, [
|
'render_callback' => [ $this, 'render' ]
|
]);
|
}
|
|
protected function buildParams():void
|
{
|
// 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('product_cat')) {
|
$this->params = [];
|
} else {
|
$this->params = [];
|
}
|
}
|
|
public function render(array $attributes, string $content, WP_Block $block)
|
{
|
$this->buildParams();
|
if (empty($this->params)) {
|
return '';
|
}
|
jvbInlineStyles('forms');
|
return $this->cache->remember(
|
$this->params,
|
function() {
|
ob_start();
|
$this->renderBlock();
|
return ob_get_clean();
|
}
|
);
|
}
|
|
protected function renderBlock():void
|
{
|
$this->renderHeader();
|
$this->renderMenu();
|
$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';
|
if(!in_array('all', $this->params)) {
|
$title = '<small>'.$title.'</small>';
|
}
|
|
?>
|
<header id="top">
|
<h1><?=$title?></h1>
|
</header>
|
<nav class="menu"><ul>
|
<?php
|
$sections = $this->getSections();
|
foreach ($sections as $section) {
|
// if (!jvbTermHasPosts($section['id'], BASE.'section')) {
|
if (!$this->termHasPosts($section['id'])) {
|
continue;
|
}
|
?>
|
<li><a href="#<?=sanitize_title($section['name'])?>" title="Jump to <?=$section['name']?>"><?=$section['name']?></a></li>
|
<?php
|
}
|
?>
|
</ul></nav>
|
<?php
|
}
|
|
protected function getSections():array
|
{
|
if (!$this->sections) {
|
$this->sections = aneb_menu_sections();
|
}
|
|
return $this->sections;
|
}
|
|
protected function renderMenu():void
|
{
|
$sections = $this->getSections();
|
foreach ($sections as $section) {
|
if($section['name'] === 'Uncategorized') {
|
continue;
|
}
|
$items = new WP_Query([
|
'post_type' => 'product',
|
// 'post_type' => BASE.'menu_item',
|
'order' => 'ASC',
|
'posts_per_page' => -1,
|
'tax_query' => [
|
[
|
'taxonomy' => 'product_cat',
|
// 'taxonomy' => BASE.'section',
|
'terms' => (int) $section['id'],
|
]
|
],
|
'fields' => 'ids'
|
]);
|
if ($items->have_posts()) {
|
$slug = sanitize_title($section['name']);
|
?>
|
<section id="<?= $slug?>" class="menu-section">
|
<h2><?=$section['name']?></h2>
|
<hr>
|
<?= (array_key_exists('description', $section) && $section['description'] !== '') ? $section['description'] : ''?>
|
<div class="menu-items">
|
<?php
|
foreach ($items->posts as $ID) {
|
$this->renderMenuItem($ID, $slug);
|
}
|
?>
|
</div>
|
</section>
|
<?php
|
}
|
wp_reset_postdata();
|
}
|
}
|
|
/**
|
* 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',
|
$this->base.'item_id',
|
$this->base.'price',
|
$this->base.'product_variations',
|
'max_order',
|
'min_order',
|
'step'
|
]);
|
$priceRange = jvbCurrency($values[$this->base.'price']);
|
$variations = $values[$this->base.'product_variations'];
|
|
if(!empty($variations) && $variations[0][$this->base.'name'] === '') {
|
$variations = [];
|
}
|
|
if (!empty($variations)) {
|
$prices = [];
|
foreach ($variations as $row) {
|
if (isset($row[$this->base.'price']) && is_numeric($row[$this->base.'price'])) {
|
$prices[] = (float) $row[$this->base.'price'];
|
}
|
}
|
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 = ($max === '') ? 50 : $max;
|
$min = ($min === '') ? 0 : $min;
|
$step = ($step === '') ? 1 : $step;
|
|
|
?>
|
<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>
|
</div>
|
<div class="description">
|
<?= Render::renderFrom($meta, 'post_excerpt')?>
|
</div>
|
<div class="info col right">
|
<?php
|
if (empty($variations)) {
|
echo Form::render(
|
$ID.'|cart_quantity',
|
'',
|
[
|
'type' => 'quantity',
|
'label' => 'Add to Order',
|
'min' => $min,
|
'max' => $max,
|
'step' => $step,
|
'data' => [
|
'price' => $values[$this->base.'price'],
|
'id' => $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',
|
]);
|
} else {
|
foreach ($variations as $index =>$row) {
|
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' => 'quantity',
|
'label' => $row[$this->base.'name'],
|
'min' => $min,
|
'max' => $max,
|
'step' => $step,
|
'data' => [
|
'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[$this->base.'name'].' to cart.',
|
'remove' => 'Remove 1 '.$row[$this->base.'name'].' from cart.',
|
]
|
);
|
echo '</div>';
|
}
|
}
|
?>
|
</div>
|
|
|
</div>
|
<?php
|
} **/
|
|
|
|
protected function renderOnThisPage():void
|
{
|
if (empty($this->details)) {
|
return;
|
}
|
echo jvbOnThisPage(array_keys($this->details));
|
}
|
|
public function renderGallery():void
|
{
|
jvbRenderGallery();
|
}
|
}
|