$ufID = $config->userSystem->createUser($params, $mailParam);
- //if contact doesn't already exist create UF Match
- if ($ufID !== FALSE &&
- isset($params['contactID'])
+ // Create UF Match if we have contactID unless we're Standalone
+ // since in Standalone uf_match is the same table as User.
+ if (
+ CIVICRM_UF !== 'Standalone'
+ && $ufID !== FALSE
+ && isset($params['contactID'])
) {
// create the UF Match record
$ufmatch['uf_id'] = $ufID;
*
* Generated from standaloneusers/xml/schema/CRM/Standaloneusers/User.xml
* DO NOT EDIT. Generated by CRM_Core_CodeGen
- * (GenCodeChecksum:12f08a726067ec0e3cc7f45411d9526a)
+ * (GenCodeChecksum:5769c2469482e66ebeec050ea3e82a97)
*/
use CRM_Standaloneusers_ExtensionUtil as E;
*
* @var string
*/
- public static $_tableName = 'civicrm_user';
+ public static $_tableName = 'civicrm_uf_match';
/**
* Field to show when displaying a record.
public $id;
/**
- * FK to Contact - possibly redundant
+ * Which Domain is this match entry for
+ *
+ * @var int|string
+ * (SQL type: int unsigned)
+ * Note that values will be retrieved from the database as a string.
+ */
+ public $domain_id;
+
+ /**
+ * UF ID. Redundant in Standalone. Needs to be identical to id.
+ *
+ * @var int|string
+ * (SQL type: int unsigned)
+ * Note that values will be retrieved from the database as a string.
+ */
+ public $uf_id;
+
+ /**
+ * Email (e.g. for password resets)
+ *
+ * @var string|null
+ * (SQL type: varchar(255))
+ * Note that values will be retrieved from the database as a string.
+ */
+ public $uf_name;
+
+ /**
+ * FK to Contact ID
*
* @var int|string|null
* (SQL type: int unsigned)
*/
public $hashed_password;
- /**
- * Email (e.g. for password resets)
- *
- * @var string
- * (SQL type: varchar(255))
- * Note that values will be retrieved from the database as a string.
- */
- public $email;
-
/**
* FK to Role
*
public $timezone;
/**
- * The language for the user.
+ * UI language preferred by the given user/contact
*
- * @var int|string|null
- * (SQL type: int unsigned)
+ * @var string|null
+ * (SQL type: varchar(5))
* Note that values will be retrieved from the database as a string.
*/
public $language;
* Class constructor.
*/
public function __construct() {
- $this->__table = 'civicrm_user';
+ $this->__table = 'civicrm_uf_match';
parent::__construct();
}
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(), 'domain_id', 'civicrm_domain', 'id');
Civi::$statics[__CLASS__]['links'][] = new CRM_Core_Reference_Basic(self::getTableName(), 'contact_id', 'civicrm_contact', 'id');
CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'links_callback', Civi::$statics[__CLASS__]['links']);
}
'id' => [
'name' => 'id',
'type' => CRM_Utils_Type::T_INT,
- 'title' => E::ts('ID'),
+ 'title' => E::ts('UF Match ID'),
'description' => E::ts('Unique User ID'),
'required' => TRUE,
'usage' => [
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.id',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.id',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'type' => 'Number',
],
'readonly' => TRUE,
+ 'add' => '5.67',
+ ],
+ 'domain_id' => [
+ 'name' => 'domain_id',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('Domain ID'),
+ 'description' => E::ts('Which Domain is this match entry for'),
+ 'required' => TRUE,
+ 'usage' => [
+ 'import' => FALSE,
+ 'export' => FALSE,
+ 'duplicate_matching' => FALSE,
+ 'token' => FALSE,
+ ],
+ 'where' => 'civicrm_uf_match.domain_id',
+ 'table_name' => 'civicrm_uf_match',
+ 'entity' => 'User',
+ 'bao' => 'CRM_Standaloneusers_DAO_User',
+ 'localizable' => 0,
+ 'FKClassName' => 'CRM_Core_DAO_Domain',
+ 'html' => [
+ 'label' => E::ts("Domain"),
+ ],
+ 'pseudoconstant' => [
+ 'table' => 'civicrm_domain',
+ 'keyColumn' => 'id',
+ 'labelColumn' => 'name',
+ ],
+ 'add' => '3.0',
+ ],
+ 'uf_id' => [
+ 'name' => 'uf_id',
+ 'type' => CRM_Utils_Type::T_INT,
+ 'title' => E::ts('CMS ID'),
+ 'description' => E::ts('UF ID. Redundant in Standalone. Needs to be identical to id.'),
+ 'required' => TRUE,
+ 'usage' => [
+ 'import' => FALSE,
+ 'export' => FALSE,
+ 'duplicate_matching' => FALSE,
+ 'token' => FALSE,
+ ],
+ 'where' => 'civicrm_uf_match.uf_id',
+ 'default' => '0',
+ 'table_name' => 'civicrm_uf_match',
+ 'entity' => 'User',
+ 'bao' => 'CRM_Standaloneusers_DAO_User',
+ 'localizable' => 0,
+ 'add' => '1.1',
+ ],
+ 'uf_name' => [
+ 'name' => 'uf_name',
+ 'type' => CRM_Utils_Type::T_STRING,
+ 'title' => E::ts('CMS Unique Identifier'),
+ 'description' => E::ts('Email (e.g. for password resets)'),
+ 'maxlength' => 255,
+ 'size' => CRM_Utils_Type::HUGE,
+ 'usage' => [
+ 'import' => FALSE,
+ 'export' => FALSE,
+ 'duplicate_matching' => FALSE,
+ 'token' => FALSE,
+ ],
+ 'where' => 'civicrm_uf_match.uf_name',
+ 'table_name' => 'civicrm_uf_match',
+ 'entity' => 'User',
+ 'bao' => 'CRM_Standaloneusers_DAO_User',
+ 'localizable' => 0,
+ 'html' => [
+ 'type' => 'Email',
+ ],
'add' => NULL,
],
'contact_id' => [
'name' => 'contact_id',
'type' => CRM_Utils_Type::T_INT,
'title' => E::ts('Contact ID'),
- 'description' => E::ts('FK to Contact - possibly redundant'),
+ 'description' => E::ts('FK to Contact ID'),
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.contact_id',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.contact_id',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'FKClassName' => 'CRM_Contact_DAO_Contact',
- 'add' => NULL,
+ 'html' => [
+ 'label' => E::ts("Contact"),
+ ],
+ 'add' => '1.1',
],
'username' => [
'name' => 'username',
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.username',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.username',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.hashed_password',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.hashed_password',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'readonly' => TRUE,
'add' => NULL,
],
- 'email' => [
- 'name' => 'email',
- 'type' => CRM_Utils_Type::T_STRING,
- 'title' => E::ts('Email'),
- 'description' => E::ts('Email (e.g. for password resets)'),
- 'required' => TRUE,
- 'maxlength' => 255,
- 'size' => CRM_Utils_Type::HUGE,
- 'usage' => [
- 'import' => FALSE,
- 'export' => FALSE,
- 'duplicate_matching' => FALSE,
- 'token' => FALSE,
- ],
- 'where' => 'civicrm_user.email',
- 'table_name' => 'civicrm_user',
- 'entity' => 'User',
- 'bao' => 'CRM_Standaloneusers_DAO_User',
- 'localizable' => 0,
- 'html' => [
- 'type' => 'Text',
- ],
- 'add' => NULL,
- ],
'roles' => [
'name' => 'roles',
'type' => CRM_Utils_Type::T_STRING,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.roles',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.roles',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.when_created',
+ 'where' => 'civicrm_uf_match.when_created',
'default' => 'CURRENT_TIMESTAMP',
- 'table_name' => 'civicrm_user',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.when_last_accessed',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.when_last_accessed',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.when_updated',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.when_updated',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.is_active',
+ 'where' => 'civicrm_uf_match.is_active',
'default' => '1',
- 'table_name' => 'civicrm_user',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.timezone',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.timezone',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
],
'language' => [
'name' => 'language',
- 'type' => CRM_Utils_Type::T_INT,
- 'title' => E::ts('Language'),
- 'description' => E::ts('The language for the user.'),
+ 'type' => CRM_Utils_Type::T_STRING,
+ 'title' => E::ts('Preferred Language'),
+ 'description' => E::ts('UI language preferred by the given user/contact'),
+ 'maxlength' => 5,
+ 'size' => CRM_Utils_Type::SIX,
'usage' => [
'import' => FALSE,
'export' => FALSE,
'duplicate_matching' => FALSE,
'token' => FALSE,
],
- 'where' => 'civicrm_user.language',
- 'table_name' => 'civicrm_user',
+ 'where' => 'civicrm_uf_match.language',
+ 'table_name' => 'civicrm_uf_match',
'entity' => 'User',
'bao' => 'CRM_Standaloneusers_DAO_User',
'localizable' => 0,
- 'html' => [
- 'type' => 'Select',
- ],
- 'pseudoconstant' => [
- 'optionGroupName' => 'languages',
- 'optionEditPath' => 'civicrm/admin/options/languages',
- ],
- 'add' => NULL,
+ 'add' => '2.1',
],
];
CRM_Core_DAO_AllCoreTables::invoke(__CLASS__, 'fields_callback', Civi::$statics[__CLASS__]['fields']);
* @return array
*/
public static function &import($prefix = FALSE) {
- $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'user', $prefix, []);
+ $r = CRM_Core_DAO_AllCoreTables::getImports(__CLASS__, 'uf_match', $prefix, []);
return $r;
}
* @return array
*/
public static function &export($prefix = FALSE) {
- $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'user', $prefix, []);
+ $r = CRM_Core_DAO_AllCoreTables::getExports(__CLASS__, 'uf_match', $prefix, []);
return $r;
}
*/
public static function indices($localize = TRUE) {
$indices = [
+ 'I_civicrm_uf_match_uf_id' => [
+ 'name' => 'I_civicrm_uf_match_uf_id',
+ 'field' => [
+ 0 => 'uf_id',
+ ],
+ 'localizable' => FALSE,
+ 'sig' => 'civicrm_uf_match::0::uf_id',
+ ],
'UI_username' => [
'name' => 'UI_username',
'field' => [
],
'localizable' => FALSE,
'unique' => TRUE,
- 'sig' => 'civicrm_user::1::username',
+ 'sig' => 'civicrm_uf_match::1::username',
+ ],
+ 'UI_uf_name_domain_id' => [
+ 'name' => 'UI_uf_name_domain_id',
+ 'field' => [
+ 0 => 'uf_name',
+ 1 => 'domain_id',
+ ],
+ 'localizable' => FALSE,
+ 'unique' => TRUE,
+ 'sig' => 'civicrm_uf_match::1::uf_name::domain_id',
+ ],
+ 'UI_contact_domain_id' => [
+ 'name' => 'UI_contact_domain_id',
+ 'field' => [
+ 0 => 'contact_id',
+ 1 => 'domain_id',
+ ],
+ 'localizable' => FALSE,
+ 'unique' => TRUE,
+ 'sig' => 'civicrm_uf_match::1::contact_id::domain_id',
],
];
return ($localize && !empty($indices)) ? CRM_Core_DAO_AllCoreTables::multilingualize(__CLASS__, $indices) : $indices;
}
$user = User::get(FALSE)
- ->addSelect('id', 'email', 'username')
+ ->addSelect('id', 'uf_name', 'username')
->addWhere('is_active', '=', TRUE)
->setLimit(1)
->addWhere(
- filter_var($identifier, FILTER_VALIDATE_EMAIL) ? 'email' : 'username',
+ filter_var($identifier, FILTER_VALIDATE_EMAIL) ? 'uf_name' : 'username',
'=',
$identifier)
->execute()
->setSelect(['id'])
->addWhere('workflow_name', '=', 'password_reset')
->addWhere('is_default', '=', TRUE)
- ->addWhere('is_active', '=', TRUE)
+ ->addWhere('is_active', '=', TRUE)
->execute()->first()['id'];
if (!$tplID) {
// Some sites may deliberately disable this, but it's unusual, so leave a notice in the log.
Civi::log()->notice("There is no active, default password_reset message template, which has prevented emailing a reset to {username}", ['username' => $user['username']]);
return;
}
- if (!filter_var($user['email'] ?? '', FILTER_VALIDATE_EMAIL)) {
+ if (!filter_var($user['uf_name'] ?? '', FILTER_VALIDATE_EMAIL)) {
Civi::log()->warning("User $user[id] has an invalid email. Failed to send password reset.");
return;
}
// Generate a once-use token that expires in 1 hour.
// We'll store this on the User record, that way invalidating any previous token that may have been generated.
- $expires = time() + 60*60;
+ $expires = time() + 60 * 60;
$token = dechex($expires) . substr(preg_replace('@[/+=]+@', '', base64_encode(random_bytes(64))), 0, 32);
User::update(FALSE)
$resetUrlHtml = htmlspecialchars($resetUrlPlaintext);
// The template_params are used in the template like {$resetUrlHtml} and {$resetUrlHtml}
$params = [
- 'id' => $tplID,
- 'template_params' => compact('resetUrlPlaintext', 'resetUrlHtml'),
- 'from' => "\"$domainFromName\" <$domainFromEmail>",
- 'to_email' => $user['email'],
- 'disable_smarty' => 1,
- ];
+ 'id' => $tplID,
+ 'template_params' => compact('resetUrlPlaintext', 'resetUrlHtml'),
+ 'from' => "\"$domainFromName\" <$domainFromEmail>",
+ 'to_email' => $user['uf_name'],
+ 'disable_smarty' => 1,
+ ];
try {
civicrm_api3('MessageTemplate', 'send', $params);
$params + ['userID' => $user['id'], 'exception' => $e]);
}
}
+
}
unset($item['password']);
}
}
- return parent::write($items);
+ unset($item);
+
+ // Call parent to do the main saving.
+ $saved = parent::write($items);
+
+ // Enforce uf_id === id
+ foreach ($saved as $bao) {
+ if ($bao->uf_id !== $bao->id) {
+ $bao->uf_id = $bao->id;
+ $bao->save();
+ }
+ }
+ return $saved;
}
}
use CRM_Core_Session;
use Civi;
+use Civi\Api4\User;
/**
* This is a single home for security related functions for Civi Standalone.
* - 'cms_name'
* - 'cms_pass' plaintext password
* - 'notify' boolean
- * @param string $mailParam
+ * @param string $emailParam
* Name of the $param which contains the email address.
*
* @return int|bool
* uid if user was created, false otherwise
*/
- public function createUser(&$params, $mailParam) {
+ public function createUser(&$params, $emailParam) {
try {
- $mail = $params[$mailParam];
- $userID = \Civi\Api4\User::create(FALSE)
+ $email = $params[$emailParam];
+ $userID = User::create(FALSE)
->addValue('username', $params['cms_name'])
- ->addValue('email', $mail)
+ ->addValue('uf_name', $email)
->addValue('password', $params['cms_pass'])
+ ->addValue('contact_id', $params['contact_id'] ?? NULL)
+ // ->addValue('uf_id', 0) // does not work without this.
->execute()->single()['id'];
}
catch (\Exception $e) {
- \Civi::log()->warning("Failed to create user '$mail': " . $e->getMessage());
+ \Civi::log()->warning("Failed to create user '$email': " . $e->getMessage());
return FALSE;
}
- // @todo This is what Drupal does, but it's unclear why.
+ // @todo This next line is what Drupal does, but it's unclear why.
// I think it assumes we want to be logged in as this contact, and as there's no uf match, it's not in civi.
// But I'm not sure if we are always becomming this user; I'm not sure waht calls this function.
// CRM_Core_Config::singleton()->inCiviCRM = FALSE;
public function updateCMSName($ufID, $email) {
\Civi\Api4\User::update(FALSE)
->addWhere('id', '=', $ufID)
- ->addValue('email', $email)
+ ->addValue('uf_name', $email)
->execute();
}
<fieldset af-fieldset="User1" class="af-container" af-title="User">
<af-field name="roles" />
<af-field name="username" />
- <af-field name="email" />
+ <af-field name="uf_name" />
<af-field name="is_active" />
<af-field name="timezone" />
<af-field name="language" />
<div af-fieldset="">
<div class="af-markup">
-
-
+
+
<div class="help"><p>{{:: ts('User accounts allow people to access CiviCRM. What they can access is determined by which roles the users have, and what permissions are granted to those roles.') }}</p>
</div>
-
-
+
+
</div>
<div class="af-container af-layout-cols" af-title="Filters">
<af-field name="username" defn="{required: false, input_attrs: {}}" />
- <af-field name="email" defn="{required: false, input_attrs: {}}" />
+ <af-field name="uf_name" defn="{required: false, input_attrs: {}}" />
<af-field name="is_active" defn="{label: 'Active'}" />
<af-field name="roles" defn="{input_attrs: {multiple: true}}" />
</div>
'select' => [
'id',
'username',
- 'email',
+ 'uf_name',
'is_active',
'when_created',
'when_last_accessed',
],
[
'type' => 'field',
- 'key' => 'email',
+ 'key' => 'uf_name',
'dataType' => 'String',
'label' => E::ts('Email'),
'sortable' => TRUE,
SET FOREIGN_KEY_CHECKS=0;
-DROP TABLE IF EXISTS `civicrm_user`;
+DROP TABLE IF EXISTS `civicrm_uf_match`;
DROP TABLE IF EXISTS `civicrm_role`;
SET FOREIGN_KEY_CHECKS=1;
-- /*******************************************************
-- *
--- * civicrm_user
+-- * civicrm_uf_match
-- *
--- * A standalone user account
+-- * Standalone User Account. In Standalone this is a superset of the original civicrm_uf_match table.
-- *
-- *******************************************************/
-CREATE TABLE `civicrm_user` (
+CREATE TABLE `civicrm_uf_match` (
`id` int unsigned NOT NULL AUTO_INCREMENT COMMENT 'Unique User ID',
- `contact_id` int unsigned COMMENT 'FK to Contact - possibly redundant',
+ `domain_id` int unsigned NOT NULL COMMENT 'Which Domain is this match entry for',
+ `uf_id` int unsigned NOT NULL DEFAULT 0 COMMENT 'UF ID. Redundant in Standalone. Needs to be identical to id.',
+ `uf_name` varchar(255) COMMENT 'Email (e.g. for password resets)',
+ `contact_id` int unsigned COMMENT 'FK to Contact ID',
`username` varchar(60) NOT NULL,
`hashed_password` varchar(128) NOT NULL COMMENT 'Hashed, not plaintext password',
- `email` varchar(255) NOT NULL COMMENT 'Email (e.g. for password resets)',
`roles` varchar(128) COMMENT 'FK to Role',
`when_created` timestamp DEFAULT CURRENT_TIMESTAMP,
`when_last_accessed` timestamp NULL,
`when_updated` timestamp NULL,
`is_active` tinyint NOT NULL DEFAULT 1,
`timezone` varchar(32) NULL COMMENT 'User\'s timezone',
- `language` int unsigned COMMENT 'The language for the user.',
+ `language` varchar(5) COMMENT 'UI language preferred by the given user/contact',
PRIMARY KEY (`id`),
+ INDEX `I_civicrm_uf_match_uf_id`(uf_id),
UNIQUE INDEX `UI_username`(username),
- CONSTRAINT FK_civicrm_user_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE CASCADE
+ UNIQUE INDEX `UI_uf_name_domain_id`(uf_name, domain_id),
+ UNIQUE INDEX `UI_contact_domain_id`(contact_id, domain_id),
+ CONSTRAINT FK_civicrm_uf_match_domain_id FOREIGN KEY (`domain_id`) REFERENCES `civicrm_domain`(`id`),
+ CONSTRAINT FK_civicrm_uf_match_contact_id FOREIGN KEY (`contact_id`) REFERENCES `civicrm_contact`(`id`) ON DELETE SET NULL
)
ENGINE=InnoDB;
SET FOREIGN_KEY_CHECKS=0;
-DROP TABLE IF EXISTS `civicrm_user`;
+DROP TABLE IF EXISTS `civicrm_uf_match`;
DROP TABLE IF EXISTS `civicrm_role`;
SET FOREIGN_KEY_CHECKS=1;
\ No newline at end of file
<div class="message warning" style="display:none;" id="anonAccessDenied">{ts}You may need to login to access that.{/ts}</div>
<form>
<div>
- <label for="exampleInputEmail1" class="form-label">Username</label>
- <input type="email" class="form-control" id="usernameInput" aria-describedby="emailHelp">
+ <label for="usernameInput" class="form-label">Username</label>
+ <input type="password" class="form-control" id="usernameInput" >
</div>
<div>
- <label for="exampleInputPassword1" class="form-label">Password</label>
+ <label for="passwordInput" class="form-label">Password</label>
<input type="password" class="form-control" id="passwordInput">
</div>
<div id="error" style="display:none;" class="form-alert">Your username and password do not match</div>
->execute()->single();
$this->assertEquals('user_one', $user['username']);
- $this->assertEquals('user_one@example.org', $user['email']);
+ $this->assertEquals('user_one@example.org', $user['uf_name']);
$this->assertStringStartsWith('$', $user['hashed_password']);
$this->assertTrue($security->checkPassword('secret1', $user['hashed_password']));
public function testPerms() {
[$contactID, $userID, $security] = $this->createFixtureContactAndUser();
+ $ufID = \CRM_Core_BAO_UFMatch::getUFId($contactID);
+ $this->assertEquals($userID, $ufID);
// Create a custom role
$roleID = \Civi\Api4\Role::create(FALSE)
}
protected function switchToOurUFClasses() {
+ return;
if (!empty($this->originalUFPermission)) {
throw new \RuntimeException("are you calling switchToOurUFClasses twice?");
}
}
protected function switchBackFromOurUFClasses($justInCase = FALSE) {
+ return;
if (!$justInCase && empty($this->originalUFPermission)) {
throw new \RuntimeException("are you calling switchBackFromOurUFClasses() twice?");
}
$this->originalUFPermission = $this->originalUF = NULL;
}
+ public function dump(string $s = '') {
+ $d = \CRM_Core_DAO::executeQuery("SELECT * FROM civicrm_uf_match;");
+ print "\ndump---------- $s\n";
+ foreach ($d->fetchAll() as $row) {
+ print json_encode($row, JSON_UNESCAPED_SLASHES) . "\n";
+ }
+ print "--------------\n";
+ }
+
/**
* @return Array[int, int, \Civi\Standalone\Security]
*/
public function createFixtureContactAndUser(): array {
-
$contactID = \Civi\Api4\Contact::create(FALSE)
->setValues([
'contact_type' => 'Individual',
'display_name' => 'Admin McDemo',
])->execute()->first()['id'];
- $params = ['cms_name' => 'user_one', 'cms_pass' => 'secret1', 'notify' => FALSE, 'contactID' => $contactID, 'email' => 'user_one@example.org'];
- $this->switchToOurUFClasses();
+ $params = ['cms_name' => 'user_one', 'cms_pass' => 'secret1', 'notify' => FALSE, 'contact_id' => $contactID, 'email' => 'user_one@example.org'];
+ // $this->switchToOurUFClasses();
$userID = \CRM_Core_BAO_CMSUser::create($params, 'email');
- $this->switchBackFromOurUFClasses();
+ // $this->switchBackFromOurUFClasses();
$this->assertGreaterThan(0, $userID);
$this->contactID = $contactID;
$this->userID = $userID;
'password' => 'shhh',
'contact_id' => $stafferContactID,
'roles:name' => ['staff'],
- 'email' => 'testuser1@example.org',
+ 'uf_name' => 'testuser1@example.org',
])
->execute()->first()['id'];
$user = User::get(FALSE)->addWhere('id', '=', $userID)->execute()->first();
[
'name' => 'User',
'class' => 'CRM_Standaloneusers_DAO_User',
- 'table' => 'civicrm_user',
+ 'table' => 'civicrm_uf_match',
],
];
<table>
<base>CRM/Standaloneusers</base>
<class>User</class>
- <name>civicrm_user</name>
- <comment>A standalone user account</comment>
+ <name>civicrm_uf_match</name>
+ <comment>Standalone User Account. In Standalone this is a superset of the original civicrm_uf_match table.</comment>
<labelField>username</labelField>
<searchField>username</searchField>
<descriptionField>email</descriptionField>
<field>
<name>id</name>
+ <title>UF Match ID</title>
<type>int unsigned</type>
<required>true</required>
<comment>Unique User ID</comment>
<html>
<type>Number</type>
</html>
+ <add>5.67</add>
</field>
<primaryKey>
<name>id</name>
<autoincrement>true</autoincrement>
</primaryKey>
-
+ <field>
+ <name>domain_id</name>
+ <title>Domain ID</title>
+ <type>int unsigned</type>
+ <required>true</required>
+ <comment>Which Domain is this match entry for</comment>
+ <pseudoconstant>
+ <table>civicrm_domain</table>
+ <keyColumn>id</keyColumn>
+ <labelColumn>name</labelColumn>
+ </pseudoconstant>
+ <html>
+ <label>Domain</label>
+ </html>
+ <add>3.0</add>
+ </field>
+ <foreignKey>
+ <name>domain_id</name>
+ <table>civicrm_domain</table>
+ <key>id</key>
+ <add>3.0</add>
+ </foreignKey>
+ <field>
+ <name>uf_id</name>
+ <title>CMS ID</title>
+ <type>int unsigned</type>
+ <required>true</required>
+ <default>0</default>
+ <comment>UF ID. Redundant in Standalone. Needs to be identical to id.</comment>
+ <add>1.1</add>
+ </field>
+ <index>
+ <name>I_civicrm_uf_match_uf_id</name>
+ <fieldName>uf_id</fieldName>
+ <add>3.3</add>
+ </index>
+ <field>
+ <name>uf_name</name>
+ <title>CMS Unique Identifier</title>
+ <type>varchar</type>
+ <length>255</length>
+ <comment>Email (e.g. for password resets)</comment>
+ <html>
+ <type>Email</type>
+ </html>
+ </field>
<field>
<name>contact_id</name>
+ <title>Contact ID</title>
<type>int unsigned</type>
- <comment>FK to Contact - possibly redundant</comment>
+ <comment>FK to Contact ID</comment>
+ <html>
+ <label>Contact</label>
+ </html>
+ <add>1.1</add>
</field>
<foreignKey>
<name>contact_id</name>
<table>civicrm_contact</table>
<key>id</key>
- <onDelete>CASCADE</onDelete>
+ <add>1.1</add>
+ <onDelete>SET NULL</onDelete>
</foreignKey>
<field>
<comment>Hashed, not plaintext password</comment>
</field>
- <field>
- <name>email</name>
- <type>varchar</type>
- <required>true</required>
- <length>255</length>
- <comment>Email (e.g. for password resets)</comment>
- <html>
- <type>Text</type>
- </html>
- </field>
-
<field>
<name>roles</name>
<type>varchar</type>
<field>
<name>language</name>
- <type>int unsigned</type>
- <title>Language</title>
- <pseudoconstant>
- <optionGroupName>languages</optionGroupName>
- </pseudoconstant>
- <html>
- <type>Select</type>
- </html>
- <comment>The language for the user.</comment>
+ <title>Preferred Language</title>
+ <type>varchar</type>
+ <length>5</length>
+ <comment>UI language preferred by the given user/contact</comment>
+ <add>2.1</add>
</field>
-
+ <index>
+ <name>UI_uf_name_domain_id</name>
+ <fieldName>uf_name</fieldName>
+ <fieldName>domain_id</fieldName>
+ <unique>true</unique>
+ <add>2.1</add>
+ </index>
+ <index>
+ <name>UI_contact_domain_id</name>
+ <fieldName>contact_id</fieldName>
+ <fieldName>domain_id</fieldName>
+ <unique>true</unique>
+ <add>1.6</add>
+ </index>
</table>