id = sanitize_key($id); $this->taxonomy = jvbCheckBase($taxonomy); $this->name = str_replace(BASE, '', $taxonomy); $this->icon = new JVBIcons(); $this->cache = CacheManager::for(jvbNoBase($taxonomy), WEEK_IN_SECONDS); $this->base = $config['base'] ?? ''; $this->config = wp_parse_args($config, [ 'name' => $id, 'multiple' => true, 'max_selections'=> 0, 'hierarchical' => false, 'search' => true, 'createNew' => false, 'taxonomy' => $this->taxonomy, 'required' => false, 'group_by_family'=> false, 'placeholder' => '', 'show_examples' => false, 'show_breadcrumbs'=> true, 'expand_siblings'=> true, 'show_popularity'=> false, 'no_results' => 'Nothing here.', 'base' => $this->base, // 'association' => array(), 'common' => array(), 'types' => '', //for feed block implementation 'hidden' => false, 'label' => '', 'renderTemplates' => true, ]); add_action('wp_footer', [$this, 'outputDialog']); $tax = get_taxonomy($this->taxonomy); $this->plural = JVB_TAXONOMY[$taxonomy]['plural']; $this->taxonomyName = JVB_TAXONOMY[$taxonomy]['singular']; } /** * Render selector component * @param array $selected * * @return string */ public function render(array $selected = []):string { $this->selected = $selected; $wrapper_classes = $this->getWrapperClasses(); ob_start(); ?>
renderSelectedItems($selected); ?>
selected = $selected; $wrapper_classes = $this->getWrapperClasses(); $icon = new JVBIcons(); ob_start(); ?>
config['hidden'] ?>> renderModalContent(); ?>
get_param('query')); $page = (int)$request->get_param('page') ?: 1; $per_page = 10; $args = [ 'post_type' => BASE.'artist', 'posts_per_page' => $per_page, 'paged' => $page, 'orderby' => 'title', 'order' => 'ASC', 'post_status' => 'publish', 's' => $query ]; // If shop_id provided, exclude artists already in shop $shop_id = $request->get_param('shop_id'); if ($shop_id) { $existing_artists = get_posts([ 'post_type' => BASE.'artist', 'posts_per_page' => -1, 'fields' => 'ids', 'tax_query' => [[ 'taxonomy' => BASE.'shop', 'terms' => $shop_id ]] ]); if (!empty($existing_artists)) { $args['post__not_in'] = $existing_artists; } } $key = $this->cache->generateKey($args); $cache = $this->cache->get($key); if ($cache) { return new WP_REST_Response($cache); } $posts = get_posts($args); $results = []; foreach ($posts as $post) { $city_terms = wp_get_object_terms($post->ID, BASE.'city'); $city = !empty($city_terms) ? $city_terms[0]->name : ''; $results[] = [ 'id' => $post->ID, 'title' => $post->post_title, 'thumbnail' => get_the_post_thumbnail_url($post->ID, 'thumbnail'), 'city' => $city, 'url' => get_permalink($post->ID) ]; } $result = [ 'results' => $results, 'hasMore' => count($posts) === $per_page ]; $this->cache->set($key, $result); return new WP_REST_Response($result); } /** * Get wrapper CSS classes * @return string */ protected function getWrapperClasses():string { $classes = [ 'jvb-selector', 'selector-' . strtolower($this->name) ]; if ($this->config['multiple']) { $classes[] = 'multiple'; } if ($this->config['hierarchical']) { $classes[] = 'hierarchical'; } if ($this->config['required']) { $classes[] = 'required'; } return implode(' ', $classes); } /** * Get configuration for frontend JavaScript * @return array */ protected function getFrontendConfig(array $selected = []):array { $out = [ // 'name' => $this->config['name'], 'multiple' => $this->config['multiple'], 'maxSelections' => $this->config['max_selections'], // 'hierarchical' => $this->config['hierarchical'], 'search' => $this->config['search'], 'createNew' => (bool)$this->config['createNew'], 'required' => $this->config['required'], // 'placeholder' => $this->config['placeholder'], 'noResults' => $this->config['no_results'], // 'group_by_family' => $this->config['group_by_family'], // 'labels' => $this->getLabels(), 'base' => $this->config['base'], 'selected' => $this->getSelectedData($selected), // 'values' => $this->getAvailableTerms(), // 'hierarchy' => [], // 'breadcrumbs' => [], // 'association' => $this->config['association'] // 'common' => $this->config['common'] ]; if ($this->config['hierarchical']) { $out['hierarchy'] = $this->getTermHierarchy(); $out['breadcrumbs'] = $this->getBreadcrumbData(); } return $out; } /** * Get theme hierarchy data * @param int $parent ID of parent * * @return array */ protected function getTermHierarchy(int $parent = 0):array { $terms = get_terms([ 'taxonomy' => $this->taxonomy, 'parent' => $parent, 'hide_empty' => false ]); if (is_wp_error($terms)) { return []; } $hierarchy = []; foreach ($terms as $term) { $children = $this->getTermHierarchy($term->term_id); $hierarchy[] = [ 'id' => $term->term_id, 'name' => $term->name, 'parent' => $term->parent, 'children' => $children ]; } return $hierarchy; } /** * Get theme breadcrumb data * @return array */ protected function getBreadcrumbData():array { $breadcrumbs = []; foreach ($this->selected as $term_id => $title) { $breadcrumbs[$term_id] = $this->getTermAncestors($term_id); } return $breadcrumbs; } /** * Get term ancestors with names * @param int $term_id * * @return array */ protected function getTermAncestors(int $term_id):array { $cache = $this->cache->get('term-ancestors-'.$term_id); if ($cache) { return $cache; } $ancestors = get_ancestors($term_id, $this->taxonomy, 'taxonomy'); $path = []; foreach (array_reverse($ancestors) as $ancestor_id) { $term = get_term($ancestor_id, $this->taxonomy); if ($term && !is_wp_error($term)) { $path[] = [ 'id' => $term->term_id, 'name' => $term->name ]; } } $this->cache->set('term-ancestors-'.$term_id, $path); return $path; } /** * @return array */ protected function getAvailableTerms():array { $cache = $this->cache->get($this->taxonomy . '-terms'); if ($cache) { return $cache; } $terms = get_terms([ 'taxonomy' => $this->taxonomy, 'hide_empty' => false, 'fields' => 'id=>name' ]); $result = is_wp_error($terms) ? [] : $terms; $this->cache->set($this->taxonomy . '-terms', $result); return $result; } /** * Get text labels * @return array */ protected function getLabels():array { return [ 'search' => __('Search...', 'jvb'), 'select' => __('Select', 'jvb'), 'selected' => __('Selected', 'jvb'), 'remove' => __('Remove', 'jvb'), 'clear' => __('Clear', 'jvb'), 'createNew' => __('Create New', 'jvb'), 'loading' => __('Loading...', 'jvb'), 'saving' => __('Saving...', 'jvb'), 'parent_themes' => __('Parent Themes', 'jvb'), 'sub_themes' => __('Sub-themes', 'jvb'), 'related_themes' => __('Related Themes', 'jvb'), 'popular_combinations' => __('Popular Combinations', 'jvb'), 'view_examples' => __('View Examples', 'jvb'), 'back_to_parent' => __('Back to Parent', 'jvb') ]; } /** * Render selected items * @return void */ protected function renderSelectedItems(array $selected = []):void { if (empty($selected) && empty($this->selected)) { echo '
'; return; } $selected = empty($selected) ? $this->selected : $selected; $out = '
'; foreach ($selected as $ID) { $term = get_term((int)$ID, $this->taxonomy); if (!$term || is_wp_error($term)) { continue; } $out .= $this->renderSelectedItem($term); } $out .= '
'; echo $out; } /** * Render single selected item * @param WP_Term $term * * @return string */ protected function renderSelectedItem(WP_Term $term):string { return '
'.$term->name.'
'; } /** * Render modal content * @return void */ protected function renderModalContent():void { ?> config)): ?> taxonomy); return sprintf( __('Select %s', 'jvb'), $tax_obj ? $tax_obj->labels->name : __('Items', 'jvb') ); } /** * Get popularity label * @param int $count * * @return string */ protected function getPopularityLabel(int $count):string { switch (true) { case $count > 100: return __('Very Popular', 'jvb'); case $count > 50: return __('Popular', 'jvb'); case $count > 10: return __('Common', 'jvb'); default: return __('Unique', 'jvb'); } } /** * Render single selectable item * @param WP_Term $term * @param bool $selected * * @return void */ protected function renderSelectableItem(WP_Term $term, bool $selected = false):void { ?> > $this->get_term_examples($term->term_id), // 'popular_combinations' => $this->get_term_combinations($term->term_id), // 'related_themes' => carbon_get_term_meta($term->term_id, 'jvb_related_themes') ?: [] // ]; // } /** * Get theme examples */ // protected function get_term_examples($term_id, $limit = 4) { // $examples = []; // $posts = get_posts([ // 'post_type' => "e_{$this->context}", // 'posts_per_page' => $limit, // 'tax_query' => [[ // 'taxonomy' => $this->taxonomy, // 'terms' => $term_id // ]], // 'orderby' => 'rand' // ]); // // foreach ($posts as $post) { // if (has_post_thumbnail($post)) { // $examples[] = [ // 'title' => get_the_title($post), // 'thumbnail' => get_the_post_thumbnail_url($post, 'thumbnail'), // 'full' => get_the_post_thumbnail_url($post, 'full') // ]; // } // } // // return $examples; // } /** * Render hidden inputs for form submission * @return void */ protected function renderHiddenInputs():void { if (empty($this->selected)) { return; } foreach ($this->selected as $term_id => $name) { printf( '', esc_attr($this->id), esc_attr($term_id) ); } } /** * Get selected terms data for frontend * @return array */ protected function getSelectedData(array $selected = []):array { $data = []; $selected = (empty($selected)) ? $this->selected : $selected; foreach ($selected as $ID) { $term = get_term((int) $ID, $this->taxonomy); if (!$term || is_wp_error($term)) { continue; } $data[] = [ 'id' => $term->term_id, 'name' => $term->name, 'parent' => $term->parent ]; } return $data; } /** * @var array Loading state messages */ protected $loading_quips = [ "Making you look good...", "Processing perfection...", "Converting ink to pixels...", "Teaching robots about art..." ]; /** * Get loading state HTML * @return string */ protected function getLoadingState():string { ob_start(); ?>
getRandomQuip()); ?>
loading_quips[array_rand($this->loading_quips)]; } public function outputDialog() { ?> renderModalContent(); ?>