roles = array_keys(JVB_USER); $this->content = array_map(function($content) { return strtolower($content['plural']); },JVB_CONTENT); } /** * @param WP_User $user * * @return void */ public function reset(WP_User $user):void { foreach ($this->content as $content => $plural) { $content = jvbCheckBase($content); $this->grantContent($user, $content, false); $this->grantOthersContent($user, $content, false); } } private function isValidContentType(string $type): bool { $type = jvbNoBase($type); // Check in JVB_CONTENT array if (array_key_exists($type, JVB_CONTENT)) { 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; } } return false; } /** * Ensures the role can manage the content before changing capabilities * @param int|WP_User $user * @param string $content * @return bool */ public function checkRole(int|WP_User $user, string $content):bool { if(user_can($user, 'manage_options')) { return true; } if (is_int($user)) { $user = get_userdata($user); } $roles = array_keys($user->roles); foreach ($roles as $role) { $role = jvbNoBase($role); $config = JVB_USER[$role]??false; if (!$config) { return false; } foreach ($config as $type) { if (is_array($type) && in_array($content, $type)) { return true; } elseif ($content === $type) { return true; } } } return false; } /** * Grants content management capabilities to a user * @param WP_User $user * @param string $content * @param bool $add * @return void */ public function grantContent(WP_User $user, string $content, bool $add = true):void { if (!$this->isValidContentType($content) || !$this->checkRole($user, $content)) { return; } $capabilities = $this->getCapabilities($content); foreach ($capabilities as $cap) { if ($add) { $user->add_cap($cap); } else { $user->remove_cap($cap); } } } /** * Grants management of other users' content to a user * @param WP_User $user * @param string $content * @param bool $add * @return void */ public function grantOthersContent(WP_USER $user, string $content, bool $add = true) { if (!$this->isValidContentType($content) || !$this->checkRole($user, $content)) { return; } $capabilities = $this->getOthersCapabilities($content); foreach ($capabilities as $cap) { if ($add) { $user->add_cap($cap); } else { $user->remove_cap($cap); } } } /** * @param WP_User $user * @param string $type * @param bool $add * * @return void */ public function setUserAs(WP_User $user, string $type):void { $role = jvbNoBase(array_keys($user->roles)[0]); $config = $this->getTypesConfig($role); if (!$config || !array_key_exists($type, $config)) { return; } foreach ($config as $role => $content) { if ($type !== $role) { foreach ($content as $c) { $this->grantContent($user, $c, false); } } else { foreach ($content as $c) { $this->grantContent($user, $c); } } } } protected function getTypesConfig(string $role):bool|array { $check = get_transient(BASE.'role_config_'.$role); if ($check) { return $check; } $check = JVB_USER[$role]['can_create'] ??false; if (!$check) { return false; } $out = []; foreach ($check as $types) { foreach ($types as $type => $content) { $out[$type] = $content; } } set_transient(BASE.'role_config_'.$role, $out, MONTH_IN_SECONDS); return $out; } /** * @param WP_User $user * @param string $type * * @return int|false */ public function addUserLink(WP_User $user, string $type) { if (!in_array($type, $this->roles)) { return false; } $link = get_user_meta($user->ID, BASE.'link', true); if ($link === '') { $type = BASE.$type; $name = $user->display_name; $status = ($user->has_cap('skip_moderation')) ? 'publish' : 'draft'; $link = wp_insert_post([ 'post_type' => $type, 'post_status' => $status, 'post_title' => $name, 'post_author' => $user->ID, ]); if ($link) { update_user_meta($user->ID, BASE.'link', $link); update_post_meta($link, BASE.'link', $user->ID); } } return $link; } public function registerRole(string $slug, array $config): void { $role_name = BASE . $slug; $display_name = $config['label'] ?? ucfirst($slug); // Build capabilities for this role $capabilities = $this->buildRoleCapabilities($slug, $config); // Remove role first to ensure clean slate remove_role($role_name); // Add the role with capabilities add_role($role_name, $display_name, $capabilities); // Add management capabilities to administrator if ($config['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); } } } private function buildRoleCapabilities(string $slug, array $config): array { //Everyone can see the things $capabilities = [ 'read' => true, ]; // Dashboard access if ($this->config['has_dashboard'] ?? false) { $capabilities['access_dashboard'] = true; } if (Features::forSite()->has('favourites') && $config['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); } } // Management capabilities if (!empty($this->config['manage_others'])) { foreach ($this->config['manage_others'] 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}'"); continue; } $addCaps = $this->getOthersCapabilities($type); foreach ($addCaps as $cap) { $capabilities[$cap] = true; } } } return apply_filters(BASE . 'role_capabilities', $capabilities, $slug); } /** * Add content capabilities to capability array */ private function addContentCapabilities(array &$capabilities, $content_type, array $config): 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); } } /** * Add capabilities for a single content type */ private function addSingleContentCapabilities(array &$capabilities, string $type, array $config): void { $caps = $this->getCapabilities($type); foreach ($caps as $cap) { $capabilities[$cap] = true; } if (array_key_exists('approve_new', $config)) { $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 { $role = get_role($role_name); if (!$role) { return; } $capabilities = $this->getCapabilities(jvbNoBase($content_slug)); foreach ($capabilities as $capability) { if ($grant) { $role->add_cap($capability); } else { $role->remove_cap($capability); } } } public function grantRoleOthersCapabilities(string $role_name, string $content_slug, bool $grant = true): void { $role = get_role($role_name); if (!$role) { return; } $capabilities = $this->getOthersCapabilities(jvbNoBase($content_slug)); foreach ($capabilities as $capability) { if ($grant) { $role->add_cap($capability); } else { $role->remove_cap($capability); } } } protected function getCapabilities(string $content):array { $content = jvbNoBase($content); if (!$this->isValidContentType($content)) { return []; } $plural = $this->getContentPlural($content); return [ 'edit_' . $plural, 'delete_' . $plural, 'read_' . $plural, 'edit_published_' . $plural, 'delete_published_' . $plural, 'edit_private_' . $plural, 'delete_private_' . $plural, 'publish_' . $plural, ]; } protected function getOthersCapabilities(string $content):array { $content = jvbNoBase($content); if (!$this->isValidContentType($content)) { return []; } $plural = $this->getContentPlural($content); return [ 'edit_others_' . $plural, 'delete_others_' . $plural, 'read_private_' . $plural, 'edit_private_' . $plural, 'delete_private_' . $plural, ]; } private function getContentPlural(string $content): string { $content = jvbNoBase($content); if (array_key_exists($content, JVB_CONTENT)) { return strtolower(JVB_CONTENT[$content]['plural'] ?? $content . 's'); } return strtolower($content . 's'); } public function activate(): void { foreach (JVB_USER as $slug => $config) { $this->registerRole($slug, $config); } } }