Jake Vanderwerf
2026-01-11 474109a5df0a06f5343ab184838fe2d80e3872a8
inc/registry/PostTypeRegistrar.php
@@ -1,6 +1,9 @@
<?php
namespace JVBase\registry;
use JVBase\forms\TaxonomySelector;
use JVBase\managers\CRUD;
use JVBase\utility\Features;
use WP_Post;
use JVBase\meta\MetaRegistry;
use JVBase\managers\CacheManager;
@@ -31,6 +34,13 @@
      $singular = $this->config['singular'] ?? ucfirst($this->slug);
      $plural = $this->config['plural'] ?? $singular . 's';
      $loweredPlural = strtolower($plural);
      $capsMap = $this->config['capability_type']??[];
      if (empty($capsMap)){
         $capsMap = [
            $this->slug,
            str_replace('-', '_',sanitize_title($loweredPlural))
         ];
      }
      $args = [
         'labels'              => $this->buildLabels($singular, $plural),
         'public'              => $this->config['public'] ?? true,
@@ -40,16 +50,16 @@
         'show_in_menu'        => $this->config['show_in_menu'] ?? true,
         'query_var'           => $this->config['query_var'] ?? true,
         'rewrite'             => $this->config['rewrite'] ?? ['slug' => $this->slug, 'with_front' => false],
         'capability_type'     => [$this->slug, $loweredPlural],
         'capability_type'     => $capsMap,
         'capabilities'    => [
            'edit_post'          => "edit_{$this->slug}",
            'read_post'          => "read_{$this->slug}",
            'delete_post'        => "delete_{$this->slug}",
            'edit_posts'         => "edit_{$loweredPlural}",
            'edit_others_posts'  => "edit_others_{$loweredPlural}",
            'publish_posts'      => "publish_{$loweredPlural}",
            'read_private_posts' => "read_private_{$loweredPlural}",
            'create_posts'       => "edit_{$loweredPlural}",
            'edit_posts'         => "edit_{$capsMap[1]}",
            'edit_others_posts'  => "edit_others_{$capsMap[1]}",
            'publish_posts'      => "publish_{$capsMap[1]}",
            'read_private_posts' => "read_private_{$capsMap[1]}",
            'create_posts'       => "edit_{$capsMap[1]}",
         ],
         'has_archive'         => $this->config['has_archive'] ?? true,
         'hierarchical'        => $this->config['hierarchical'] ?? false,
@@ -58,12 +68,18 @@
         'show_in_rest'        => $this->config['show_in_rest'] ?? true,
      ];
      if (jvbCheck('is_calendar', $this->config)) {
      if ($this->config['is_calendar']??false) {
         $args['rewrite']['slug'] = $args['rewrite']['slug']??$this->slug.'/%eyear%/%emonth%/%eday%';
      }
      if (isset($this->config['icon'])) {
      if ($this->config['rewrite_taxonomy']??false && array_key_exists($this->config['rewrite_taxonomy'], JVB_TAXONOMY)) {
         $args['rewrite']['slug'] = "{$this->slug}/%{$this->config['rewrite_taxonomy']}%";
      }
      if ($this->config['icon']??false) {
         $args['menu_icon'] = jvbCSSIcon($this->config['icon']);
      }
      if ($this->config['is_timeline']??false) {
      }
      register_post_type($this->post_type, $args);
@@ -103,50 +119,17 @@
         }
      }
      $postType = $this->post_type;
      add_action("save_post_{$this->post_type}", function($post_id, $post, $update) use ($postType) {
         if (jvbNoSaveIt($post_id, $post)) {
            return;
         }
         $this->invalidatePostCache($postType, $post, $update ? 'update' : 'create');
      }, 10, 3);
      add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 3);
      add_action("delete_post", function($post_id, $post) use ($postType) {
         $post = get_post($post_id);
         if ($post && $post->post_type === $postType) {
            $this->invalidatePostCache($postType, $post, 'delete');
         }
      }, 10, 2);
      add_action("transition_post_status", function($new_status, $old_status, $post) use ($postType) {
         if ($post->post_type === $postType && $new_status !== $old_status) {
            $this->invalidatePostCache($postType, $post, 'status_change');
         }
      }, 10, 3);
   }
   protected function invalidatePostCache(string $type, $post, string $action) {
      error_log('Clearing Cache for '.print_r($type, true));
      $cache = new CacheManager(jvbNoBase($type));
      $cache->delete($post->ID);
      // Clear specific cache groups
      CacheManager::invalidateGroup($type);
      CacheManager::invalidateGroup("user_content_{$post->post_author}");
      // Clear related caches (taxonomies attached to this post)
      $taxonomies = get_object_taxonomies($post->post_type);
      foreach ($taxonomies as $taxonomy) {
         $terms = wp_get_post_terms($post->ID, $taxonomy, ['fields' => 'ids']);
         if (!empty($terms)) {
            CacheManager::invalidateGroup($taxonomy);
         }
      if ($this->config['hide_children'] ?? false) {
         add_action('template_redirect', [$this, 'redirectChildToParent']);
      }
      // Trigger custom action for additional handling
      do_action("jvb_cache_invalidated_{$type}", $post, $action);
      if (array_key_exists('rewrite_taxonomy', $this->config) && array_key_exists($this->config['rewrite_taxonomy'], JVB_TAXONOMY)) {
         add_action('init', [$this, 'addRewriteRules'], 20);
         add_action('post_type_link', [$this, 'rewriteTaxonomySingle'], 15, 2);
         add_filter('post_type_archive_link', [$this, 'rewriteTaxonomyArchive'], 15, 2);
      }
   }
   public function hideFromPublic(bool $is_viewable): bool
@@ -180,4 +163,91 @@
      return get_post_type_archive_link($post->post_type);
   }
   public function addRewriteRules(): void
   {
      $type = $this->config['rewrite_taxonomy'];
      $taxonomy = jvbCheckBase($type);
      // Rule 1: Post type archive - /faq/
      add_rewrite_rule(
         "{$this->slug}/?$",
         "index.php?post_type={$this->post_type}",
         'top'
      );
      // Rule 2: Single posts with taxonomy - /faq/section/post/
      add_rewrite_rule(
         "{$this->slug}/([^/]+)/([^/]+)/?$",
         "index.php?post_type={$this->post_type}&name=\$matches[2]&{$taxonomy}=\$matches[1]",
         'top'
      );
      // Rule 3: Un-sectioned posts - /faq/post/
      // Use 'bottom' priority so taxonomy rules match first
      add_rewrite_rule(
         "{$this->slug}/([^/]+)/?$",
         "index.php?post_type={$this->post_type}&name=\$matches[1]",
         'bottom'
      );
   }
   public function rewriteTaxonomySingle(string $url, \WP_Post $post): string
   {
      if ($post->post_type === $this->post_type) {
         $type = $this->config['rewrite_taxonomy'];
         $taxonomy = jvbCheckBase($type);
         $terms = wp_get_post_terms($post->ID, $taxonomy);
         if (!empty($terms) && !is_wp_error($terms)) {
            $path = TaxonomySelector::getTermPath($terms[0], true);
            $path = implode('/', array_map(function($term) {
               return sanitize_title($term);
            }, $path));
            return str_replace("%{$type}%", $path, $url);
         }
         return str_replace("/%{$type}%", '', $url);
      }
      return $url;
   }
   public function rewriteTaxonomyArchive(string $url, string $post_type):string
   {
      if ($post_type === $this->post_type) {
         $url = get_home_url(null, "/{$this->slug}/");
      }
      return $url;
   }
   /**
    * Redirect child timeline posts to their parent post
    */
   public function redirectChildToParent(): void
   {
      if (!is_singular($this->post_type)) {
         return;
      }
      global $post;
      // If this post has a parent, redirect to parent
      if ($post->post_parent) {
         $parent_url = get_permalink($post->post_parent);
         // Add anchor or query param to indicate which child was accessed
         $redirect_url = add_query_arg('update', $post->ID, $parent_url);
         wp_redirect($redirect_url, 301);
         exit;
      }
   }
   public function renderDashPage(string $content, string $page, string $slug):string
   {
      if ($slug === $this->slug) {
         ob_start();
         $crud = new CRUD($slug);
         $crud->render();
         return ob_get_clean();
      }
      return $content;
   }
}