| | |
| | | <?php |
| | | namespace JVBase\rest\routes; |
| | | |
| | | use JVBase\rest\RestRouteManager; |
| | | use JVBase\meta\Meta; |
| | | use JVBase\registrar\Registrar; |
| | | use JVBase\rest\Rest; |
| | | use JVBase\integrations\Umami; |
| | | use JVBase\meta\MetaManager; |
| | | use JVBase\managers\TaxonomyRelationships; |
| | | use JVBase\rest\Route; |
| | | use JVBase\base\Site; |
| | | 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 |
| | | |
| | | class FeedRoutes extends Rest |
| | | { |
| | | protected int $per_page = 36; |
| | | protected Umami $tracker; |
| | | protected int $per_page = 36; |
| | | protected ?Umami $tracker = null; |
| | | |
| | | public function __construct() |
| | | { |
| | | $this->cache_name = 'feed'; |
| | | $this->cache_ttl = 86400; |
| | | protected ?array $fields = null; |
| | | protected ?array $timelineSharedFields = null; |
| | | protected ?array $timelineUniqueFields = null; |
| | | |
| | | if (jvbSiteUsesUmami()) { |
| | | $this->tracker = JVB()->connect('umami'); |
| | | } |
| | | parent::__construct(); |
| | | } |
| | | public function __construct() |
| | | { |
| | | $this->cacheName = 'feed'; |
| | | $this->cacheTtl = 86400; |
| | | parent::__construct(); |
| | | $this->cache |
| | | ->connect('post', true) |
| | | ->connect('taxonomy', true) |
| | | ->connect('user', true); |
| | | |
| | | /** |
| | | * Registers feed routes |
| | | * @return void |
| | | */ |
| | | public function registerRoutes():void |
| | | { |
| | | register_rest_route($this->namespace, '/feed', [ |
| | | 'methods' => ['GET', 'POST'], |
| | | 'callback' => [$this, 'handleFeedRequest'], |
| | | 'permission_callback' => [$this, 'checkPermission'], |
| | | ]); |
| | | } |
| | | if (JVB_TESTING) { |
| | | $this->cache->flush(); |
| | | } |
| | | |
| | | /** |
| | | * Formats an item |
| | | * @param int $postID |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function formatItem(int $postID, string $type = 'post'):array |
| | | { |
| | | switch ($type) { |
| | | case 'post': |
| | | $post = get_post($postID); |
| | | $type = jvbNoBase($post->post_type); |
| | | $metaType = 'post'; |
| | | break; |
| | | default: |
| | | $post = get_term($postID, jvbCheckBase($type)); |
| | | $type = jvbNoBase($type); |
| | | $metaType = 'term'; |
| | | break; |
| | | } |
| | | if (!$post || is_wp_error($post)) { |
| | | return []; |
| | | } |
| | | $formatted = $this->cache->get($postID, $type); |
| | | // $formatted = false; |
| | | if ($formatted) { |
| | | return $formatted; |
| | | } |
| | | } |
| | | |
| | | public function init():void |
| | | { |
| | | if (Site::hasIntegration('umami')) { |
| | | $this->tracker = JVB()->connect('umami'); |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Registers feed routes |
| | | * @return void |
| | | */ |
| | | public function registerRoutes(): void |
| | | { |
| | | Route::for('feed') |
| | | ->get([$this, 'handleFeedRequest']) |
| | | ->args([ |
| | | 'content' => 'string', |
| | | 'page' => 'integer|default:1|min:1', |
| | | 'taxonomy' => 'string', |
| | | 'match' => 'string|enum:all,any|default:all', |
| | | 'orderby' => 'string', |
| | | 'order' => 'string|enum:ASC,DESC', |
| | | 'date-filter' => 'string', |
| | | 'dateFrom' => 'string', |
| | | 'dateTo' => 'string', |
| | | 'context' => 'string', |
| | | 'source' => 'string', |
| | | 'favourites' => 'boolean', |
| | | 'user' => 'integer', |
| | | 'highlight' => 'string', |
| | | ]) |
| | | ->auth('public') |
| | | ->rateLimit(30, 60) |
| | | ->post([$this, 'handleFeedRequest']) |
| | | ->args([ |
| | | 'content' => 'string', |
| | | 'page' => 'integer|default:1|min:1', |
| | | 'taxonomy' => 'string', |
| | | 'match' => 'string|enum:all,any|default:all', |
| | | 'orderby' => 'string', |
| | | 'order' => 'string|enum:ASC,DESC', |
| | | 'date-filter' => 'string', |
| | | 'dateFrom' => 'string', |
| | | 'dateTo' => 'string', |
| | | 'context' => 'string', |
| | | 'source' => 'string', |
| | | 'favourites' => 'boolean', |
| | | 'user' => 'integer', |
| | | 'highlight' => 'string', |
| | | ]) |
| | | ->auth('public') |
| | | ->rateLimit(30) |
| | | ->register(); |
| | | |
| | | // Feed types endpoint |
| | | Route::for('feed/types') |
| | | ->get([$this, 'getFeedTypes']) |
| | | ->auth('public') |
| | | ->rateLimit() |
| | | ->register(); |
| | | } |
| | | |
| | | /** |
| | | * Formats an item |
| | | * @param int $postID |
| | | * |
| | | * @return 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'; |
| | | break; |
| | | default: |
| | | $post = get_term($postID, jvbCheckBase($type)); |
| | | $type = jvbNoBase($type); |
| | | $metaType = 'term'; |
| | | break; |
| | | } |
| | | if (!$post || is_wp_error($post)) { |
| | | return []; |
| | | } |
| | | |
| | | return $this->cache->remember( |
| | | $postID, |
| | | function() use ($postID, $type, $metaType, $post, $skip) { |
| | | $registrar = null; |
| | | switch ($metaType) { |
| | | case 'post': |
| | | $registrar = Registrar::getInstance($type); |
| | | $meta = Meta::forPost($postID); |
| | | if (!$skip && $registrar->isTimeline()) { |
| | | return $this->formatTimeline($postID, $post); |
| | | } |
| | | break; |
| | | case 'term': |
| | | |
| | | $meta = Meta::forTerm($postID); |
| | | $registrar = Registrar::getInstance($type); |
| | | break; |
| | | case 'user': |
| | | $meta = Meta::forUser($postID); |
| | | $registrar = Registrar::getInstance($type); |
| | | break; |
| | | } |
| | | if (!$registrar) { |
| | | return []; |
| | | } |
| | | $fields = $registrar->getFields(); |
| | | |
| | | //Allow custom filtering for public fields |
| | | if (!empty($registrar->getConfig('feed')['fields'])) { |
| | | $fields = array_filter($fields, function($field) use ($registrar) { |
| | | return in_array($field, $registrar->getConfig('feed')['fields']); |
| | | }, ARRAY_FILTER_USE_KEY); |
| | | } |
| | | |
| | | $values = $meta->getAll(array_keys($fields)); |
| | | |
| | | $out = [ |
| | | 'fields' => $values, |
| | | ]; |
| | | |
| | | //Format Taxonomies |
| | | $out['taxonomies'] = $this->extractTaxonomies($values, $postID, $type); |
| | | |
| | | //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['fields']) && $out['fields'][$key] !== '') { |
| | | $IDs = array_map('absint', explode(',',$out['fields'][$key])); |
| | | foreach ($IDs as $ID) { |
| | | $imgIDs[$ID] = jvbImageData($ID); |
| | | } |
| | | } |
| | | } |
| | | $out['images'] = $imgIDs; |
| | | |
| | | $out['id'] = $postID; |
| | | $out['content'] = $type; |
| | | $out['icon'] = $registrar->getIcon()??jvbDefaultIcon(); |
| | | if ($out['icon'] === '') { |
| | | $out['icon'] = jvbDefaultIcon(); |
| | | } |
| | | |
| | | 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': |
| | | |
| | | $owner = $registrar->hasFeature('is_content') ? $meta->get('owner') : null; |
| | | if (!is_null($owner)) { |
| | | $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; |
| | | } |
| | | ); |
| | | } |
| | | |
| | | |
| | | $fields = apply_filters( |
| | | 'jvbFeedFields', |
| | | [], |
| | | $type |
| | | ); |
| | | |
| | | $meta = new MetaManager($postID, $metaType); |
| | | $formatted = [ |
| | | 'id' => $postID, |
| | | 'icon' => $type, |
| | | ]; |
| | | protected function initTimelineFields(string $content):void |
| | | { |
| | | $registrar = Registrar::getInstance($content); |
| | | if (!$registrar || !$registrar->hasFeature('is_timeline')){ |
| | | return; |
| | | } |
| | | $this->fields = $registrar->getFields(); |
| | | |
| | | 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); |
| | | } |
| | | 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; |
| | | } |
| | | $formatted['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), |
| | | ]); |
| | | 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); |
| | | } |
| | | 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) |
| | | ]; |
| | | } |
| | | $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'); |
| | | |
| | | } 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 |
| | | ]; |
| | | } |
| | | } |
| | | $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']; |
| | | } |
| | | if (array_key_exists('icon', $config)) { |
| | | $value['icon'] = $config['icon']; |
| | | } |
| | | $formatted[$field] = $value; |
| | | } |
| | | } |
| | | protected function formatTimeline(int $postID, WP_Post $post):array |
| | | { |
| | | if (!$this->timelineSharedFields || !$this->timelineUniqueFields){ |
| | | $this->initTimelineFields($post->post_type); |
| | | } |
| | | $item = $this->formatItem($postID, 'post', true); |
| | | //Step 1: Get the fields that apply to all posts |
| | | $mainMeta = Meta::forPost($post->ID); |
| | | $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; |
| | | } |
| | | $item['taxonomies'] = $this->extractTaxonomies($item['fields'], $postID, jvbNoBase($post->post_type)); |
| | | |
| | | protected function formatTaxonomy(WP_Term $term, int $postID, string $type) |
| | | { |
| | | return [ |
| | | 'ID' => $term->term_id, |
| | | 'title' => htmlspecialchars_decode($term->name), |
| | | 'url' => get_term_link($term->term_id, $term->taxonomy), |
| | | 'umami_click' => $this->tracker->trackTaxonomyClick($term->term_id, $term->taxonomy, [ |
| | | 'from' => $type.'_'.$postID |
| | | ]) |
| | | ]; |
| | | } |
| | | 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; |
| | | } |
| | | $subFields = []; |
| | | $images = []; |
| | | foreach ($children as $child) { |
| | | $meta = Meta::forPost($child); |
| | | $f = $meta->getAll($this->timelineUniqueFields); |
| | | $f = ['id' => $child] + $f; |
| | | $subFields[] = $f; |
| | | $item['taxonomies'] = array_merge($item['taxonomies'], $this->extractTaxonomies($f, $postID, jvbNoBase($post->post_type))); |
| | | $images[$f['post_thumbnail']] = jvbImageData((int) $f['post_thumbnail']); |
| | | } |
| | | $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)]); |
| | | |
| | | protected function getTaxonomies(int $postID, string $content):array |
| | | { |
| | | $taxonomies = jvbTaxonomiesForContent($content); |
| | | $out = []; |
| | | foreach ($taxonomies as $tax) { |
| | | $terms = get_the_terms($postID, $tax); |
| | | $t = []; |
| | | if ($terms && !is_wp_error($terms)) { |
| | | $config = jvbNoBase($tax); |
| | | $out[] = [ |
| | | '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 |
| | | ]) |
| | | ]; |
| | | }, $terms), |
| | | ]; |
| | | } |
| | | } |
| | | return $out; |
| | | } |
| | | $item['fields']['timeline'] = $subFields; |
| | | $item['images'] = $item['images'] + $images; |
| | | |
| | | |
| | | 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)) ? |
| | | BASE.$data['content'] : |
| | | BASE.array_key_first(JVB_CONTENT), |
| | | 'paged' => intval($data['page'] ?? 1), |
| | | 'posts_per_page' => $this->per_page, |
| | | ]; |
| | | if (!empty($data['context'])) { |
| | | $args = $this->applyContextFilters( |
| | | $args, |
| | | [ |
| | | 'id' => $data['source'], |
| | | 'type'=>$data['context'] |
| | | ] |
| | | ); |
| | | } |
| | | $args = $this->applyContextFilters($args, $data); |
| | | $args = $this->applyTaxonomyFilters($args, $data); |
| | | $args = $this->applyOrderFilters($args, $data); |
| | | $args = $this->applyDateFilters($args, $data); |
| | | return $item; |
| | | } |
| | | protected function extractTaxonomies(array $fields, int $postID, string $content):array { |
| | | $taxonomies = []; |
| | | foreach ($fields as $key => $value) { |
| | | if (empty($value)) { |
| | | continue; |
| | | } |
| | | |
| | | $args = $this->applyFavouritesFilter($args, $data); |
| | | $registrar = Registrar::getInstance($key); |
| | | if (!$registrar || $registrar->registrar->public === false){ |
| | | continue; |
| | | } |
| | | |
| | | return $args; |
| | | } |
| | | /** |
| | | * @param WP_REST_Request $request |
| | | * |
| | | * @return WP_REST_Response |
| | | */ |
| | | public function handleFeedRequest(WP_REST_Request $request):WP_REST_Response |
| | | { |
| | | $args = $this->buildRequestArgs($request); |
| | | $terms = array_map('absint', explode(',', $value)); |
| | | $terms = array_filter($terms); // Remove 0 values |
| | | |
| | | error_log('Final Args: '.print_r($args, true)); |
| | | if (empty($terms)) { |
| | | continue; |
| | | } |
| | | foreach($terms as $termID) { |
| | | $term = get_term($termID, jvbCheckBase($key)); |
| | | if ($term && !is_wp_error($term)) { |
| | | $taxonomies[$key][$termID] = $this->formatTaxonomy($term, $postID, $content); |
| | | } |
| | | } |
| | | } |
| | | return $taxonomies; |
| | | } |
| | | |
| | | protected function formatTaxonomy(WP_Term|int $term, int $postID, string $type) |
| | | { |
| | | return $this->cache->remember( |
| | | $term->term_id, |
| | | function () use ($term, $postID, $type) { |
| | | $base = [ |
| | | 'ID' => $term->term_id, |
| | | 'title' => html_entity_decode($term->name), |
| | | 'url' => get_term_link($term->term_id, $term->taxonomy), |
| | | ]; |
| | | if ($this->tracker) { |
| | | $base['umami_click'] =$this->tracker->trackTaxonomyClick($term->term_id, $term->taxonomy, [ |
| | | 'from' => $type . '_' . $postID |
| | | ]); |
| | | } |
| | | return $base; |
| | | } |
| | | ); |
| | | } |
| | | |
| | | protected function getAuthorData(WP_Post $post) |
| | | { |
| | | $author = $post->post_author; |
| | | $userLink = get_user_meta($author, BASE.'profile_link', true); |
| | | return $this->cache->remember( |
| | | $userLink, |
| | | function () use ($userLink, $author) { |
| | | $label = jvbUserRole($author); |
| | | $registrar = Registrar::getInstance($label); |
| | | if ($registrar) { |
| | | $label = $registrar->getSingular(); |
| | | } 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 |
| | | { |
| | | $registrar = Registrar::getInstance($content)??false; |
| | | $taxonomies = $registrar->registrar->taxonomies; |
| | | $out = []; |
| | | foreach ($taxonomies as $tax) { |
| | | $terms = get_the_terms($postID, $tax); |
| | | $t = []; |
| | | if ($terms && !is_wp_error($terms)) { |
| | | $config = Registrar::getInstance($tax); |
| | | $out[] = [ |
| | | 'icon' => $config->getIcon(), |
| | | 'title' => $config->getPlural(), |
| | | 'terms' => array_map(function ($term) use ($tax, $postID, $content) { |
| | | $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; |
| | | } |
| | | |
| | | |
| | | $key = $this->cache->generateKey($args); |
| | | $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); |
| | | return new WP_REST_Response($cached); |
| | | } |
| | | // Fetch and format items |
| | | $items = $this->fetchFeedItems($args); |
| | | protected function buildRequestArgs(WP_REST_Request $request): array |
| | | { |
| | | $data = $request->get_params(); |
| | | $args = [ |
| | | 'post_type' => (array_key_exists($data['content'], $this->buildFeedTypesConfig())) ? |
| | | jvbCheckBase($data['content']) : null, |
| | | 'paged' => intval($data['page'] ?? 1), |
| | | 'posts_per_page' => $this->per_page, |
| | | ]; |
| | | if (!empty($data['context'])) { |
| | | $args = $this->applyContextFilters( |
| | | $args, |
| | | [ |
| | | 'id' => $data['source']??'0', |
| | | 'type' => $data['context'] |
| | | ] |
| | | ); |
| | | } |
| | | if (array_key_exists('taxonomy', $data) && is_string($data['taxonomy'])) { |
| | | $data['taxonomy'] = json_decode($data['taxonomy'], true); |
| | | } |
| | | $args = $this->applyContextFilters($args, $data); |
| | | $args = $this->applyTaxonomyFilters($args, $data); |
| | | $args = $this->applyOrderFilters($args, $data); |
| | | $args = $this->applyDateFilters($args, $data); |
| | | |
| | | return $this->applyFavouritesFilter($args, $data); |
| | | } |
| | | |
| | | $ttl = (str_contains($args['orderby'], 'RAND')) ? 1800 : $this->cache_ttl; |
| | | $this->cache->set($key, $items, $ttl); |
| | | /** |
| | | * @param WP_REST_Request $request |
| | | * |
| | | * @return WP_REST_Response |
| | | */ |
| | | public function handleFeedRequest(WP_REST_Request $request): WP_REST_Response |
| | | { |
| | | $args = $this->buildRequestArgs($request); |
| | | $key = $this->cache->generateKey($args); |
| | | |
| | | if ($request->get_param('highlight')) { |
| | | $highlight = json_decode($request->get_param('highlight'), true); |
| | | $args['highlight'] = $highlight; |
| | | } |
| | | // Check HTTP cache headers first |
| | | $cache_check = $this->checkHeaders( |
| | | $request, |
| | | $key |
| | | ); |
| | | if ($cache_check) { |
| | | return $cache_check; // Returns 304 Not Modified |
| | | } |
| | | |
| | | $items['items'] = $this->processHighlightedItem($items['items'], $args); |
| | | return new WP_REST_Response($items); |
| | | } |
| | | $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); |
| | | |
| | | /** |
| | | * @param array $args Formatted Args for WP_Query |
| | | * @param array $data parsed Request Data |
| | | * |
| | | * @return array|null |
| | | */ |
| | | protected function processHighlightedItem(array $items, array $data):array |
| | | { |
| | | error_log('Data passed to processHighlightedItem:'.print_r($data, true)); |
| | | if (empty($data['highlight']??null)) { |
| | | return $items; |
| | | } |
| | | $ttl = (str_contains($args['orderby'], 'RAND')) ? 300 : $this->cacheTtl; |
| | | $this->cache->set($key, $items, $ttl); |
| | | |
| | | // Convert to array if string |
| | | if (is_string($data['highlight'])) { |
| | | $data['highlight'] = json_decode($data['highlight'], true); |
| | | } |
| | | if ($request->get_param('highlight')) { |
| | | $highlight = json_decode($request->get_param('highlight'), true); |
| | | $args['highlight'] = $highlight; |
| | | } |
| | | |
| | | // Extract key and value |
| | | $key = array_keys($data['highlight'])[0] ?? false; |
| | | $value = array_values($data['highlight'])[0] ?? false; |
| | | error_log('Highlighted item: '.$key); |
| | | error_log('Highlighted item: '.$value); |
| | | error_log('No Single Content Types: '.print_r(jvbNoSingleContentTypes(), true)); |
| | | error_log('Page: '.print_r($data['paged'], true)); |
| | | if (in_array($key, jvbNoSingleContentTypes()) && $value && $data['paged'] === 1) { |
| | | error_log('Formatted Highlighted item: '.print_r($this->formatItem($value), true)); |
| | | error_log('Items: '.print_r($items, true)); |
| | | array_unshift($items, $this->formatItem($value)); |
| | | error_log('Items after unshift: '.print_r($items, true)); |
| | | } |
| | | return $items; |
| | | } |
| | | $items['items'] = $this->processHighlightedItem($items['items'], $args); |
| | | $response = $this->success($items); |
| | | return $this->addCacheHeaders($response); |
| | | } |
| | | |
| | | /** |
| | | * @param array $args |
| | | * @param array $context |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function applyContextFilters(array $args, array $context):array |
| | | { |
| | | /** |
| | | * @param array $items Formatted Args for WP_Query |
| | | * @param array $data parsed Request Data |
| | | * |
| | | * @return array|null |
| | | */ |
| | | protected function processHighlightedItem(array $items, array $data): array |
| | | { |
| | | if (empty($data['highlight'] ?? null)) { |
| | | return $items; |
| | | } |
| | | |
| | | 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; |
| | | } |
| | | // Convert to array if string |
| | | if (is_string($data['highlight'])) { |
| | | $data['highlight'] = json_decode($data['highlight'], true); |
| | | } |
| | | |
| | | switch (true) { |
| | | case contentIsJVBUserType($context['type']): |
| | | $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())))) { |
| | | $artists = jvbGetContentUsers($context['id']); |
| | | if (!empty($artists)) { |
| | | $args['author__in'] = $artists; |
| | | } |
| | | } else { |
| | | $args['tax_query'] = [ |
| | | 'relation' => 'AND', |
| | | [ |
| | | 'taxonomy' => BASE.$context['type'], |
| | | 'terms' => $context['id'], |
| | | ] |
| | | ]; |
| | | } |
| | | break; |
| | | case taxonomy_exists(jvbCheckBase($context['type'])): |
| | | $args['tax_query'] = [ |
| | | 'relation' => 'AND', |
| | | [ |
| | | 'taxonomy' => BASE.$context['type'], |
| | | 'terms' => $context['id'], |
| | | ] |
| | | ]; |
| | | break; |
| | | } |
| | | return $args; |
| | | } |
| | | // Extract key and value |
| | | $key = array_keys($data['highlight'])[0] ?? false; |
| | | $value = array_values($data['highlight'])[0] ?? false; |
| | | if ($key && $data['paged'] === 1) { |
| | | array_unshift($items, $this->formatItem($value)); |
| | | } |
| | | return $items; |
| | | } |
| | | |
| | | /** |
| | | * @param array $args |
| | | * @param array $filters |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function applyFavouritesFilter(array $args, array $filters):array |
| | | { |
| | | if (!array_key_exists('favourites', $filters)){ |
| | | return $args; |
| | | } |
| | | error_log('Proceeding to check for favourites:'); |
| | | global $wpdb; |
| | | /** |
| | | * @param array $args |
| | | * @param array $context |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function applyContextFilters(array $args, array $context): array |
| | | { |
| | | if (!isset($context['type'])) { |
| | | return $args; |
| | | } |
| | | |
| | | // Get post types for the current filter |
| | | $post_types = explode(',', $args['post_type']); |
| | | $registrar = Registrar::getInstance($context['type']); |
| | | switch (true) { |
| | | case $registrar->hasFeature('profile_link'): |
| | | $args['author'] = (int)get_post_meta($context['id'], BASE . 'link', true); |
| | | break; |
| | | case $registrar->getType() === 'term' && $registrar->hasFeature('is_content'): |
| | | $args['post_type'] = is_array($args['post_type']) |
| | | ? $args['post_type'] |
| | | : explode(',', $args['post_type']); |
| | | |
| | | $favourites_table = $wpdb->prefix . BASE . 'favourites'; |
| | | $placeholders = implode(',', array_fill(0, count($post_types), '%s')); |
| | | error_log('CurrentUser ID: '.print_r(get_current_user_id(), true)); |
| | | $favourited_ids = $wpdb->get_col($wpdb->prepare( |
| | | "SELECT target_id FROM {$favourites_table} |
| | | WHERE user_id = %d AND type IN ($placeholders)", |
| | | array_merge( |
| | | [get_current_user_id()], |
| | | $post_types |
| | | ) |
| | | )); |
| | | // Check if filtering global feed content |
| | | if (in_array(jvbNoBase($context['type']), Registrar::getFeatured('is_content', 'term'))) { |
| | | // Global: show posts from any content type with this taxonomy |
| | | $for_content = Registrar::getInstance($context['type'])->registrar->for ?? []; |
| | | |
| | | if (empty($favourited_ids)) { |
| | | // Force empty results |
| | | $args['post__in'] = [0]; |
| | | return $args; |
| | | } |
| | | // Convert to full post types with BASE prefix |
| | | $post_types = array_map(fn($type) => jvbCheckBase($type), $for_content); |
| | | |
| | | $args['post__in'] = isset($args['post__in']) |
| | | ? array_intersect($args['post__in'], $favourited_ids) |
| | | : $favourited_ids; |
| | | // Filter to only show_feed content types |
| | | $show_feed_types = Registrar::getFeatured('show_feed', 'post'); |
| | | $args['post_type'] = array_intersect( |
| | | $post_types, |
| | | array_map(fn($type) => jvbCheckBase($type), $show_feed_types) |
| | | ); |
| | | } |
| | | |
| | | return $args; |
| | | } |
| | | // Add term to tax query |
| | | $args['tax_query'][] = [ |
| | | 'taxonomy' => jvbCheckBase($context['type']), |
| | | 'field' => 'term_id', |
| | | 'terms' => [(int)$context['id']], |
| | | ]; |
| | | break; |
| | | } |
| | | |
| | | /** |
| | | * @param array $args |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function fetchFeedItems(array $args):array |
| | | { |
| | | if (in_array($args['post_type'], jvbContentTaxonomies())) { |
| | | return $this->handleContentTaxonomies($args); |
| | | } |
| | | $args['fields'] = 'ids'; |
| | | // Get post IDs |
| | | $query = new WP_Query($args); |
| | | return $args; |
| | | } |
| | | |
| | | // Batch prefetch related data |
| | | update_meta_cache('post', $query->posts); |
| | | update_object_term_cache($query->posts, $args['post_type']); |
| | | /** |
| | | * @param array $args |
| | | * @param array $filters |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function applyFavouritesFilter(array $args, array $data): array |
| | | { |
| | | if (empty($data['favourites']) || empty($data['user'])) { |
| | | return $args; |
| | | } |
| | | |
| | | // Format regular items |
| | | $items = array_map(function ($post) { |
| | | return $this->formatItem($post); |
| | | }, $query->posts); |
| | | $user_id = (int)$data['user']; |
| | | $content = jvbNoBase($args['post_type']); |
| | | |
| | | wp_reset_postdata(); |
| | | return [ |
| | | 'items' => $items, |
| | | 'has_more' => $query->max_num_pages > $args['paged'], |
| | | 'total' => $query->found_posts |
| | | ]; |
| | | } |
| | | // Get user's favourites for this content type |
| | | $fav_key = BASE . 'favourites_' . $content; |
| | | $favourites = get_user_meta($user_id, $fav_key, true); |
| | | |
| | | protected function handleContentTaxonomies(array $args):array |
| | | { |
| | | if (empty($favourites)) { |
| | | // No favourites - return empty result |
| | | $args['post__in'] = [0]; // Will return no results |
| | | return $args; |
| | | } |
| | | |
| | | $taxonomy = jvbNoBase($args['post_type']); |
| | | global $wpdb; |
| | | $table = $wpdb->prefix.BASE.'content_'.$taxonomy; |
| | | $fav_ids = array_filter(array_map('intval', explode(',', $favourites))); |
| | | |
| | | // Check if table exists |
| | | if ($wpdb->get_var("SHOW TABLES LIKE '{$table}'") !== $table) { |
| | | return [ |
| | | 'items' => [], |
| | | 'has_more' => false, |
| | | 'total' => 0 |
| | | ]; |
| | | } |
| | | if (empty($fav_ids)) { |
| | | $args['post__in'] = [0]; |
| | | return $args; |
| | | } |
| | | |
| | | // Build the query components |
| | | $queryBuilder = $this->buildCustomTableQuery($args, $table, $taxonomy); |
| | | $args['post__in'] = $fav_ids; |
| | | $args['orderby'] = 'post__in'; // Preserve favourite order |
| | | |
| | | // Execute count query first |
| | | $total = (int) $wpdb->get_var($queryBuilder['count_query']); |
| | | return $args; |
| | | } |
| | | |
| | | // Execute main query if we have results |
| | | $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); |
| | | } |
| | | /** |
| | | * @param array $args |
| | | * |
| | | * @return array |
| | | */ |
| | | protected function fetchFeedItems(array $args): array |
| | | { |
| | | $postType = is_array($args['post_type']) ? $args['post_type'][0] : $args['post_type']; |
| | | $slug = jvbNoBase($postType); |
| | | $registrar = Registrar::getInstance($slug); |
| | | if ($registrar && $registrar->hasFeature('is_timeline')) { |
| | | $args['post_parent'] = 0; |
| | | } |
| | | if ($registrar && $registrar->hasFeature('is_content')) { |
| | | return $this->handleContentTaxonomies($args); |
| | | } |
| | | $args['fields'] = 'ids'; |
| | | // Get post IDs |
| | | $query = new WP_Query($args); |
| | | |
| | | $page = $args['paged'] ?? 1; |
| | | $per_page = $args['posts_per_page'] ?? $this->per_page; |
| | | $has_more = ($page * $per_page) < $total; |
| | | // Batch prefetch related data |
| | | update_meta_cache('post', $query->posts); |
| | | update_object_term_cache($query->posts, $args['post_type']); |
| | | |
| | | return [ |
| | | 'items' => $items, |
| | | 'has_more' => $has_more, |
| | | 'total' => $total |
| | | ]; |
| | | } |
| | | /** |
| | | * Build SQL query components for custom table |
| | | * @param array $args WP_Query style arguments |
| | | * @param string $table Table name |
| | | * @param string $taxonomy Taxonomy type |
| | | * @return array Query components |
| | | */ |
| | | protected function buildCustomTableQuery(array $args, string $table, string $taxonomy): array |
| | | { |
| | | global $wpdb; |
| | | // Format regular items |
| | | $items = array_map(fn($post) => $this->formatItem($post), $query->posts); |
| | | |
| | | $where_conditions = ['1=1']; |
| | | $joins = []; |
| | | $params = []; |
| | | wp_reset_postdata(); |
| | | return [ |
| | | 'items' => $items, |
| | | 'has_more' => $query->max_num_pages > $args['paged'], |
| | | 'total' => $query->found_posts |
| | | ]; |
| | | } |
| | | |
| | | // Handle search |
| | | if (!empty($args['s'])) { |
| | | $search = '%' . $wpdb->esc_like($args['s']) . '%'; |
| | | $where_conditions[] = "(ct.name LIKE %s OR ct.slug LIKE %s)"; |
| | | $params[] = $search; |
| | | $params[] = $search; |
| | | } |
| | | protected function handleContentTaxonomies(array $args): array |
| | | { |
| | | |
| | | // Handle context filters (e.g., filtering shops by style through relationships) |
| | | if (!empty($args['context_filter'])) { |
| | | $context_conditions = $this->buildContextConditions($args['context_filter'], $joins, $params, $taxonomy); |
| | | if (!empty($context_conditions)) { |
| | | $where_conditions[] = $context_conditions; |
| | | } |
| | | } |
| | | $taxonomy = jvbNoBase($args['post_type']); |
| | | global $wpdb; |
| | | $table = $wpdb->prefix . BASE . 'content_' . $taxonomy; |
| | | |
| | | // Handle taxonomy filters (tax_query) |
| | | if (!empty($args['tax_query'])) { |
| | | $tax_conditions = $this->buildTaxonomyConditions($args['tax_query'], $joins, $params, $taxonomy); |
| | | if (!empty($tax_conditions)) { |
| | | $where_conditions[] = $tax_conditions; |
| | | } |
| | | } |
| | | // Check if table exists |
| | | if ($wpdb->get_var("SHOW TABLES LIKE '{$table}'") !== $table) { |
| | | return [ |
| | | 'items' => [], |
| | | 'has_more' => false, |
| | | 'total' => 0 |
| | | ]; |
| | | } |
| | | |
| | | // Handle meta queries (custom fields in the table) |
| | | if (!empty($args['meta_query'])) { |
| | | $meta_conditions = $this->buildMetaConditions($args['meta_query'], $joins, $params, $taxonomy); |
| | | if (!empty($meta_conditions)) { |
| | | $where_conditions[] = $meta_conditions; |
| | | } |
| | | } |
| | | // Build the query components |
| | | $queryBuilder = $this->buildCustomTableQuery($args, $table, $taxonomy); |
| | | |
| | | // Handle date queries |
| | | if (!empty($args['date_query'])) { |
| | | $date_conditions = $this->buildDateConditions($args['date_query'], $params); |
| | | if (!empty($date_conditions)) { |
| | | $where_conditions[] = $date_conditions; |
| | | } |
| | | } |
| | | // Execute count query first |
| | | $total = (int)$wpdb->get_var($queryBuilder['count_query']); |
| | | |
| | | // Handle specific IDs |
| | | if (!empty($args['include'])) { |
| | | $placeholders = implode(',', array_fill(0, count($args['include']), '%d')); |
| | | $where_conditions[] = "ct.term_id IN ({$placeholders})"; |
| | | $params = array_merge($params, $args['include']); |
| | | } |
| | | // Execute main query if we have results |
| | | $items = []; |
| | | if ($total > 0) { |
| | | $results = $wpdb->get_results($queryBuilder['main_query'], ARRAY_A); |
| | | $items = array_map( |
| | | fn($ID) => $this->formatItem($ID['term_id'], $taxonomy), |
| | | $results |
| | | ); |
| | | } |
| | | |
| | | if (!empty($args['exclude'])) { |
| | | $placeholders = implode(',', array_fill(0, count($args['exclude']), '%d')); |
| | | $where_conditions[] = "ct.term_id NOT IN ({$placeholders})"; |
| | | $params = array_merge($params, $args['exclude']); |
| | | } |
| | | $page = $args['paged'] ?? 1; |
| | | $per_page = $args['posts_per_page'] ?? $this->per_page; |
| | | $has_more = ($page * $per_page) < $total; |
| | | |
| | | // Build ORDER BY |
| | | $order_by = $this->buildOrderBy($args, $taxonomy); |
| | | return [ |
| | | 'items' => $items, |
| | | 'has_more' => $has_more, |
| | | 'total' => $total |
| | | ]; |
| | | } |
| | | |
| | | // Build LIMIT |
| | | $page = $args['paged'] ?? 1; |
| | | $per_page = $args['posts_per_page'] ?? $this->per_page; |
| | | $offset = ($page - 1) * $per_page; |
| | | /** |
| | | * Build SQL query components for custom table |
| | | * @param array $args WP_Query style arguments |
| | | * @param string $table Table name |
| | | * @param string $taxonomy Taxonomy type |
| | | * @return array Query components |
| | | */ |
| | | protected function buildCustomTableQuery(array $args, string $table, string $taxonomy): array |
| | | { |
| | | global $wpdb; |
| | | |
| | | // Combine everything |
| | | $joins_sql = !empty($joins) ? implode(' ', $joins) : ''; |
| | | $where_sql = implode(' AND ', $where_conditions); |
| | | $where_conditions = ['1=1']; |
| | | $joins = []; |
| | | $params = []; |
| | | |
| | | $base_query = "FROM {$table} ct |
| | | // Handle search |
| | | if (!empty($args['s'])) { |
| | | $search = '%' . $wpdb->esc_like($args['s']) . '%'; |
| | | $where_conditions[] = "(ct.name LIKE %s OR ct.slug LIKE %s)"; |
| | | $params[] = $search; |
| | | $params[] = $search; |
| | | } |
| | | |
| | | // Handle context filters (e.g., filtering shops by style through relationships) |
| | | if (!empty($args['context_filter'])) { |
| | | $context_conditions = $this->buildContextConditions($args['context_filter'], $joins, $params, $taxonomy); |
| | | if (!empty($context_conditions)) { |
| | | $where_conditions[] = $context_conditions; |
| | | } |
| | | } |
| | | |
| | | // Handle taxonomy filters (tax_query) |
| | | if (!empty($args['tax_query'])) { |
| | | $tax_conditions = $this->buildTaxonomyConditions($args['tax_query'], $joins, $params, $taxonomy); |
| | | if (!empty($tax_conditions)) { |
| | | $where_conditions[] = $tax_conditions; |
| | | } |
| | | } |
| | | |
| | | // Handle meta queries (custom fields in the table) |
| | | if (!empty($args['meta_query'])) { |
| | | $meta_conditions = $this->buildMetaConditions($args['meta_query'], $joins, $params, $taxonomy); |
| | | if (!empty($meta_conditions)) { |
| | | $where_conditions[] = $meta_conditions; |
| | | } |
| | | } |
| | | |
| | | // Handle date queries |
| | | if (!empty($args['date_query'])) { |
| | | $date_conditions = $this->buildDateConditions($args['date_query'], $params); |
| | | if (!empty($date_conditions)) { |
| | | $where_conditions[] = $date_conditions; |
| | | } |
| | | } |
| | | |
| | | // Handle specific IDs |
| | | if (!empty($args['include'])) { |
| | | $placeholders = implode(',', array_fill(0, count($args['include']), '%d')); |
| | | $where_conditions[] = "ct.term_id IN ({$placeholders})"; |
| | | $params = array_merge($params, $args['include']); |
| | | } |
| | | |
| | | if (!empty($args['exclude'])) { |
| | | $placeholders = implode(',', array_fill(0, count($args['exclude']), '%d')); |
| | | $where_conditions[] = "ct.term_id NOT IN ({$placeholders})"; |
| | | $params = array_merge($params, $args['exclude']); |
| | | } |
| | | |
| | | // Build ORDER BY |
| | | $order_by = $this->buildOrderBy($args, $taxonomy); |
| | | |
| | | // Build LIMIT |
| | | $page = $args['paged'] ?? 1; |
| | | $per_page = $args['posts_per_page'] ?? $this->per_page; |
| | | $offset = ($page - 1) * $per_page; |
| | | |
| | | // Combine everything |
| | | $joins_sql = !empty($joins) ? implode(' ', $joins) : ''; |
| | | $where_sql = implode(' AND ', $where_conditions); |
| | | |
| | | $base_query = "FROM {$table} ct |
| | | LEFT JOIN {$wpdb->terms} t ON ct.term_id = t.term_id |
| | | LEFT JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id |
| | | {$joins_sql} |
| | | WHERE {$where_sql}"; |
| | | |
| | | $count_query = "SELECT COUNT(DISTINCT ct.term_id) {$base_query}"; |
| | | $count_query = "SELECT COUNT(DISTINCT ct.term_id) {$base_query}"; |
| | | |
| | | $main_query = "SELECT ct.term_id |
| | | $main_query = "SELECT ct.term_id |
| | | {$base_query} |
| | | {$order_by} |
| | | LIMIT %d OFFSET %d"; |
| | | |
| | | // Add limit parameters |
| | | $count_params = $params; |
| | | $main_params = array_merge($params, [$per_page, $offset]); |
| | | // Add limit parameters |
| | | $count_params = $params; |
| | | $main_params = array_merge($params, [$per_page, $offset]); |
| | | |
| | | return [ |
| | | 'main_query' => $wpdb->prepare($main_query, $main_params), |
| | | 'count_query' => $wpdb->prepare($count_query, $count_params) |
| | | ]; |
| | | } |
| | | return [ |
| | | 'main_query' => $wpdb->prepare($main_query, $main_params), |
| | | 'count_query' => $wpdb->prepare($count_query, $count_params) |
| | | ]; |
| | | } |
| | | |
| | | /** |
| | | * Build context-based filter conditions (e.g., shops filtered by style relationships) |
| | | * @param array $context_filter Context filter data |
| | | * @param array &$joins Reference to joins array |
| | | * @param array &$params Reference to params array |
| | | * @param string $taxonomy Current taxonomy type |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildContextConditions(array $context_filter, array &$joins, array &$params, string $taxonomy): string |
| | | { |
| | | global $wpdb; |
| | | /** |
| | | * Build context-based filter conditions (e.g., shops filtered by style relationships) |
| | | * @param array $context_filter Context filter data |
| | | * @param array &$joins Reference to joins array |
| | | * @param array &$params Reference to params array |
| | | * @param string $taxonomy Current taxonomy type |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildContextConditions(array $context_filter, array &$joins, array &$params, string $taxonomy): string |
| | | { |
| | | global $wpdb; |
| | | |
| | | $context_type = $context_filter['type'] ?? ''; |
| | | $context_id = $context_filter['id'] ?? 0; |
| | | $context_type = $context_filter['type'] ?? ''; |
| | | $context_id = $context_filter['id'] ?? 0; |
| | | |
| | | if (empty($context_type) || empty($context_id)) { |
| | | return ''; |
| | | } |
| | | if (empty($context_type) || empty($context_id)) { |
| | | return ''; |
| | | } |
| | | |
| | | $relationships_table = $wpdb->prefix . BASE . 'taxonomy_relationships'; |
| | | $relationships_table = $wpdb->prefix . BASE . 'taxonomy_relationships'; |
| | | |
| | | switch ($context_type) { |
| | | case 'style': |
| | | case 'theme': |
| | | case 'pstyle': |
| | | // For shops filtered by style/theme through relationships |
| | | if ($taxonomy === 'shop') { |
| | | $joins[] = "INNER JOIN {$relationships_table} tr ON ct.term_id = tr.term_id"; |
| | | $where_condition = "tr.related_term_id = %d AND tr.related_taxonomy = %s"; |
| | | $params[] = $context_id; |
| | | $params[] = BASE . $context_type; |
| | | return $where_condition; |
| | | } |
| | | break; |
| | | switch ($context_type) { |
| | | case 'style': |
| | | case 'theme': |
| | | case 'pstyle': |
| | | // For shops filtered by style/theme through relationships |
| | | if ($taxonomy === 'shop') { |
| | | $joins[] = "INNER JOIN {$relationships_table} tr ON ct.term_id = tr.term_id"; |
| | | $where_condition = "tr.related_term_id = %d AND tr.related_taxonomy = %s"; |
| | | $params[] = $context_id; |
| | | $params[] = BASE . $context_type; |
| | | return $where_condition; |
| | | } |
| | | break; |
| | | |
| | | case 'city': |
| | | // Filter by city |
| | | if (isset($this->getCustomTableFields($taxonomy)['city'])) { |
| | | $params[] = $context_id; |
| | | return "ct.city = %d"; |
| | | } |
| | | break; |
| | | } |
| | | case 'city': |
| | | // Filter by city |
| | | if (isset($this->getCustomTableFields($taxonomy)['city'])) { |
| | | $params[] = $context_id; |
| | | return "ct.city = %d"; |
| | | } |
| | | break; |
| | | } |
| | | |
| | | return ''; |
| | | } |
| | | return ''; |
| | | } |
| | | |
| | | /** |
| | | * Build taxonomy filter conditions for custom table |
| | | * @param array $tax_query Tax query array |
| | | * @param array &$joins Reference to joins array |
| | | * @param array &$params Reference to params array |
| | | * @param string $taxonomy Current taxonomy type |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildTaxonomyConditions(array $tax_query, array &$joins, array &$params, string $taxonomy): string |
| | | { |
| | | global $wpdb; |
| | | /** |
| | | * Build taxonomy filter conditions for custom table |
| | | * @param array $tax_query Tax query array |
| | | * @param array &$joins Reference to joins array |
| | | * @param array &$params Reference to params array |
| | | * @param string $taxonomy Current taxonomy type |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildTaxonomyConditions(array $tax_query, array &$joins, array &$params, string $taxonomy): string |
| | | { |
| | | global $wpdb; |
| | | |
| | | $conditions = []; |
| | | $relation = $tax_query['relation'] ?? 'AND'; |
| | | $conditions = []; |
| | | $relation = $tax_query['relation'] ?? 'AND'; |
| | | |
| | | foreach ($tax_query as $key => $query) { |
| | | if ($key === 'relation' || !is_array($query)) { |
| | | continue; |
| | | } |
| | | foreach ($tax_query as $key => $query) { |
| | | if ($key === 'relation' || !is_array($query)) { |
| | | continue; |
| | | } |
| | | |
| | | $query_taxonomy = $query['taxonomy'] ?? ''; |
| | | $terms = (array)($query['terms'] ?? []); |
| | | $field = $query['field'] ?? 'term_id'; |
| | | $operator = $query['operator'] ?? 'IN'; |
| | | $query_taxonomy = $query['taxonomy'] ?? ''; |
| | | $terms = (array)($query['terms'] ?? []); |
| | | $field = $query['field'] ?? 'term_id'; |
| | | $operator = $query['operator'] ?? 'IN'; |
| | | |
| | | if (empty($query_taxonomy) || empty($terms)) { |
| | | continue; |
| | | } |
| | | if (empty($query_taxonomy) || empty($terms)) { |
| | | continue; |
| | | } |
| | | |
| | | // Check if this taxonomy field exists in our custom table |
| | | $custom_fields = $this->getCustomTableFields($taxonomy); |
| | | $taxonomy_clean = str_replace(BASE, '', $query_taxonomy); |
| | | // Check if this taxonomy field exists in our custom table |
| | | $custom_fields = $this->getCustomTableFields($taxonomy); |
| | | $taxonomy_clean = str_replace(BASE, '', $query_taxonomy); |
| | | |
| | | if (isset($custom_fields[$taxonomy_clean])) { |
| | | // Field exists in custom table - direct query |
| | | $field_column = "ct.{$taxonomy_clean}"; |
| | | if (isset($custom_fields[$taxonomy_clean])) { |
| | | // Field exists in custom table - direct query |
| | | $field_column = "ct.{$taxonomy_clean}"; |
| | | |
| | | if ($field === 'slug') { |
| | | // Need to convert slugs to IDs first |
| | | $term_ids = []; |
| | | foreach ($terms as $slug) { |
| | | $term = get_term_by('slug', $slug, $query_taxonomy); |
| | | if ($term) { |
| | | $term_ids[] = $term->term_id; |
| | | } |
| | | } |
| | | $terms = $term_ids; |
| | | } |
| | | if ($field === 'slug') { |
| | | // Need to convert slugs to IDs first |
| | | $term_ids = []; |
| | | foreach ($terms as $slug) { |
| | | $term = get_term_by('slug', $slug, $query_taxonomy); |
| | | if ($term) { |
| | | $term_ids[] = $term->term_id; |
| | | } |
| | | } |
| | | $terms = $term_ids; |
| | | } |
| | | |
| | | if (!empty($terms)) { |
| | | $placeholders = implode(',', array_fill(0, count($terms), '%d')); |
| | | $conditions[] = "{$field_column} {$operator} ({$placeholders})"; |
| | | $params = array_merge($params, $terms); |
| | | } |
| | | } else { |
| | | // Need to join with term relationships |
| | | $join_alias = "tr_{$key}"; |
| | | $joins[] = "LEFT JOIN {$wpdb->term_relationships} {$join_alias} ON ct.term_id = {$join_alias}.object_id"; |
| | | $joins[] = "LEFT JOIN {$wpdb->term_taxonomy} tt_{$key} ON {$join_alias}.term_taxonomy_id = tt_{$key}.term_taxonomy_id"; |
| | | if (!empty($terms)) { |
| | | $placeholders = implode(',', array_fill(0, count($terms), '%d')); |
| | | $conditions[] = "{$field_column} {$operator} ({$placeholders})"; |
| | | $params = array_merge($params, $terms); |
| | | } |
| | | } else { |
| | | // Need to join with term relationships |
| | | $join_alias = "tr_{$key}"; |
| | | $joins[] = "LEFT JOIN {$wpdb->term_relationships} {$join_alias} ON ct.term_id = {$join_alias}.object_id"; |
| | | $joins[] = "LEFT JOIN {$wpdb->term_taxonomy} tt_{$key} ON {$join_alias}.term_taxonomy_id = tt_{$key}.term_taxonomy_id"; |
| | | |
| | | if ($field === 'slug') { |
| | | $joins[] = "LEFT JOIN {$wpdb->terms} t_{$key} ON tt_{$key}.term_id = t_{$key}.term_id"; |
| | | $field_column = "t_{$key}.slug"; |
| | | $term_values = $terms; // Use slugs directly |
| | | } else { |
| | | $field_column = "tt_{$key}.term_id"; |
| | | $term_values = array_map('intval', $terms); |
| | | } |
| | | if ($field === 'slug') { |
| | | $joins[] = "LEFT JOIN {$wpdb->terms} t_{$key} ON tt_{$key}.term_id = t_{$key}.term_id"; |
| | | $field_column = "t_{$key}.slug"; |
| | | $term_values = $terms; // Use slugs directly |
| | | } else { |
| | | $field_column = "tt_{$key}.term_id"; |
| | | $term_values = array_map('intval', $terms); |
| | | } |
| | | |
| | | $placeholders = implode(',', array_fill(0, count($term_values), $field === 'slug' ? '%s' : '%d')); |
| | | $taxonomy_condition = "tt_{$key}.taxonomy = %s"; |
| | | $terms_condition = "{$field_column} {$operator} ({$placeholders})"; |
| | | $placeholders = implode(',', array_fill(0, count($term_values), $field === 'slug' ? '%s' : '%d')); |
| | | $taxonomy_condition = "tt_{$key}.taxonomy = %s"; |
| | | $terms_condition = "{$field_column} {$operator} ({$placeholders})"; |
| | | |
| | | $conditions[] = "({$taxonomy_condition} AND {$terms_condition})"; |
| | | $params[] = $query_taxonomy; |
| | | $params = array_merge($params, $term_values); |
| | | } |
| | | } |
| | | $conditions[] = "({$taxonomy_condition} AND {$terms_condition})"; |
| | | $params[] = $query_taxonomy; |
| | | $params = array_merge($params, $term_values); |
| | | } |
| | | } |
| | | |
| | | return !empty($conditions) ? '(' . implode(" {$relation} ", $conditions) . ')' : ''; |
| | | } |
| | | return !empty($conditions) ? '(' . implode(" {$relation} ", $conditions) . ')' : ''; |
| | | } |
| | | |
| | | /** |
| | | * Build meta query conditions for custom table fields |
| | | * @param array $meta_query Meta query array |
| | | * @param array &$joins Reference to joins array |
| | | * @param array &$params Reference to params array |
| | | * @param string $taxonomy Taxonomy type |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildMetaConditions(array $meta_query, array &$joins, array &$params, string $taxonomy): string |
| | | { |
| | | global $wpdb; |
| | | $conditions = []; |
| | | $relation = $meta_query['relation'] ?? 'AND'; |
| | | /** |
| | | * Build meta query conditions for custom table fields |
| | | * @param array $meta_query Meta query array |
| | | * @param array &$joins Reference to joins array |
| | | * @param array &$params Reference to params array |
| | | * @param string $taxonomy Taxonomy type |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildMetaConditions(array $meta_query, array &$joins, array &$params, string $taxonomy): string |
| | | { |
| | | global $wpdb; |
| | | $conditions = []; |
| | | $relation = $meta_query['relation'] ?? 'AND'; |
| | | |
| | | // Get fields for this taxonomy to know which are in the custom table |
| | | $custom_fields = $this->getCustomTableFields($taxonomy); |
| | | // Get fields for this taxonomy to know which are in the custom table |
| | | $custom_fields = $this->getCustomTableFields($taxonomy); |
| | | |
| | | foreach ($meta_query as $key => $query) { |
| | | if ($key === 'relation' || !is_array($query)) { |
| | | continue; |
| | | } |
| | | foreach ($meta_query as $key => $query) { |
| | | if ($key === 'relation' || !is_array($query)) { |
| | | continue; |
| | | } |
| | | |
| | | $meta_key = $query['key'] ?? ''; |
| | | $meta_value = $query['value'] ?? ''; |
| | | $compare = $query['compare'] ?? '='; |
| | | $meta_key = $query['key'] ?? ''; |
| | | $meta_value = $query['value'] ?? ''; |
| | | $compare = $query['compare'] ?? '='; |
| | | |
| | | if (empty($meta_key)) { |
| | | continue; |
| | | } |
| | | if (empty($meta_key)) { |
| | | continue; |
| | | } |
| | | |
| | | // Remove BASE prefix if present |
| | | $clean_key = str_replace(BASE, '', $meta_key); |
| | | // Remove BASE prefix if present |
| | | $clean_key = str_replace(BASE, '', $meta_key); |
| | | |
| | | // Check if this field exists in our custom table |
| | | if (isset($custom_fields[$clean_key])) { |
| | | // Field is in custom table, query directly |
| | | $column = "ct.{$clean_key}"; |
| | | $condition = $this->buildMetaComparison($column, $meta_value, $compare, $params); |
| | | if ($condition) { |
| | | $conditions[] = $condition; |
| | | } |
| | | } else { |
| | | // Field is in term meta, need to join |
| | | $join_alias = "tm_{$key}"; |
| | | $joins[] = "LEFT JOIN {$wpdb->termmeta} {$join_alias} ON ct.term_id = {$join_alias}.term_id AND {$join_alias}.meta_key = %s"; |
| | | $params[] = $meta_key; |
| | | // Check if this field exists in our custom table |
| | | if (isset($custom_fields[$clean_key])) { |
| | | // Field is in custom table, query directly |
| | | $column = "ct.{$clean_key}"; |
| | | $condition = $this->buildMetaComparison($column, $meta_value, $compare, $params); |
| | | if ($condition) { |
| | | $conditions[] = $condition; |
| | | } |
| | | } else { |
| | | // Field is in term meta, need to join |
| | | $join_alias = "tm_{$key}"; |
| | | $joins[] = "LEFT JOIN {$wpdb->termmeta} {$join_alias} ON ct.term_id = {$join_alias}.term_id AND {$join_alias}.meta_key = %s"; |
| | | $params[] = $meta_key; |
| | | |
| | | $column = "{$join_alias}.meta_value"; |
| | | $condition = $this->buildMetaComparison($column, $meta_value, $compare, $params); |
| | | if ($condition) { |
| | | $conditions[] = $condition; |
| | | } |
| | | } |
| | | } |
| | | $column = "{$join_alias}.meta_value"; |
| | | $condition = $this->buildMetaComparison($column, $meta_value, $compare, $params); |
| | | if ($condition) { |
| | | $conditions[] = $condition; |
| | | } |
| | | } |
| | | } |
| | | |
| | | return !empty($conditions) ? '(' . implode(" {$relation} ", $conditions) . ')' : ''; |
| | | } |
| | | return !empty($conditions) ? '(' . implode(" {$relation} ", $conditions) . ')' : ''; |
| | | } |
| | | |
| | | /** |
| | | * Build comparison condition for meta fields |
| | | * @param string $column Column name |
| | | * @param mixed $value Value to compare |
| | | * @param string $compare Comparison operator |
| | | * @param array &$params Reference to params array |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildMetaComparison(string $column, $value, string $compare, array &$params): string |
| | | { |
| | | switch (strtoupper($compare)) { |
| | | case 'LIKE': |
| | | $params[] = '%' . $value . '%'; |
| | | return "{$column} LIKE %s"; |
| | | /** |
| | | * Build comparison condition for meta fields |
| | | * @param string $column Column name |
| | | * @param mixed $value Value to compare |
| | | * @param string $compare Comparison operator |
| | | * @param array &$params Reference to params array |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildMetaComparison(string $column, $value, string $compare, array &$params): string |
| | | { |
| | | switch (strtoupper($compare)) { |
| | | case 'LIKE': |
| | | $params[] = '%' . $value . '%'; |
| | | return "{$column} LIKE %s"; |
| | | |
| | | case 'NOT LIKE': |
| | | $params[] = '%' . $value . '%'; |
| | | return "{$column} NOT LIKE %s"; |
| | | case 'NOT LIKE': |
| | | $params[] = '%' . $value . '%'; |
| | | return "{$column} NOT LIKE %s"; |
| | | |
| | | case 'IN': |
| | | if (is_array($value)) { |
| | | $placeholders = implode(',', array_fill(0, count($value), '%s')); |
| | | $params = array_merge($params, $value); |
| | | return "{$column} IN ({$placeholders})"; |
| | | } |
| | | break; |
| | | case 'IN': |
| | | if (is_array($value)) { |
| | | $placeholders = implode(',', array_fill(0, count($value), '%s')); |
| | | $params = array_merge($params, $value); |
| | | return "{$column} IN ({$placeholders})"; |
| | | } |
| | | break; |
| | | |
| | | case 'NOT IN': |
| | | if (is_array($value)) { |
| | | $placeholders = implode(',', array_fill(0, count($value), '%s')); |
| | | $params = array_merge($params, $value); |
| | | return "{$column} NOT IN ({$placeholders})"; |
| | | } |
| | | break; |
| | | case 'NOT IN': |
| | | if (is_array($value)) { |
| | | $placeholders = implode(',', array_fill(0, count($value), '%s')); |
| | | $params = array_merge($params, $value); |
| | | return "{$column} NOT IN ({$placeholders})"; |
| | | } |
| | | break; |
| | | |
| | | case 'BETWEEN': |
| | | if (is_array($value) && count($value) === 2) { |
| | | $params[] = $value[0]; |
| | | $params[] = $value[1]; |
| | | return "{$column} BETWEEN %s AND %s"; |
| | | } |
| | | break; |
| | | case 'BETWEEN': |
| | | if (is_array($value) && count($value) === 2) { |
| | | $params[] = $value[0]; |
| | | $params[] = $value[1]; |
| | | return "{$column} BETWEEN %s AND %s"; |
| | | } |
| | | break; |
| | | |
| | | case '!=': |
| | | case '<>': |
| | | $params[] = $value; |
| | | return "{$column} != %s"; |
| | | case '!=': |
| | | case '<>': |
| | | $params[] = $value; |
| | | return "{$column} != %s"; |
| | | |
| | | case '>': |
| | | $params[] = $value; |
| | | return "{$column} > %s"; |
| | | case '>': |
| | | $params[] = $value; |
| | | return "{$column} > %s"; |
| | | |
| | | case '>=': |
| | | $params[] = $value; |
| | | return "{$column} >= %s"; |
| | | case '>=': |
| | | $params[] = $value; |
| | | return "{$column} >= %s"; |
| | | |
| | | case '<': |
| | | $params[] = $value; |
| | | return "{$column} < %s"; |
| | | case '<': |
| | | $params[] = $value; |
| | | return "{$column} < %s"; |
| | | |
| | | case '<=': |
| | | $params[] = $value; |
| | | return "{$column} <= %s"; |
| | | case '<=': |
| | | $params[] = $value; |
| | | return "{$column} <= %s"; |
| | | |
| | | case '=': |
| | | default: |
| | | $params[] = $value; |
| | | return "{$column} = %s"; |
| | | } |
| | | case '=': |
| | | default: |
| | | $params[] = $value; |
| | | return "{$column} = %s"; |
| | | } |
| | | |
| | | return ''; |
| | | } |
| | | return ''; |
| | | } |
| | | |
| | | /** |
| | | * Build date query conditions |
| | | * @param array $date_query Date query array |
| | | * @param array &$params Reference to params array |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildDateConditions(array $date_query, array &$params): string |
| | | { |
| | | $conditions = []; |
| | | /** |
| | | * Build date query conditions |
| | | * @param array $date_query Date query array |
| | | * @param array &$params Reference to params array |
| | | * @return string SQL condition |
| | | */ |
| | | protected function buildDateConditions(array $date_query, array &$params): string |
| | | { |
| | | $conditions = []; |
| | | |
| | | foreach ($date_query as $query) { |
| | | if (!is_array($query)) continue; |
| | | foreach ($date_query as $query) { |
| | | if (!is_array($query)) continue; |
| | | |
| | | $column = $query['column'] ?? 'updated_at'; |
| | | $year = $query['year'] ?? null; |
| | | $month = $query['month'] ?? null; |
| | | $day = $query['day'] ?? null; |
| | | $after = $query['after'] ?? null; |
| | | $before = $query['before'] ?? null; |
| | | $column = $query['column'] ?? 'updated_at'; |
| | | $year = $query['year'] ?? null; |
| | | $month = $query['month'] ?? null; |
| | | $day = $query['day'] ?? null; |
| | | $after = $query['after'] ?? null; |
| | | $before = $query['before'] ?? null; |
| | | |
| | | if ($year) { |
| | | $params[] = $year; |
| | | $conditions[] = "YEAR(ct.{$column}) = %d"; |
| | | } |
| | | if ($year) { |
| | | $params[] = $year; |
| | | $conditions[] = "YEAR(ct.{$column}) = %d"; |
| | | } |
| | | |
| | | if ($month) { |
| | | $params[] = $month; |
| | | $conditions[] = "MONTH(ct.{$column}) = %d"; |
| | | } |
| | | if ($month) { |
| | | $params[] = $month; |
| | | $conditions[] = "MONTH(ct.{$column}) = %d"; |
| | | } |
| | | |
| | | if ($day) { |
| | | $params[] = $day; |
| | | $conditions[] = "DAY(ct.{$column}) = %d"; |
| | | } |
| | | if ($day) { |
| | | $params[] = $day; |
| | | $conditions[] = "DAY(ct.{$column}) = %d"; |
| | | } |
| | | |
| | | if ($after) { |
| | | $params[] = $after; |
| | | $conditions[] = "ct.{$column} > %s"; |
| | | } |
| | | if ($after) { |
| | | $params[] = $after; |
| | | $conditions[] = "ct.{$column} > %s"; |
| | | } |
| | | |
| | | if ($before) { |
| | | $params[] = $before; |
| | | $conditions[] = "ct.{$column} < %s"; |
| | | } |
| | | } |
| | | if ($before) { |
| | | $params[] = $before; |
| | | $conditions[] = "ct.{$column} < %s"; |
| | | } |
| | | } |
| | | |
| | | return !empty($conditions) ? '(' . implode(' AND ', $conditions) . ')' : ''; |
| | | } |
| | | return !empty($conditions) ? '(' . implode(' AND ', $conditions) . ')' : ''; |
| | | } |
| | | |
| | | /** |
| | | * Build ORDER BY clause |
| | | * @param array $args Query arguments |
| | | * @param string $taxonomy Taxonomy type |
| | | * @return string ORDER BY clause |
| | | */ |
| | | protected function buildOrderBy(array $args, string $taxonomy): string |
| | | { |
| | | $orderby = $args['orderby'] ?? 'name'; |
| | | $order = $args['order'] ?? 'ASC'; |
| | | /** |
| | | * Build ORDER BY clause |
| | | * @param array $args Query arguments |
| | | * @param string $taxonomy Taxonomy type |
| | | * @return string ORDER BY clause |
| | | */ |
| | | protected function buildOrderBy(array $args, string $taxonomy): string |
| | | { |
| | | $orderby = $args['orderby'] ?? 'name'; |
| | | $order = $args['order'] ?? 'ASC'; |
| | | |
| | | // Validate order direction |
| | | if (!in_array($order, ['ASC', 'DESC'])) { |
| | | $order = 'ASC'; |
| | | } |
| | | // Validate order direction |
| | | if (!in_array($order, ['ASC', 'DESC'])) { |
| | | $order = 'ASC'; |
| | | } |
| | | |
| | | if (str_contains($orderby, 'RAND')) { |
| | | return "ORDER BY {$orderby}"; |
| | | } |
| | | if (str_contains($orderby, 'RAND')) { |
| | | return "ORDER BY {$orderby}"; |
| | | } |
| | | |
| | | switch ($orderby) { |
| | | case 'name': |
| | | return "ORDER BY ct.name {$order}"; |
| | | switch ($orderby) { |
| | | case 'name': |
| | | return "ORDER BY ct.name {$order}"; |
| | | |
| | | case 'count': |
| | | return "ORDER BY tt.count {$order}"; |
| | | case 'count': |
| | | return "ORDER BY tt.count {$order}"; |
| | | |
| | | case 'term_id': |
| | | case 'id': |
| | | return "ORDER BY ct.term_id {$order}"; |
| | | case 'term_id': |
| | | case 'id': |
| | | return "ORDER BY ct.term_id {$order}"; |
| | | |
| | | case 'slug': |
| | | return "ORDER BY t.slug {$order}"; |
| | | case 'slug': |
| | | return "ORDER BY t.slug {$order}"; |
| | | |
| | | case 'date': |
| | | case 'updated': |
| | | return "ORDER BY ct.updated_at {$order}"; |
| | | case 'date': |
| | | case 'updated': |
| | | return "ORDER BY ct.updated_at {$order}"; |
| | | |
| | | default: |
| | | // Check if it's a custom field in our table |
| | | $custom_fields = $this->getCustomTableFields($taxonomy); |
| | | if (isset($custom_fields[$orderby])) { |
| | | return "ORDER BY ct.{$orderby} {$order}"; |
| | | } |
| | | default: |
| | | // Check if it's a custom field in our table |
| | | $custom_fields = $this->getCustomTableFields($taxonomy); |
| | | if (isset($custom_fields[$orderby])) { |
| | | return "ORDER BY ct.{$orderby} {$order}"; |
| | | } |
| | | |
| | | // Default to name |
| | | return "ORDER BY ct.name {$order}"; |
| | | } |
| | | } |
| | | // Default to name |
| | | return "ORDER BY ct.name {$order}"; |
| | | } |
| | | } |
| | | |
| | | /** |
| | | * Get custom table fields for a taxonomy |
| | | * @param string $taxonomy Taxonomy type |
| | | * @return array Field definitions |
| | | */ |
| | | protected function getCustomTableFields(string $taxonomy): array |
| | | { |
| | | 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 = $this->success($feedTypes); |
| | | return $this->addCacheHeaders($response); |
| | | } |
| | | |
| | | public function getFeedTypesConfig():array |
| | | { |
| | | return $this->buildFeedTypesConfig(); |
| | | } |
| | | /** |
| | | * Build feed types configuration from Features |
| | | */ |
| | | protected function buildFeedTypesConfig(): array |
| | | { |
| | | return $this->cache->remember( |
| | | 'contentTypes', |
| | | function () { |
| | | $config = []; |
| | | |
| | | // Get content types with show_feed |
| | | $contentTypes = Registrar::getFeatured('show_feed', 'post'); |
| | | foreach ($contentTypes as $slug) { |
| | | $this->cache->tag('content:'.$slug); |
| | | $registrar = Registrar::getInstance($slug); |
| | | if (!$registrar) continue; |
| | | |
| | | $config[$slug] = [ |
| | | 'type' => 'content', |
| | | 'singular' => $registrar->getSingular(), |
| | | 'plural' => $registrar->getPlural(), |
| | | 'icon' => $registrar->getIcon(), |
| | | 'taxonomies' => $registrar->registrar->taxonomies, |
| | | ]; |
| | | } |
| | | |
| | | // Get taxonomies with show_feed (content taxonomies) |
| | | $taxonomies = Registrar::getFeatured('show_feed', 'term'); |
| | | foreach ($taxonomies as $slug) { |
| | | $registrar = Registrar::getInstance($slug); |
| | | if (!$registrar || !($registrar->hasFeature('is_content') ?? false)) { |
| | | continue; |
| | | } |
| | | |
| | | $this->cache->tag('taxonomy:'.$slug); |
| | | |
| | | $config[$slug] = [ |
| | | 'type' => 'taxonomy', |
| | | 'singular' => $registrar->getSingular(), |
| | | 'plural' => $registrar->getPlural(), |
| | | 'icon' => $registrar->getIcon(), |
| | | 'taxonomies' => [], // Content taxonomies don't have sub-taxonomies |
| | | 'for_content' => $registrar->registrar->for ?? [] |
| | | ]; |
| | | } |
| | | |
| | | return $config; |
| | | }); |
| | | } |
| | | } |