<?php
|
namespace JVBase\blocks;
|
|
use JVBase\managers\CacheManager;
|
use JVBase\utility\Features;
|
use JVBase\utility\Checker;
|
use JVBase\forms\TaxonomySelector;
|
use WP_Block;
|
|
if (!defined('ABSPATH')) {
|
exit;
|
}
|
|
class FeedBlock
|
{
|
protected CacheManager $cache;
|
protected array $config;
|
protected string $path = JVB_DIR.'/build/feed';
|
|
public function __construct()
|
{
|
// Initialize cache with connections
|
$this->cache = CacheManager::for('feed_block', WEEK_IN_SECONDS);
|
|
// Set up cache connections for all feed content types
|
$this->setupCacheConnections();
|
|
add_action('init', [$this, 'registerBlock']);
|
}
|
|
/**
|
* Set up cache connections for feed content
|
*/
|
protected function setupCacheConnections(): void
|
{
|
// Connect to all content types that show in feed
|
$contentTypes = Features::getTypesWithFeature('show_feed', 'content');
|
foreach ($contentTypes as $type) {
|
CacheManager::for('feed_content')->connectTo('post', $type);
|
}
|
|
// Connect to all taxonomies that show in feed
|
$taxonomies = Features::getTypesWithFeature('show_feed', 'taxonomy');
|
foreach ($taxonomies as $tax) {
|
CacheManager::for('feed_taxonomy')->connectTo('taxonomy', $tax);
|
}
|
}
|
|
public function registerBlock()
|
{
|
register_block_type($this->path, [
|
'render_callback' => [$this, 'render']
|
]);
|
}
|
|
protected function buildParams(array $attributes): array
|
{
|
if (!jvbCheck('inheritQuery', $attributes)) {
|
return [
|
'title' => $attributes['title'],
|
'content' => $attributes['contentTypes'],
|
'taxonomies' => $this->getTaxonomies($attributes['contentTypes'])
|
];
|
}
|
$config = [
|
'is_gallery' => false,
|
'content' => '',
|
'taxonomies' => []
|
];
|
$type = get_queried_object();
|
|
if (is_post_type_archive() || is_singular()) {
|
$content = is_singular() ? jvbNoBase($type->post_type) : jvbNoBase($type->name);
|
$mainConfig = JVB_CONTENT[$content]??false;
|
if ($mainConfig && array_key_exists('feed', $mainConfig) && array_key_exists('config', $mainConfig['feed'])){
|
$config = array_merge($config, $mainConfig['feed']['config']);
|
} else {
|
$config['content'] = $content;
|
$config['icon'] = JVB_CONTENT[$content]['icon']??['logo-triangle'];
|
}
|
if (is_singular()) {
|
$config['source'] = $type->ID;
|
}
|
|
$config['taxonomies'] = $this->getTaxonomies([$content]);
|
} elseif (is_tax()) {
|
$content = jvbNoBase($type->taxonomy);
|
$mainConfig = JVB_TAXONOMY[$content]??false;
|
if ($mainConfig) {
|
$config['content'] = $mainConfig['for_content'];
|
$config['context'] = $content; // ← ADD THIS
|
$config['taxonomies'] = $this->getTaxonomies($mainConfig['for_content']);
|
if (array_key_exists('feed', $mainConfig) && array_key_exists('config', $mainConfig['feed'])){
|
$config = array_merge($config, $mainConfig['feed']['config']);
|
}
|
}
|
$config['source'] = $type->term_id;
|
}
|
|
if (!is_array($config['content'])) {
|
$config['content'] = [$config['content']];
|
}
|
|
return $config;
|
}
|
|
/**
|
* Get taxonomies for given content types
|
* Uses Checker instead of globals
|
*/
|
protected function getTaxonomies(array $content): array
|
{
|
$checker = Checker::getInstance();
|
$taxonomies = [];
|
|
foreach ($content as $contentType) {
|
$contentTaxonomies = $checker->getTaxonomiesForContent($contentType);
|
$contentTaxonomies = array_filter($contentTaxonomies, function($taxonomy) {
|
return array_key_exists('show_feed', JVB_TAXONOMY[$taxonomy]) && JVB_TAXONOMY[$taxonomy]['show_feed'];
|
});
|
$taxonomies = array_merge($taxonomies, $contentTaxonomies);
|
}
|
|
return array_unique($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();
|
}
|
);
|
}
|
|
protected function renderBlock(): string
|
{
|
$ids = (array_key_exists('ids', $this->config) && !empty($this->config['ids'])) ? ' id="'.implode(' ',$this->config['ids']).'"' : '';
|
$classes = (array_key_exists('classes', $this->config) && !empty($this->config['classes'])) ? ' class="'.implode(' ',$this->config['classes']).'"' : '';
|
$source = (array_key_exists('source', $this->config)) ? ' data-source="'.$this->config['source'].'"' : '';
|
$context = (array_key_exists('context', $this->config)) ? ' data-context="'.$this->config['context'].'"' : '';
|
$icons = (array_key_exists('icon', $this->config)) ? ' data-icon="'.$this->config['icon'].'"' : ' data-icon="logo-triangle"';
|
$gallery = (array_key_exists('is_gallery', $this->config) && $this->config['is_gallery']) ? ' data-gallery' : '';
|
$content = (array_key_exists('content', $this->config)) ? ' data-content="'.implode(',',$this->config['content']).'"' : '';
|
ob_start();
|
?>
|
<section<?= $ids.$classes ?> class="feed-block"<?= $content.$source.$context.$gallery.$icons ?>>
|
<?php
|
$this->renderFilters();
|
$this->renderGrid();
|
$this->renderLoader();
|
$this->renderTemplates();
|
echo TaxonomySelector::outputSelectorModal();
|
?>
|
</section>
|
<?php
|
return ob_get_clean();
|
}
|
|
protected function renderFilters(): void
|
{
|
if (empty($this->config)) {
|
return;
|
}
|
|
$feedContent = $this->getFeedContent();
|
$hasMany = count($this->config['content']) > 1;
|
?>
|
<form class="feed-filters" data-save="feed-<?=$this->config['context']?>">
|
<?php if ($hasMany) {
|
//If we have multiple content, only show the content first
|
?>
|
<details class="col a-start">
|
<summary class="row btw">
|
<span class="label">SHOWING: </span>
|
<?php
|
$labels = [];
|
foreach ($this->config['content'] as $i => $type) :
|
|
$checked = $i === 0 ? ' checked' : '';
|
$label = $feedContent[$type]['plural'] ?? ucfirst($type);
|
?>
|
<input type="radio"
|
id="filter-<?= esc_attr($type) ?>"
|
class="btn"
|
name="content"
|
data-filter="content"
|
value="<?= esc_attr($type) ?>"
|
<?= $checked ?>>
|
<label for="filter-<?= esc_attr($type) ?>" title="Show <?= $label ?>" class="row">
|
<?= jvbIcon($feedContent[$type]['icon']) ?>
|
<span class="screen-reader-text"><?= $label ?></span>
|
</label>
|
<?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>
|
<?php } ?>
|
|
|
<?php if (Features::forSite()->has('favourites') && is_user_logged_in()) : ?>
|
<input type="checkbox" id="favourites" class="btn" name="favourites" value="on"
|
data-filter="favourites">
|
<label for="favourites" title="Show Favourites" class="row">
|
<?= jvbIcon('heart').jvbIcon('heart', ['style' => 'fill']) ?>
|
<span class="screen-reader-text">Show Favourites Only</span>
|
</label>
|
<?php endif; ?>
|
<?php if ($hasMany) { ?>
|
</summary>
|
<?php } ?>
|
|
<div class="filters">
|
<div class="filter-group row start">
|
<span class="label">FILTER BY:</span>
|
|
<?php
|
$checker = Checker::getInstance();
|
foreach ($this->config['taxonomies'] as $tax) :
|
$taxConfig = JVB_TAXONOMY[$tax] ?? null;
|
if (!$taxConfig) continue;
|
|
$contentForTax = $checker->getContentForTaxonomy($tax);
|
$hidden = empty($contentForTax) ? ' hidden' : '';
|
|
$taxSelector = new TaxonomySelector(
|
'feed-'.$tax,
|
$tax,
|
[
|
'icon' => $taxConfig['icon']??'logo-triangle',
|
'update' => '.selected-items-section .selected-items',
|
'types' => $contentForTax,
|
'autocomplete' => false,
|
'hidden' => $hidden,
|
'output' => 'minimal'
|
]
|
);
|
echo $taxSelector->render();
|
endforeach;
|
?>
|
</div>
|
<div class="selected-items-section">
|
<div class="selected-items row"></div>
|
<div class="filter-actions row">
|
<?= jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match']) ?>
|
<button type="button" class="clear-filters row">
|
<?= jvbIcon('x') ?>
|
Clear All Filters
|
</button>
|
</div>
|
</div>
|
</div>
|
|
<div class="row btw nowrap">
|
<div class="order-by filter-group row start w-full">
|
<span class="label">ORDER BY:</span>
|
<?php
|
//TODO: Get content types that can be sorted alphabetically
|
?>
|
<input type="radio" id="order-title" class="btn" name="orderby" value="title" data-for="artist,shop" data-filter="orderby" hidden>
|
<label for="order-title" title="Order by Name" class="row">
|
<?= jvbIcon('alphabetical') ?>
|
<span class="label">Name</span>
|
</label>
|
|
<input type="radio" id="order-date" class="btn" name="orderby" value="date" data-filter="orderby" checked>
|
<label for="order-date" title="Order by Date" class="row">
|
<?= jvbIcon('calendar', ['title' => 'Date']) ?>
|
<span class="label">Date</span>
|
</label>
|
|
<input type="radio" id="order-random" class="btn" name="orderby" value="random" data-filter="orderby">
|
<label for="order-random" title="Random Order" class="row">
|
<?= jvbIcon('shuffle') ?>
|
<span class="label">Random</span>
|
</label>
|
</div>
|
|
<div class="order-direction filter-group row start w-full" data-for-order="date,title">
|
<span class="label">ORDER:</span>
|
<input type="radio" id="order-desc" class="btn" name="order" value="desc" data-filter="order" checked>
|
<label for="order-desc" title="Sort Descending (A-Z, 1-10)" class="row">
|
<?= jvbIcon('sort-descending') ?>
|
<span class="label" >DESC (A-Z)</span>
|
</label>
|
|
<input type="radio" id="order-asc" class="btn" name="order" value="asc" data-filter="order">
|
<label for="order-asc" title="Sort Ascending (Z-A, 10-1)" class="row">
|
<?= jvbIcon('sort-ascending') ?>
|
<span class="label" >ASC (Z-A)</span>
|
</label>
|
</div>
|
</div>
|
<?php if ($hasMany) { ?>
|
</details>
|
<?php } ?>
|
</form>
|
<?php
|
}
|
|
protected function renderGrid(): void
|
{
|
?>
|
<div class="item-grid"></div>
|
<?php
|
}
|
|
protected function renderLoader(): void
|
{
|
?>
|
<button type="button" class="load-more">
|
<?= jvbIcon('arrow-elbow-left-down') ?>
|
Show Me More
|
<?= jvbIcon('arrow-elbow-right-down') ?>
|
</button>
|
|
<?= jvbLoadingScreen() ?>
|
<?php
|
if (array_key_exists('is_gallery', $this->config)) {
|
jvbRenderGallery();
|
}
|
}
|
|
protected function renderTemplates(): void
|
{
|
echo '<template class="feed-item">'.apply_filters('jvbFeedItem', '<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>
|
|
<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>', $this->config).'</template>';
|
|
echo '<template class="emptyState">'.apply_filters('jvbFeedEmptyState', '<div class="feed-empty-state">
|
<h3>NOTHING HERE...</h3>
|
<p>Try tweaking those filters a bit.</p>
|
<p>Edmonton\'s got talent - let\'s find it.</p>
|
</div>', $this->config). '</template>';
|
|
echo '<template class="placeholderTemplate"><div class="placeholder">'.apply_filters('jvbFeedPlaceholder', '').'</div></template>';
|
}
|
|
/**
|
* Get feed content using Features instead of get_option
|
* Returns array of slug => config for types that show in feed
|
*/
|
public function getFeedContent(): array
|
{
|
return JVB()->routes('feed')->getFeedTypesConfig();
|
}
|
}
|