cache = Cache::for('feed_block', WEEK_IN_SECONDS);
if (JVB_TESTING) {
$this->cache->flush();
}
$this->cache->flush();
add_action('init', [$this, 'registerBlock']);
}
public function registerBlock()
{
register_block_type($this->path, [
'render_callback' => [$this, 'render']
]);
}
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 = '';
return sprintf(
'',
$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 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);
$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']??[];
$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) {
$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);
}
protected function renderFiltersAndControls():string
{
return sprintf(
'
Filters %s
%s%s%s%s%s
%sClear Filters ',
$this->renderContentLabels(),
$this->renderSearch(),
$this->renderContent(),
$this->renderFilters(),
$this->renderOrderControls(),
$this->renderViewControls(),
jvbIcon('x')
);
}
protected function renderSearch():string
{
return sprintf(
'
Search:
%s
',
jvbSearch()
);
}
protected function renderContentLabels():string
{
return sprintf(
'Showing: %s ',
$this->content[0]??''
);
}
protected function renderContent():string
{
$favourites = '';
if (Site::has('favourites')) {
$favourites = sprintf(
'
%s%sShow Favourites Only ',
jvbIcon('heart'),
jvbIcon('heart', ['style' => 'fill'])
);
}
if (count($this->content) === 1) {
return empty($favourites)
? sprintf(
' ',
implode(',', $this->content)
)
: sprintf(
'
%s
',
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(
'
%s%s ',
$type,
$registrar->getSingular(),
$type,
$checked ? ' checked' : '',
$disabled ? ' disabled' : '',
$type,
$registrar->getSingular(),
jvbIcon($registrar->getIcon()),
$registrar->getSingular()
);
}, $this->content));
return sprintf(
'Showing:
%s%s
',
$content,
$favourites
);
}
protected function renderFilters():string
{
if (empty ($this->taxonomies)) {
return '';
}
$inside = implode('', array_filter(array_map(function($tax) {
$registrar = Registrar::getInstance($tax);
if (!$registrar) return '';
$current = BASE.$this->content[0];
$contentFor = $registrar->registrar->for;
$hidden = in_array($current, $contentFor) ? '' : ' hidden';
$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();
}, $this->taxonomies)));
return sprintf(
'',
$inside,
str_replace('class="toggle-text"', 'class="toggle-text" hidden', jvbRenderToggleTextField('match', 'Match', 'Filters', 'ALL', 'ANY', false, ['filter' => 'match'])),
jvbIcon('x')
);
}
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'
];
$custom = implode(',', array_map(function($ord) {
return $ord['slug'];
}, $custom));
$i = 0;
$orderby = sprintf(
'
Order by: %s
',
implode('', array_map(function ($by) use (&$i){
$checked = $i === 0 ? ' checked' : '';
$i++;
return sprintf(
'
%s%s ',
$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))
);
$order = [
[
'slug' => 'desc',
'icon' => 'sort-descending',
'label' => 'Descending (A-Z, 1-10)'
],
[
'slug' => 'asc',
'icon' => 'sort-ascending',
'label' => 'Ascending (Z-A, 10-1)'
]
];
$i = 0;
$order = sprintf(
'
Order:
%s
',
$custom === '' ? '' : ','.$custom,
implode('', array_map(function ($ord) use (&$i) {
$checked = $i=== 0 ? ' checked' : '';
$i++;
return sprintf(
'
%s
%s
',
$ord['slug'],
$ord['slug'],
$checked,
$ord['slug'],
$ord['label'],
jvbIcon($ord['icon']),
$ord['label']
);
}, $order))
);
return sprintf(
'%s%s
',
$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']
];
$i = 0;
return sprintf(
'Switch View: %s
',
implode('', array_map(function ($view) use (&$i) {
$checked = $i === 0 ? ' checked' : '';
$i++;
return sprintf(
'
%s%s
',
$view['slug'],
$view['slug'],
$view['slug'],
$checked,
$view['slug'],
$view['label'],
jvbIcon($view['icon']),
$view['label'],
);
}, $views))
);
}
protected function renderPlaceholders():string
{
$placeholders = '';
$total = count($this->content) - 1;
$icons = [];
$icon = apply_filters('jvbFeedPlaceholder', '');
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]);
}
$placeholders .= sprintf(
'%s
',
$icon
);
}
return sprintf(
'%s
',
$placeholders
);
}
protected function renderGrid():string
{
if (empty($this->args)) {
return $this->renderPlaceholders();
}
$items = $this->getItems();
$out = '';
$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 .= '
';
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(
'%sShow Me More %s ',
$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(
'%s %s ',
jvbIcon(jvbDefaultIcon()),
jvbIcon('x')
);
$defaultEmptyState = sprintf(
'
%sNOTHING HERE%s
Try tweaking those filters a bit.
',
jvbIcon(jvbDefaultIcon()),
jvbIcon(jvbDefaultIcon()),
);
$emptyState = apply_filters('jvbFeedEmptyState', $defaultEmptyState, $this->content);
$templates[] = sprintf(
'%s ',
$emptyState
);
$placeholder = apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon()));
$templates[] = sprintf(
'%s
',
$placeholder
);
return implode('', $templates);
}
protected function renderActions():string
{
return is_user_logged_in() ? sprintf(
'%sHard Refresh ',
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(
'%s%s ',
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(
'
%s%s
%s%s
',
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(
'%s ',
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(
'%s%s%s
',
$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(
'',
$ID ? get_the_permalink($ID) : '',
$img
);
/**
* Start the Details with the fields
* Plugins can modify the summary title with the 'jvbFeedItemSummary' filter
*/
$summary = sprintf(
'%s ',
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 .= ' ';
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(
'%s ',
$data,
$meta && !empty($value) ? $value : '',
);
case 'post_date':
case 'post_modified':
return sprintf(
'%s ',
$data,
$meta && !empty($value) ? date('c', strtotime($value)) : '',
$meta && !empty($value) ? date('F j, Y', strtotime($value)) : '',
);
}
return match ($fieldType) {
'date' => sprintf(
'%s ',
$data,
$meta && !empty($value) ? date('c', strtotime($value)) : '',
$meta && !empty($value) ? date('F j, Y', strtotime($value)) : '',
),
'datetime' => sprintf(
'%s ',
$data,
$meta && !empty($value) ? date('c', strtotime($value)) : '',
$meta && !empty($value) ? date('F j, Y at h:iA', strtotime($value)) : '',
),
'time' => sprintf(
'%s ',
$data,
$value,
$value
),
'upload' => empty($value) ? sprintf(
' ',
$data
) :
str_replace(' sprintf(
'',
$data,
$config['taxonomy']??$config['post_type']??$config['role']??'',
empty($value) ? sprintf('%s ',
$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(
'%s%s ',
$url,
!empty($icon) ? jvbIcon($icon) : '',
$name
);
}, explode(',', $value))))
),
default => sprintf(
'%s
',
$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);
}
}