<?php
-// phpcs:disable
+/**
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ */
+
use CRM_Standaloneusers_ExtensionUtil as E;
-// phpcs:enable
class CRM_Standaloneusers_BAO_Role extends CRM_Standaloneusers_DAO_Role implements \Civi\Core\HookInterface {
* @param \Civi\Core\Event\PostEvent $event
*/
public static function self_hook_civicrm_post(\Civi\Core\Event\PostEvent $event) {
- // Remove role from users on deletion
- if ($event->action === 'delete') {
- $users = \Civi\Api4\User::get(FALSE)
- ->addSelect('id', 'roles')
- ->addWhere('roles', 'CONTAINS', $event->id)
- ->execute();
- foreach ($users as $user) {
- $roles = array_diff($user['roles'], [$event->id]);
- \Civi\Api4\User::update(FALSE)
- ->addValue('roles', $roles)
- ->addWhere('id', '=', $user['id'])
- ->execute();
- }
- }
-
// Reset cache
Civi::cache('metadata')->clear();
}
<?php
-
/**
* @package CRM
* @copyright CiviCRM LLC https://civicrm.org/licensing
*/
+use Civi\Api4\UserRole;
+use CRM_Standaloneusers_ExtensionUtil as E;
+
/**
* Business access object for the User entity.
*/
class CRM_Standaloneusers_BAO_User extends CRM_Standaloneusers_DAO_User implements \Civi\Core\HookInterface {
/**
- * Event fired after an action is taken on a User record.
+ * Event fired before an action is taken on a User record.
* @param \Civi\Core\Event\PreEvent $event
*/
public static function self_hook_civicrm_pre(\Civi\Core\Event\PreEvent $event) {
- if (in_array($event->action, ['create', 'edit'])
- && empty($event->params['when_updated'])) {
+ if (
+ in_array($event->action, ['create', 'edit'], TRUE) &&
+ empty($event->params['when_updated'])
+ ) {
// Track when_updated.
$event->params['when_updated'] = date('YmdHis');
}
}
+ /**
+ * Event fired after an action is taken on a User record.
+ * @param \Civi\Core\Event\PostEvent $event
+ */
+ public static function self_hook_civicrm_post(\Civi\Core\Event\PostEvent $event) {
+ // Handle virtual "roles" field (defined in UserSpecProvider)
+ // @see \Civi\Api4\Service\Spec\Provider\UserSpecProvider
+ if (
+ in_array($event->action, ['create', 'edit'], TRUE) &&
+ isset($event->params['roles']) && $event->id
+ ) {
+ if ($event->params['roles']) {
+ $newRoles = array_map(function($role_id) {
+ return ['role_id' => $role_id];
+ }, $event->params['roles']);
+ UserRole::replace(FALSE)
+ ->addWhere('user_id', '=', $event->id)
+ ->setRecords($newRoles)
+ ->execute();
+ }
+ else {
+ UserRole::delete(FALSE)
+ ->addWhere('user_id', '=', $event->id)
+ ->execute();
+ }
+ }
+ }
+
public static function updateLastAccessed() {
$sess = CRM_Core_Session::singleton();
$ufID = (int) $sess->get('ufID');
*
* Generated from standaloneusers/xml/schema/CRM/Standaloneusers/User.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:889ac5b24fb6913d046bd2e52dcb65ea)
+ * (GenCodeChecksum:29abb6899d43b942155a37ffeddaa10d)
*/
use CRM_Standaloneusers_ExtensionUtil as E;
*/
public $hashed_password;
- /**
- * FK to Role
- *
- * @var string|null
- * (SQL type: varchar(128))
- * Note that values will be retrieved from the database as a string.
- */
- public $roles;
-
/**
* @var string|null
* (SQL type: timestamp)
'readonly' => TRUE,
'add' => NULL,
],
- 'roles' => [
- 'name' => 'roles',
- 'type' => CRM_Utils_Type::T_STRING,
- 'title' => E::ts('Roles'),
- 'description' => E::ts('FK to Role'),
- 'maxlength' => 128,
- 'size' => CRM_Utils_Type::HUGE,
- 'usage' => [
- 'import' => FALSE,
- 'export' => FALSE,
- 'duplicate_matching' => FALSE,
- 'token' => FALSE,
- ],
- 'where' => 'civicrm_uf_match.roles',
- 'table_name' => 'civicrm_uf_match',
- 'entity' => 'User',
- 'bao' => 'CRM_Standaloneusers_DAO_User',
- 'localizable' => 0,
- 'serialize' => self::SERIALIZE_SEPARATOR_BOOKEND,
- 'html' => [
- 'type' => 'Select',
- ],
- 'pseudoconstant' => [
- 'table' => 'civicrm_role',
- 'keyColumn' => 'id',
- 'labelColumn' => 'label',
- 'nameColumn' => 'name',
- 'condition' => 'name != "everyone"',
- ],
- 'add' => NULL,
- ],
'when_created' => [
'name' => 'when_created',
'type' => CRM_Utils_Type::T_TIMESTAMP,
--- /dev/null
+<?php
+
+/**
+ * @package CRM
+ * @copyright CiviCRM LLC https://civicrm.org/licensing
+ *
+ * Generated from standaloneusers/xml/schema/CRM/Standaloneusers/UserRole.xml
+ * DO NOT EDIT. Generated by CRM_Core_CodeGen
+ * (GenCodeChecksum:261a62783e6628b718b812b096080e69)
+ */
+use CRM_Standaloneusers_ExtensionUtil as E;
+
+/**
+ * Database access object for the UserRole entity.
+ */
+class CRM_Standaloneusers_DAO_UserRole extends CRM_Core_DAO {
+ const EXT = E::LONG_NAME;
+ const TABLE_ADDED = '';
+
+ /**
+ * Static instance to hold the table name.
+ *
+ * @var string
+ */
+ public static $_tableName = 'civicrm_user_role';
+
+ /**
+ * Should CiviCRM log any modifications to this table in the civicrm_log table.
+ *
+ * @var bool
+ */
+ public static $_log = TRUE;
+
+ /**
+ * Unique UserRole ID
+ *
+ * @var int|string|null
+ * (SQL type: int unsigned)
+ * Note that values will be retrieved from the database as a string.
+ */
+ public $id;
+
+ /**
+ * FK to User
+ *
+ * @var int|string|null
+ * (SQL type: int unsigned)
+ * Note that values will be retrieved from the database as a string.
+ */
+ public $user_id;
+
+ /**
+ * FK to Role
+ *
+ * @var int|string|null
+ * (SQL type: int unsigned)
+ * Note that values will be retrieved from the database as a string.
+ */
+ public $role_id;
+
+ /**
+ * Class constructor.
+ */
+ public function __construct() {
+ $this->__table = 'civicrm_user_role';
+ parent::__construct();
+ }
+
+ /**
+ * Returns localized title of this entity.
+ *
+ * @param bool $plural
+ * Whether to return the plural version of the title.
+ */
+ public static function getEntityTitle($plural = FALSE) {
+ return $plural ? E::ts('User Roles') : E::ts('User Role');
+ }
+
+ /**
+ * Returns foreign keys and entity references.
+ *
+ * @return array
+ * [CRM_Core_Reference_Interface]
+ */
+ public static function getReferenceColumns() {
+ if (!isset(Civi::$statics[__CLASS__]['links'])) {
+ Civi::$statics[__CLASS__]['links'] = static::createReferenceColumns(__CLASS__);
+ Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'user_id', 'civicrm_uf_match', 'id');
+ Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'role_id', 'civicrm_role', 'id');
+ CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
+ }
+ return Civi::$statics[__CLASS__]['links'];
+ }
+
+ /**
+ * Returns all the column names of this table
+ *
+ * @return array
+ */
+ public static function &fields() {
+ if (!isset(Civi::$statics[__CLASS__]['fields'])) {
+ Civi::$statics[__CLASS__]['fields'] = [
+ 'id' => [
+ 'name' => 'id',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('ID'),
+ 'description' => E::ts('Unique UserRole ID'),
+ 'required' => TRUE,
+ 'usage' => [
+ 'import' => FALSE,
+ 'export' => FALSE,
+ 'duplicate_matching' => FALSE,
+ 'token' => FALSE,
+ ],
+ 'where' => 'civicrm_user_role.id',
+ 'table_name' => 'civicrm_user_role',
+ 'entity' => 'UserRole',
+ 'bao' => 'CRM_Standaloneusers_DAO_UserRole',
+ 'localizable' => 0,
+ 'html' => [
+ 'type' => 'Number',
+ ],
+ 'readonly' => TRUE,
+ 'add' => NULL,
+ ],
+ 'user_id' => [
+ 'name' => 'user_id',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('User ID'),
+ 'description' => E::ts('FK to User'),
+ 'usage' => [
+ 'import' => FALSE,
+ 'export' => FALSE,
+ 'duplicate_matching' => FALSE,
+ 'token' => FALSE,
+ ],
+ 'where' => 'civicrm_user_role.user_id',
+ 'table_name' => 'civicrm_user_role',
+ 'entity' => 'UserRole',
+ 'bao' => 'CRM_Standaloneusers_DAO_UserRole',
+ 'localizable' => 0,
+ 'FKClassName' => 'CRM_Standaloneusers_DAO_User',
+ 'html' => [
+ 'type' => 'EntityRef',
+ 'label' => E::ts("User"),
+ ],
+ 'add' => NULL,
+ ],
+ 'role_id' => [
+ 'name' => 'role_id',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('Role ID'),
+ 'description' => E::ts('FK to Role'),
+ 'usage' => [
+ 'import' => FALSE,
+ 'export' => FALSE,
+ 'duplicate_matching' => FALSE,
+ 'token' => FALSE,
+ ],
+ 'where' => 'civicrm_user_role.role_id',
+ 'table_name' => 'civicrm_user_role',
+ 'entity' => 'UserRole',
+ 'bao' => 'CRM_Standaloneusers_DAO_UserRole',
+ 'localizable' => 0,
+ 'FKClassName' => 'CRM_Standaloneusers_DAO_Role',
+ 'html' => [
+ 'type' => 'EntityRef',
+ 'label' => E::ts("Role"),
+ ],
+ 'add' => NULL,
+ ],
+ ];
+ CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
+ }
+ return Civi::$statics[__CLASS__]['fields'];
+ }
+
+ /**
+ * Return a mapping from field-name to the corresponding key (as used in fields()).
+ *
+ * @return array
+ * Array(string $name => string $uniqueName).
+ */
+ public static function &fieldKeys() {
+ if (!isset(Civi::$statics[__CLASS__]['fieldKeys'])) {
+ Civi::$statics[__CLASS__]['fieldKeys'] = array_flip(CRM_Utils_Array::collect('name', self::fields()));
+ }
+ return Civi::$statics[__CLASS__]['fieldKeys'];
+ }
+
+ /**
+ * Returns the names of this table
+ *
+ * @return string
+ */
+ public static function getTableName() {
+ return self::$_tableName;
+ }
+
+ /**
+ * Returns if this table needs to be logged
+ *
+ * @return bool
+ */
+ public function getLog() {
+ return self::$_log;
+ }
+
+ /**
+ * Returns the list of fields that can be imported
+ *
+ * @param bool $prefix
+ *
+ * @return array
+ */
+ public static function &import($prefix = FALSE) {
+ $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'user_role', $prefix, []);
+ return $r;
+ }
+
+ /**
+ * Returns the list of fields that can be exported
+ *
+ * @param bool $prefix
+ *
+ * @return array
+ */
+ public static function &export($prefix = FALSE) {
+ $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'user_role', $prefix, []);
+ return $r;
+ }
+
+ /**
+ * Returns the list of indices
+ *
+ * @param bool $localize
+ *
+ * @return array
+ */
+ public static function indices($localize = TRUE) {
+ $indices = [];
+ return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
+ }
+
+}
<?php
-
/*
+--------------------------------------------------------------------+
| Copyright CiviCRM LLC. All rights reserved. |
namespace Civi\Api4\Service\Spec\Provider;
+use CRM_Standaloneusers_ExtensionUtil as E;
+use Civi\Api4\Query\Api4SelectQuery;
use Civi\Api4\Service\Spec\FieldSpec;
use Civi\Api4\Service\Spec\RequestSpec;
* @inheritDoc
*/
public function modifySpec(RequestSpec $spec) {
- $password = new FieldSpec('password', 'User', 'String');
- $password->setTitle(ts('New password'));
- $password->setDescription('Provide a new password for this user.');
- $password->setInputType('Password');
- $spec->addFieldSpec($password);
+ // Write-only `password` field
+ if (in_array($spec->getAction(), ['create', 'update', 'save'], TRUE)) {
+ $password = new FieldSpec('password', 'User', 'String');
+ $password->setTitle(E::ts('New password'));
+ $password->setDescription(E::ts('Provide a new password for this user.'));
+ $password->setInputType('Password');
+ $spec->addFieldSpec($password);
+ }
+ // Virtual "roles" field is a facade to the FK values in `civicrm_user_role`.
+ // It makes forms easier to write by acting as if user.roles were a simple field on the User record.
+ $roles = new FieldSpec('roles', 'User', 'Array');
+ $roles->setTitle(E::ts('Roles'));
+ $roles->setDescription(E::ts('Role ids belonging to this user.'));
+ $roles->setInputType('Select');
+ $roles->setInputAttrs(['multiple' => TRUE]);
+ $roles->setSerialize(\CRM_Core_DAO::SERIALIZE_COMMA);
+ $roles->setSuffixes(['id', 'name', 'label']);
+ $roles->setOptionsCallback([__CLASS__, 'getRolesOptions']);
+ $roles->setColumnName('id');
+ $roles->setSqlRenderer([__CLASS__, 'getRolesSql']);
+ $spec->addFieldSpec($roles);
+ }
+
+ public static function getRolesOptions(): array {
+ $roles = \Civi::cache('metadata')->get('user_roles');
+ if (!$roles) {
+ $select = \CRM_Utils_SQL_Select::from('civicrm_role')
+ ->select(['id', 'name', 'label'])
+ ->where('is_active = 1')
+ ->where('name != "everyone"')
+ ->orderBy('label')
+ ->toSQL();
+ $roles = \CRM_Core_DAO::executeQuery($select)->fetchAll();
+ \Civi::cache('metadata')->set('user_roles', $roles);
+ }
+ return $roles;
+ }
+
+ public static function getRolesSql(array $field, Api4SelectQuery $query): string {
+ return "(SELECT GROUP_CONCAT(ur.role_id) FROM civicrm_user_role ur WHERE ur.user_id = {$field['sql_name']})";
}
/**
* @inheritDoc
*/
public function applies($entity, $action) {
- return $entity === 'User' && in_array($action, ['create', 'update', 'save']);
+ return $entity === 'User';
}
}
--- /dev/null
+<?php
+namespace Civi\Api4;
+
+/**
+ * UserRole entity: links Users with their Role(s).
+ *
+ * Provided by the Standalone Users extension.
+ *
+ * @searchable bridge
+ * @package Civi\Api4
+ */
+class UserRole extends Generic\DAOEntity {
+ use \Civi\Api4\Generic\Traits\EntityBridge;
+
+}
SET FOREIGN_KEY_CHECKS=0;
+DROP TABLE IF EXISTS `civicrm_user_role`;
DROP TABLE IF EXISTS `civicrm_uf_match`;
DROP TABLE IF EXISTS `civicrm_role`;
`contact_id` int unsigned COMMENT 'FK to Contact ID',
`username` varchar(60) NOT NULL,
`hashed_password` varchar(128) NOT NULL DEFAULT "" COMMENT 'Hashed, not plaintext password',
- `roles` varchar(128) COMMENT 'FK to Role',
`when_created` timestamp DEFAULT CURRENT_TIMESTAMP,
`when_last_accessed` timestamp NULL,
`when_updated` timestamp NULL,
CONSTRAINT FK_civicrm_uf_match_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE SET NULL
)
ENGINE=InnoDB;
+
+-- /*******************************************************
+-- *
+-- * civicrm_user_role
+-- *
+-- * Bridge between users and roles
+-- *
+-- *******************************************************/
+CREATE TABLE `civicrm_user_role` (
+ `id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique UserRole ID',
+ `user_id` int unsigned COMMENT 'FK to User',
+ `role_id` int unsigned COMMENT 'FK to Role',
+ PRIMARY KEY (`id`),
+ CONSTRAINT FK_civicrm_user_role_user_id FOREIGN KEY (`user_id`) REFERENCES `civicrm_uf_match`(`id`) ON DELETE CASCADE,
+ CONSTRAINT FK_civicrm_user_role_role_id FOREIGN KEY (`role_id`) REFERENCES `civicrm_role`(`id`) ON DELETE CASCADE
+)
+ENGINE=InnoDB;
SET FOREIGN_KEY_CHECKS=0;
+DROP TABLE IF EXISTS `civicrm_user_role`;
DROP TABLE IF EXISTS `civicrm_uf_match`;
DROP TABLE IF EXISTS `civicrm_role`;
<comment>Hashed, not plaintext password</comment>
</field>
- <field>
- <name>roles</name>
- <type>varchar</type>
- <title>Roles</title>
- <length>128</length>
- <comment>FK to Role</comment>
- <pseudoconstant>
- <table>civicrm_role</table>
- <keyColumn>id</keyColumn>
- <labelColumn>label</labelColumn>
- <nameColumn>name</nameColumn>
- <condition>name != "everyone"</condition>
- </pseudoconstant>
- <html>
- <type>Select</type>
- </html>
- <serialize>SEPARATOR_BOOKEND</serialize>
- </field>
-
<field>
<name>when_created</name>
<type>timestamp</type>
--- /dev/null
+<?php
+// This file declares a new entity type. For more details, see "hook_civicrm_entityTypes" at:
+// https://docs.civicrm.org/dev/en/latest/hooks/hook_civicrm_entityTypes
+return [
+ [
+ 'name' => 'UserRole',
+ 'class' => 'CRM_Standaloneusers_DAO_UserRole',
+ 'table' => 'civicrm_user_role',
+ ],
+];
--- /dev/null
+<?xml version="1.0" encoding="iso-8859-1" ?>
+
+<table>
+ <base>CRM/Standaloneusers</base>
+ <class>UserRole</class>
+ <name>civicrm_user_role</name>
+ <comment>Bridge between users and roles</comment>
+ <log>true</log>
+
+ <field>
+ <name>id</name>
+ <type>int unsigned</type>
+ <required>true</required>
+ <comment>Unique UserRole ID</comment>
+ <html>
+ <type>Number</type>
+ </html>
+ </field>
+ <primaryKey>
+ <name>id</name>
+ <autoincrement>true</autoincrement>
+ </primaryKey>
+
+ <field>
+ <name>user_id</name>
+ <type>int unsigned</type>
+ <comment>FK to User</comment>
+ <html>
+ <label>User</label>
+ <type>EntityRef</type>
+ </html>
+ </field>
+ <foreignKey>
+ <name>user_id</name>
+ <table>civicrm_uf_match</table>
+ <key>id</key>
+ <onDelete>CASCADE</onDelete>
+ </foreignKey>
+
+ <field>
+ <name>role_id</name>
+ <type>int unsigned</type>
+ <comment>FK to Role</comment>
+ <html>
+ <label>Role</label>
+ <type>EntityRef</type>
+ </html>
+ </field>
+ <foreignKey>
+ <name>role_id</name>
+ <table>civicrm_role</table>
+ <key>id</key>
+ <onDelete>CASCADE</onDelete>
+ </foreignKey>
+
+</table>