From 75a097a018a0090f5902758353c578fce4aa2a25 Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Sat, 23 May 2026 18:43:42 +0000
Subject: [PATCH] =CustomBlocks.php overhaul relatively complete. Also refactored the gallery in gallery.min.js and the jvbRenderGallery.

---
 inc/managers/RoleManager.php |  637 +++++++++++++++++++++++++++++++++++++++++++++++++++------
 1 files changed, 561 insertions(+), 76 deletions(-)

diff --git a/inc/managers/RoleManager.php b/inc/managers/RoleManager.php
index 417c2ee..b275f12 100644
--- a/inc/managers/RoleManager.php
+++ b/inc/managers/RoleManager.php
@@ -1,7 +1,8 @@
 <?php
 namespace JVBase\managers;
 
-use JVBase\utility\Features;
+use JVBase\registrar\Registrar;
+use JVBase\base\Site;
 use WP_User;
 use WP_Role;
 
@@ -12,15 +13,53 @@
 {
     private array $roles;
 	private array $content;
+	private array $subTypes = [];
 
     public function __construct()
     {
-       $this->roles = array_keys(JVB_USER);
+       $this->roles = Registrar::getRegistered('user');
+
 	   $this->content = array_map(function($content) {
-		   return strtolower($content['plural']);
-	   },JVB_CONTENT);
+		   $registrar = Registrar::getInstance($content);
+		   return strtolower(str_replace(' ', '_', $registrar->getPlural()??$registrar->getSingular().'s'));
+	   },array_merge(
+		   Registrar::getRegistered('post'),
+		   Registrar::getFeatured('is_content', 'term')
+	   ));
+	   add_action('set_user_role', [$this, 'updateRoles'], 10, 3);
+
+	   $this->checkRoleSubTypes();
     }
 
+	public function checkRoleSubTypes():void
+	{
+		foreach ($this->roles as $role) {
+			$registrar = Registrar::getInstance($role);
+			if ($registrar->getUserSubtype()){
+				$this->subTypes[jvbCheckBase($registrar->getUserSubtype())] = $registrar->getCreatable();
+			}
+		}
+		if (!empty($this->subTypes)) {
+			add_action('set_object_terms', [$this, 'maybeSwitchPermissions'],10, 6);
+		}
+	}
+
+	public function updateRoles(int $userID, string $role, array $oldRoles):void
+	{
+		if (doing_action('set_user_role') > 1) {
+			return;
+		}
+		$temp = jvbNoBase($role);
+		$registrar = Registrar::getInstance($temp);
+		if ($registrar) {
+			$user = get_userdata($userID);
+			if (!$user) {
+				return;
+			}
+			$this->reset($user);
+			$this->setUserAs($user, $temp);
+		}
+	}
 
     /**
      * @param WP_User $user
@@ -40,17 +79,13 @@
 	{
 		$type = jvbNoBase($type);
 
-		// Check in JVB_CONTENT array
-		if (array_key_exists($type, JVB_CONTENT)) {
+		$registrar = Registrar::getInstance($type);
+		if ($registrar && $registrar->getType() === 'post') {
 			return true;
 		}
 
-		// Check in JVB_TAXONOMY for content taxonomies
-		if (array_key_exists($type, JVB_TAXONOMY)) {
-			$tax_config = JVB_TAXONOMY[$type];
-			if ($tax_config['is_content'] ?? false) {
-				return true;
-			}
+		if ($registrar && $registrar->getType() === 'term' && $registrar->hasFeature('is_content')) {
+			return true;
 		}
 
 		return false;
@@ -74,12 +109,11 @@
 		$roles = array_keys($user->roles);
 
 		foreach ($roles as $role) {
-			$role = jvbNoBase($role);
-			$config = JVB_USER[$role]??false;
-			if (!$config) {
+			$registrar = Registrar::getInstance($role);
+			if (!$registrar) {
 				return false;
 			}
-			foreach ($config as $type) {
+			foreach ($registrar->getCreatable() as $type) {
 				if (is_array($type) && in_array($content, $type)) {
 					return true;
 				} elseif ($content === $type) {
@@ -140,7 +174,6 @@
     /**
      * @param WP_User $user
      * @param string $type
-     * @param bool $add
      *
      * @return void
      */
