directories = $this->getDirectories(); if (empty($this->directories)) { return; } $this->perPage = $perPage; $this->cache = Cache::for('directory', WEEK_IN_SECONDS); $this->cache->connect('post', true) ->connect('taxonomy', true) ->connect('user', true); if (JVB_TESTING) { $this->cache->flush(); } jvb_register_do_once('buildDirectories', [$this, 'activate']); add_action('init', [$this, 'registerDirectories']); add_action('render_block', [$this, 'renderBlock'], 99999, 3); } public function registerDirectories():void { $singular = (array_key_exists('directory_label', JVB_SITE)) ? JVB_SITE['directory_label'][0] : 'Directory'; $plural = (array_key_exists('directory_label', JVB_SITE)) ? JVB_SITE['directory_label'][1] : 'Directories'; $config = [ 'labels' => [ 'name' => $plural, 'singular_name' => $singular, 'menu_name' => $plural, 'name_admin_bar' => $singular, 'add_new' => "Add New", 'add_new_item' => "Add New {$singular}", 'new_item' => "New {$singular}", 'edit_item' => "Edit {$singular}", 'view_item' => "View {$singular}", 'all_items' => "All {$plural}", 'search_items' => "Search {$plural}", 'parent_item_colon' => "Parent {$plural}:", 'not_found' => "No {$plural} found.", 'not_found_in_trash' => "No {$plural} found in Trash.", ], 'public' => true, 'menu_icon' => jvbCSSIcon('list-dashes'), 'publicly_queryable' => true, 'show_in_menu' => true, 'show_in_admin_bar' => false, 'has_archive' => true, 'hierarchical' => true, 'rewrite' => [ 'slug' => sanitize_title(strtolower($plural)), 'with_front' => false, ], 'capability_type' => 'post', 'show_in_rest' => true, 'supports'=>['title', 'author', 'thumbnail', 'editor', 'revisions', 'custom-fields', 'excerpt', 'content'] ]; register_post_type(BASE.'directory', $config); } public function getDirectories():array { $directories = get_option(BASE.'directories'); if (!$directories) { $directories = []; //content $content = Registrar::getFeatured('show_directory', 'post'); if(!empty($content)) { foreach ($content as $key) { $directories[$key] = 'content'; } } $taxonomies = Registrar::getFeatured('show_directory', 'term'); if(!empty($taxonomies)) { foreach ($taxonomies as $key) { $directories[$key] = 'taxonomy'; } } $users = Registrar::getFeatured('show_directory', 'user'); if(!empty($users)) { foreach ($users as $key) { $directories[$key] = 'user'; } } update_option(BASE.'directories', $directories); } return $directories; } public function activate():void { // $tmp = new self(); // $this->registerDirectories(); $created = []; $directories = []; $this->getDirectories(); foreach($this->directories as $directory => $type) { $registrar = Registrar::getInstance($directory); if (!$registrar){ error_log('Could not find registrar for making directory for '.$directory); continue; } $config = $registrar->getConfig('directory'); $title = $config['title']; $excerpt = implode(' ', $config['description']??[]); $ID = wp_insert_post([ 'post_type' => BASE.'directory', 'post_title' => $title, 'post_status' => 'publish', 'post_excerpt' => $excerpt, 'slug' => sanitize_title($title) ]); if (!is_wp_error($ID)) { add_post_meta($ID, self::$type, $type); add_post_meta($ID, self::$slug, $directory); $created[$directory] = (int)$ID; $slug = sanitize_title($title); $directories[$directory] = [ 'slug' => $slug, 'title' => $title, 'ID' => $ID, 'url' => get_the_permalink($ID), 'page' => $title, 'description' =>$config[$directory]['description']??[], 'type' => $type, 'extra' => $config[$directory]['directory_extra'] ??[], ]; } if ($config['isGrouped']) { $title = $title.', but Grouped'; $slug = sanitize_title($title).'-grouped'; $excerpt = $config['groupedDescription']??'Too many options? This is grouped by type.'; $ID = wp_insert_post([ 'post_type' => BASE.'directory', 'post_title' => $title, 'post_status' => 'publish', 'post_excerpt' => $excerpt, 'slug' => $slug, ]); if (!is_wp_error($ID)) { add_post_meta($ID, self::$type, $type); add_post_meta($ID, self::$slug, $directory.'-grouped'); add_post_meta($ID, BASE.'grouped_directory', 'yup'); $created[$directory.'-grouped'] = (int)$ID; $directories[$directory.'-grouped'] = [ 'slug' => $slug, 'title' => $title, 'ID' => $ID, 'url' => get_the_permalink($ID), 'page' => $title, 'description' =>$config[$directory]['description']??[], 'type' => $type, 'extra' => $config[$directory]['directory_extra'] ??[], ]; } } } // if (Features::forSite()->has('has_map')) { // $ID = wp_insert_post([ // 'post_type' => BASE.'directory', // 'post_title' => 'Map', // 'post_status'=> 'publish', // 'slug' => 'map', // ]); // if (!is_wp_error($ID)) { // add_post_meta($ID, self::$type, 'map'); // $created['map'] = (int)$ID; // $directories['map'] = [ // 'slug' => 'map', // 'title' => 'Map', // 'ID' => $ID, // 'url' => get_the_permalink($ID), // 'page' => 'Map', // 'type' => 'term', // ]; // } // } if (!empty($created)) { update_option(BASE.'directory_ids', $created); } if (!empty($directories)) { update_option(BASE.'directory_list', $directories); } } protected function buildDirectoryList():array { $saved = get_option(BASE.'directory_list', []); if (empty($saved)) { $all = new WP_Query([ 'post_type' => BASE.'directory', 'post_status' => 'publish', 'posts_per_page' => -1, ]); foreach($all->posts as $post) { $config = Registrar::getInstance($post->post_name)->getConfig('directory')??false; $saved[$post->post_name] = [ 'slug' => $post->post_name, 'title' => $post->post_title, 'ID' => $post->ID, 'url' => get_the_permalink($post->ID), 'page' => $post->post_title, 'description' => ($config) ?$config['description'] :'', 'type' => get_post_meta($post->ID, self::$type,true), 'extra' => ($config) ?$config['directory_extra'] : [], ]; } update_option(BASE.'directory_list', $saved); wp_reset_postdata(); } return $saved; } public function getDirectoryPageIDs():array { if (empty($this->directoryPageIDs)) { $this->directoryPageIDs = get_option(BASE.'directory_ids', []); } return $this->directoryPageIDs; } public function getDirectoryList():array { if (empty($this->directoryList)) { $this->directoryList = $this->buildDirectoryList(); } return $this->directoryList; } public static function getConfig(int $ID):Registrar|false { $slug = get_post_meta($ID, self::$slug, true); return Registrar::getInstance($slug); } public function letters():array { return [ 'a', 'b', 'c', 'd', 'e', 'f', 'g', 'h', 'i', 'j', 'k', 'l', 'm', 'n', 'o', 'p', 'q', 'r', 's', 't', 'u', 'v', 'w', 'x', 'y', 'z' ]; } public function alphabetizeMe( array $list, string $name = '', string $url = '', string $ID = '', $extra = false ):array { if ($name == '') { $name = get_the_title(); } if ($url == '') { $url = get_the_permalink(); } if ($ID == '') { $ID = get_the_ID(); } $first = strtolower(mb_substr($name, 0, 1)); $list[$first][] = array( 'name' => $name, 'url' => $url, 'id' => $ID, 'extra' => $extra, ); return $list; } public function directories(string $search = 'all'):array { $directories = $this->getDirectoryList(); if ($search === 'all') { return $directories; } return $directories[$search]??[]; } public function isDirectory():bool { return (is_post_type_archive(BASE.'directory') || is_singular(BASE.'directory')); } private function renderArchive(): string { $this->getDirectoryList(); return $this->cache->remember( 'archive', function() { $cache = '

'.$this->referAs().' of '.$this->referAs(true).'

You like lists? We\'ve got \'em!

'; return $cache; } ); } protected function renderIndex(string $current = '', bool $open = false):string { $cache = $this->cache->remember( 'index', function() { $cache = ''; return $cache; } ); if ($current !== '' && array_key_exists($current, $this->directories())) { $open = ($open) ? ' open' : ''; $cache = 'Other '.$this->referAs(true).':'. str_replace('id="'.$current.'"', 'id="'.$current.'" class="current"', $cache) .''; } return $cache; } private function renderDirectory(): string { $slug = get_post_meta(get_the_ID(), self::$slug, true); if ($slug === '') { return ''; } $type = $this->directories[$slug]; $registrar = Registrar::getInstance($slug); $config = $registrar->getConfig('directory'); $paged = isset($_GET['page']) ? max(1, (int)$_GET['page']) : 1; $cacheKey = $slug . '_page_' . $paged; return $this->cache->remember( $cacheKey, function() use ($slug, $type, $registrar, $config, $paged) { $out = '

' . $this->directoryTitle($registrar) . '

'; $out .= '
'; foreach ($config['description'] ?? [] as $p) { $out .= '

' . $p . '

'; } $out .= '
'; $out .= $this->renderIndex($slug); $list = []; $query = null; switch ($type) { case 'content': $args = [ 'post_type' => jvbCheckBase($slug), 'posts_per_page' => $this->perPage, 'paged' => $paged, 'orderby' => 'title', 'order' => 'ASC' ]; if ($registrar->hasFeature('is_timeline')) { $args['post_parent'] = 0; } $get = new WP_Query($args); $hasExtra = $registrar->hasFeature('directory_extra'); if ($get->have_posts()) { while ( $get->have_posts() ) { $get->the_post(); $extra = []; if ($hasExtra) { foreach ($config['directory_extra'] as $item ) { $item = jvbCheckBase( $item ); $terms = get_the_terms( get_the_ID(), jvbCheckBase( $item ) ); if ( $terms && ! is_wp_error( $terms ) ) { $term = $terms[0]; $extra[] = [ 'name' => (get_term_meta( $term->term_id, BASE . 'singular', true ) !== '') ? get_term_meta( $term->term_id, BASE . 'singular', true ) : html_entity_decode($term->name), 'url' => get_term_link( $term->term_id, $item ), 'id' => $term->term_id, 'type' => $item, ]; } } } $list = $this->alphabetizeMe( $list, get_the_title(), get_the_permalink(), get_the_ID(), $extra ); } } wp_reset_postdata(); break; case 'taxonomy': // For taxonomy, we need to manually paginate $offset = ($paged - 1) * $this->perPage; $get = get_terms([ 'taxonomy' => jvbCheckBase($slug), 'hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC', 'number' => $this->perPage, 'offset' => $offset, ]); // Get total for pagination $total_terms = wp_count_terms([ 'taxonomy' => jvbCheckBase($slug), 'hide_empty' => true, ]); if ($get && !is_wp_error($get)) { $extra = []; foreach ($get as $term) { $list = $this->alphabetizeMe( $list, html_entity_decode($term->name), get_term_link( $term->term_id, jvbCheckBase( $slug ) ), $term->term_id, $extra ); } } break; case 'user': $get = get_users([ 'role' => jvbCheckBase($slug), 'orderby' => 'display_name', 'order' => 'ASC', ]); break; } $out .= '
'; if (empty($list)) { $out .= '

Nothing here.

We don\'t have anything here yet.

'; } else { $out .= $this->renderLettersIndex($list, $type, $slug); $out .= $this->renderLettersList($list, $slug); if ($type === 'content' && $query) { $out .= $this->renderPagination($query); } elseif ($type === 'taxonomy' && is_numeric($total_terms)) { $out .= $this->renderTaxonomyPagination($total_terms, $paged); } } $out .= '
'; $out .= $this->renderIndex($slug, true); return $out; } ); } private function renderGroupedDirectory():string { $slug = get_post_meta(get_the_ID(), self::$slug, true); if ($slug === '') { return ''; } return $this->cache->remember( $slug.'_group', function() use ($slug){ $out = '

'.$this->directories[$slug]['title'].'

'; $out .= '
'; foreach ($this->directories[$slug]['description']??[] as $p) { $out .= '

'.$p.'

'; } $out .= '
'; $out .= $this->renderIndex($slug); $taxonomy = str_replace('-grouped', '', $slug); $tax = jvbCheckBase($taxonomy); $list = $this->renderListChunk($tax, 0); if ($list !== '') { $out .= '
'.$list.'
'; } else { $out .= '

Nothing here.

We don\'t have anything here yet.

'; } $out .= $this->renderIndex($slug, true); return $out; } ); } protected function renderListChunk(string $taxonomy, int $parent):string { $get = get_terms([ 'taxonomy' => jvbCheckBase(str_replace('-grouped', '', $taxonomy)), 'hide_empty' => true, 'orderby' => 'name', 'order' => 'ASC', 'parent' => $parent ]); if (!$get || is_wp_error($get)) { return ''; } $out = ''; return $out; } public function renderLettersIndex(array $list, string $type = '', string $slug = ''): string { $letters_on_page = array_keys($list); $letterPageMap = []; if ($type !== '' && $slug !== '') { $letterPageMap = $this->getLetterPageMap($type, $slug); } $out = ''; return $out; } public function renderLettersList(array $list, string $type):string { $umami = JVB()->connect('umami'); $out = ''; return $out; } public function renderBlock(string $content, array $block, WP_Block $instance) { if (!is_post_type_archive(BASE.'directory') && !is_singular(BASE.'directory')) { return $content; } if ($block['blockName'] !== 'core/post-content' && ($block['blockName'] !== 'core/group')) { return $content; } // For archive page if (is_post_type_archive(BASE.'directory') && $block['blockName'] === 'core/group') { return ($block['attrs']['tagName']??'' === 'main') ? '
'.$this->renderArchive().'
' : $content; } // For single directory posts if ($block['blockName'] === 'core/group') { switch (get_post_meta(get_the_ID(), BASE.'grouped_directory', true)) { case '': return '
' . $this->renderDirectory() . '
'; case 'yup': return '
' . $this->renderGroupedDirectory() . '
'; } } return $content; } protected function directoryTitle(Registrar $registrar):string { $config = $registrar->getConfig('directory'); return $config['title']?: $registrar->getPlural(); } public function referAs($plural = false):string { if (!empty(JVB_SITE) && array_key_exists('directory_label', JVB_SITE)) { return ($plural) ? JVB_SITE['directory_label'][1] : JVB_SITE['directory_label'][0]; } return ($plural) ? 'Directories' : 'Directory'; } /***************************************************** * PAGINATION HELPERS ****************************************************/ protected function renderPagination(WP_Query $query): string { if ($query->max_num_pages <= 1) { return ''; } $current = max(1, isset($_GET['page']) ? (int)$_GET['page'] : 1); $pagination = paginate_links([ 'base' => add_query_arg('page', '%#%'), 'format' => '', 'current' => $current, 'total' => $query->max_num_pages, 'type' => 'array', 'prev_text' => jvbIcon('arrow-square-left'), 'next_text' => jvbIcon('arrow-square-right'), ]); if (!$pagination) { return ''; } $out = ''; return $out; } protected function renderTaxonomyPagination(int $total, int $paged): string { $max_pages = ceil($total / $this->perPage); if ($max_pages <= 1) { return ''; } $current = max(1, isset($_GET['page']) ? (int)$_GET['page'] : 1); $pagination = paginate_links([ 'base' => add_query_arg('page', '%#%'), 'format' => '', 'current' => $current, 'total' => $max_pages, 'type' => 'array', 'prev_text' => jvbIcon('arrow-square-left'), 'next_text' => jvbIcon('arrow-square-right'), ]); if (!$pagination) { return ''; } $out = ''; return $out; } protected function getLetterRanges(array $letters): array { if (empty($letters)) { return []; } sort($letters); $ranges = []; $start = $letters[0]; $prev = $letters[0]; foreach (array_slice($letters, 1) as $letter) { // Check if letters are consecutive if (ord($letter) !== ord($prev) + 1) { $ranges[] = ['start' => $start, 'end' => $prev]; $start = $letter; } $prev = $letter; } // Add final range $ranges[] = ['start' => $start, 'end' => $prev]; return $ranges; } protected function getLetterPageMap(string $type, string $slug): array { return $this->cache->remember( $slug . '_letter_page_map', function() use ($type, $slug) { $titles = []; $registrar = Registrar::getInstance($slug); switch ($type) { case 'content': global $wpdb; $post_type = jvbCheckBase($slug); $where = $wpdb->prepare("post_type = %s AND post_status = 'publish'", $post_type); if ($registrar && $registrar->hasFeature('is_timeline')) { $where .= " AND post_parent = 0"; } $titles = $wpdb->get_col( "SELECT post_title FROM {$wpdb->posts} WHERE {$where} ORDER BY post_title" ); break; case 'taxonomy': global $wpdb; $taxonomy = jvbCheckBase($slug); $titles = $wpdb->get_col($wpdb->prepare( "SELECT t.name FROM {$wpdb->terms} t INNER JOIN {$wpdb->term_taxonomy} tt ON t.term_id = tt.term_id WHERE tt.taxonomy = %s AND tt.count > 0 ORDER BY t.name ASC", $taxonomy )); break; case 'user': $users = get_users([ 'role' => jvbCheckBase($slug), 'orderby' => 'display_name', 'order' => 'ASC', 'fields' => 'display_name', ]); $titles = array_column($users, 'display_name'); break; } return $this->calculateLetterPages($titles); } ); } protected function calculateLetterPages(array $titles): array { $letterCounts = []; // Count items per letter foreach ($titles as $title) { $letter = strtolower(mb_substr($title, 0, 1)); if (!isset($letterCounts[$letter])) { $letterCounts[$letter] = 0; } $letterCounts[$letter]++; } // Calculate which page each letter starts on $letterPages = []; $runningTotal = 0; foreach ($this->letters() as $letter) { if (!isset($letterCounts[$letter])) { continue; } $count = $letterCounts[$letter]; $startPosition = $runningTotal + 1; $startPage = (int)ceil($startPosition / $this->perPage); $letterPages[$letter] = [ 'page' => $startPage, 'count' => $count, 'start_position' => $startPosition, ]; $runningTotal += $count; } return $letterPages; } }