> $rules * @param list $previousUserIds User ids that had type "user" rules before the last save (for cleanup). */ public static function syncFromRules(array $rules, array $previousUserIds = []): void { $roleSync = self::aggregateRoleSyncStateBySlug($rules); self::stripIvyformsCapsFromNonAdministratorRoles(); self::applyCapsToRoles($roleSync); $userSync = self::aggregateUserSyncStateByUserId($rules); self::syncUserCaps($userSync, $previousUserIds); } /** * @param list> $rules * @return list */ public static function extractUserIdsFromUserRules(array $rules): array { $ids = []; foreach ($rules as $rule) { if (!is_array($rule) || ($rule['type'] ?? '') !== 'user') { continue; } $uid = (int) ($rule['user_id'] ?? 0); if ($uid > 0) { $ids[$uid] = true; } } return array_map('intval', array_keys($ids)); } /** * @param mixed[] $rule */ private static function ruleHasGlobalFormScope(array $rule): bool { return !array_key_exists('form_ids', $rule) || $rule['form_ids'] === null; } /** * @param array $rule * @param 'role'|'user' $principalType * @param array, needs_plugin_cap: bool}> $accumulated */ private static function accumulateDelegationRule(array $rule, string $principalType, array &$accumulated): void { if (($rule['type'] ?? '') !== $principalType) { return; } $principalKey = self::principalKeyForDelegationRule($rule, $principalType); if ($principalKey === null) { return; } $permissions = $rule['permissions'] ?? []; if (!is_array($permissions)) { return; } $sanitized = PermissionCatalog::sanitize($permissions); if ($sanitized === []) { return; } if (!isset($accumulated[$principalKey])) { $accumulated[$principalKey] = [ 'global_caps' => [], 'needs_plugin_cap' => false, ]; } $accumulated[$principalKey]['needs_plugin_cap'] = true; if (!self::ruleHasGlobalFormScope($rule)) { return; } foreach ($sanitized as $logical) { $accumulated[$principalKey]['global_caps'][PermissionCatalog::wpCapability($logical)] = true; } } /** * @param array $rule * @param 'role'|'user' $principalType * * @return string|int|null */ private static function principalKeyForDelegationRule(array $rule, string $principalType) { if ($principalType === 'role') { $slug = strtolower(trim((string) ($rule['role_slug'] ?? ''))); if ($slug === '' || $slug === 'administrator') { return null; } return $slug; } $userId = (int) ($rule['user_id'] ?? 0); return $userId > 0 ? $userId : null; } /** * @param list> $rules * @return array, needs_plugin_cap: bool}> */ private static function aggregateRoleSyncStateBySlug(array $rules): array { $byRole = []; foreach ($rules as $rule) { if (!is_array($rule)) { continue; } self::accumulateDelegationRule($rule, 'role', $byRole); } return $byRole; } /** * @param list> $rules * @return array, needs_plugin_cap: bool}> */ private static function aggregateUserSyncStateByUserId(array $rules): array { $byUser = []; foreach ($rules as $rule) { if (!is_array($rule)) { continue; } self::accumulateDelegationRule($rule, 'user', $byUser); } $normalized = []; foreach ($byUser as $uid => $state) { $normalized[(int) $uid] = $state; } return $normalized; } /** * @param array, needs_plugin_cap: bool}> $userSyncById * @param list $previousUserIds */ private static function syncUserCaps(array $userSyncById, array $previousUserIds): void { $newUserIds = array_map('intval', array_keys($userSyncById)); $previousUserIds = array_map('intval', $previousUserIds); $affected = array_values(array_unique(array_merge($previousUserIds, $newUserIds))); foreach ($affected as $userId) { $userId = (int) $userId; if ($userId <= 0) { continue; } clean_user_cache($userId); $user = new WP_User($userId); if ((int) $user->ID <= 0) { continue; } if (self::userShouldSkipUserCapSynchronization($user)) { continue; } if (!in_array($userId, $newUserIds, true)) { self::stripIvyformsCapsFromUser($user); clean_user_cache($userId); continue; } $state = $userSyncById[$userId]; self::applyCapsToUser($user, $state['global_caps'], $state['needs_plugin_cap']); clean_user_cache($userId); } } private static function userShouldSkipUserCapSynchronization(WP_User $user): bool { return IvyFormsAccess::userHasImplicitElevatedAccess($user) || user_can($user, IvyFormsAccess::CAP_MANAGE_ALL); } private static function stripIvyformsCapsFromUser(WP_User $user): void { foreach (self::capsToStripFromRoles() as $cap) { $user->remove_cap($cap); } } /** * @param array $globalCaps ivyforms_* from rules with form_ids null only */ private static function applyCapsToUser(WP_User $user, array $globalCaps, bool $needsPluginCap): void { foreach (PermissionCatalog::keys() as $key) { $cap = PermissionCatalog::wpCapability($key); if (isset($globalCaps[$cap])) { $user->add_cap($cap); continue; } $user->remove_cap($cap); } if ($needsPluginCap) { $user->add_cap(IvyFormsAccess::CAP_ACCESS_PLUGIN); return; } $user->remove_cap(IvyFormsAccess::CAP_ACCESS_PLUGIN); } private static function stripIvyformsCapsFromNonAdministratorRoles(): void { $wpRoles = wp_roles(); if (!$wpRoles instanceof \WP_Roles) { return; } $stripCaps = self::capsToStripFromRoles(); foreach (array_keys($wpRoles->roles) as $slug) { if ($slug === 'administrator') { continue; } $role = get_role($slug); if ($role === null) { continue; } foreach ($stripCaps as $cap) { $role->remove_cap($cap); } } } /** * @return list */ private static function capsToStripFromRoles(): array { $caps = []; foreach (PermissionCatalog::keys() as $key) { $caps[] = PermissionCatalog::wpCapability($key); } $caps[] = IvyFormsAccess::CAP_ACCESS_PLUGIN; return $caps; } /** * @param array, needs_plugin_cap: bool}> $roleSyncBySlug */ private static function applyCapsToRoles(array $roleSyncBySlug): void { foreach ($roleSyncBySlug as $slug => $state) { $role = get_role($slug); if ($role === null) { continue; } $globalCaps = $state['global_caps']; foreach (array_keys($globalCaps) as $cap) { $role->add_cap($cap); } if ($state['needs_plugin_cap']) { $role->add_cap(IvyFormsAccess::CAP_ACCESS_PLUGIN); } } } }