@@ -170,15 +203,19 @@
 		if ($check) {
 			return $check;
 		}
-		$check = JVB_USER[$role]['can_create'] ??false;
-		if (!$check) {
+
+		$registrar = Registrar::getInstance($role);
+		if (!$registrar) {
 			return false;
 		}
 		$out = [];
-		foreach ($check as $types) {
-			foreach ($types as $type => $content) {
-				$out[$type] = $content;
+		foreach ($registrar->getCreatable() as $types) {
+			if (is_array($types)) {
+				$out = array_merge($out, $types);
+			} else {
+				$out = [$types];
 			}
+			$out = array_unique($out);
 		}
 		set_transient(BASE.'role_config_'.$role, $out, MONTH_IN_SECONDS);
 		return $out;
@@ -195,7 +232,7 @@
         if (!in_array($type, $this->roles)) {
             return false;
         }
-        $link = get_user_meta($user->ID, BASE.'link', true);
+        $link = get_user_meta($user->ID, BASE.'profile_link', true);
         if ($link === '') {
             $type = BASE.$type;
             $name = $user->display_name;
@@ -208,20 +245,24 @@
                 'post_author'   => $user->ID,
             ]);
 			if ($link) {
-				update_user_meta($user->ID, BASE.'link', $link);
-				update_post_meta($link, BASE.'link', $user->ID);
+				update_user_meta($user->ID, BASE.'profile_link', $link);
+				update_post_meta($link, BASE.'profile_link', $user->ID);
 			}
         }
         return $link;
     }
 
