| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\managers\CacheManager; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\rest\RestRouteManager; |
| | | use JVBase\integrations\Umami; |
| | | use JVBase\meta\MetaManager; |
| | |
| | | $this->cache_name = 'feed'; |
| | | $this->cache_ttl = 86400; |
| | | parent::__construct(); |
| | | $this->cache |
| | | ->connect('post') |
| | | ->connect('taxonomy') |
| | | ->connect('user'); |
| | | |
| | | if (JVB_TESTING) { |
| | | $this->cache->clear(); |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | } |
| | |
| | | if (Features::hasIntegration('umami')) { |
| | | $this->tracker = JVB()->connect('umami'); |
| | | } |
| | | $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); |
| | | } |
| | | |
| | | } |
| | | |
| | | /** |
| | |
| | | $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 []; |
| | | } |
| | | // |
| | | // return $cache->remember($postID, |
| | | // function() use ($postID, $type, $metaType, $post, $skip) { |
| | | |
| | | return $this->cache->remember( |
| | | $postID, |
| | | function() use ($postID, $type, $metaType, $post, $skip) { |
| | | $config = null; |
| | | switch ($metaType) { |
| | | case 'post': |
| | |
| | | $out['user_id'] = $owner; |
| | | } |
| | | $out['url'] = get_term_link($postID, $type); |
| | | $out['title'] = html_entity_decode($post->name); |
| | | break; |
| | | case 'post': |
| | | $out['date'] = $post->post_date; |
| | | $out['modified'] = $post->post_modified; |
| | | $out['user_id'] = (int)$post->post_author; |
| | | $out['url'] = get_the_permalink($postID); |
| | | $out['title']= get_the_title($postID); |
| | | break; |
| | | } |
| | | // return $out; |
| | | // } |
| | | // ); |
| | | return $out; |
| | | return $out; |
| | | } |
| | | ); |
| | | } |
| | | |
| | | |
| | |
| | | $item['taxonomies'] = array_merge($item['taxonomies'], $this->extractTaxonomies($f, $postID, jvbNoBase($post->post_type))); |
| | | $images[$f['post_thumbnail']] = jvbImageData((int) $f['post_thumbnail']); |
| | | } |
| | | $item['fields']['number'] = count($children); |
| | | $item['number'] = (int)get_post_meta($post->ID,BASE.'number', true); |
| | | $item['fields']['before'] = get_post_thumbnail_id($children[0]); |
| | | $item['fields']['after'] = get_post_thumbnail_id($children[array_key_last($children)]); |
| | | |
| | | $item['fields']['timeline'] = $subFields; |
| | | $item['images'] = $item['images'] + $images; |
| | | |
| | | |
| | | return $item; |
| | | } |
| | | protected function extractTaxonomies(array $fields, int $postID, string $content):array { |
| | |
| | | |
| | | protected function formatTaxonomy(WP_Term|int $term, int $postID, string $type) |
| | | { |
| | | $cache = CacheManager::for(jvbNoBase($term->taxonomy)); |
| | | return $cache->remember( |
| | | 'feed_link_'.$term->term_id, |
| | | return $this->cache->remember( |
| | | $term->term_id, |
| | | function () use ($term, $postID, $type) { |
| | | $base = [ |
| | | 'ID' => $term->term_id, |
| | | 'title' => htmlspecialchars_decode($term->name), |
| | | 'title' => html_entity_decode($term->name), |
| | | 'url' => get_term_link($term->term_id, $term->taxonomy), |
| | | ]; |
| | | if ($this->tracker) { |
| | |
| | | |
| | | protected function getAuthorData(WP_Post $post) |
| | | { |
| | | $author = $this->cache->get($post->post_author, 'author_data'); |
| | | if (!$author) { |
| | | $author = [ |
| | | 'id' => $post->post_author, |
| | | 'label' => 'Artist', |
| | | 'value' => get_the_author_meta('display_name', $post->post_author), |
| | | 'icon' => 'artist', |
| | | 'url' => get_the_permalink(get_user_meta($post->post_author, BASE . 'link', true)), |
| | | ]; |
| | | $this->cache->set($post->post_author, $author, 'author_data'); |
| | | } |
| | | return $author; |
| | | $author = $post->post_author; |
| | | $userLink = get_user_meta($author, BASE.'link', true); |
| | | return $this->cache->remember( |
| | | $userLink, |
| | | function () use ($userLink, $author) { |
| | | $label = jvbUserRole($author); |
| | | if (array_key_exists($label, JVB_USER)) { |
| | | $label = JVB_USER[$label]['label']; |
| | | } else { |
| | | $label = 'Artist'; |
| | | } |
| | | return [ |
| | | 'id' => $userLink, |
| | | 'label' => $label, |
| | | 'value' => get_the_title($userLink), |
| | | 'icon' => 'user', |
| | | 'url' => get_the_permalink($userLink), |
| | | ]; |
| | | } |
| | | ); |
| | | } |
| | | |
| | | protected function getTaxonomies(int $postID, string $content): array |
| | |
| | | 'icon' => $config, |
| | | 'title' => JVB_TAXONOMY[$config]['plural'], |
| | | 'terms' => array_map(function ($term) use ($tax, $postID, $content) { |
| | | return [ |
| | | 'ID' => $term->term_id, |
| | | 'title' => htmlspecialchars_decode($term->name), |
| | | 'url' => get_term_link($term->term_id, $tax), |
| | | 'umami_click' => $this->tracker->trackTaxonomyClick($term->term_id, $tax, [ |
| | | 'from' => $content . '_' . $postID |
| | | ]) |
| | | ]; |
| | | $item = $this->cache->remember( |
| | | $term->term_id, |
| | | function() use ($term, $tax, $content, $postID) { |
| | | return [ |
| | | 'ID' => $term->term_id, |
| | | 'title' => html_entity_decode($term->name), |
| | | 'url' => get_term_link($term->term_id, $tax), |
| | | ]; |
| | | } |
| | | ); |
| | | $item['umami_click'] = $this->tracker->trackTaxonomyClick($term->term_id, $tax, [ |
| | | 'from' => $content.'_'.$postID |
| | | ]); |
| | | return $item; |
| | | }, $terms), |
| | | ]; |
| | | |
| | | } |
| | | } |
| | | return $out; |
| | |
| | | return $this->applyFavouritesFilter($args, $data); |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | // protected function applyTaxonomyFilters(array $args, array $data): array |
| | | // { |
| | | // if (!array_key_exists('taxonomy', $data) || 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); |
| | | $key = $this->cache->generateKey($args); |
| | | |
| | | // Check HTTP cache headers first |
| | | $cache_check = $this->checkHeaders( |
| | | $request, |
| | | $cacheContext['content_types'], |
| | | $cacheContext['additional_params'] |
| | | $key |
| | | ); |
| | | if ($cache_check) { |
| | | return $cache_check; // Returns 304 Not Modified |
| | | } |
| | | |
| | | $key = $this->cache->generateKey($args); |
| | | $cached = $this->cache->get($key); |
| | | if ($cached) { |
| | | if ($request->get_param('highlight')) { |
| | |
| | | // Fetch and format items |
| | | $items = $this->fetchFeedItems($args); |
| | | |
| | | $ttl = (str_contains($args['orderby'], 'RAND')) ? 1800 : $this->cache_ttl; |
| | | $ttl = (str_contains($args['orderby'], 'RAND')) ? 300 : $this->cache_ttl; |
| | | $this->cache->set($key, $items, $ttl); |
| | | |
| | | if ($request->get_param('highlight')) { |
| | |
| | | // Get content types with show_feed |
| | | $contentTypes = Features::getTypesWithFeature('show_feed', 'content'); |
| | | foreach ($contentTypes as $slug) { |
| | | $this->cache->tag('content:'.$slug); |
| | | $contentConfig = JVB_CONTENT[$slug] ?? null; |
| | | if (!$contentConfig) continue; |
| | | |
| | |
| | | continue; |
| | | } |
| | | |
| | | $this->cache->tag('taxonomy:'.$slug); |
| | | |
| | | $config[$slug] = [ |
| | | 'type' => 'taxonomy', |
| | | 'singular' => $taxConfig['singular'] ?? ucfirst($slug), |