| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\managers\CacheManager; |
| | | use JVBase\rest\RestRouteManager; |
| | | use JVBase\integrations\Umami; |
| | | use JVBase\meta\MetaManager; |
| | | use JVBase\managers\TaxonomyRelationships; |
| | | use JVBase\utility\Checker; |
| | | use JVBase\utility\Features; |
| | | use WP_Query; |
| | | use WP_Post; |
| | | use WP_Term; |
| | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | | } |
| | | /** |
| | | * FeedRoutes - Optimized API endpoints for the feed block |
| | | * TODO: Look at Content Routes' setup and make this more like that one; it's a bit more organized |
| | | * Or NewsRoutes |
| | | */ |
| | | |
| | | class FeedRoutes extends RestRouteManager |
| | | { |
| | | protected int $per_page = 36; |
| | | protected Umami $tracker; |
| | | protected ?Umami $tracker = null; |
| | | protected ?Checker $checker = null; |
| | | protected ?array $fields = null; |
| | | protected ?array $timelineSharedFields = null; |
| | | protected ?array $timelineUniqueFields = null; |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->cache_name = 'feed'; |
| | | $this->cache_ttl = 86400; |
| | | parent::__construct(); |
| | | } |
| | | |
| | | public function init():void |
| | | { |
| | | $this->checker = Checker::getInstance(); |
| | | |
| | | if (jvbSiteUsesUmami()) { |
| | | $this->tracker = JVB()->connect('umami'); |
| | | } |
| | | parent::__construct(); |
| | | |
| | | $this->cache->clear(); |
| | | $this->setupCacheConnections(); |
| | | } |
| | | |
| | | /** |
| | | * Set up cache connections for automatic invalidation |
| | | */ |
| | | protected function setupCacheConnections(): void |
| | | { |
| | | // Connect to all content types with show_feed |
| | | $contentTypes = Features::getTypesWithFeature('show_feed', 'content'); |
| | | foreach ($contentTypes as $type) { |
| | | CacheManager::for('feed_item_'.$type)->connectTo('post'); |
| | | $this->cache->connectTo('post', $type); |
| | | } |
| | | |
| | | // Connect to all taxonomies with show_feed |
| | | $taxonomies = Features::getTypesWithFeature('show_feed', 'taxonomy'); |
| | | foreach ($taxonomies as $tax) { |
| | | CacheManager::for('feed_item_'.$tax)->connectTo('taxonomy'); |
| | | $this->cache->connectTo('taxonomy', $tax); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | 'callback' => [$this, 'handleFeedRequest'], |
| | | 'permission_callback' => [$this, 'checkPermission'], |
| | | ]); |
| | | |
| | | register_rest_route($this->namespace, 'feed/types', [ |
| | | 'permission_callback' => [$this, 'checkPermission'], |
| | | 'methods' => 'GET', |
| | | 'callback' => [$this, 'getFeedTypes'] |
| | | ]); |
| | | } |
| | | |
| | | /** |
| | |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function formatItem(int $postID, string $type = 'post'):array |
| | | protected function formatItem(int $postID, string $type = 'post', $skip = false): array |
| | | { |
| | | switch ($type) { |
| | | case 'post': |
| | | $post = get_post($postID); |
| | | $type = jvbNoBase($post->post_type); |
| | | $metaType = 'post'; |
| | | $cache = CacheManager::for('feed_item_'.$type); |
| | | break; |
| | | default: |
| | | $post = get_term($postID, jvbCheckBase($type)); |
| | | $type = jvbNoBase($type); |
| | | $metaType = 'term'; |
| | | $cache = CacheManager::for('feed_item_'.$type); |
| | | break; |
| | | } |
| | | if (!$post || is_wp_error($post)) { |
| | | return []; |
| | | } |
| | | $formatted = $this->cache->get($postID, $type); |
| | | // $formatted = false; |
| | | if ($formatted) { |
| | | return $formatted; |
| | | |
| | | return $cache->remember($postID, |
| | | function() use ($postID, $type, $metaType, $post, $skip) { |
| | | $config = null; |
| | | switch ($metaType) { |
| | | case 'post': |
| | | $config = JVB_CONTENT[$type]; |
| | | if (!$skip && array_key_exists('is_timeline', $config) && $config['is_timeline']) { |
| | | return $this->formatTimeline($postID, $post); |
| | | } |
| | | break; |
| | | case 'term': |
| | | $config = JVB_TAXONOMY[$type]; |
| | | break; |
| | | } |
| | | if (!$config) { |
| | | return []; |
| | | } |
| | | $fields = $config['fields']; |
| | | |
| | | |
| | | $fields = apply_filters( |
| | | 'jvbFeedFields', |
| | | [], |
| | | $type |
| | | ); |
| | | //Allow custom filtering for public fields |
| | | if (array_key_exists('feed', $config) && array_key_exists('fields', $config['feed'])) { |
| | | $fields = array_filter($fields, function($field) use ($config) { |
| | | return in_array($field, $config['feed']['fields']); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | } |
| | | |
| | | $meta = new MetaManager($postID, $metaType); |
| | | $formatted = [ |
| | | 'id' => $postID, |
| | | 'icon' => $type, |
| | | $values = $meta->getAll(array_keys($fields)); |
| | | |
| | | $out = [ |
| | | 'fields' => $values, |
| | | ]; |
| | | |
| | | if (jvbSiteUsesUmami()) { |
| | | $args = ($metaType === 'post') ? [ 'owner_id' => $post->post_author] : []; |
| | | $formatted['umami_view'] = $this->tracker->trackFeedView($postID, $type, $args); |
| | | $formatted['umami_fav'] = $this->tracker->trackFavouriteToggle($postID, $type, false); |
| | | //Format Taxonomies |
| | | $temp = array_filter($fields, function($field) { |
| | | return $field['type'] === 'taxonomy'; |
| | | }); |
| | | foreach ($temp as $key => $config) { |
| | | if (array_key_exists($key, $out) && $out[$key] !== '') { |
| | | $IDs = array_map('absint', explode(',', $out[$key])); |
| | | $data = []; |
| | | $icon = JVB_TAXONOMY[$config['taxonomy']]['icon']??'triangle'; |
| | | foreach ($IDs as $ID) { |
| | | $term = get_term($ID, jvbCheckBase($config['taxonomy'])); |
| | | if ($term && !is_wp_error($term)) { |
| | | $data[$ID] = [ |
| | | 'id' => $ID, |
| | | 'icon' => $icon, |
| | | 'name' => $term->name, |
| | | 'url' => get_term_link($ID, jvbCheckBase($config['taxonomy'])), |
| | | ]; |
| | | if ($this->tracker) { |
| | | $data[$ID]['umami_click'] = $this->tracker->trackClick($ID, $config['taxonomy'], ['from' => $type.'_'.$postID]); |
| | | } |
| | | } |
| | | } |
| | | if (!empty($data)) { |
| | | $out['fields'][$key] = $data; |
| | | } |
| | | } |
| | | } |
| | | |
| | | //Add images |
| | | $imgIDs = []; |
| | | $temp = array_filter($fields, function($field) { |
| | | return in_array($field['type'], [ 'upload', 'image', 'gallery']); |
| | | }); |
| | | foreach ($temp as $key => $config) { |
| | | if (array_key_exists($key, $out) && $out[$key] !== '') { |
| | | $IDs = array_map('absint', explode(',',$out[$key])); |
| | | foreach ($IDs as $ID) { |
| | | $imgIDs[$ID] = jvbImageData($ID); |
| | | } |
| | | } |
| | | } |
| | | $out['images'] = $imgIDs; |
| | | |
| | | $out['id'] = $postID; |
| | | $out['content'] = $type; |
| | | $out['icon'] = $config['icon']??'triangle'; |
| | | |
| | | if ($this->tracker) { |
| | | $args = ($metaType === 'post') ? ['owner_id' => $post->post_author] : []; |
| | | $out['umami_view'] = $this->tracker->trackFeedView($postID, $type, $args); |
| | | $out['umami_fav'] = $this->tracker->trackFavouriteToggle($postID, $type, false); |
| | | $out['umami_click'] = $this->tracker->trackClick($postID, $type); |
| | | } |
| | | |
| | | switch ($metaType) { |
| | | case 'term': |
| | | if (jvbSiteUsesUmami()) { |
| | | $formatted['umami_click'] = $this->tracker->trackTaxonomyClick($postID, $type); |
| | | } |
| | | $owner = (in_array($type, jvbContentTaxonomies()) ? $meta->getValue('owner') : null); |
| | | if (!is_null($owner)) { |
| | | $formatted['user_id'] = $owner; |
| | | $out['user_id'] = $owner; |
| | | } |
| | | $formatted['url'] = get_term_link($postID, $type); |
| | | $out['url'] = get_term_link($postID, $type); |
| | | break; |
| | | default: |
| | | $formatted = array_merge($formatted, [ |
| | | 'date' => $post->post_date, |
| | | 'user_id' => (int)$post->post_author, |
| | | 'url' => get_the_permalink($postID), |
| | | ]); |
| | | case 'post': |
| | | $out['date'] = $post->post_date; |
| | | $out['user_id'] = (int)$post->post_author; |
| | | $out['url'] = get_the_permalink($postID); |
| | | break; |
| | | } |
| | | $order = array_keys($fields); |
| | | foreach ($fields as $field => $config) { |
| | | $value = []; |
| | | if ($field === 'umami_click') { |
| | | if ($config === 'profile') { |
| | | $formatted['umami_click'] = $this->tracker->trackProfileClick($postID, $type); |
| | | return $out; |
| | | } |
| | | if ($config === 'contentTaxonomy') { |
| | | $formatted['umami_click'] = $this->tracker->trackContentTaxonomyClick($postID, $type); |
| | | } |
| | | } else { |
| | | if (array_key_exists('field', $config)) { |
| | | if ($field === 'image' && array_key_exists('gallery', $config)) { |
| | | //Array === post types |
| | | if (is_array($config['gallery'])) { |
| | | $posts = get_posts([ |
| | | 'post_type' => array_map(function ($item) { return BASE.$item; }, $config['gallery']), |
| | | 'author' => $post->post_author, |
| | | 'posts_per_page' => 5, |
| | | 'orderby' => 'date', |
| | | 'order' => 'DESC', |
| | | ]); |
| | | $formatted['content'] = array_map(function ($content) { |
| | | return [ |
| | | 'url' => get_permalink($content->ID), |
| | | 'title' => $content->post_title, |
| | | 'image' => jvbImageData((int)get_post_thumbnail_id($content->ID)), |
| | | ]; |
| | | }, $posts); |
| | | } else { |
| | | //String === $meta |
| | | $ids = explode(',', $meta->getValue($config['gallery'])); |
| | | $formatted['content'] = array_map(function ($id) { |
| | | return [ |
| | | 'image' => jvbImageData((int)$id) |
| | | ]; |
| | | }, $ids); |
| | | } |
| | | } |
| | | switch ($config['field']) { |
| | | case 'post_author': |
| | | $author = $this->getAuthorData($post); |
| | | $value = [ |
| | | 'value' => $author['value'], |
| | | 'url' => $author['url'] |
| | | ]; |
| | | break; |
| | | case 'name': |
| | | $value = $post->name; |
| | | break; |
| | | case 'post_title': |
| | | $value = $post->post_title; |
| | | break; |
| | | case 'image': |
| | | case 'image_portrait': |
| | | case 'featured_image': |
| | | $value = $meta->getValue($config['field']); |
| | | $value = jvbImageData((int)$value); |
| | | break; |
| | | case 'top_style': |
| | | case 'city': |
| | | case 'top_theme': |
| | | $terms = explode(',', $meta->getValue($field)); |
| | | $terms = array_filter(array_map(function ($termID) use ($config, $postID, $type) { |
| | | $term = get_term($termID, jvbCheckBase($config['icon'])); |
| | | if ($term && !is_wp_error($term)) { |
| | | return $this->formatTaxonomy($term, $postID, $type); |
| | | } |
| | | return []; |
| | | }, $terms)); |
| | | $value = [ |
| | | 'terms' => $terms |
| | | ]; |
| | | break; |
| | | default: |
| | | $value = [ |
| | | 'value' => $meta->getValue($field) |
| | | ]; |
| | | ); |
| | | |
| | | } |
| | | |
| | | } elseif (array_key_exists('taxonomy', $config)) { |
| | | $terms = get_the_terms($postID, BASE.$config['taxonomy']); |
| | | if ($terms && !is_wp_error($terms)) { |
| | | $terms = array_map(function ($term) use ($postID, $type) { |
| | | return $this->formatTaxonomy($term, $postID, $type); |
| | | }, $terms); |
| | | $value = [ |
| | | 'terms' => $terms |
| | | ]; |
| | | protected function initTimelineFields(string $content):void |
| | | { |
| | | $content = jvbNoBase($content); |
| | | if (!Features::forContent($content)->has('is_timeline')){ |
| | | return; |
| | | } |
| | | $config = Features::getConfig($content); |
| | | $this->fields = $config['fields']; |
| | | |
| | | $this->timelineSharedFields = array_keys(array_filter($this->fields, function ($field) { |
| | | if (!array_key_exists('for_all', $field) || $field['for_all'] === false){ |
| | | return true; |
| | | } |
| | | return false; |
| | | })); |
| | | array_unshift($this->timelineSharedFields, 'post_thumbnail'); |
| | | array_unshift($this->timelineSharedFields, 'post_title'); |
| | | |
| | | $this->timelineUniqueFields = array_keys(array_filter($this->fields, function ($field) { |
| | | if (array_key_exists('for_all', $field) && $field['for_all'] === true) { |
| | | return true; |
| | | } |
| | | return false; |
| | | })); |
| | | } |
| | | |
| | | if (array_key_exists('label', $config)) { |
| | | $value['label'] = $config['label']; |
| | | protected function formatTimeline(int $postID, WP_Post $post):array |
| | | { |
| | | if (!$this->timelineSharedFields || !$this->timelineUniqueFields){ |
| | | $this->initTimelineFields($post->post_type); |
| | | } |
| | | if (array_key_exists('icon', $config)) { |
| | | $value['icon'] = $config['icon']; |
| | | } |
| | | $formatted[$field] = $value; |
| | | } |
| | | } |
| | | $item = $this->formatItem($postID, 'post', true); |
| | | //Step 1: Get the fields that apply to all posts |
| | | $mainMeta = new MetaManager($post->ID, 'post'); |
| | | $item['fields'] = $mainMeta->getAll($this->timelineSharedFields); |
| | | |
| | | $formatted['order'] = $order; |
| | | $this->cache->set($postID, $formatted, $type); |
| | | //Step 2: Get the fields for each individual posts |
| | | $children = get_children(['post_parent' => $post->ID, 'orderby' => 'date', 'order' => 'ASC', 'post_status' => ['publish'], 'fields'=> 'ids']); |
| | | array_unshift($children, $post->ID); |
| | | |
| | | return $formatted; |
| | | $subFields = []; |
| | | $images = []; |
| | | foreach ($children as $child) { |
| | | $meta = new MetaManager($child, 'post'); |
| | | $f = $meta->getAll($this->timelineUniqueFields); |
| | | $f = ['id' => $child] + $f; |
| | | $subFields[] = $f; |
| | | |
| | | $images[$f['post_thumbnail']] = jvbImageData((int) $f['post_thumbnail']); |
| | | } |
| | | $item['fields']['order'] = $subFields; |
| | | $item['images'] = $item['images'] + $images; |
| | | |
| | | return $item; |
| | | } |
| | | |
| | | protected function formatTaxonomy(WP_Term $term, int $postID, string $type) |
| | |
| | | ]) |
| | | ]; |
| | | } |
| | | |
| | | protected function getAuthorData(WP_Post $post) |
| | | { |
| | | $author = $this->cache->get($post->post_author, 'author_data'); |
| | |
| | | |
| | | protected function buildRequestArgs(WP_REST_Request $request):array |
| | | { |
| | | global $jvb_everything; |
| | | $data = $request->get_params(); |
| | | error_log('Feed Request: '.print_r($data, true)); |
| | | $args = [ |
| | | 'post_type' => (array_key_exists($data['content'], $jvb_everything)) ? |
| | | 'post_type' => (array_key_exists($data['content'], $this->buildFeedTypesConfig())) ? |
| | | BASE.$data['content'] : |
| | | BASE.array_key_first(JVB_CONTENT), |
| | | 'paged' => intval($data['page'] ?? 1), |
| | |
| | | $args = $this->applyContextFilters( |
| | | $args, |
| | | [ |
| | | 'id' => $data['source'], |
| | | 'id' => $data['source']??'0', |
| | | 'type'=>$data['context'] |
| | | ] |
| | | ); |
| | |
| | | $args = $this->applyDateFilters($args, $data); |
| | | |
| | | $args = $this->applyFavouritesFilter($args, $data); |
| | | error_log('Final Args: '.print_r($args, true)); |
| | | return $args; |
| | | } |
| | | |
| | | protected function applyTaxonomyFilters(array $args, array $data): array |
| | | { |
| | | if (!isset($data['taxonomy']) || empty($data['taxonomy'])) { |
| | | return $args; |
| | | } |
| | | |
| | | $taxonomyFilters = $data['taxonomy']; |
| | | |
| | | // Validate taxonomies exist and sanitize |
| | | $validFilters = []; |
| | | foreach ($taxonomyFilters as $taxonomy => $terms) { |
| | | if (!taxonomy_exists(jvbCheckBase($taxonomy))) { |
| | | continue; |
| | | } |
| | | |
| | | $validFilters[] = [ |
| | | 'taxonomy' => jvbCheckBase($taxonomy), |
| | | 'field' => 'term_id', |
| | | 'terms' => array_map('absint', (array)$terms), |
| | | 'operator' => 'IN' |
| | | ]; |
| | | } |
| | | |
| | | if (empty($validFilters)) { |
| | | return $args; |
| | | } |
| | | |
| | | // Determine relation based on match filter |
| | | $relation = ($data['match'] ?? 'all') === 'all' ? 'AND' : 'OR'; |
| | | |
| | | $args['tax_query'] = array_merge( |
| | | ['relation' => $relation], |
| | | $validFilters |
| | | ); |
| | | |
| | | return $args; |
| | | } |
| | | |
| | | /** |
| | | * @param WP_REST_Request $request |
| | | * |
| | |
| | | public function handleFeedRequest(WP_REST_Request $request):WP_REST_Response |
| | | { |
| | | $args = $this->buildRequestArgs($request); |
| | | $cacheContext = $this->buildCacheContext($args, $request); |
| | | |
| | | error_log('Final Args: '.print_r($args, true)); |
| | | |
| | | // Determine content type(s) for cache checking |
| | | $content_types = []; |
| | | if (!empty($data['content'])) { |
| | | $content_types[] = $data['content']; |
| | | } |
| | | if (!empty($data['type'])) { |
| | | $types = is_array($data['type']) ? $data['type'] : [$data['type']]; |
| | | $content_types = array_merge($content_types, $types); |
| | | } |
| | | // Check HTTP cache headers first |
| | | $cache_check = $this->checkHeaders($request, $content_types ?: ['feed']); |
| | | $cache_check = $this->checkHeaders( |
| | | $request, |
| | | $cacheContext['content_types'], |
| | | $cacheContext['additional_params'] |
| | | ); |
| | | if ($cache_check) { |
| | | return $cache_check; // Returns 304 Not Modified |
| | | } |
| | | |
| | | error_log('Feed Request Args: '.print_r($args, true)); |
| | | $key = $this->cache->generateKey($args); |
| | | $cached = $this->cache->get($key); |
| | | if ($cached) { |
| | |
| | | // Fetch and format items |
| | | $items = $this->fetchFeedItems($args); |
| | | |
| | | error_log('Feed Got items: ' .print_r($items, true)); |
| | | |
| | | |
| | | $ttl = (str_contains($args['orderby'], 'RAND')) ? 1800 : $this->cache_ttl; |
| | | $this->cache->set($key, $items, $ttl); |
| | |
| | | } |
| | | |
| | | /** |
| | | * Build cache context from query args |
| | | * Extracts content types and parameters needed for proper cache checking |
| | | * |
| | | * @param array $args Built WP_Query arguments |
| | | * @param WP_REST_Request $request Original request |
| | | * @return array Cache context with content_types and additional_params |
| | | */ |
| | | protected function buildCacheContext(array $args, WP_REST_Request $request): array |
| | | { |
| | | // Extract content types from post_type in args |
| | | $post_types = is_array($args['post_type']) |
| | | ? $args['post_type'] |
| | | : [$args['post_type']]; |
| | | |
| | | $content_types = array_map('jvbNoBase', $post_types); |
| | | $content_types[] = 'feed'; // Always include base feed type |
| | | |
| | | // Build additional params for ETag uniqueness |
| | | $additional_params = [ |
| | | 'order' => $args['orderby'] ?? 'date', |
| | | 'direction' => $args['order'] ?? 'DESC', |
| | | 'page' => $args['paged'] ?? 1, |
| | | ]; |
| | | |
| | | if ($request->get_param('favourites')) { |
| | | $additional_params['user'] = (int)$request->get_param('user'); |
| | | } |
| | | |
| | | // Include author filter if present (from context or favourites) |
| | | if (!empty($args['author'])) { |
| | | $additional_params['author'] = $args['author']; |
| | | } |
| | | |
| | | if (!empty($args['author__in'])) { |
| | | $additional_params['author__in'] = $args['author__in']; |
| | | } |
| | | |
| | | // Include taxonomy filters if present |
| | | if (!empty($args['tax_query'])) { |
| | | $tax_filters = []; |
| | | foreach ($args['tax_query'] as $key => $query) { |
| | | if ($key === 'relation' || !is_array($query)) { |
| | | continue; |
| | | } |
| | | |
| | | $taxonomy = jvbNoBase($query['taxonomy'] ?? ''); |
| | | if ($taxonomy) { |
| | | $tax_filters[$taxonomy] = $query['terms'] ?? []; |
| | | // Also add taxonomy to content_types for timestamp checking |
| | | $content_types[] = $taxonomy; |
| | | } |
| | | } |
| | | if (!empty($tax_filters)) { |
| | | $additional_params['taxonomies'] = $tax_filters; |
| | | } |
| | | } |
| | | |
| | | // Include date filters if present |
| | | if (!empty($args['date_query'])) { |
| | | $additional_params['date_filter'] = md5(serialize($args['date_query'])); |
| | | } |
| | | |
| | | // Include meta queries if present |
| | | if (!empty($args['meta_query'])) { |
| | | $additional_params['meta_filter'] = md5(serialize($args['meta_query'])); |
| | | } |
| | | |
| | | return [ |
| | | 'content_types' => array_unique($content_types), |
| | | 'additional_params' => $additional_params |
| | | ]; |
| | | } |
| | | |
| | | /** |
| | | * @param array $args Formatted Args for WP_Query |
| | | * @param array $data parsed Request Data |
| | | * |
| | |
| | | */ |
| | | protected function applyContextFilters(array $args, array $context):array |
| | | { |
| | | |
| | | error_log('Args at Context Filters: '.print_r($args, true)); |
| | | error_log('Request at Context Filters: '.print_r($context, true)); |
| | | if (!isset($context['type'])) { |
| | | return $args; |
| | | } |
| | |
| | | $args['author'] = (int)get_post_meta($context['id'], BASE.'link', true); |
| | | break; |
| | | case taxIsJVBContentTax($context['type']): |
| | | $args['post_type'] = (is_array($args['post_type'])) ? $args['post_type'] : explode(',',$args['post_type']); |
| | | if (array_intersect($args['post_type'], array_map(function ($type) { return jvbCheckBase($type); },array_keys(jvbGlobalFeedContent())))) { |
| | | $args['post_type'] = is_array($args['post_type']) |
| | | ? $args['post_type'] |
| | | : explode(',', $args['post_type']); |
| | | |
| | | // Check if filtering global feed content |
| | | $globalFeedTypes = array_map('jvbCheckBase', |
| | | array_keys(Features::getTypesWithFeature('show_feed', 'content')) |
| | | ); |
| | | |
| | | if (array_intersect($args['post_type'], $globalFeedTypes)) { |
| | | $artists = jvbGetContentUsers($context['id']); |
| | | if (!empty($artists)) { |
| | | $args['author__in'] = $artists; |
| | |
| | | global $wpdb; |
| | | |
| | | // Get post types for the current filter |
| | | $post_types = explode(',', $args['post_type']); |
| | | $post_types = is_array($args['post_type']) |
| | | ? $args['post_type'] |
| | | : [$args['post_type']]; |
| | | |
| | | $favourites_table = $wpdb->prefix . BASE . 'favourites'; |
| | | $placeholders = implode(',', array_fill(0, count($post_types), '%s')); |
| | |
| | | */ |
| | | protected function fetchFeedItems(array $args):array |
| | | { |
| | | if (in_array($args['post_type'], jvbContentTaxonomies())) { |
| | | $postType = is_array($args['post_type']) ? $args['post_type'][0] : $args['post_type']; |
| | | $slug = jvbNoBase($postType); |
| | | |
| | | if (Features::forContent($slug)->has('is_timeline')) { |
| | | $args['post_parent'] = 0; |
| | | } |
| | | if (in_array($slug, Features::getTypesWithFeature('is_content', 'taxonomy'))) { |
| | | return $this->handleContentTaxonomies($args); |
| | | } |
| | | $args['fields'] = 'ids'; |
| | |
| | | update_object_term_cache($query->posts, $args['post_type']); |
| | | |
| | | // Format regular items |
| | | $items = array_map(function ($post) { |
| | | return $this->formatItem($post); |
| | | }, $query->posts); |
| | | $items = array_map(fn($post) => $this->formatItem($post), $query->posts); |
| | | |
| | | wp_reset_postdata(); |
| | | return [ |
| | |
| | | $items = []; |
| | | if ($total > 0) { |
| | | $results = $wpdb->get_results($queryBuilder['main_query'],ARRAY_A); |
| | | $items = array_map(function ($ID) use ($taxonomy) { |
| | | return $this->formatItem($ID['term_id'], $taxonomy); |
| | | }, $results); |
| | | $items = array_map( |
| | | fn($ID) => $this->formatItem($ID['term_id'], $taxonomy), |
| | | $results |
| | | ); |
| | | } |
| | | |
| | | $page = $args['paged'] ?? 1; |
| | |
| | | 'total' => $total |
| | | ]; |
| | | } |
| | | |
| | | /** |
| | | * Build SQL query components for custom table |
| | | * @param array $args WP_Query style arguments |
| | |
| | | { |
| | | return jvbContentTaxonomiesTableFields($taxonomy)['fields'] ?? []; |
| | | } |
| | | |
| | | /** |
| | | * Get available feed types (for block editor) |
| | | * Returns structured data about content types that can be shown in feed |
| | | */ |
| | | public function getFeedTypes(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | // Check HTTP cache |
| | | $cache_check = $this->checkHeaders($request, ['feed_types']); |
| | | if ($cache_check) { |
| | | return $cache_check; |
| | | } |
| | | |
| | | $feedTypes = $this->buildFeedTypesConfig(); |
| | | |
| | | $response = new WP_REST_Response($feedTypes); |
| | | return $this->addCacheHeaders($response); |
| | | } |
| | | |
| | | public function getFeedTypesConfig():array |
| | | { |
| | | return $this->buildFeedTypesConfig(); |
| | | } |
| | | /** |
| | | * Build feed types configuration from Features |
| | | */ |
| | | protected function buildFeedTypesConfig(): array |
| | | { |
| | | if (!$this->checker) { |
| | | $this->checker = Checker::getInstance(); |
| | | } |
| | | return $this->cache->remember( |
| | | 'contentTypes', |
| | | function () { |
| | | $config = []; |
| | | |
| | | // Get content types with show_feed |
| | | $contentTypes = Features::getTypesWithFeature('show_feed', 'content'); |
| | | foreach ($contentTypes as $slug) { |
| | | $contentConfig = JVB_CONTENT[$slug] ?? null; |
| | | if (!$contentConfig) continue; |
| | | |
| | | $config[$slug] = [ |
| | | 'type' => 'content', |
| | | 'singular' => $contentConfig['singular'] ?? ucfirst($slug), |
| | | 'plural' => $contentConfig['plural'] ?? ucfirst($slug) . 's', |
| | | 'icon' => $slug, |
| | | 'taxonomies' => $this->checker->getTaxonomiesForContent($slug), |
| | | ]; |
| | | } |
| | | |
| | | // Get taxonomies with show_feed (content taxonomies) |
| | | $taxonomies = Features::getTypesWithFeature('show_feed', 'taxonomy'); |
| | | foreach ($taxonomies as $slug) { |
| | | $taxConfig = JVB_TAXONOMY[$slug] ?? null; |
| | | if (!$taxConfig || !($taxConfig['is_content'] ?? false)) { |
| | | continue; |
| | | } |
| | | |
| | | $config[$slug] = [ |
| | | 'type' => 'taxonomy', |
| | | 'singular' => $taxConfig['singular'] ?? ucfirst($slug), |
| | | 'plural' => $taxConfig['plural'] ?? ucfirst($slug) . 's', |
| | | 'icon' => $slug, |
| | | 'taxonomies' => [], // Content taxonomies don't have sub-taxonomies |
| | | 'for_content' => $taxConfig['for_content'] ?? [], |
| | | ]; |
| | | } |
| | | |
| | | return $config; |
| | | }); |
| | | } |
| | | } |