<?php
|
namespace JVBase\managers;
|
|
use JVBase\utility\Features;
|
use WP_User;
|
use WP_Role;
|
|
if (!defined('ABSPATH')) {
|
exit; // Exit if accessed directly
|
}
|
class RoleManager
|
{
|
private array $roles;
|
private array $content;
|
|
public function __construct()
|
{
|
$this->roles = array_keys(JVB_USER);
|
$this->content = array_map(function($content) {
|
return strtolower($content['plural']??$content['singular'].'s');
|
},JVB_CONTENT);
|
add_action('set_user_role', [$this, 'updateRoles'], 10, 3);
|
}
|
|
public function updateRoles(int $userID, string $role, array $oldRoles):void
|
{
|
if (doing_action('set_user_role') > 1) {
|
return;
|
}
|
$temp = jvbNoBase($role);
|
if (array_key_exists($temp, JVB_USER)) {
|
$user = get_userdata($userID);
|
if (!$user) {
|
return;
|
}
|
$this->reset($user);
|
$this->setUserAs($user, $temp);
|
}
|
}
|
|
/**
|
* @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
|
*
|
* @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);
|
}
|
}
|
}
|
|
/**
|
* @param string $content
|
* @return array|string[]
|
* Note: must match what is created in PostTypeRegistrar.php::register
|
*/
|
protected function getCapabilities(string $content):array
|
{
|
$content = jvbNoBase($content);
|
if (!$this->isValidContentType($content)) {
|
return [];
|
}
|
|
$plural = $this->getContentPlural($content);
|
|
return [
|
"edit_{$content}",
|
"read_{$content}",
|
"delete_{$content}",
|
"edit_{$plural}",
|
"edit_others_{$plural}",
|
"publish_{$plural}",
|
"read_private_{$plural}",
|
"edit_{$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}",
|
];
|
}
|
|
public static function getPlural(string $content): string
|
{
|
$self = new self;
|
return $self->getContentPlural($content);
|
}
|
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];
|
}
|
return str_replace('-', '_', sanitize_title(strtolower($content . 's')));
|
}
|
|
public function activate(): void
|
{
|
foreach (JVB_USER as $slug => $config) {
|
$this->registerRole($slug, $config);
|
}
|
}
|
}
|