| | |
| | | <?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')) { |
| | |
| | | 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' ] |
| | |
| | | |
| | | 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 = []; |
| | |
| | | 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(); |
| | |
| | | $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'; |
| | |
| | | ?> |
| | | <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; |
| | | } |
| | | ?> |
| | |
| | | } |
| | | ?> |
| | | </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 = jvb_menu_sections(); |
| | | } |
| | | |
| | | return $this->sections; |
| | |
| | | { |
| | | $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'], |
| | | ] |
| | | ], |
| | |
| | | } |
| | | } |
| | | |
| | | 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> |
| | |
| | | <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>'; |
| | | } |
| | | } |
| | | ?> |
| | |
| | | |
| | | </div> |
| | | <?php |
| | | } |
| | | } **/ |
| | | |
| | | |
| | | |
| | | protected function renderOnThisPage():void |
| | |
| | | } |
| | | echo jvbOnThisPage(array_keys($this->details)); |
| | | } |
| | | |
| | | public function renderGallery():void |
| | | { |
| | | jvbRenderGallery(); |
| | | } |
| | | } |