From 772462eeca3002a1d52508aeba485aab2b4742ad Mon Sep 17 00:00:00 2001
From: Jake Vanderwerf <get@jakevanderwerf.ca>
Date: Tue, 03 Mar 2026 19:06:19 +0000
Subject: [PATCH] =MAJOR OVERHAUL. Likely should have made a new branch ages ago. Key changes: Registrar.php is the base for custom post types, taxonomies, and user roles. Replaces JVB_CONTENT, JVB_TAXONOMY, and JVB_USER constants, eliminates most of Features.php (except for JVB_SITE, JVB_MEMBERSHIP), and has built in sanitizing and validation via sub-classes. Also started a major overhaul of the Schema output. Created a shit ton of property traits and classes to help sanitize and ensure proper data for different schema types. Still a bunch to do, but better to be starting committing changes here on this other branch.
---
inc/managers/RoleManager.php | 413 +++++++++++++++++++++++++++++++++++++++++++++++++---------
1 files changed, 349 insertions(+), 64 deletions(-)
diff --git a/inc/managers/RoleManager.php b/inc/managers/RoleManager.php
index 4913bc4..0f235d6 100644
--- a/inc/managers/RoleManager.php
+++ b/inc/managers/RoleManager.php
@@ -1,6 +1,7 @@
<?php
namespace JVBase\managers;
+use JVBase\registrar\Registrar;
use JVBase\utility\Features;
use WP_User;
use WP_Role;
@@ -15,10 +16,17 @@
public function __construct()
{
- $this->roles = array_keys(JVB_USER);
+ $this->roles = array_keys(array_map(function ($instance) {
+ return $instance->slug;
+ }, 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);
}
@@ -28,7 +36,8 @@
return;
}
$temp = jvbNoBase($role);
- if (array_key_exists($temp, JVB_USER)) {
+ $registrar = Registrar::getInstance($temp);
+ if ($registrar) {
$user = get_userdata($userID);
if (!$user) {
return;
@@ -56,17 +65,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;
@@ -91,11 +96,11 @@
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) {
@@ -185,15 +190,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;
@@ -230,13 +239,17 @@
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);
@@ -245,7 +258,7 @@
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);
@@ -254,7 +267,7 @@
}
}
- private function buildRoleCapabilities(string $slug, array $config): array
+ private function buildRoleCapabilities(string $slug, Registrar $registrar): array
{
//Everyone can see the things
$capabilities = [
@@ -262,24 +275,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 (Features::forSite()->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}'");
@@ -298,44 +319,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
@@ -393,7 +400,6 @@
"edit_others_{$plural}",
"publish_{$plural}",
"read_private_{$plural}",
- "edit_{$plural}",
];
}
protected function getOthersCapabilities(string $content):array
@@ -420,22 +426,301 @@
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']??JVB_TAXONOMY[$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
{
- foreach (JVB_USER as $slug => $config) {
- $this->registerRole($slug, $config);
+ foreach (Registrar::getRegistered('user') as $role) {
+ $this->registerRole($role);
}
}
+
+ /******************************************************************
+ * 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);
+
+ 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;
+ }
+
+ $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);
+
+ 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);
+
+ 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;
+ }
}
--
Gitblit v1.10.0