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/FeedBlock.php | 1160 +++++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 862 insertions(+), 298 deletions(-)
diff --git a/inc/blocks/FeedBlock.php b/inc/blocks/FeedBlock.php
index 6bbe0e4..3cc10ba 100644
--- a/inc/blocks/FeedBlock.php
+++ b/inc/blocks/FeedBlock.php
@@ -1,343 +1,907 @@
<?php
namespace JVBase\blocks;
-use JVBase\managers\CacheManager;
+use JVBase\managers\Cache;
+use JVBase\meta\Meta;
+use JVBase\registrar\Registrar;
+use JVBase\base\Site;
use JVBase\forms\TaxonomySelector;
+use JVBase\ui\CRUDSkeleton;
use WP_Block;
+use WP_Query;
if (!defined('ABSPATH')) {
- exit; // Exit if accessed directly
+ exit;
}
class FeedBlock
{
- protected CacheManager $cache;
- protected array $config;
- protected string $path = JVB_DIR.'/build/feed';
+ protected Cache $cache;
+ protected array $config;
+ protected string $path = JVB_DIR.'/build/feed';
- public function __construct()
- {
- $this->cache = new CacheManager('feed', WEEK_IN_SECONDS);
- add_action('init', [$this, 'registerBlock']);
- }
+ protected array $content = [];
+ protected array $taxonomies = [];
+ protected ?string $context = null;
+ protected ?int $contextID = null;
- public function registerBlock()
- {
- register_block_type($this->path, [
- 'render_callback' => [$this, 'render']
- ]);
- }
+ protected bool $isGallery = false;
+ protected array $args;
+ protected bool $isContentTax = false;
+ protected bool $hasMore = false;
- protected function buildParams(array $attributes):array
- {
+ public function __construct()
+ {
+// // Initialize cache with connections
+// $this->cache = Cache::for('feed_block','post', WEEK_IN_SECONDS);
+// if (JVB_TESTING) {
+// $this->cache->flush();
+// }
- if (!jvbCheck('inheritQuery', $attributes)) {
- return [
- 'title' => $attributes['title'],
- 'content' => $attributes['contentTypes'],
- 'taxonomies'=> $this->getTaxonomies($attributes['contentTypes'])
- ];
- }
+ add_action('init', [$this, 'registerBlock']);
+ }
- if (isJVBUserType()) {
- return $this->buildProfileConfig();
- } elseif (isJVBContentTax()) {
- return $this->buildShopConfig();
- } elseif (is_tax()) {
- return $this->buildTaxonomyConfig();
- }
+ public function registerBlock()
+ {
+ register_block_type($this->path, [
+ 'render_callback' => [$this, 'render']
+ ]);
+ }
- return [];
- }
+ public function render(array $attributes): string
+ {
+ if (is_post_type_archive(BASE.'directory')) {
+ return '';
+ }
+ $this->determineContent($attributes);
+ if (empty($this->content)) {
+ return '';
+ }
+ $this->determineTaxonomies();
+ $classes = '';
- protected function buildProfileConfig():array
- {
- $obj = get_queried_object();
- $content = jvbGetUserContentTypes($obj->ID);
- return [
- 'is_gallery' => true,
- 'content' => $content,
- 'context' => jvbNoBase($obj->post_type),
- 'taxonomies' => $this->getTaxonomies($content)
- ];
- }
-
- protected function buildShopConfig():array
- {
- $type = jvbNoBase(get_queried_object()->taxonomy);
- $content = jvbContentTaxContent($type);
-
- $context = [
- 'content' => $content,
- 'taxonomies' => $this->getTaxonomies($content),
- 'context' => $type,
- ];
- unset($context['taxonomies'][array_search($type, $context['taxonomies'])]);
- return $context;
- }
-
- protected function buildTaxonomyConfig():array
- {
- $type = jvbNoBase(get_queried_object()->taxonomy);
- $content = jvbContentTaxContent($type);
- return [
- 'content' => $content,
- 'taxonomies' => $this->getTaxonomies($content),
- 'context' => $type
- ];
- }
-
- protected function getTaxonomies(array $content):array
- {
- global $jvb_taxonomy_for;
- $taxonomies = [];
- foreach ($jvb_taxonomy_for as $taxonomy => $for) {
- if (array_intersect($for, $content)) {
- $taxonomies[] = $taxonomy;
- }
- }
- return $taxonomies;
- }
-
- public function render(array $attributes, string $content, WP_Block $block)
- {
- $this->config = $this->buildParams($attributes);
-
- return $this->cache->remember(
- $this->config,
- function() {
- return $this->renderBlock();
- }
+ return sprintf(
+ '<section class="feed-block%s" data-content="%s"%s%s%s>
+ %s%s%s%s%s
+ <footer>%s</footer>
+ </section>',
+ $classes,
+ $this->getContent(),
+ $this->isGallery ? ' data-gallery' : '',
+ is_null($this->context) ? '' : ' data-context="'.$this->context.'"',
+ is_null($this->contextID) ? '' : ' data-context-id="'.$this->contextID.'"',
+ $this->renderFiltersAndControls(),
+ $this->renderGrid(),
+ $this->renderTemplates(),
+ $this->renderLoader(),
+ TaxonomySelector::outputSelectorModal(),
+ $this->renderActions()
);
- }
+ }
- protected function renderBlock():string
- {
- $work = isJVBUserType() ? ' id="work"' : '';
+ protected function determineContent(array $attrs):void
+ {
+ if (array_key_exists('inheritQuery', $attrs) && $attrs['inheritQuery'] === true) {
+ $args = [
+ 'posts_per_page' => 36,
+ 'fields' => 'ids',
+ ];
+ if (is_post_type_archive() &&!is_tax()) {
+ $obj = get_queried_object();
+ $registrar = Registrar::getInstance($obj->name);
+ if ($registrar && $registrar->hasFeature('is_timeline')) {
+ $args['post_parent'] = 0;
+ }
+ $this->content = [jvbNoBase($obj->name)];
+ $args['post_type'] = $obj->name;
+ $this->args = $args;
+ return;
+ } elseif (!empty(Registrar::getProfileTypes()) && is_singular(Registrar::getProfileTypes())) {
+ global $post;
+ $author = $post->post_author;
+ $role = jvbUserRole($author);
- ob_start();
- ?>
- <section<?= $work ?> class="feed-block"
- data-source="<?= get_queried_object_id(); ?>"
- <?= (array_key_exists('context', $this->config)) ? ' data-context="'.$this->config['context'].'"' : '' ?>
- <?= (array_key_exists('is_gallery', $this->config)) ? ' data-gallery="true"' : ''?>>
- <?php
- $this->renderFilters();
- $this->renderGrid();
- $this->renderLoader();
- $this->renderTemplates();
- ?>
- </section>
- <?php
- return ob_get_clean();
- }
+ $args['post_author'] = $author;
+ $this->context = jvbNoBase($role);
+ $this->contextID = $author;
+ $registrar = Registrar::getInstance($role);
+ if (!$registrar) {
+ return;
+ }
+ $this->content = $registrar->getCreatable();
+ $args['post_type'] = array_map('jvbCheckBase', $this->content);
+ foreach($args['post_type'] as $post_type) {
+ $reg = Registrar::getInstance($post_type);
+ if ($reg && $reg->hasFeature('is_timeline')) {
+ $args['post_parent'] = 0;
+ }
+ }
+ $this->args = $args;
+ return;
+ } elseif (is_tax()) {
+ $obj = get_queried_object();
+ $this->context = jvbNoBase($obj->taxonomy);
+ $this->contextID = $obj->term_id;
+ $args['tax_query'] = [];
+ $args['tax_query'][] = [
+ 'taxonomy' => $obj->taxonomy,
+ 'terms' => $obj->term_id,
+ ];
+ $registrar = Registrar::getInstance($obj->taxonomy);
+ if (!$registrar) {
+ return;
+ }
+ if ($registrar->hasFeature('is_content')) {
+ $this->isContentTax = true;
+ $this->content = [$registrar->getBased()];
+ return;
+ }
+ $this->content = array_map(function ($item) { return jvbNoBase($item); }, $registrar->registrar->for);
+ $args['post_type'] = array_map('jvbCheckBase', $registrar->registrar->for);
+ foreach($args['post_type'] as $post_type) {
+ $reg = Registrar::getInstance($post_type);
+ if ($reg && $reg->hasFeature('is_timeline')) {
+ $args['post_parent'] = 0;
+ }
+ }
+ $this->args = $args;
+ return;
+ }
+ }
+ // not inheriting, getting from config
+ $this->content = $attrs['contentTypes']??[];
- protected function renderFilters():void
- {
- if (empty($this->config)) {
- return;
- }
- $many = count($this->config['content']) > 1;
- global $jvb_everything;
- global $jvb_taxonomy_for;
- ?>
- <form class="feed-filters" data-save="feed">
- <details>
- <summary class="row btw">
- <span class="label">SHOWING: </span>
- <?php
- $labels = [];
- foreach ($this->config['content'] as $i => $type) :
- $checked = $i === 0 ? ' checked' : '';
+ $args['post_type'] = array_map('jvbCheckBase', $attrs['contentTypes']);
+ $this->args = $args;
+ }
+ protected function getContent():string
+ {
+ return implode(',', $this->content);
+ }
+ protected function determineTaxonomies():void
+ {
+ $taxonomies = [];
+ $ignore = [];
+ foreach ($this->content as $content) {
- $label = $jvb_everything[$type]['plural'];
- ?>
+ $registrar = Registrar::getInstance($content);
+ if (!$registrar) continue;
+ if ($registrar->hasFeature('is_content')) {
+ foreach ($registrar->registrar->for as $c) {
+ $contentRegistrar = Registrar::getInstance($c);
+ if (!$contentRegistrar) continue;
+ $theTax = $contentRegistrar->registrar->taxonomies;
+ foreach ($theTax as $tax) {
+ if (!in_array($tax, $ignore) && !in_array($tax, $taxonomies)) {
+ $taxReg = Registrar::getInstance($tax);
+ if ($taxReg->hasFeature('show_feed')) {
+ $taxonomies[] = $tax;
+ } else {
+ $ignore[] = $tax;
+ }
+ }
+ }
+ }
+ } else {
+ $theTax = $registrar->registrar->taxonomies;
+ foreach ($theTax as $tax) {
+ if (!in_array($tax, $ignore) && !in_array($tax, $taxonomies)) {
+ $taxReg = Registrar::getInstance($tax);
+ if ($taxReg->hasFeature('show_feed')) {
+ $taxonomies[] = $tax;
+ } else {
+ $ignore[] = $tax;
+ }
+ }
+ }
+ }
+ }
+ $this->taxonomies = array_unique($taxonomies);
+ }
- <input type="radio"
- id="filter-<?= esc_attr($type) ?>"
- class="btn"
- name="content"
- value="<?= esc_attr($type) ?>"
- <?= $checked ?>>
- <label for="filter-<?= esc_attr($type) ?>" title="Show <?= $label ?>" class="row">
- <?= jvbIcon($type, ['title'=> $label]) ?>
- <span class="screen-reader-text"><?= $label ?></span>
- </label>
+ protected function renderFiltersAndControls():string
+ {
+ return sprintf(
+ '<details class="all-filters col top left" data-ignore>
+ <summary>Filters %s</summary>
+ %s%s%s%s%s
+ </details>
+ <button data-action="clear-filters" data-ignore hidden>%s<span>Clear Filters</span></span></button>',
+ $this->renderContentLabels(),
+ $this->renderSearch(),
+ $this->renderContent(),
+ $this->renderFilters(),
+ $this->renderOrderControls(),
+ $this->renderViewControls(),
+ jvbIcon('x')
+ );
+ }
+ protected function renderSearch():string
+ {
+ return sprintf(
+ '<div class="search row left nowrap">
+ <span class="label">Search:</span>
+ %s
+ </div>',
+ jvbSearch()
+ );
+ }
+ protected function renderContentLabels():string
+ {
+ return sprintf(
+ '<span class="label">Showing: <span class="current">%s</span></span>',
+ $this->content[0]??''
+ );
- <?php
- $labels['filter-'.$type] = $label;
- endforeach;
- ?>
- <ul class="filter-label">
- <?php
- $i = 0;
- foreach ($labels as $id =>$label) {
- $active = $i === 0 ? ' class="active"' : '';
- ?>
- <li id="<?=$id?>"<?=$active?>>
- <?=$label?>
- </li>
- <?php
- $i++;
- }
- ?>
- </ul>
+ }
+ protected function renderContent():string
+ {
+ $favourites = '';
+ if (Site::has('favourites')) {
+ $favourites = sprintf(
+ '<input type="checkbox" id="favourites" class="btn" name="favourites" value="on" data-filter="favourites">
+ <label for="favourites" title="Show Favourites">%s%s<span class="screen-reader-text">Show Favourites Only</span></label>',
+ jvbIcon('heart'),
+ jvbIcon('heart', ['style' => 'fill'])
+ );
+ }
+ if (count($this->content) === 1) {
+ return empty($favourites)
+ ? sprintf(
+ '<input type="hidden" data-filter="content" name="content" value="%s">',
+ implode(',', $this->content)
+ )
+ : sprintf(
+ '<div class="content row right">
+ <input type="hidden" name="content" value="%s">
+ %s
+ </div>',
+ implode(',', $this->content),
+ $favourites
+ );
+ }
+ $i = 0;
+ $content = implode('', array_map(function($type) use (&$i) {
+ $registrar = Registrar::getInstance($type);
+
+ $args = [
+ 'post_type' => $registrar->getBased(),
+ 'posts_per_page' => 1,
+ 'fields' => 'ids',
+ ];
+ if (!is_null($this->context)) {
+ $context = Registrar::getInstance($this->context);
+ switch ($context->getType()) {
+ case 'term':
+ $args['tax_query'] = [];
+ $args['tax_query'][] = [
+ 'taxonomy' => $context->getBased(),
+ 'terms' => $this->contextID
+ ];
+ break;
+ case 'user':
+ $args['author'] = $this->contextID;
+ break;
+ }
+ }
+ $check = new WP_Query($args);
+ $hasPosts = !empty($check->posts);
+ wp_reset_postdata();
+
+ $disabled = !$hasPosts;
+ $checked = $i === 0 && $hasPosts;
+ if ($hasPosts) {
+ $i++;
+ }
+ return sprintf(
+ '<input type="radio"
+ id="filter-%s"
+ class="btn"
+ name="content"
+ data-filter="content"
+ data-label="%s"
+ value="%s"%s%s>
+ <label for="filter-%s" title="Show %s">%s<span class="label">%s</span></label>',
+ $type,
+ $registrar->getSingular(),
+ $type,
+ $checked ? ' checked' : '',
+ $disabled ? ' disabled' : '',
+ $type,
+ $registrar->getSingular(),
+ jvbIcon($registrar->getIcon()),
+ $registrar->getSingular()
+ );
+ }, $this->content));
- <?php if (is_user_logged_in()) : ?>
- <input type="checkbox" id="favourites" class="btn" name="favourites" value="on">
- <label for="favourites" title="Show Favourites" class="row">
- <?= jvbIcon('heart', ['title' =>'Favourites']) ?>
- <span class="screen-reader-text">Show Favourites Only</span>
- </label>
- <?php endif; ?>
- <?php if ($many) {
- echo '</summary>';
- } ?>
+ return sprintf(
+ '<div class="content row left nowrap"><span class="label">Showing:</span>
+ %s%s
+ </div>',
+ $content,
+ $favourites
+ );
+ }
- <div class="filters">
- <div class="filter-group">
- <span class="label">FILTER BY:</span>
+ protected function renderFilters():string
+ {
+ if (empty ($this->taxonomies)) {
+ return '';
+ }
+ $inside = implode('', array_filter(array_map(function($tax) {
+ $registrar = Registrar::getInstance($tax);
+ if (!$registrar) return '';
- <?php
- foreach ($jvb_taxonomy_for as $tax => $items) :
- $hidden = !in_array($tax, $this->config['taxonomies']) ? ' hidden' : '';
- if (in_array($tax, $this->config['taxonomies'])) {
- $tax = new TaxonomySelector(
- 'feed-'.$tax,
- $tax,
- [
- 'update' => '.selected-items-section .selected-items',
- 'types' => $items,
- 'hidden'=> $hidden
- ]
- );
- echo $tax->render();
- }
+ $current = BASE.$this->content[0];
+ $contentFor = $registrar->registrar->for;
+ $hidden = in_array($current, $contentFor) ? '' : ' hidden';
- endforeach; ?>
- </div>
- <div class="selected-items-section">
- <div class="selected-items row"></div>
- <div class="filter-actions row">
- <?= jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY') ?>
- <button type="button" class="clear-filters row">
- <?= jvbIcon('close', ['title' => 'Clear']) ?>
- Clear All Filters
- </button>
- </div>
- </div>
- </div>
+ $selector = new TaxonomySelector(
+ 'feed-'.$tax,
+ $tax,
+ [
+ 'icon' => $registrar->getIcon(),
+ 'update'=> '.selected-items-section .selected-items',
+ 'types' => $contentFor,
+ 'autocomplete' => false,
+ 'hidden' => $hidden,
+ 'output' => 'minimal',
+ 'search' => true
+ ]
+ );
+ return $selector->render();
- <div class="filter-group">
- <div class="order-by">
- <span class="label">ORDER BY:</span>
- <input type="radio" id="order-title" class="btn" name="orderby" value="title" data-for="artist,shop" hidden>
- <label for="order-title" title="Order by Name" class="row">
- <?= jvbIcon('alphabetical') ?>
- <span class="label">Name</span>
- </label>
+ }, $this->taxonomies)));
+ return sprintf(
+ '<div class="taxonomies row left">
+ <div class="row top wrap">
+ <span class="label">Filter By:</span>
+ %s
+ </div>
+ <div class="selected-items-section">
+ <div class="selected-items row left"></div>
+ <div class="filter-actions row">
+ %s
+ <button type="button" class="clear-filters" hidden>
+ %s
+ <span>Clear All Filters</span>
+ </button>
+ </div>
+ </div>
+ </div>',
+ $inside,
+ str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])),
+ jvbIcon('x')
+ );
+ }
- <input type="radio" id="order-date" class="btn" name="orderby" value="date" checked>
- <label for="order-date" title="Order by Date" class="row">
- <?= jvbIcon('calendar', ['title'=>'Date']) ?>
- <span class="label">Date</span>
- </label>
+ protected function renderOrderControls():string
+ {
+ $orderby = [
+ [
+ 'slug' => 'title',
+ 'icon' => 'alphabetical',
+ 'label' => 'Name'
+ ],
+ [
+ 'slug' => 'date',
+ 'icon' => 'calendar',
+ 'label' => 'Date Created',
+ ],
+ [
+ 'slug' => 'date_modified',
+ 'icon' => 'clock-clockwise',
+ 'label' => 'Date Modified'
+ ]
+ ];
+ $custom = $this->getCustomOrdering();
+ $orderby = $orderby + $custom;
+ $orderby[] = [
+ 'slug' => 'random',
+ 'icon' => 'shuffle',
+ 'label' => 'Randomly'
+ ];
- <input type="radio" id="order-random" class="btn" name="orderby" value="random">
- <label for="order-random" title="Random Order" class="row">
- <?= jvbIcon('random') ?>
- <span class="label">Random</span>
- </label>
- </div>
+ $custom = implode(',', array_map(function($ord) {
+ return $ord['slug'];
+ }, $custom));
- <div class="order-direction radio-group-label" data-for-order="date,title">
- <span class="label">ORDER:</span>
- <input type="radio" id="order-desc" class="btn" name="order" value="desc" checked>
- <label for="order-desc" title="Newest First" class="row">
- <?= jvbIcon('desc') ?>
- </label>
+ $i = 0;
+ $orderby = sprintf(
+ '<div class="orderby row left">
+ <span class="label">Order by:</span>%s
+ </div>',
+ implode('', array_map(function ($by) use (&$i){
+ $checked = $i === 0 ? ' checked' : '';
+ $i++;
+ return sprintf(
+ '<input type="radio" id="order-%s" class="btn" name="orderby" value="%s" data-filter="orderby"%s%s>
+ <label for="order-%s" title="Order %s">%s<span class="label">%s</span></label>',
+ $by['slug'],
+ $by['slug'],
+ $checked,
+ empty($by['for']??[]) ? '' : ' data-for="'.implode($by['for']).'"',
+ $by['slug'],
+ $by['slug'] === 'random' ? $by['label'] : 'by '.$by['label'],
+ jvbIcon($by['icon']),
+ $by['label']
+ );
+ }, $orderby))
+ );
- <input type="radio" id="order-asc" class="btn" name="order" value="asc">
- <label for="order-asc" title="Oldest First" class="row">
- <?= jvbIcon('asc') ?>
- </label>
- </div>
- </div>
- </details>
- </form>
- <?php
- }
+ $order = [
+ [
+ 'slug' => 'desc',
+ 'icon' => 'sort-descending',
+ 'label' => 'Descending (A-Z, 1-10)'
+ ],
+ [
+ 'slug' => 'asc',
+ 'icon' => 'sort-ascending',
+ 'label' => 'Ascending (Z-A, 10-1)'
+ ]
+ ];
- protected function renderGrid():void
- {
- ?>
- <div class="item-grid"></div>
- <?php
- }
+ $i = 0;
+ $order = sprintf(
+ '<div class="order-direction row left" data-for-order="date,date_modified,title%s">
+ <span class="label">Order:</span>
+ %s
+ </div>',
+ $custom === '' ? '' : ','.$custom,
+ implode('', array_map(function ($ord) use (&$i) {
+ $checked = $i=== 0 ? ' checked' : '';
+ $i++;
+ return sprintf(
+ '<input type="radio" id="order-%s" class="btn" name="order" value="%s" data-filter="order"%s>
+ <label for="order-%s" title="Sort %s">
+ %s
+ <span class="label">%s</span>
+ </label>',
+ $ord['slug'],
+ $ord['slug'],
+ $checked,
+ $ord['slug'],
+ $ord['label'],
+ jvbIcon($ord['icon']),
+ $ord['label']
+ );
+ }, $order))
+ );
- protected function renderLoader():void
- {
- ?>
- <button type="button" class="load-more">
- <?= jvbIcon('elbow-left-down', ['title' =>'More']) ?>
- Show Me More
- <?= jvbIcon('elbow-right-down', ['title'=> 'More']) ?>
- </button>
+ return sprintf(
+ '<div class="ordering row top left nowrap">%s%s</div>',
+ $orderby,
+ $order
+ );
+ }
+ protected function getCustomOrdering():array
+ {
+ $custom = [];
+ foreach ($this->content as $content) {
+ $registrar = Registrar::getInstance($content);
+ if (!$registrar || empty($registrar->config('feed')->getCustomOrder())) {
+ continue;
+ }
+ $custom = array_merge_recursive($custom, $registrar->config('feed')->getCustomOrder());
+ }
+ return $custom;
+ }
+ protected function renderViewControls():string
+ {
+ $views = [
+ 'grid' => ['slug' => 'grid', 'icon' => 'squares-four', 'label' => 'Grid View'],
+ 'list' => ['slug' => 'list', 'icon' => 'rows', 'label' => 'List View']
+ ];
- <?= jvbLoadingScreen() ?>
- <?php
- if (array_key_exists('is_gallery', $this->config)) {
- jvbRenderGallery();
- }
- }
+ $i = 0;
+ return sprintf(
+ '<div class="view row left nowrap"><span class="label">Switch View:</span>%s</div>',
+ implode('', array_map(function ($view) use (&$i) {
+ $checked = $i === 0 ? ' checked' : '';
+ $i++;
+ return sprintf(
+ '<input type="radio"
+ data-view="%s" value="%s" class="btn" name="view" id="view-%s"%s>
+ <label for="view-%s" title="%s">
+ %s<span class="label">%s</span>
+</label>',
+ $view['slug'],
+ $view['slug'],
+ $view['slug'],
+ $checked,
+ $view['slug'],
+ $view['label'],
+ jvbIcon($view['icon']),
+ $view['label'],
+ );
+ }, $views))
+ );
+ }
- protected function renderTemplates():void
- {
- echo '<template class="feed-item">
- <details class="item feed" data-umami-event="view_feed">
- <summary class="row btw">
- <span class="handle">DETAILS</span>
- <button class="favourite" title="Add to favourites" onclick="toggleFavourite(this)">
- '.jvbIcon('heart')
- .jvbIcon('heart', ['style'=>'fill']).'
- </button>
- <div class="feed-images">
- <a>
- <img width="300px" height="300px" loading="lazy" decoding="async">
- </a>
- </div>
- </summary>
+ protected function renderPlaceholders():string
+ {
+ $placeholders = '';
+ $total = count($this->content) - 1;
+ $icons = [];
+ $icon = apply_filters('jvbFeedPlaceholder', '');
- <div class="item-info">
- <h3><a></a></h3>
- <div class="item">
- <span class="label"></span>
- <a></a>
- <p></p>
- </div>
- <div class="item-list">
- <span class="label"></span>
- <ul>
- <li>
- <a></a>
- </li>
- </ul>
- </div>
- </div>
- </div>
- </details>
- </template>';
+ for ($i=1; $i<=36; $i++) {
+ if (empty($icon)) {
+ $rand = $total === 0 ? $total : rand(0, $total);
+ $content = $this->content[$rand];
+ if (!in_array($content, $icons)) {
+ $icons[$content] = Registrar::getInstance($content)->getIcon();
+ }
+ $icon = jvbIcon($icons[$content]);
+ }
- echo '<template class="emptyState">
- <div class="feed-empty-state">
- <h3>NOTHING HERE...</h3>
+ $placeholders .= sprintf(
+ '<div class="placeholder">%s</div>',
+ $icon
+ );
+ }
+
+ return sprintf(
+ '<div class="item-grid">%s</div>',
+ $placeholders
+ );
+ }
+
+
+ protected function renderGrid():string
+ {
+ if (empty($this->args)) {
+ return $this->renderPlaceholders();
+ }
+ $items = $this->getItems();
+
+ $out = '<div class="item-grid">';
+ $out .= implode('',array_map(function ($ID) {
+ $content = $this->isContentTax
+ ? jvbNoBase($this->content[0])
+ : jvbNoBase(get_post_type($ID));
+
+ return $this->renderItem($content, $ID);
+ }, $items));
+ $out .= '</div>';
+ return $out;
+ }
+ protected function getItems():array
+ {
+ if ($this->isContentTax) {
+ $items = get_terms([
+ 'taxonomy' => $this->content,
+ 'fields' => 'ids',
+ 'meta_key' => BASE.'date_modified',
+ 'meta_type' => 'DATETIME',
+ 'orderby' => 'meta_value',
+ 'order' => 'desc',
+ 'number' => $this->args['posts_per_page']
+ ]);
+ $items = $items && !is_wp_error($items) ? $items : [];
+ } else {
+ $items = new WP_Query($this->args);
+ $this->hasMore = $items->found_posts > $this->args['posts_per_page'];
+ $items = $items->posts;
+ wp_reset_postdata();
+ }
+ return $items;
+ }
+
+ protected function renderLoader():string
+ {
+ $button = sprintf(
+ '<button type="button" class="load-more"%s>%s<span>Show Me More</span>%s</button>',
+ $this->hasMore ? '' : ' hidden',
+ jvbIcon('arrow-elbow-left-down'),
+ jvbIcon('arrow-elbow-right-down'),
+ );
+ return sprintf(
+ '%s%s%s',
+ $button,
+ jvbLoadingScreen(),
+ $this->isGallery ? jvbRenderGallery(false) : '',
+ );
+ }
+
+ protected function renderTemplates():string
+ {
+
+ $templates = [];
+ foreach ($this->content as $content) {
+ $templates[] = $this->getDefaultTemplate($content);
+ }
+
+ $templates[] = sprintf(
+ '<template class="feedTerm"><button class="remove-term">%s<span></span>%s</button></template>',
+ jvbIcon(jvbDefaultIcon()),
+ jvbIcon('x')
+ );
+ $defaultEmptyState = sprintf(
+ '<div class="empty-state">
+ <h3>%sNOTHING HERE%s</h3>
<p>Try tweaking those filters a bit.</p>
- <p>Edmonton\'s got talent - let\'s find it.</p>
- </div>
- </template>';
+ </div>',
+ jvbIcon(jvbDefaultIcon()),
+ jvbIcon(jvbDefaultIcon()),
+ );
+ $emptyState = apply_filters('jvbFeedEmptyState', $defaultEmptyState, $this->content);
+ $templates[] = sprintf(
+ '<template class="emptyState">%s</template>',
+ $emptyState
+ );
- echo '<template class="placeholderTemplate"><div class="placeholder"></div></template>';
- }
+ $placeholder = apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon()));
+ $templates[] = sprintf(
+ '<template class="placeholderTemplate"><div class="placeholder">%s</div></template>',
+ $placeholder
+ );
+
+ return implode('', $templates);
+ }
+
+ protected function renderActions():string
+ {
+ return is_user_logged_in() ? sprintf(
+ '<button data-action="refresh" data-ignore>%s<span>Hard Refresh</span></span></button>',
+ jvbIcon('arrows-clockwise')
+ ) : '';
+ }
+
+ public static function getFavouritesButton(string $content):string
+ {
+ $registrar = Registrar::getInstance($content);
+ if (!$registrar || !Site::has('favourites') || !$registrar->hasFeature('favouritable')) {
+ return '';
+ }
+ return sprintf(
+ '<button class="favourite" type="button" title="Add to favourites" data-action="favourite">%s%s</button>',
+ jvbIcon('heart'),
+ jvbIcon('heart', ['style'=>'fill'])
+ );
+ }
+ public static function getUpvotesButton(string $content):string
+ {
+ $registrar = Registrar::getInstance($content);
+ if (!Site::has('karma') || !$registrar || !$registrar->hasFeature('karma')){
+ return '';
+ }
+ return sprintf(
+ '<div class="karma row">
+ <button type="button" class="vote" data-action="upvote">
+ %s%s
+ </button>
+ <button type="button" class="vote" data-action="downvote">
+ %s%s
+ </button>
+ <span class="score"></span>
+ </div>',
+ jvbIcon('arrow-fat-up'),
+ jvbIcon('arrow-fat-up', ['style'=>'fill']),
+ jvbIcon('arrow-fat-down'),
+ jvbIcon('arrow-fat-down', ['style'=>'fill'])
+ );
+ }
+ protected function getDefaultTemplate(string $content): string
+ {
+ return sprintf(
+ '<template class="feedItem%s">%s</template>',
+ ucfirst($content),
+ $this->renderItem($content)
+ );
+ }
+
+ protected function renderItem(string $content, ?int $ID = null):string
+ {
+ /**
+ * Allow plugins to replace the content within the feed item
+ */
+ $function = BASE.'render_'.$content.'_feed_item';
+ if (function_exists($function)) {
+ $out = $function($ID);
+ } else {
+ $out = $this->buildFeedItem($content, $ID);
+ }
+ $registrar = Registrar::getInstance($content);
+ $data = [];
+ if ($registrar && $registrar->hasFeature('is_timeline')) {
+ $data[] = 'data-timeline';
+ }
+
+ $data = !empty($data) ? ' '.implode(' ', $data) : '';
+
+ return sprintf(
+ '<div class="feed item col %s"%s>%s%s%s</div>',
+ $content,
+ $data,
+ self::getFavouritesButton($content),
+ self::getUpvotesButton($content),
+ $out
+ );
+ }
+ protected function buildFeedItem(string $content, ?int $ID = null):string
+ {
+ $registrar = Registrar::getInstance($content);
+ $meta = is_null($ID) ? false :
+ match ($registrar->getType()) {
+ 'post' => Meta::forPost($ID),
+ 'term' => Meta::forTerm($ID),
+ 'user' => Meta::forUser($ID),
+ default => false
+ };
+
+ [$images, $fields] = $registrar->getFeedFields();
+
+ /**
+ * Get the main image for the feed item.
+ * Output can be overridden with the $imagesFn
+ */
+ $imagesFn = BASE.'render_'.$content.'_feed_item_images';
+ if (function_exists($imagesFn)) {
+ $img = $imagesFn($ID, $images);
+ } else {
+ $img = '';
+ foreach ($images as $config) {
+ $field = $config['name'];
+ $img .= $meta ? jvbFormatImage($meta->get($field), 'tiny', 'medium')
+ : $this->defaultFieldTemplate($config['type'], $field);
+ }
+ }
+ $img = sprintf(
+ '<div class="images"><a href="%s">%s</a></div>',
+ $ID ? get_the_permalink($ID) : '',
+ $img
+ );
+
+ /**
+ * Start the Details with the fields
+ * Plugins can modify the summary title with the 'jvbFeedItemSummary' filter
+ */
+ $summary = sprintf(
+ '<details><summary>%s</summary>',
+ apply_filters('jvbFeedItemSummary', jvbIcon('dots-three'), $content)
+ );
+ /**
+ * Work through the fields
+ * Each field output can be overridden with $field or $fieldType
+ */
+ foreach ($fields as $config) {
+ $f = $config['name'];
+ $functions = [
+ BASE.'render_'.$content.'_field_'.$f, //Overrides field for this content
+ BASE.'render_field_'.$f, //Overrides field with this name
+ BASE.'render_field_type_'.$config['type'] //Overrides field of this type
+ ];
+
+ $didIt = false;
+ foreach ($functions as $func) {
+ if (!$didIt && function_exists($func)) {
+ $didIt = true;
+ $summary .= $func($ID, is_null($ID));
+ }
+ }
+ if (!$didIt) {
+ $summary .= $this->defaultFieldTemplate($config['type'], $f, is_null($ID), $meta, $config);
+ }
+
+ }
+ $summary .= '</details>';
+
+ return $img.$summary;
+ }
+
+ protected function defaultFieldTemplate(string $fieldType, string $fieldName, bool $isTemplate = true, bool|Meta $meta = false, array $config = []):string
+ {
+ $data = ' data-field="'.$fieldName.'"';
+ $value = $meta ? $meta->get($fieldName) : '';
+ if (!$isTemplate && empty($value)) {
+ return '';
+ }
+ switch ($fieldName) {
+ case 'post_title':
+ return sprintf(
+ '<h3%s>%s</h3>',
+ $data,
+ $meta && !empty($value) ? $value : '',
+ );
+ case 'post_date':
+ case 'post_modified':
+ return sprintf(
+ '<time%s%s>%s</time>',
+ $data,
+ $meta && !empty($value) ? date('c', strtotime($value)) : '',
+ $meta && !empty($value) ? date('F j, Y', strtotime($value)) : '',
+ );
+ }
+
+ return match ($fieldType) {
+ 'date' => sprintf(
+ '<time%s%s>%s</time>',
+ $data,
+ $meta && !empty($value) ? date('c', strtotime($value)) : '',
+ $meta && !empty($value) ? date('F j, Y', strtotime($value)) : '',
+ ),
+ 'datetime' => sprintf(
+ '<time%s%s>%s</time>',
+ $data,
+ $meta && !empty($value) ? date('c', strtotime($value)) : '',
+ $meta && !empty($value) ? date('F j, Y at h:iA', strtotime($value)) : '',
+ ),
+ 'time' => sprintf(
+ '<time%s%s>%s</time>',
+ $data,
+ $value,
+ $value
+ ),
+ 'upload' => empty($value) ? sprintf(
+ '<img%s width="300px" height="300px" loading="lazy" decoding="async">',
+ $data
+ ) :
+ str_replace('<img', '<img' . $data, jvbFormatImage($value)),
+ 'selector' => sprintf(
+ '<ul%s class="terms %s">%s</ul>',
+ $data,
+ $config['taxonomy']??$config['post_type']??$config['role']??'',
+ empty($value) ? sprintf('<li><a>%s</a></li>',
+ $this->iconFor($config)
+ ) :
+ implode('', array_filter(array_map(function ($ID) use ($config) {
+ $type = $config['subtype'];
+ $taxonomy = isset($config['taxonomy']) ? jvbCheckBase($config['taxonomy']) : '';
+
+ $item = match ($type) {
+ 'taxonomy' => get_term($ID, $taxonomy),
+ 'user' => get_userdata($ID),
+ 'post' => get_post($ID),
+ default => false,
+ };
+
+ if (!$item || is_wp_error($item)) {
+ return '';
+ }
+
+ $icon = $this->iconFor($config);
+
+ $name = match ($type) {
+ 'taxonomy' => $item->name,
+ 'user' => $item->display_name,
+ 'post' => $item->post_title,
+ default => '',
+ };
+ $url = match ($type) {
+ 'taxonomy' => get_term_link((int)$ID, $taxonomy),
+ 'user' => get_the_permalink(get_user_meta($ID, BASE . 'userLink', true)),
+ 'post' => get_the_permalink($ID),
+ default => ''
+ };
+ return sprintf(
+ '<li><a href="%s">%s%s</a></li>',
+ $url,
+ !empty($icon) ? jvbIcon($icon) : '',
+ $name
+ );
+ }, explode(',', $value))))
+ ),
+ default => sprintf(
+ '<p%s>%s</p>',
+ $data,
+ $value
+ ),
+ };
+ }
+ protected function iconFor(array $fieldConfig):string
+ {
+ $icon = match ($fieldConfig['type']??'') {
+ 'taxonomy' => Registrar::getInstance($fieldConfig['taxonomy'])->getIcon(),
+ 'user' => isset($fieldConfig['role']) ? Registrar::getInstance($fieldConfig['role'])->getIcon() : 'user',
+ 'post' => Registrar::getInstance($fieldConfig['post_type'])->getIcon(),
+ default => ''
+ };
+
+ return empty($icon) ? $icon : jvbIcon($icon);
+ }
+
}
--
Gitblit v1.10.0