Merge branch 'pick_profile_by_id'

[#73] Identify profiles by ID
This commit is contained in:
Jens Schuppe 2024-04-05 12:27:06 +02:00
commit aa2c938abe
5 changed files with 200 additions and 89 deletions

View file

@ -33,6 +33,12 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
*/ */
protected ?CRM_Twingle_Profile $profile = NULL; protected ?CRM_Twingle_Profile $profile = NULL;
/**
* The ID of this profile.
* @var int|NULL
*/
protected $profile_id = NULL;
/** /**
* @var string * @var string
* *
@ -139,10 +145,10 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
$op = CRM_Utils_Request::retrieve('op', 'String', $this); $op = CRM_Utils_Request::retrieve('op', 'String', $this);
$this->_op = is_string($op) ? $op : 'create'; $this->_op = is_string($op) ? $op : 'create';
// Verify that a profile with the given name exists. // Verify that a profile with the given id exists.
$profile_name = CRM_Utils_Request::retrieve('name', 'String', $this); if ($this->_op != 'copy' && $this->_op != 'create') {
if (is_string($profile_name)) { $this->profile_id = CRM_Utils_Request::retrieve('id', 'Int', $this);
$this->profile = CRM_Twingle_Profile::getProfile($profile_name); $this->profile = CRM_Twingle_Profile::getProfile($this->profile_id);
} }
// Set redirect destination. // Set redirect destination.
@ -162,12 +168,12 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
switch ($this->_op) { switch ($this->_op) {
case 'delete': case 'delete':
if (isset($profile_name)) { if ($this->profile) {
CRM_Utils_System::setTitle(E::ts('Delete Twingle API profile <em>%1</em>', [1 => $profile_name])); CRM_Utils_System::setTitle(E::ts('Delete Twingle API profile <em>%1</em>', [1 => $this->profile->getName()]));
$this->addButtons([ $this->addButtons([
[ [
'type' => 'submit', 'type' => 'submit',
'name' => ($profile_name == 'default' ? E::ts('Reset') : E::ts('Delete')), 'name' => ($this->profile->getName() == 'default' ? E::ts('Reset') : E::ts('Delete')),
'isDefault' => TRUE, 'isDefault' => TRUE,
], ],
]); ]);
@ -175,47 +181,57 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
parent::buildQuickForm(); parent::buildQuickForm();
return; return;
case 'edit':
// When editing without a valid profile name, edit the default profile.
if (!isset($profile_name)) {
$profile_name = 'default';
$this->profile = CRM_Twingle_Profile::getProfile($profile_name);
}
if (!isset($this->profile)) {
throw new BaseException(E::ts('Could not retrieve profile with name "%1"', [1 => $profile_name]));
}
CRM_Utils_System::setTitle(E::ts('Edit Twingle API profile <em>%1</em>', [1 => $this->profile->getName()]));
break;
case 'copy': case 'copy':
// Retrieve the source profile name. // Retrieve the source profile name.
$profile_name = CRM_Utils_Request::retrieve('source_name', 'String', $this); $source_id = CRM_Utils_Request::retrieve('source_id', 'Int', $this);
// When copying without a valid profile name, copy the default profile. // When copying without a valid profile id, copy the default profile.
if (!is_string($profile_name)) { if (!$source_id) {
$profile_name = 'default'; $this->profile = CRM_Twingle_Profile::createDefaultProfile();
} else {
try {
$source_profile = CRM_Twingle_Profile::getProfile($source_id);
$this->profile = $source_profile->copy();
$this->profile->validate();
} catch (ProfileValidationError $e) {
if ($e->getErrorCode() == ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED) {
Civi::log()->error($e->getLogMessage());
CRM_Core_Session::setStatus(E::ts('The profile is invalid and cannot be copied.'), E::ts('Error'));
CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/settings/twingle/profiles', 'reset=1'));
return;
}
} catch (ProfileException $e) {
if ($e->getErrorCode() == ProfileException::ERROR_CODE_PROFILE_NOT_FOUND) {
Civi::log()->error($e->getLogMessage());
CRM_Core_Session::setStatus(E::ts('The profile to be copied could not be found.'), E::ts('Error'));
CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/settings/twingle/profiles', 'reset=1'));
return;
}
}
catch (Civi\Core\Exception\DBQueryException $e) {
Civi::log()->error($e->getMessage());
CRM_Core_Session::setStatus(E::ts('A database error has occurred. See the log for details.'), E::ts('Error'));
CRM_Utils_System::redirect(CRM_Utils_System::url('civicrm/admin/settings/twingle/profiles', 'reset=1'));
return;
}
} }
$originalProfile = CRM_Twingle_Profile::getProfile($profile_name);
if (!isset($originalProfile)) {
throw new BaseException(E::ts('Could not retrieve profile with name "%1"', [1 => $profile_name]));
}
$this->profile = clone $originalProfile;
// Propose a new name for this profile.
$profile_name = $profile_name . '_copy';
$this->profile->setName($profile_name);
CRM_Utils_System::setTitle(E::ts('New Twingle API profile')); CRM_Utils_System::setTitle(E::ts('New Twingle API profile'));
break; break;
case 'edit':
CRM_Utils_System::setTitle(E::ts('Edit Twingle API profile <em>%1</em>', [1 => $this->profile->getName()]));
break;
case 'create': case 'create':
// Load factory default profile values. // Load factory default profile values.
$this->profile = CRM_Twingle_Profile::createDefaultProfile($profile_name ?? 'default'); $this->profile = CRM_Twingle_Profile::createDefaultProfile(E::ts('New Profile'));
CRM_Utils_System::setTitle(E::ts('New Twingle API profile')); CRM_Utils_System::setTitle(E::ts('New Twingle API profile'));
break; break;
} }
// Assign template variables. // Assign template variables.
$this->assign('op', $this->_op); $this->assign('op', $this->_op);
$this->assign('profile_name', $profile_name); $this->assign('profile_name', $this->profile->getName());
$this->assign('is_default', $this->profile->is_default());
// Add form elements. // Add form elements.
$is_default = $profile_name == 'default'; $is_default = $profile_name == 'default';
@ -632,9 +648,12 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form {
*/ */
public function setDefaultValues() { public function setDefaultValues() {
$defaults = parent::setDefaultValues(); $defaults = parent::setDefaultValues();
if (in_array($this->_op, ['create', 'edit', 'copy'], TRUE)) { if (in_array($this->_op, ['create', 'edit', 'copy'])) {
$defaults['name'] = isset($this->profile) ? $this->profile->getName() : NULL; if (!$this->profile) {
$profile_data = isset($this->profile) ? $this->profile->getData() : []; $this->profile = CRM_Twingle_Profile::createDefaultProfile()->copy();
}
$defaults['name'] = $this->profile->getName();
$profile_data = $this->profile->getData();
foreach ($profile_data as $element_name => $value) { foreach ($profile_data as $element_name => $value) {
$defaults[$element_name] = $value; $defaults[$element_name] = $value;
} }

View file

@ -134,7 +134,8 @@ class CRM_Twingle_Form_Settings extends CRM_Core_Form {
// if activity creation is active, make sure the fields are set // if activity creation is active, make sure the fields are set
$protection_mode = $this->_submitValues['twingle_protect_recurring'] ?? NULL; $protection_mode = $this->_submitValues['twingle_protect_recurring'] ?? NULL;
if ($protection_mode == CRM_Twingle_Config::RCUR_PROTECTION_ACTIVITY) { if ($protection_mode == CRM_Twingle_Config::RCUR_PROTECTION_ACTIVITY) {
foreach (['twingle_protect_recurring_activity_type', foreach ([
'twingle_protect_recurring_activity_type',
'twingle_protect_recurring_activity_subject', 'twingle_protect_recurring_activity_subject',
'twingle_protect_recurring_activity_status', 'twingle_protect_recurring_activity_status',
'twingle_protect_recurring_activity_assignee', 'twingle_protect_recurring_activity_assignee',

View file

@ -22,10 +22,11 @@ class CRM_Twingle_Page_Profiles extends CRM_Core_Page {
public function run():void { public function run():void {
CRM_Utils_System::setTitle(E::ts('Twingle API Profiles')); CRM_Utils_System::setTitle(E::ts('Twingle API Profiles'));
$profiles = []; $profiles = [];
foreach (CRM_Twingle_Profile::getProfiles() as $profile_name => $profile) { foreach (CRM_Twingle_Profile::getProfiles() as $profile_id => $profile) {
$profiles[$profile_name]['name'] = $profile_name; $profiles[$profile_id]['id'] = $profile_id;
$profiles[$profile_id]['name'] = $profile->getName();
foreach (CRM_Twingle_Profile::allowedAttributes() as $attribute) { foreach (CRM_Twingle_Profile::allowedAttributes() as $attribute) {
$profiles[$profile_name][$attribute] = $profile->getAttribute($attribute); $profiles[$profile_id][$attribute] = $profile->getAttribute($attribute);
} }
} }
$this->assign('profiles', $profiles); $this->assign('profiles', $profiles);

View file

@ -25,7 +25,13 @@ use Civi\Twingle\Exceptions\ProfileException as ProfileException;
class CRM_Twingle_Profile { class CRM_Twingle_Profile {
/** /**
* @var string * @var int $id
* The id of the profile.
*/
protected $id = NULL;
/**
* @var string $name
* The name of the profile. * The name of the profile.
*/ */
protected $name; protected $name;
@ -43,8 +49,10 @@ class CRM_Twingle_Profile {
* The name of the profile. * The name of the profile.
* @param array<string, mixed> $data * @param array<string, mixed> $data
* The properties of the profile * The properties of the profile
* @param int|NULL $id
*/ */
public function __construct($name, $data) { public function __construct($name, $data, $id = NULL) {
$this->id = $id;
$this->name = $name; $this->name = $name;
$allowed_attributes = self::allowedAttributes(); $allowed_attributes = self::allowedAttributes();
$this->data = $data + array_combine( $this->data = $data + array_combine(
@ -65,6 +73,24 @@ class CRM_Twingle_Profile {
WHERE name = %1', [1 => [$this->name, 'String']]); WHERE name = %1', [1 => [$this->name, 'String']]);
} }
/**
* Copy this profile by returning a clone with all unique information removed.
*
* @return CRM_Twingle_Profile
*/
public function copy() {
$copy = clone $this;
// Remove unique data
$copy->id = NULL;
$copy->data['selector'] = NULL;
// Propose a new name for this profile.
$profile_name = $this->getName() . '_copy';
$copy->setName($profile_name);
return $copy;
}
/** /**
* Checks whether the profile's selector matches the given project ID. * Checks whether the profile's selector matches the given project ID.
* *
@ -117,6 +143,24 @@ class CRM_Twingle_Profile {
return $this->data; return $this->data;
} }
/**
* Retrieves the profile id.
*
* @return int
*/
public function getId() {
return $this->id;
}
/**
* Set the profile id.
*
* @param int $id
*/
public function setId(int $id) {
$this->id = $id;
}
/** /**
* Retrieves the profile name. * Retrieves the profile name.
* *
@ -189,43 +233,85 @@ class CRM_Twingle_Profile {
} }
/** /**
* Persists the profile within the CiviCRM settings. * Persists the profile within the database.
*
* @throws \Civi\Twingle\Exceptions\ProfileException
*/ */
public function saveProfile(): void { public function saveProfile(): void {
// make sure it's valid // make sure it's valid
$this->verifyProfile(); $this->verifyProfile();
// check if the profile exists try {
$profile_id = CRM_Core_DAO::singleValueQuery( if ($this->id !== NULL) {
'SELECT id FROM civicrm_twingle_profile WHERE name = %1', [1 => [$this->name, 'String']]); // existing profile -> just update the config
if (isset($profile_id)) { CRM_Core_DAO::executeQuery(
// existing profile -> just update the config "UPDATE civicrm_twingle_profile SET config = %2, name = %3 WHERE id = %1",
CRM_Core_DAO::executeQuery( [
'UPDATE civicrm_twingle_profile SET config = %2 WHERE name = %1', 1 => [$this->id, 'String'],
[ 2 => [json_encode($this->data), 'String'],
1 => [$this->name, 'String'], 3 => [$this->name, 'String'],
2 => [json_encode($this->data), 'String'], ]);
]); }
else {
// new profile -> add new entry to the DB
CRM_Core_DAO::executeQuery(
"INSERT IGNORE INTO civicrm_twingle_profile(name,config,last_access,access_counter) VALUES (%1, %2, null, 0)",
[
1 => [$this->name, 'String'],
2 => [json_encode($this->data), 'String'],
]);
}
} }
else { catch (Exception $e) {
// new profile -> add new entry to the DB throw new ProfileException(
CRM_Core_DAO::executeQuery( E::ts("Could not save/update profile: %1", [1 => $e->getMessage()]),
'INSERT IGNORE INTO civicrm_twingle_profile(name,config,last_access,access_counter) VALUES (%1, %2, null, 0)', ProfileException::ERROR_CODE_COULD_NOT_SAVE_PROFILE
[ );
1 => [$this->name, 'String'],
2 => [json_encode($this->data), 'String'],
]);
} }
} }
/** /**
* Deletes the profile from the database * Deletes the profile from the database
*
* @throws \Civi\Twingle\Exceptions\ProfileException
*/ */
public function deleteProfile(): void { public function deleteProfile() {
CRM_Core_DAO::executeQuery( // Do only reset default profile
'DELETE FROM civicrm_twingle_profile WHERE name = %1', if ($this->getName() == 'default') {
[1 => [$this->name, 'String']] try {
); $default_profile = CRM_Twingle_Profile::createDefaultProfile();
$default_profile->setId($this->getId());
$default_profile->saveProfile();
// Reset counter
CRM_Core_DAO::executeQuery("UPDATE civicrm_twingle_profile SET access_counter = 0, last_access = NULL WHERE id = %1", [
1 => [
$this->id,
'Integer'
]
]);
} catch (Exception $e) {
throw new ProfileException(
E::ts("Could not reset default profile: %1", [1 => $e->getMessage()]),
ProfileException::ERROR_CODE_COULD_NOT_RESET_PROFILE
);
}
}
else {
try {
CRM_Core_DAO::executeQuery("DELETE FROM civicrm_twingle_profile WHERE id = %1", [
1 => [
$this->id,
'Integer'
]
]);
} catch (Exception $e) {
throw new ProfileException(
E::ts("Could not delete profile: %1", [1 => $e->getMessage()]),
ProfileException::ERROR_CODE_COULD_NOT_DELETE_PROFILE
);
}
}
} }
/** /**
@ -307,9 +393,9 @@ class CRM_Twingle_Profile {
*/ */
public static function createDefaultProfile($name = 'default') { public static function createDefaultProfile($name = 'default') {
return new CRM_Twingle_Profile($name, [ return new CRM_Twingle_Profile($name, [
'selector' => '', 'selector' => NULL,
'xcm_profile' => '', 'xcm_profile' => '',
'location_type_id' => CRM_Twingle_Submission::LOCATION_TYPE_ID_WORK, 'location_type_id' => CRM_Twingle_Submission::LOCATION_TYPE_ID_WORK,
'location_type_id_organisation' => CRM_Twingle_Submission::LOCATION_TYPE_ID_WORK, 'location_type_id_organisation' => CRM_Twingle_Submission::LOCATION_TYPE_ID_WORK,
// "Donation" // "Donation"
'financial_type_id' => 1, 'financial_type_id' => 1,
@ -392,18 +478,23 @@ class CRM_Twingle_Profile {
} }
/** /**
* Retrieves the profile with the given name. * Retrieves the profile with the given ID.
* *
* @param string $name * @param int|NULL $id
* *
* @return CRM_Twingle_Profile|NULL * @return CRM_Twingle_Profile | NULL
* @throws \Civi\Core\Exception\DBQueryException
* @throws \Civi\Twingle\Exceptions\ProfileException
*/ */
public static function getProfile($name) { public static function getProfile(int $id = NULL) {
$profile_data = CRM_Core_DAO::singleValueQuery( if (!empty($id)) {
'SELECT config FROM civicrm_twingle_profile WHERE name = %1', $profile_data = CRM_Core_DAO::executeQuery("SELECT id, name, config FROM civicrm_twingle_profile WHERE id = %1",
[1 => [$name, 'String']] [1 => [$id, 'Integer']]);
); if ($profile_data->fetch()) {
return isset($profile_data) ? new CRM_Twingle_Profile($name, (array) json_decode($profile_data, TRUE)) : NULL; return new CRM_Twingle_Profile($profile_data->name, json_decode($profile_data->config, 1), (int) $profile_data->id);
}
}
throw new ProfileException('Profile not found.', ProfileException::ERROR_CODE_PROFILE_NOT_FOUND);
} }
/** /**
@ -412,16 +503,14 @@ class CRM_Twingle_Profile {
* *
* @return array<string, \CRM_Twingle_Profile> * @return array<string, \CRM_Twingle_Profile>
* profile_name => CRM_Twingle_Profile * profile_name => CRM_Twingle_Profile
* @throws \Civi\Core\Exception\DBQueryException
*/ */
public static function getProfiles() { public static function getProfiles() {
// todo: cache? // todo: cache?
$profiles = []; $profiles = [];
$profile_data = CRM_Core_DAO::executeQuery('SELECT name, config FROM civicrm_twingle_profile'); $profile_data = CRM_Core_DAO::executeQuery("SELECT id, name, config FROM civicrm_twingle_profile");
while ($profile_data->fetch()) { while ($profile_data->fetch()) {
$profiles[$profile_data->name] = new CRM_Twingle_Profile( $profiles[$profile_data->id] = new CRM_Twingle_Profile($profile_data->name, json_decode($profile_data->config, 1), (int) $profile_data->id);
$profile_data->name,
json_decode($profile_data->config, 1)
);
} }
return $profiles; return $profiles;
} }

View file

@ -33,6 +33,7 @@
</thead> </thead>
<tbody> <tbody>
{foreach from=$profiles item=profile} {foreach from=$profiles item=profile}
{assign var="profile_id" value=$profile.id}
{assign var="profile_name" value=$profile.name} {assign var="profile_name" value=$profile.name}
<tr> <tr>
<td>{$profile.name}</td> <td>{$profile.name}</td>
@ -42,12 +43,12 @@
<td>{ts domain="de.systopia.twingle"}{$profile_stats.$profile_name.access_counter_txt}{/ts}</td> <td>{ts domain="de.systopia.twingle"}{$profile_stats.$profile_name.access_counter_txt}{/ts}</td>
<td>{ts domain="de.systopia.twingle"}{$profile_stats.$profile_name.last_access_txt}{/ts}</td> <td>{ts domain="de.systopia.twingle"}{$profile_stats.$profile_name.last_access_txt}{/ts}</td>
<td> <td>
<a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=edit&name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Edit profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Edit{/ts}</a> <a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=edit&id=$profile_id"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Edit profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Edit{/ts}</a>
<a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=copy&source_name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Copy profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Copy{/ts}</a> <a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=copy&source_id=$profile_id"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Copy profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Copy{/ts}</a>
{if $profile_name == 'default'} {if $profile_name == 'default'}
<a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=delete&name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Reset profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Reset{/ts}</a> <a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=delete&id=$profile_id"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Reset profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Reset{/ts}</a>
{else} {else}
<a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=delete&name=$profile_name"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Delete profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Delete{/ts}</a> <a href="{crmURL p="civicrm/admin/settings/twingle/profile" q="op=delete&id=$profile_id"}" title="{ts domain="de.systopia.twingle" 1=$profile.name}Delete profile %1{/ts}" class="action-item crm-hover-button">{ts domain="de.systopia.twingle"}Delete{/ts}</a>
{/if} {/if}
</td> </td>