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( '
%s%s%s%s%s
', $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
', $this->renderContentLabels(), $this->renderSearch(), $this->renderContent(), $this->renderFilters(), $this->renderOrderControls(), $this->renderViewControls(), jvbIcon('x') ); } protected function renderSearch():string { return sprintf( '', jvbSearch() ); } protected function renderContentLabels():string { return sprintf( 'Showing: %s', $this->content[0]??'' ); } protected function renderContent():string { $favourites = ''; if (Site::has('favourites')) { $favourites = sprintf( ' ', 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( ' ', $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( '
Filter By: %s
%s
', $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( ' ', $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( ' ', $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( ' ', $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( '', $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( '', 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( '', $emptyState ); $placeholder = apply_filters('jvbFeedPlaceholder', jvbIcon(jvbLogoIcon())); $templates[] = sprintf( '', $placeholder ); return implode('', $templates); } protected function renderActions():string { return is_user_logged_in() ? sprintf( '', 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( '', 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( '
', 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( '', 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( '
%s
', $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( '%s', $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); } }