-	public function registerRole(string $slug, array $config): void
+	public function registerRole(string $slug): void
 	{
 		$role_name = BASE . $slug;
 		$display_name = $config['label'] ?? ucfirst($slug);
 
+		$registrar = Registrar::getInstance($slug);
+		if (!$registrar){
+			return;
+		}
 		// Build capabilities for this role
-		$capabilities = $this->buildRoleCapabilities($slug, $config);
+		$capabilities = $this->buildRoleCapabilities($slug, $registrar);
 
 		// Remove role first to ensure clean slate
 		remove_role($role_name);
@@ -230,16 +271,16 @@
 		add_role($role_name, $display_name, $capabilities);
 
 		// Add management capabilities to administrator
-		if ($config['has_dashboard'] ?? false) {
+		if ($registrar->hasFeature('has_dashboard') ?? false) {
 			$admin_role = get_role('administrator');
 			if ($admin_role) {
-				$admin_role->add_cap("manage_{$role_name}s", true);
-				$admin_role->add_cap("edit_{$role_name}_settings", true);
+				$admin_role->add_cap("manage_{$role_name}s");
+				$admin_role->add_cap("edit_{$role_name}_settings");
 			}
 		}
 	}
 
-	private function buildRoleCapabilities(string $slug, array $config): array
+	private function buildRoleCapabilities(string $slug, Registrar $registrar): array
 	{
 		//Everyone can see the things
 		$capabilities = [
@@ -247,24 +288,32 @@
 		];
 
 		// Dashboard access
-		if ($this->config['has_dashboard'] ?? false) {
+		if ($registrar->hasFeature('has_dashboard') ?? false) {
 			$capabilities['access_dashboard'] = true;
 		}
 
-		if (Features::forSite()->has('favourites') && $config['can_favourite'] ?? true) {
+		if (Site::has('favourites') && $registrar->hasFeature('can_favourite') ?? true) {
 			$capabilities['can_favourite'] = true;
 		}
 
 		// Content creation capabilities
-		if (!empty($config['can_create'])) {
-			foreach ($config['can_create'] as $content_type) {
-				$this->addContentCapabilities($capabilities, $content_type, $config);
+		if (!empty($registrar->getCreatable())) {
+			$content = [];
+			foreach ($registrar->getCreatable() as $content_type) {
+				if (is_array($content_type)) {
+					$content = array_merge($content, $content_type);
+				}else {
+					$content[] = $content_type;
+				}
+			}
+			foreach  ($content as $c) {
+				$this->addContentCapabilities($capabilities, $c, $registrar);
 			}
 		}
 
 		// Management capabilities
-		if (!empty($this->config['manage_others'])) {
-			foreach ($this->config['manage_others'] as $type) {
+		if (!empty($registrar->getManageOthers())) {
+			foreach ($registrar->getManageOthers() as $type) {
 				// Skip if content type doesn't exist
 				if (!$this->isValidContentType($type)) {
 					error_log("Warning: User role '{$slug}' references non-existent content type '{$type}'");
@@ -283,44 +332,30 @@
 	/**
 	 * Add content capabilities to capability array
 	 */
-	private function addContentCapabilities(array &$capabilities, $content_type, array $config): void
+	private function addContentCapabilities(array &$capabilities, $content_type, Registrar $registrar): void
 	{
-		if (is_array($content_type)) {
-			// Handle array format for type-specific permissions
-			foreach ($content_type as $sub_type => $types) {
-				foreach ($types as $type) {
-					if (!$this->isValidContentType($type)) {
-						error_log("Warning: Role references non-existent content type '{$type}'");
-						continue;
-					}
-					$this->addSingleContentCapabilities($capabilities, $type, $config);
-				}
-			}
-		} else {
-			if (!$this->isValidContentType($content_type)) {
-				error_log("Warning: Role references non-existent content type '{$content_type}'");
-				return;
-			}
-			$this->addSingleContentCapabilities($capabilities, $content_type, $config);
+		if (!$this->isValidContentType($content_type)) {
+			error_log("Warning: Role references non-existent content type '{$content_type}'");
+			return;
 		}
+		$this->addSingleContentCapabilities($capabilities, $content_type, $registrar);
 	}
 
 	/**
 	 * Add capabilities for a single content type
 	 */
-	private function addSingleContentCapabilities(array &$capabilities, string $type, array $config): void
+	private function addSingleContentCapabilities(array &$capabilities, string $type, Registrar $registrar): void
 	{
 		$caps = $this->getCapabilities($type);
 		foreach ($caps as $cap) {
 			$capabilities[$cap] = true;
 		}
 
-		if (array_key_exists('approve_new', $config)) {
+		if ($registrar->hasFeature('approve_new')) {
 			$plural = $this->getContentPlural($type);
 			// Publish capability depends on approval setting
 			$capabilities["publish_{$plural}"] = !($config['approve_new'] ?? false);
 		}
-
 	}
 
 	public function grantRoleCapabilities(string $role_name, string $content_slug, bool $grant = true): void
@@ -372,13 +407,15 @@
 
 		return [
 			"edit_{$content}",
+			"edit_published_{$content}",
 			"read_{$content}",
 			"delete_{$content}",
+			"delete_published_{$content}",
 			"edit_{$plural}",
-			"edit_others_{$plural}",
 			"publish_{$plural}",
 			"read_private_{$plural}",
-			"edit_{$plural}",
+			"edit_private_{$plural}",
+			"delete_private_{$plural}",
 		];
 	}
 	protected function getOthersCapabilities(string $content):array
@@ -391,9 +428,6 @@
 		return [
 			"edit_others_{$plural}",
 			"delete_others_{$plural}",
-			"read_private_{$plural}",
-			"edit_private_{$plural}",
-			"delete_private_{$plural}",
 		];
 	}
 
@@ -405,22 +439,473 @@
 	public function getContentPlural(string $content): string
 	{
 		$content = jvbNoBase($content);
-		$config = Features::getConfig($content);
-		$capsMap = $config['capability_type']??[];
-		if (empty($capsMap)){
-			$capsMap = [
-				$content,
-				str_replace('-', '_',sanitize_title(strtolower(JVB_CONTENT[$content]['plural'])))
-			];
-			return $capsMap[1];
+		$registrar = Registrar::getInstance($content);
+		if ($registrar && $registrar->getPlural()) {
+			return str_replace(' ', '_', $registrar->getPlural());
 		}
-		return str_replace('-', '_', sanitize_title(strtolower($content . 's')));
+		return str_replace(' ', '_', $content.'s');
 	}
 
-	public function activate(): void
+	public static function activate(): void
 	{
-		foreach (JVB_USER as $slug => $config) {
-			$this->registerRole($slug, $config);
+		error_log('[RoleManager]::activate');
+		error_log('Registering roles...');
+		$instance = new self;
+		foreach (Registrar::getRegistered('user') as $role) {
+			$instance->registerRole($role);
 		}
+		error_log('Roles registered!');
+		error_log('Removing unneeded roles...');
+		remove_role('contributor');
+		remove_role('author');
+		remove_role('editor');
+		error_log('Roles removed!');
+
+		error_log('Adding Admin Capabilities');
+		$instance->addAdminCaps();
+		error_log('Ensuring Existing User\'s Roles...');
+		$instance->ensureRoleCaps();
+		error_log('Roles activated!');
+	}
+		protected function addAdminCaps():void
+		{
+			$users = get_users(['role' => 'administrator']);
+			foreach (array_merge(Registrar::getRegistered('post'), Registrar::getFeatured('is_content')) as $slug) {
+				$this->grantRoleCapabilities('administrator', $slug);
+				$this->grantRoleOthersCapabilities('administrator', $slug);
+
+				foreach ($users as $user) {
+					$this->grantContent($user, $slug);
+					$this->grantOthersContent($user, $slug);
+				}
+			}
+		}
+		protected function ensureRoleCaps():void
+		{
+			$roles = Registrar::getRegistered('user');
+			foreach ($roles as $role) {
+				$registrar = Registrar::getInstance($role);
+				$creatable = $registrar->getCreatable();
+				$manageable = $registrar->getManageOthers();
+				if (!empty($creatable) || !empty($manageable)) {
+					$users = get_users(['role' => jvbCheckBase($role)]);
+					foreach ($users as $user) {
+						foreach ($creatable as $slug) {
+							$this->grantContent($user, $slug);
+						}
+						foreach ($manageable as $slug) {
+							$this->grantOthersContent($user, $slug);
+						}
+					}
+				}
+			}
+		}
+
+	/******************************************************************
+	 * OWNABLE and MANAGABLE terms (ie: tattoo shops)
+	******************************************************************/
+	/**
+	 * Grant ownership of a content taxonomy term
+	 * Owners have full control over the term and its members
+	 *
+	 * @param int $userID User ID
+	 * @param int $termID Term ID
+	 * @param string $taxonomy Taxonomy slug (without BASE)
+	 * @return bool Success
+	 */
+	public function grantOwnership(int $userID, int $termID, string $taxonomy): bool
+	{
+		if (!get_userdata($userID) || !term_exists($termID)){
+			return false;
+		}
+		$taxonomy = jvbNoBase($taxonomy);
+
+		$registrar = Registrar::getInstance($taxonomy);
+		// Verify this is an ownable content taxonomy
+		if (!$registrar || !$registrar->hasFeature('is_content') ||
+				!$registrar->hasFeature('is_ownable')) {
+			return false;
+		}
+
+		$user = get_userdata($userID);
+		if (!$user) {
+			return false;
+		}
+
+		// Grant both ownership and management
+		$user->add_cap(BASE . 'can_own_' . $termID);
+		$user->add_cap(BASE . 'can_manage_' . $termID);
+
+		$owners = get_term_meta($termID, BASE.'owners', true);
+		if (empty($owners)) {
+			$owners = [];
+		}
+		$owners[] = $userID;
+		$owners = array_unique($owners);
+		update_term_meta($termID, BASE.'owners', $owners);
+
+
+		do_action(BASE . 'granted_ownership', $userID, $termID, $taxonomy);
+
+		return true;
+	}
+
+	/**
+	 * Revoke ownership of a content taxonomy term
+	 *
+	 * @param int $userID User ID
+	 * @param int $termID Term ID
+	 * @param string $taxonomy Taxonomy slug (without BASE)
+	 * @return bool Success
+	 */
+	public function revokeOwnership(int $userID, int $termID, string $taxonomy): bool
+	{
+		if (!get_userdata($userID) || !term_exists($termID)){
+			return false;
+		}
+		$taxonomy = jvbNoBase($taxonomy);
+
+		$user = get_userdata($userID);
+		if (!$user) {
+			return false;
+		}
+
+		$owners = get_term_meta($termID, BASE.'owners', true);
+		if (empty($owners)) {
+			$owners = [];
+		}
+		if (in_array($userID, $owners)) {
+			unset($owners[array_search($userID, $owners)]);
+		}
+		$owners = array_unique($owners);
+		update_term_meta($termID, BASE.'owners', $owners);
+
+		$user->remove_cap(BASE . 'can_own_' . $termID);
+
+		do_action(BASE . 'revoked_ownership', $userID, $termID, $taxonomy);
+
+		return true;
+	}
+
+	/**
+	 * Grant management capabilities for a content taxonomy term
+	 * Managers can approve members and edit content but don't own the term
+	 *
+	 * @param int $userID User ID
+	 * @param int $termID Term ID
+	 * @param string $taxonomy Taxonomy slug (without BASE)
+	 * @return bool Success
+	 */
+	public function grantManagement(int $userID, int $termID, string $taxonomy): bool
+	{
+		if (!get_userdata($userID) || !term_exists($termID)){
+			return false;
+		}
+		$taxonomy = jvbNoBase($taxonomy);
+
+		$registrar = Registrar::getInstance($taxonomy);
+		// Verify this is an ownable content taxonomy
+		if (!$registrar || !$registrar->hasFeature('is_content') ||
+			!$registrar->hasFeature('is_ownable')) {
+			return false;
+		}
+
+		$user = get_userdata($userID);
+		if (!$user) {
+			return false;
+		}
+
+		$user->add_cap(BASE . 'can_manage_' . $termID);
+
+		$managers = get_term_meta($termID, BASE.'managers', true);
+		if (empty($managers)) {
+			$managers = [];
+		}
+		$managers[] = $userID;
+		$managers = array_unique($managers);
+		update_term_meta($termID, BASE.'managers', $managers);
+
+		do_action(BASE . 'granted_management', $userID, $termID, $taxonomy);
+
+		return true;
+	}
+
+	/**
+	 * Revoke management capabilities for a content taxonomy term
+	 *
+	 * @param int $userID User ID
+	 * @param int $termID Term ID
+	 * @param string $taxonomy Taxonomy slug (without BASE)
+	 * @return bool Success
+	 */
+	public function revokeManagement(int $userID, int $termID, string $taxonomy): bool
+	{
+		if (!get_userdata($userID) || !term_exists($termID)){
+			return false;
+		}
+		$taxonomy = jvbNoBase($taxonomy);
+
+		$user = get_userdata($userID);
+		if (!$user) {
+			return false;
+		}
+
+		$user->remove_cap(BASE . 'can_manage_' . $termID);
+
+		$managers = get_term_meta($termID, BASE.'managers', true);
+		if (empty($managers)) {
+			$managers = [];
+		}
+		if (in_array($userID, $managers)) {
+			unset($managers[array_search($userID, $managers)]);
+		}
+		$managers = array_unique($managers);
+		update_term_meta($termID, BASE.'managers', $managers);
+
+		do_action(BASE . 'revoked_management', $userID, $termID, $taxonomy);
+
+		return true;
+	}
+
+	/**
+	 * Check if user owns a term
+	 *
+	 * @param int $userID User ID
+	 * @param int $termID Term ID
+	 * @return bool
+	 */
+	public function isOwner(int $userID, int $termID): bool
+	{
+		return user_can($userID, BASE . 'can_own_' . $termID);
+	}
+
+	/**
+	 * Check if user can manage a term (owner or manager)
+	 *
+	 * @param int $userID User ID
+	 * @param int $termID Term ID
+	 * @return bool
+	 */
+	public function isManager(int $userID, int $termID): bool
+	{
+		return user_can($userID, BASE . 'can_manage_' . $termID) ||
+			user_can($userID, BASE . 'can_own_' . $termID);
+	}
+
+	/**
+	 * Get all terms a user owns
+	 *
+	 * @param int $userID User ID
+	 * @param string|null $taxonomy Optional: filter by taxonomy
+	 * @return array Array of term IDs
+	 */
+	public function getOwnedTerms(int $userID, ?string $taxonomy = null): array
+	{
+		$user = get_userdata($userID);
+		if (!$user) {
+			return [];
+		}
+
+		$owned = [];
+		foreach ($user->allcaps as $cap => $value) {
+			if ($value && strpos($cap, BASE . 'can_own_') === 0) {
+				$termID = (int) str_replace(BASE . 'can_own_', '', $cap);
+				if ($termID) {
+					$owned[] = $termID;
+				}
+			}
+		}
+
+		// Filter by taxonomy if specified
+		if ($taxonomy && !empty($owned)) {
+			$taxonomy = jvbCheckBase($taxonomy);
+			$filtered = [];
+			foreach ($owned as $termID) {
+				$term = get_term($termID);
+				if ($term && !is_wp_error($term) && $term->taxonomy === $taxonomy) {
+					$filtered[] = $termID;
+				}
+			}
+			return $filtered;
+		}
+
+		return $owned;
+	}
+
+	/**
+	 * Get all terms a user can manage (owns or manages)
+	 *
+	 * @param int $userID User ID
+	 * @param string|null $taxonomy Optional: filter by taxonomy
+	 * @return array Array of term IDs
+	 */
+	public function getManagedTerms(int $userID, ?string $taxonomy = null): array
+	{
+		$user = get_userdata($userID);
+		if (!$user) {
+			return [];
+		}
+
+		$managed = [];
+		foreach ($user->allcaps as $cap => $value) {
+			if ($value && (strpos($cap, BASE . 'can_manage_') === 0 ||
+					strpos($cap, BASE . 'can_own_') === 0)) {
+				$termID = (int) str_replace([BASE . 'can_manage_', BASE . 'can_own_'], '', $cap);
+				if ($termID && !in_array($termID, $managed)) {
+					$managed[] = $termID;
+				}
+			}
+		}
+
+		// Filter by taxonomy if specified
+		if ($taxonomy && !empty($managed)) {
+			$taxonomy = jvbCheckBase($taxonomy);
+			$filtered = [];
+			foreach ($managed as $termID) {
+				$term = get_term($termID);
+				if ($term && !is_wp_error($term) && $term->taxonomy === $taxonomy) {
+					$filtered[] = $termID;
+				}
+			}
+			return $filtered;
+		}
+
+		return $managed;
+	}
+
+	/**
+	 * Get all ownable taxonomies
+	 *
+	 * @return array Array of taxonomy slugs
+	 */
+	public function getOwnableTaxonomies(): array
+	{
+		static $ownable = null;
+
+		if ($ownable === null) {
+			$ownable = array_map(function ($instance) {
+				return $instance->slug;
+			}, Registrar::getFeatured('is_ownable', 'term'));
+		}
+
+		return $ownable;
+	}
+
+	/**
+	 * Get all invitable taxonomies
+	 *
+	 * @return array Array of taxonomy slugs
+	 */
+	public function getInvitableTaxonomies(): array
+	{
+		static $invitable = null;
+
+		if ($invitable === null) {
+			$invitable = array_map(function ($instance) {
+				return $instance->slug;
+			}, Registrar::getFeatured('invitable', 'term'));
+		}
+
+		return $invitable;
+	}
+
+	public function getPermission(string $action, string $content, ?int $ID = null):?string
+	{
+		$plural = $this->getContentPlural($content);
+		switch ($action) {
+			case 'edit':
+				if ($ID) {
+					return "edit_{$content}";
+				}
+				return "edit_{$plural}";
+		}
+		return null;
+	}
+
+	public function maybeSwitchPermissions(int $object_id, array $terms, array $tt_ids, string $taxonomy, bool $append, array $old_tt_ids):void
+	{
+		//This shouldn't happen, but whatever
+		if (empty($this->subTypes)) {
+			return;
+		}
+
+		if (!in_array($taxonomy, array_keys($this->subTypes))) {
+			return;
+		}
+
+		$new = array_diff($tt_ids, $old_tt_ids);
+		$old = array_diff($old_tt_ids, $tt_ids);
+
+		$userID = (int)get_post_meta($object_id, BASE.'profile_link',true);
+		if ($userID === 0) {
+			return;
+		}
+		$user = get_userdata($userID);
+		if (!$user) {
+			return;
+		}
+
+		//Revoke old first
+		if (!empty($old)) {
+			$old = array_filter(array_map(function ($id) use ($taxonomy) {
+				$termID = $this->getTermIDFromTTID($id);
+				return $this->getRootTermSlug($termID, $taxonomy);
+			}, $old));
+			foreach ($old as $slug) {
+				if (!array_key_exists($slug, $this->subTypes[$taxonomy])) {
+					error_log('[RoleManager]::maybeSwitchPermissions Could not find creatable types for role subtype '.$slug);
+					continue;
+				}
+				foreach ($this->subTypes[$taxonomy][$slug] ?? [] as $s) {
+					$this->grantContent($user, $s, false);
+				}
+			}
+		}
+
+		if (!empty($new)) {
+			$new = array_filter(array_map(function ($id) use ($taxonomy){
+				$termID = $this->getTermIDFromTTID($id);
+				return $this->getRootTermSlug($termID, $taxonomy);
+			}, $new));
+			foreach ($new as $slug) {
+				if (!array_key_exists($slug, $this->subTypes[$taxonomy])) {
+					error_log('[RoleManager]::maybeSwitchPermissions Could not find creatable types for role subtype '.$slug);
+					continue;
+				}
+				foreach ($this->subTypes[$taxonomy][$slug] ?? [] as $s) {
+					$this->grantContent($user, $s);
+				}
+			}
+		}
+	}
+
+	/**
+	 * Helper function to get term_id from term_taxonomy_id
+	 * @param int $tt_id
+	 *
+	 * @return int
+	 */
+	private function getTermIDFromTTID(int $tt_id):int
+	{
+		global $wpdb;
+		return $wpdb->get_var($wpdb->prepare(
+			"SELECT term_id FROM {$wpdb->term_taxonomy} WHERE term_taxonomy_id = %d",
+			$tt_id
+		));
+	}
+
+	private function getRootTermSlug(int $termID, string $taxonomy): string|false
+	{
+		$term = get_term($termID, $taxonomy);
+		if (!$term || is_wp_error($term)) {
+			return false;
+		}
+		while ($term->parent !== 0) {
+			$term = get_term($term->parent, $taxonomy);
+			if (!$term || is_wp_error($term)) {
+				return false;
+			}
+		}
+		return $term->slug;
 	}
 }

--
Gitblit v1.10.0