| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\base\Site; |
| | | use JVBase\Instance; |
| | | use JVBase\integrations\services\Umami; |
| | | use JVBase\managers\Cache; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\integrations\Umami; |
| | | use JVBase\rest\Route; |
| | | use JVBase\base\Site; |
| | | use WP_Query; |
| | | use WP_Post; |
| | | use WP_Term; |
| | | use WP_Query; |
| | | use WP_REST_Request; |
| | | use WP_REST_Response; |
| | | use WP_Term; |
| | | |
| | | if (!defined('ABSPATH')) { |
| | | exit; // Exit if accessed directly |
| | |
| | | |
| | | class FeedRoutes extends Rest |
| | | { |
| | | use Instance; |
| | | protected int $per_page = 36; |
| | | protected ?Umami $tracker = null; |
| | | |
| | |
| | | $this->cacheName = 'feed'; |
| | | $this->cacheTtl = 86400; |
| | | parent::__construct(); |
| | | $this->cache |
| | | ->connect('post', true) |
| | | ->connect('taxonomy', true) |
| | | ->connect('user', true); |
| | | $this->cache = Cache::for('feed'); |
| | | |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | |
| | | } |
| | | |
| | | public function init():void |
| | | { |
| | | if (Site::hasIntegration('umami')) { |
| | | $this->tracker = JVB()->connect('umami'); |
| | | $this->tracker = Umami::getInstance(); |
| | | } |
| | | } |
| | | |
| | |
| | | 'context' => 'string', |
| | | 'contextId' => 'string', |
| | | 'favourites' => 'boolean', |
| | | 'user' => 'integer', |
| | | 'highlight' => 'string', |
| | | ]) |
| | | ->auth('public') |
| | | ->rateLimit(30, 60) |
| | | ->rateLimit(30) |
| | | ->post([$this, 'handleFeedRequest']) |
| | | ->args([ |
| | | 'content' => 'string', |
| | |
| | | 'context' => 'string', |
| | | 'contextId' => 'string', |
| | | 'favourites' => 'boolean', |
| | | 'user' => 'integer', |
| | | 'highlight' => 'string', |
| | | ]) |
| | | ->auth('public') |
| | |
| | | } |
| | | |
| | | /** |
| | | * Formats an item |
| | | * @param int $postID |
| | | * |
| | | * @param string $type |
| | | * @param $skip |
| | | * @return array |
| | | */ |
| | | protected function formatItem(int $postID, string $type = 'post', $skip = false): array |
| | |
| | | $post = get_post($postID); |
| | | $type = jvbNoBase($post->post_type); |
| | | $metaType = 'post'; |
| | | $this->cache = Cache::for('feedPosts', 'post'); |
| | | break; |
| | | default: |
| | | $post = get_term($postID, jvbCheckBase($type)); |
| | | $type = jvbNoBase($type); |
| | | $metaType = 'term'; |
| | | $this->cache = Cache::for('feedTerms', 'term'); |
| | | break; |
| | | } |
| | | if (!$post || is_wp_error($post)) { |
| | |
| | | |
| | | protected function formatTaxonomy(WP_Term|int $term, int $postID, string $type) |
| | | { |
| | | $this->cache = Cache::for('feedTaxonomies', 'term'); |
| | | return $this->cache->remember( |
| | | $term->term_id, |
| | | function () use ($term, $postID, $type) { |
| | |
| | | |
| | | protected function getAuthorData(WP_Post $post) |
| | | { |
| | | //Connects to their profile, not user id |
| | | $this->cache = Cache::for('feedUsers', 'post'); |
| | | $author = $post->post_author; |
| | | $userLink = get_user_meta($author, BASE.'profile_link', true); |
| | | return $this->cache->remember( |
| | |
| | | protected function getTaxonomies(int $postID, string $content): array |
| | | { |
| | | $registrar = Registrar::getInstance($content)??false; |
| | | if (!$registrar) { |
| | | return []; |
| | | } |
| | | $this->cache = Cache::for('feedTaxonomyLinks', 'term'); |
| | | $taxonomies = $registrar->registrar->taxonomies; |
| | | $out = []; |
| | | foreach ($taxonomies as $tax) { |
| | |
| | | public function handleFeedRequest(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $args = $this->buildRequestArgs($request); |
| | | $key = $this->cache->generateKey($args); |
| | | |
| | | // Check HTTP cache headers first |
| | | $cache_check = $this->checkHeaders( |
| | | $request, |
| | | $key |
| | | ); |
| | | if ($cache_check) { |
| | | return $cache_check; // Returns 304 Not Modified |
| | | } |
| | | |
| | | $cached = $this->cache->get($key); |
| | | if ($cached) { |
| | | if ($request->get_param('highlight')) { |
| | | $highlight = json_decode($request->get_param('highlight'), true); |
| | | $args['highlight'] = $highlight; |
| | | } |
| | | $cached['items'] = $this->processHighlightedItem($cached['items'], $args); |
| | | $response = $this->success($cached); |
| | | return $this->addCacheHeaders($response); |
| | | } |
| | | // Fetch and format items |
| | | $items = $this->fetchFeedItems($args); |
| | | |
| | | $ttl = (str_contains($args['orderby'], 'RAND')) ? 300 : $this->cacheTtl; |
| | | $this->cache->set($key, $items, $ttl); |
| | | |
| | | if ($request->get_param('highlight')) { |
| | | $highlight = json_decode($request->get_param('highlight'), true); |
| | | $highlight = $request->get_param('highlight'); |
| | | if (!empty($highlight)) { |
| | | $highlight = json_decode($highlight, true); |
| | | $args['highlight'] = $highlight; |
| | | $items['items'] = $this->processHighlightedItem($items['items'], $args); |
| | | } |
| | | |
| | | $items['items'] = $this->processHighlightedItem($items['items'], $args); |
| | | $response = $this->success($items); |
| | | return $this->addCacheHeaders($response); |
| | | return $this->success($items); |
| | | } |
| | | |
| | | /** |
| | | * @param array $items Formatted Args for WP_Query |
| | | * @param array $data parsed Request Data |
| | | * |
| | | * @return array|null |
| | | * @return array |
| | | */ |
| | | protected function processHighlightedItem(array $items, array $data): array |
| | | { |
| | |
| | | |
| | | /** |
| | | * @param array $args |
| | | * @param array $filters |
| | | * @param array $data |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function applyFavouritesFilter(array $args, array $data): array |
| | | { |
| | | if (empty($data['favourites']) || empty($data['user'])) { |
| | | if (empty($data['favourites'])) { |
| | | return $args; |
| | | } |
| | | |
| | | $user_id = (int)$data['user']; |
| | | $user_id = get_current_user_id(); |
| | | if (!$user_id) { |
| | | //Shouldn't happen, but whatever |
| | | return $args; |
| | | } |
| | | $content = jvbNoBase($args['post_type']); |
| | | |
| | | // Get user's favourites for this content type |
| | |
| | | $args['fields'] = 'ids'; |
| | | // Get post IDs |
| | | $query = new WP_Query($args); |
| | | |
| | | // Batch prefetch related data |
| | | update_meta_cache('post', $query->posts); |
| | | update_object_term_cache($query->posts, $args['post_type']); |
| | | |
| | | // Format regular items |
| | | $items = array_map(fn($post) => $this->formatItem($post), $query->posts); |
| | | |
| | | wp_reset_postdata(); |
| | | |
| | | return [ |
| | | 'items' => $items, |
| | | 'has_more' => $query->max_num_pages > $args['paged'], |
| | |
| | | */ |
| | | 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 = $this->success($feedTypes); |
| | | return $this->addCacheHeaders($response); |
| | | return $this->success($this->buildFeedTypesConfig()); |
| | | } |
| | | |
| | | public function getFeedTypesConfig():array |
| | |
| | | // Get content types with show_feed |
| | | $contentTypes = Registrar::withFeature('show_feed', 'post'); |
| | | foreach ($contentTypes as $slug) { |
| | | $this->cache->tag('content:'.$slug); |
| | | $registrar = Registrar::getInstance($slug); |
| | | if (!$registrar) continue; |
| | | |
| | |
| | | if (!$registrar || !($registrar->hasFeature('is_content') ?? false)) { |
| | | continue; |
| | | } |
| | | |
| | | $this->cache->tag('taxonomy:'.$slug); |
| | | |
| | | $config[$slug] = [ |
| | | 'type' => 'taxonomy', |
| | | 'singular' => $registrar->getSingular(), |