From 42fa8304ddb811b0f725f245130f70c0f5e86a6c Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 04 Nov 2025 06:12:02 +0000
Subject: [PATCH] =Refactored LoginManager to be more extensible and configurable, as well as an AjaxRateLimiter

---
 inc/registry/PostTypeRegistrar.php |  143 ++++++++++++++++++++++++++++++++++++++++++-----
 1 files changed, 126 insertions(+), 17 deletions(-)

diff --git a/inc/registry/PostTypeRegistrar.php b/inc/registry/PostTypeRegistrar.php
index e6f7272..1d6c2f4 100644
--- a/inc/registry/PostTypeRegistrar.php
+++ b/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,25 +34,32 @@
 		$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,
-			'publicly_queryable'  => $this->config['publicly_queryable'] ?? true,
+			'publicly_queryable'  => $this->config['publicly_queryable'] ?? $this->config['public'] ?? true,
 			'show_ui'             => $this->config['show_ui'] ?? true,
 //			'show_in_menu'        => false,
 			'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,6 +119,18 @@
 			}
 		}
 
+		add_filter('jvbDashboardPage', [$this, 'renderDashPage'], 10, 3);
+
+		if ($this->config['hide_children'] ?? false) {
+			add_action('template_redirect', [$this, 'redirectChildToParent']);
+		}
+
+		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);
+		}
+
 		$postType = $this->post_type;
 		add_action("save_post_{$this->post_type}", function($post_id, $post, $update) use ($postType) {
 			if (jvbNoSaveIt($post_id, $post)) {
@@ -128,20 +156,14 @@
 	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}");
+		$cache = CacheManager::for(jvbNoBase($type))->invalidate();
 
 		// 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);
+				CacheManager::for(jvbNoBase($taxonomy))->invalidate();
 			}
 		}
 
@@ -180,4 +202,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;
+	}
 }

--
Gitblit v1.10.0