From a9b3b28d001941921aa70d37fdc87c758a163a44 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Fri, 05 Jun 2026 16:47:03 +0000
Subject: [PATCH] =Some hefty changes to FeedBlock. Transitioning to loading first page in php to save on extra requests. Got a bit to do yet, but I have to work on Northeh for a bit here.
---
inc/blocks/FeedBlock.php | 1143 ++++++++++++++++++++++++++++++++++++++++++--------------
1 files changed, 845 insertions(+), 298 deletions(-)
diff --git a/inc/blocks/FeedBlock.php b/inc/blocks/FeedBlock.php
index 6bbe0e4..abe8395 100644
--- a/inc/blocks/FeedBlock.php
+++ b/inc/blocks/FeedBlock.php
@@ -1,343 +1,890 @@
<?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', WEEK_IN_SECONDS);
+ if (JVB_TESTING) {
+ $this->cache->flush();
+ }
+ $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;
+ $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