];
}
+ /**
+ * Callback for Role.permissions pseudoconstant values.
+ *
+ * Permissions for Civi Standalone, not used by CMS-based systems.
+ *
+ * @return array
+ */
+ public static function permissions() {
+ $perms = $options = [];
+ \CRM_Utils_Hook::permissionList($perms);
+
+ foreach ($perms as $machineName => $details) {
+ if ($details['is_active']) {
+ $options[$machineName] = $details['title'];
+ }
+ }
+ return $options;
+ }
+
}
--- /dev/null
+<?php
+// phpcs:disable
+use CRM_Standaloneusers_ExtensionUtil as E;
+// phpcs:enable
+
+class CRM_Standaloneusers_BAO_Role extends CRM_Standaloneusers_DAO_Role {
+
+ /**
+ * Create a new Role based on array-data
+ *
+ * @param array $params key-value pairs
+ * @return CRM_Standaloneusers_DAO_Role|NULL
+ */
+ /*
+ public static function create($params) {
+ $className = 'CRM_Standaloneusers_DAO_Role';
+ $entityName = 'Role';
+ $hook = empty($params['id']) ? 'create' : 'edit';
+
+ CRM_Utils_Hook::pre($hook, $entityName, CRM_Utils_Array::value('id', $params), $params);
+ $instance = new $className();
+ $instance->copyValues($params);
+ $instance->save();
+ CRM_Utils_Hook::post($hook, $entityName, $instance->id, $instance);
+
+ return $instance;
+ }
+ */
+
+}
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*
- * Generated from standaloneusers/xml/schema/CRM/Standaloneusers/RolePermission.xml
+ * Generated from standaloneusers/xml/schema/CRM/Standaloneusers/Role.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:9bd09dba43f645696426e1ef15f776d9)
+ * (GenCodeChecksum:d28fa09f872be30740de728c9e344a6d)
*/
use CRM_Standaloneusers_ExtensionUtil as E;
/**
- * Database access object for the RolePermission entity.
+ * Database access object for the Role entity.
*/
-class CRM_Standaloneusers_DAO_RolePermission extends CRM_Core_DAO {
+class CRM_Standaloneusers_DAO_Role extends CRM_Core_DAO {
const EXT = E::LONG_NAME;
const TABLE_ADDED = '';
*
* @var string
*/
- public static $_tableName = 'civicrm_role_permission';
+ public static $_tableName = 'civicrm_role';
+
+ /**
+ * Field to show when displaying a record.
+ *
+ * @var string
+ */
+ public static $_labelField = 'label';
/**
* Should CiviCRM log any modifications to this table in the civicrm_log table.
public static $_log = TRUE;
/**
- * Unique RolePermission ID
+ * Unique Role ID
*
* @var int|string|null
* (SQL type: int unsigned)
public $id;
/**
- * FK to a role option value
+ * Machine name for this role
*
- * @var int|string|null
- * (SQL type: int unsigned)
+ * @var string
+ * (SQL type: varchar(60))
* Note that values will be retrieved from the database as a string.
*/
- public $role_id;
+ public $name;
/**
- * A single permission granted to this role
+ * Human friendly name for this role
*
* @var string
- * (SQL type: varchar(60))
+ * (SQL type: varchar(128))
+ * Note that values will be retrieved from the database as a string.
+ */
+ public $label;
+
+ /**
+ * List of permissions granted by this role
+ *
+ * @var string
+ * (SQL type: text)
+ * Note that values will be retrieved from the database as a string.
+ */
+ public $permissions;
+
+ /**
+ * Only active roles grant permissions
+ *
+ * @var bool|string|null
+ * (SQL type: tinyint)
* Note that values will be retrieved from the database as a string.
*/
- public $permission;
+ public $is_active;
/**
* Class constructor.
*/
public function __construct() {
- $this->__table = 'civicrm_role_permission';
+ $this->__table = 'civicrm_role';
parent::__construct();
}
* Whether to return the plural version of the title.
*/
public static function getEntityTitle($plural = FALSE) {
- return $plural ? E::ts('Role Permissions') : E::ts('Role Permission');
+ return $plural ? E::ts('Roles') : E::ts('Role');
}
/**
'name' => 'id',
'type' => CRM_Utils_Type::T_INT,
'title' => E::ts('ID'),
- 'description' => E::ts('Unique RolePermission ID'),
+ 'description' => E::ts('Unique Role ID'),
'required' => TRUE,
'usage' => [
'import' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_role_permission.id',
- 'table_name' => 'civicrm_role_permission',
- 'entity' => 'RolePermission',
- 'bao' => 'CRM_Standaloneusers_DAO_RolePermission',
+ 'where' => 'civicrm_role.id',
+ 'table_name' => 'civicrm_role',
+ 'entity' => 'Role',
+ 'bao' => 'CRM_Standaloneusers_DAO_Role',
'localizable' => 0,
'html' => [
'type' => 'Number',
'readonly' => TRUE,
'add' => NULL,
],
- 'role_id' => [
- 'name' => 'role_id',
- 'type' => CRM_Utils_Type::T_INT,
- 'title' => E::ts('Role ID'),
- 'description' => E::ts('FK to a role option value'),
+ 'name' => [
+ 'name' => 'name',
+ 'type' => CRM_Utils_Type::T_STRING,
+ 'title' => E::ts('Name'),
+ 'description' => E::ts('Machine name for this role'),
+ 'required' => TRUE,
+ 'maxlength' => 60,
+ 'size' => CRM_Utils_Type::BIG,
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_role_permission.role_id',
- 'table_name' => 'civicrm_role_permission',
- 'entity' => 'RolePermission',
- 'bao' => 'CRM_Standaloneusers_DAO_RolePermission',
+ 'where' => 'civicrm_role.name',
+ 'table_name' => 'civicrm_role',
+ 'entity' => 'Role',
+ 'bao' => 'CRM_Standaloneusers_DAO_Role',
'localizable' => 0,
- 'pseudoconstant' => [
- 'optionGroupName' => 'role',
- 'optionEditPath' => 'civicrm/admin/options/role',
- ],
'add' => NULL,
],
- 'permission' => [
- 'name' => 'permission',
+ 'label' => [
+ 'name' => 'label',
'type' => CRM_Utils_Type::T_STRING,
- 'title' => E::ts('Permission'),
- 'description' => E::ts('A single permission granted to this role'),
+ 'title' => E::ts('Label'),
+ 'description' => E::ts('Human friendly name for this role'),
+ 'required' => TRUE,
+ 'maxlength' => 128,
+ 'size' => CRM_Utils_Type::HUGE,
+ 'usage' => [
+ 'import' => FALSE,
+ 'export' => FALSE,
+ 'duplicate_matching' => FALSE,
+ 'token' => FALSE,
+ ],
+ 'where' => 'civicrm_role.label',
+ 'table_name' => 'civicrm_role',
+ 'entity' => 'Role',
+ 'bao' => 'CRM_Standaloneusers_DAO_Role',
+ 'localizable' => 0,
+ 'add' => NULL,
+ ],
+ 'permissions' => [
+ 'name' => 'permissions',
+ 'type' => CRM_Utils_Type::T_TEXT,
+ 'title' => E::ts('Permissions'),
+ 'description' => E::ts('List of permissions granted by this role'),
'required' => TRUE,
- 'maxlength' => 60,
- 'size' => CRM_Utils_Type::BIG,
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_role_permission.permission',
- 'table_name' => 'civicrm_role_permission',
- 'entity' => 'RolePermission',
- 'bao' => 'CRM_Standaloneusers_DAO_RolePermission',
+ 'where' => 'civicrm_role.permissions',
+ 'table_name' => 'civicrm_role',
+ 'entity' => 'Role',
+ 'bao' => 'CRM_Standaloneusers_DAO_Role',
'localizable' => 0,
+ 'serialize' => self::SERIALIZE_SEPARATOR_BOOKEND,
+ 'html' => [
+ 'type' => 'Select',
+ ],
+ 'pseudoconstant' => [
+ 'callback' => 'CRM_Core_SelectValues::permissions',
+ ],
+ 'add' => NULL,
+ ],
+ 'is_active' => [
+ 'name' => 'is_active',
+ 'type' => CRM_Utils_Type::T_BOOLEAN,
+ 'title' => E::ts('Role is active'),
+ 'description' => E::ts('Only active roles grant permissions'),
+ 'usage' => [
+ 'import' => FALSE,
+ 'export' => FALSE,
+ 'duplicate_matching' => FALSE,
+ 'token' => FALSE,
+ ],
+ 'where' => 'civicrm_role.is_active',
+ 'default' => '1',
+ 'table_name' => 'civicrm_role',
+ 'entity' => 'Role',
+ 'bao' => 'CRM_Standaloneusers_DAO_Role',
+ 'localizable' => 0,
+ 'html' => [
+ 'type' => 'CheckBox',
+ ],
'add' => NULL,
],
];
* @return array
*/
public static function &import($prefix = FALSE) {
- $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'role_permission', $prefix, []);
+ $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'role', $prefix, []);
return $r;
}
* @return array
*/
public static function &export($prefix = FALSE) {
- $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'role_permission', $prefix, []);
+ $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'role', $prefix, []);
return $r;
}
*
* Generated from standaloneusers/xml/schema/CRM/Standaloneusers/User.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:7f2d1d449d263061c7db7a6c5d57c27d)
+ * (GenCodeChecksum:8b4f87b26bd48490757533b57378e33d)
*/
use CRM_Standaloneusers_ExtensionUtil as E;
public $email;
/**
- * FK to values from OptionGroup role
+ * FK to Role
*
* @var string|null
* (SQL type: varchar(128))
'name' => 'roles',
'type' => CRM_Utils_Type::T_STRING,
'title' => E::ts('Roles'),
- 'description' => E::ts('FK to values from OptionGroup role'),
+ 'description' => E::ts('FK to Role'),
'maxlength' => 128,
'size' => CRM_Utils_Type::HUGE,
'usage' => [
'type' => 'Select',
],
'pseudoconstant' => [
- 'optionGroupName' => 'role',
- 'optionEditPath' => 'civicrm/admin/options/role',
+ 'table' => 'civicrm_role',
+ 'keyColumn' => 'id',
+ 'labelColumn' => 'label',
+ 'nameColumn' => 'name',
],
'add' => NULL,
],
namespace Civi\Api4;
/**
- * RolePermission entity.
+ * Role entity.
*
* Provided by the Standalone Users extension.
*
* @package Civi\Api4
*/
-class RolePermission extends Generic\DAOEntity {
+class Role extends Generic\DAOEntity {
}
public function checkPermission(\CRM_Core_Permission_Standalone $permissionObject, string $permissionName, $userID) {
// I think null means the current logged-in user
+ xdebug_break();
$userID = $userID ?? $this->getLoggedInUfID();
if (!$userID) {
return FALSE;
}
- // @todo handle anonymous permissions!
+ if (!isset(\Civi::$statics[__METHOD__][$userID])) {
+ if ($userID) {
+ $roleIDs = \Civi\Api4\User::get(FALSE)->addWhere('id', '=', $userID)
+ ->addSelect('roles')->execute()->first()['roles'];
+ // Grant the 'Everyone' role, too.
+ $roleIDs[] = 1;
+ }
+ else {
+ // Everyone
+ $roleIDs = [1];
+ }
- $roleIDs = \Civi\Api4\User::get(FALSE)->addWhere('id', '=', $userID)
- ->addSelect('roles')->execute()->first()['roles'];
+ $permissionsPerRole = \Civi\Api4\Role::get(FALSE)
+ ->addSelect('permissions')
+ ->addWhere('id', 'IN', $roleIDs)
+ // ->addWhere('is_active', '=', TRUE) @todo
+ ->execute()->column('permissions');
+ $permissions = array_unique(array_merge(...$permissionsPerRole));
+ \Civi::$statics[__METHOD__][$userID] = $permissions;
+ }
- // artfulrobot: I think we should cache these per request, e.g. Civi::$statics?
- // except in testing permissions shouldn't change during a request. @todo
- $found = \Civi\Api4\RolePermission::get(FALSE)
- ->selectRowCount()
- ->addWhere('role_id', 'IN', $roleIDs)
- ->addWhere('permission', '=', $permissionName)
- ->execute()->countMatched();
- return (bool) $found;
+ return in_array($permissionName, \Civi::$statics[__METHOD__][$userID]);
}
/**
* - 'cms_pass' plaintext password
* - 'notify' boolean
* @param string $mail
- * Email id for cms user.
+ * Email address for cms user.
*
* @return int|bool
* uid if user was created, false otherwise
+++ /dev/null
-<?php
-use CRM_Standaloneusers_ExtensionUtil as E;
-
-return [
- [
- 'name' => 'OptionGroup_role',
- 'entity' => 'OptionGroup',
- 'cleanup' => 'unused',
- 'update' => 'unmodified',
- 'params' => [
- 'version' => 4,
- 'values' => [
- 'name' => 'role',
- 'title' => E::ts('Role'),
- 'description' => E::ts('A role is grants permissions to users in CiviCRM Standalone.'),
- 'data_type' => 'Integer',
- 'is_reserved' => TRUE,
- 'is_active' => TRUE,
- 'is_locked' => FALSE,
- 'option_value_fields' => [
- 'label',
- 'description',
- 'icon',
- 'color',
- 'name',
- ],
- ],
- 'match' => [
- 'name',
- ],
- ],
- ],
- [
- 'name' => 'OptionGroup_role_OptionValue_admin',
- 'entity' => 'OptionValue',
- 'cleanup' => 'unused',
- 'update' => 'unmodified',
- 'params' => [
- 'version' => 4,
- 'values' => [
- 'option_group_id.name' => 'role',
- 'label' => E::ts('Administrator'),
- 'value' => '1',
- 'name' => 'admin',
- 'grouping' => NULL,
- 'filter' => 0,
- 'is_default' => FALSE,
- 'description' => NULL,
- 'is_optgroup' => FALSE,
- 'is_reserved' => TRUE,
- 'is_active' => TRUE,
- 'component_id' => NULL,
- 'domain_id' => NULL,
- 'visibility_id' => NULL,
- 'icon' => NULL,
- 'color' => NULL,
- ],
- 'match' => [
- 'name',
- 'option_group_id',
- ],
- ],
- ],
-];
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `civicrm_user`;
-DROP TABLE IF EXISTS `civicrm_role_permission`;
+DROP TABLE IF EXISTS `civicrm_role`;
SET FOREIGN_KEY_CHECKS=1;
-- /*******************************************************
-- /*******************************************************
-- *
--- * civicrm_role_permission
+-- * civicrm_role
-- *
--- * Assigns permissions to roles
+-- * A Role holds a set of permissions. Roles may be granted to Users.
-- *
-- *******************************************************/
-CREATE TABLE `civicrm_role_permission` (
- `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique RolePermission ID',
- `role_id` int unsigned COMMENT 'FK to a role option value',
- `permission` varchar(60) NOT NULL COMMENT 'A single permission granted to this role',
+CREATE TABLE `civicrm_role` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique Role ID',
+ `name` varchar(60) NOT NULL COMMENT 'Machine name for this role',
+ `label` varchar(128) NOT NULL COMMENT 'Human friendly name for this role',
+ `permissions` text NOT NULL COMMENT 'List of permissions granted by this role',
+ `is_active` tinyint DEFAULT 1 COMMENT 'Only active roles grant permissions',
PRIMARY KEY (`id`)
)
ENGINE=InnoDB;
`username` varchar(60) NOT NULL,
`password` varchar(128) NOT NULL COMMENT 'Hashed password',
`email` varchar(255) NOT NULL COMMENT 'Email (e.g. for password resets)',
- `roles` varchar(128) COMMENT 'FK to values from OptionGroup role',
+ `roles` varchar(128) COMMENT 'FK to Role',
`when_created` timestamp DEFAULT CURRENT_TIMESTAMP,
`when_last_accessed` timestamp NULL,
`when_updated` timestamp NULL,
SET FOREIGN_KEY_CHECKS=0;
DROP TABLE IF EXISTS `civicrm_user`;
-DROP TABLE IF EXISTS `civicrm_role_permission`;
+DROP TABLE IF EXISTS `civicrm_role`;
SET FOREIGN_KEY_CHECKS=1;
\ No newline at end of file
[$contactID, $userID, $security] = $this->createFixtureContactAndUser();
// Create a custom role
- $roleID = \Civi\Api4\OptionValue::create(FALSE)
+ $roleID = \Civi\Api4\Role::create(FALSE)
->setValues([
- 'option_group_id.name' => 'role',
'name' => 'demo_role',
'label' => 'demo_role',
- ])->execute()->first()['value'];
+ 'permissions' => [
+ // Main control for access to the main CiviCRM backend and API. Give to trusted roles only.
+ 'access CiviCRM',
+ 'view all contacts',
+ 'add contacts',
+ 'edit all contacts',
+ // 'administer CiviCRM' // Perform all tasks in the Administer CiviCRM control panel and Import Contacts
+ ],
+ ])->execute()->first()['id'];
// Give our user this role only.
\Civi\Api4\User::update(FALSE)
- ->addValue('roles', [$roleID])
+ ->addValue('roles:name', ['demo_role'])
->addWhere('id', '=', $userID)
->execute();
- $existingPermissions = \Civi\Api4\RolePermission::get(FALSE)
- ->selectRowCount()
- ->addWhere('role_id', '=', $demoRoleID)
- ->execute()->count();
- $this->assertEquals(0, $existingPermissions);
-
- // Assign some permissions to the role.
- \Civi\Api4\RolePermission::save(FALSE)
- ->setDefaults(['role_id' => $roleID])
- ->setRecords([
- // Master control for access to the main CiviCRM backend and API. Give to trusted roles only.
- ['permission' => 'access CiviCRM'],
- // Perform all tasks in the Administer CiviCRM control panel and Import Contacts
- // ['permission' => 'administer CiviCRM'],
- ['permission' => 'view all contacts'],
- ['permission' => 'add contacts'],
- ['permission' => 'edit all contacts'],
- ])
- ->execute();
-
$this->switchToOurUFClasses();
foreach (['access CiviCRM', 'view all contacts', 'add contacts', 'edit all contacts'] as $allowed) {
$this->assertTrue(\CRM_Core_Permission::check([$allowed], $contactID), "Should have '$allowed' permission but don't");
// https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes
return [
[
- 'name' => 'RolePermission',
- 'class' => 'CRM_Standaloneusers_DAO_RolePermission',
- 'table' => 'civicrm_role_permission',
+ 'name' => 'Role',
+ 'class' => 'CRM_Standaloneusers_DAO_Role',
+ 'table' => 'civicrm_role',
],
];
--- /dev/null
+<?xml version="1.0" encoding="iso-8859-1" ?>
+
+<table>
+ <base>CRM/Standaloneusers</base>
+ <class>Role</class>
+ <name>civicrm_role</name>
+ <comment>A Role holds a set of permissions. Roles may be granted to Users.</comment>
+ <log>true</log>
+
+ <field>
+ <name>id</name>
+ <type>int unsigned</type>
+ <required>true</required>
+ <comment>Unique Role ID</comment>
+ <html>
+ <type>Number</type>
+ </html>
+ </field>
+ <primaryKey>
+ <name>id</name>
+ <autoincrement>true</autoincrement>
+ </primaryKey>
+
+ <field>
+ <name>name</name>
+ <comment>Machine name for this role</comment>
+ <type>varchar</type>
+ <length>60</length>
+ <required>true</required>
+ </field>
+
+ <field>
+ <name>label</name>
+ <comment>Human friendly name for this role</comment>
+ <type>varchar</type>
+ <length>128</length>
+ <required>true</required>
+ </field>
+
+ <field>
+ <name>permissions</name>
+ <comment>List of permissions granted by this role</comment>
+ <type>text</type>
+ <required>true</required>
+ <pseudoconstant>
+ <callback>CRM_Core_SelectValues::permissions</callback>
+ </pseudoconstant>
+ <html>
+ <type>Select</type>
+ </html>
+ <serialize>SEPARATOR_BOOKEND</serialize>
+ </field>
+
+ <field>
+ <name>is_active</name>
+ <title>Role is active</title>
+ <comment>Only active roles grant permissions</comment>
+ <type>boolean</type>
+ <default>1</default>
+ <html>
+ <type>CheckBox</type>
+ </html>
+ </field>
+
+</table>
+++ /dev/null
-<?xml version="1.0" encoding="iso-8859-1" ?>
-
-<table>
- <base>CRM/Standaloneusers</base>
- <class>RolePermission</class>
- <name>civicrm_role_permission</name>
- <comment>Assigns permissions to roles</comment>
- <log>true</log>
-
- <field>
- <name>id</name>
- <type>int unsigned</type>
- <required>true</required>
- <comment>Unique RolePermission ID</comment>
- <html>
- <type>Number</type>
- </html>
- </field>
- <primaryKey>
- <name>id</name>
- <autoincrement>true</autoincrement>
- </primaryKey>
-
- <field>
- <name>role_id</name>
- <type>int unsigned</type>
- <comment>FK to a role option value</comment>
- <pseudoconstant>
- <optionGroupName>role</optionGroupName>
- </pseudoconstant>
- </field>
-
- <field>
- <name>permission</name>
- <type>varchar</type>
- <length>60</length>
- <required>true</required>
- <comment>A single permission granted to this role</comment>
- </field>
-
-</table>
<type>varchar</type>
<title>Roles</title>
<length>128</length>
- <comment>FK to values from OptionGroup role</comment>
+ <comment>FK to Role</comment>
<pseudoconstant>
- <optionGroupName>role</optionGroupName>
+ <table>civicrm_role</table>
+ <keyColumn>id</keyColumn>
+ <labelColumn>label</labelColumn>
+ <nameColumn>name</nameColumn>
</pseudoconstant>
<html>
<type>Select</type>
\Civi\Setup::log()->info(sprintf('[%s] Handle %s', basename(__FILE__), 'installDatabase'));
- // admin should always be role 1 on install.
- $roleID = 1;
-
- // @todo I expect there's a better way than this; this doesn't even bring in all the permissions.
- $records = [['permission' => 'authenticate with password']];
- foreach (array_keys(\CRM_Core_Permission::getCorePermissions()) as $permission) {
- $records[] = ['permission' => $permission];
- }
- \Civi\Api4\RolePermission::save(FALSE)
- ->setDefaults(['role_id' => $roleID])
- ->setRecords($records)
- ->execute();
+ $roles = \Civi\Api4\Role::save(FALSE)
+ ->setDefaults([
+ 'is_active' => TRUE,
+ ])
+ ->setRecords([
+ [
+ 'name' => 'everyone',
+ 'label' => 'Everyone, including anonymous users',
+ // @todo some standard ones, e.g. view civimail.
+ 'permissions' => [],
+ ],
+ [
+ 'name' => 'admin',
+ 'label' => 'Administrator',
+ 'permissions' => array_keys(\CRM_Core_SelectValues::permissions()),
+ ],
+ ])
+ ->execute()->indexBy('name');
// Create contact+user for admin.
$contactID = \Civi\Api4\Contact::create(FALSE)
'cms_name' => $e->getModel()->extras['adminUser'],
'cms_pass' => $e->getModel()->extras['adminPass'],
'notify' => FALSE,
- $adminEmail => $adminEmail,
'contactID' => $contactID,
];
$userID = \CRM_Core_BAO_CMSUser::create($params, $adminEmail);
// Assign 'admin' role to user
\Civi\Api4\User::update(FALSE)
->addWhere('id', '=', $userID)
- ->addValue('roles', [$roleID])
+ ->addValue('roles:name', ['admin'])
->execute();
$message = "Created new user \"{$e->getModel()->extras['adminUser']}\" (user ID #$userID, contact ID #$contactID) with 'admin' role and ";