From 96c072eb8ec9f05e7262c222df9bb09043347f0a Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Mon, 7 Aug 2023 16:46:57 +0200 Subject: [PATCH 1/3] let CRM_Twingle_Profile class handle its validation --- CRM/Twingle/Form/Profile.php | 94 +++----------------- CRM/Twingle/Profile.php | 165 +++++++++++++++++++++++++++++++---- 2 files changed, 163 insertions(+), 96 deletions(-) diff --git a/CRM/Twingle/Form/Profile.php b/CRM/Twingle/Form/Profile.php index 312b0fb..0412b1c 100644 --- a/CRM/Twingle/Form/Profile.php +++ b/CRM/Twingle/Form/Profile.php @@ -553,89 +553,23 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { * TRUE when the form was successfully validated. */ public function validate() { - $values = $this->exportValues(); - // Validate new profile names. - if ( - isset($values['name']) - && (!isset($this->profile) || $values['name'] != $this->profile->getName() || $this->_op != 'edit') - && NULL !== CRM_Twingle_Profile::getProfile($values['name']) - ) { - $this->_errors['name'] = E::ts('A profile with this name already exists.'); - } + if (in_array($this->_op, ['create', 'edit', 'copy'])) { + // Create profile with new values + $profile_values = $this->exportValues(); + $profile = new CRM_Twingle_Profile( + $profile_values['name'], + $profile_values, + $this->profile_id + ); - // Restrict profile names to alphanumeric characters and the underscore. - if (isset($values['name']) && 1 === preg_match('/[^A-Za-z0-9\_]/', $values['name'])) { - $this->_errors['name'] = - E::ts('Only alphanumeric characters and the underscore (_) are allowed for profile names.'); - } - - // Validate custom field mapping. - try { - if (isset($values['custom_field_mapping'])) { - $custom_field_mapping = preg_split('/\r\n|\r|\n/', $values['custom_field_mapping'], -1, PREG_SPLIT_NO_EMPTY); - if (!is_array($custom_field_mapping)) { - throw new BaseException( - E::ts('Could not parse custom field mapping.') - ); - } - foreach ($custom_field_mapping as $custom_field_map) { - $custom_field_map = explode('=', $custom_field_map); - if (count($custom_field_map) !== 2) { - throw new BaseException( - E::ts('Could not parse custom field mapping.') - ); - } - [$twingle_field_name, $custom_field_name] = $custom_field_map; - $custom_field_id = substr($custom_field_name, strlen('custom_')); - - // Check for custom field existence - try { - $custom_field = civicrm_api3('CustomField', 'getsingle', [ - 'id' => $custom_field_id, - ]); - } - catch (CRM_Core_Exception $exception) { - throw new BaseException( - E::ts( - 'Custom field custom_%1 does not exist.', - [1 => $custom_field_id] - ), - NULL, - $exception - ); - } - - // Only allow custom fields on relevant entities. - try { - $custom_group = civicrm_api3('CustomGroup', 'getsingle', [ - 'id' => $custom_field['custom_group_id'], - 'extends' => [ - 'IN' => [ - 'Contact', - 'Individual', - 'Organization', - 'Contribution', - 'ContributionRecur', - ], - ], - ]); - } - catch (CRM_Core_Exception $exception) { - throw new BaseException( - E::ts( - 'Custom field custom_%1 is not in a CustomGroup that extends one of the supported CiviCRM entities.', - [1 => $custom_field['id']] - ), - NULL, - $exception - ); - } - } + // Validate profile data + try { + $profile->validate(); + } + catch (ProfileValidationError $e) { + $this->setElementError($e->getAffectedFieldName(), $e->getMessage()); } - } - catch (BaseException $exception) { - $this->_errors['custom_field_mapping'] = $exception->getMessage(); } return parent::validate(); diff --git a/CRM/Twingle/Profile.php b/CRM/Twingle/Profile.php index f7ad35d..dddee4e 100644 --- a/CRM/Twingle/Profile.php +++ b/CRM/Twingle/Profile.php @@ -99,14 +99,7 @@ class CRM_Twingle_Profile { * @return bool */ public function matches($project_id) { - $selector = $this->getAttribute('selector'); - $project_ids = array_map( - function($project_id) { - return trim($project_id); - }, - explode(',', $selector) - ); - return in_array($project_id, $project_ids, TRUE); + return in_array($project_id, $this->getProjectIds()); } /** @@ -179,6 +172,29 @@ class CRM_Twingle_Profile { $this->name = $name; } + /** + * Is this the default profile? + * + * @return bool + */ + public function is_default() { + return $this->name == 'default'; + } + + /** + * Retrieves the profile's project IDs. + * + * @return array + */ + public function getProjectIds(): array { + return array_map( + function($project_id) { + return trim($project_id); + }, + explode(',', $this->getAttribute("selector")) + ); + } + /** * Retrieves an attribute of the profile. * @@ -225,21 +241,138 @@ class CRM_Twingle_Profile { /** * Verifies whether the profile is valid (i.e. consistent and not colliding * with other profiles). + * + * @throws \CRM_Twingle_Exceptions_ProfileValidationError + * @throws \Civi\Core\Exception\DBQueryException + * When the profile could not be successfully validated. */ - public function verifyProfile(): void { - // TODO: check - // data of this profile consistent? - // conflicts with other profiles? + public function validate() { + + // Name cannot be empty + if (empty($this->getName())) { + throw new ProfileValidationError( + 'name', + E::ts('Profile name cannot be empty.'), + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ); + } + + // Restrict profile names to alphanumeric characters, space and the underscore. + $contains_illegal_characters = preg_match("/[^A-Za-z0-9_\s]/", $this->getName()); + if ($contains_illegal_characters) { + throw new ProfileValidationError( + 'name', + E::ts('Only alphanumeric characters, space and the underscore (_) are allowed for profile names.'), + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ); + } + + // Check if profile name is already used for other profile + $profile_name_duplicates = array_filter( + CRM_Twingle_Profile::getProfiles(), function($profile) { + return $profile->getName() == $this->getName() && $this->getId() != $profile->getId(); + }); + if (!empty($profile_name_duplicates)) { + throw new ProfileValidationError( + 'name', + E::ts("A profile with the name '%1' already exists.", [1 => $this->getName()]), + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ); + } + + // Check if project_id is already used in other profile + $profiles = $this::getProfiles(); + foreach ($profiles as $profile) { + if ($profile->getId() == $this->getId() || $profile->is_default()) continue; + $project_ids = $this->getProjectIds(); + $id_duplicates = array_intersect($profile->getProjectIds(), $project_ids); + if (!empty($id_duplicates)) { + throw new ProfileValidationError( + 'selector', + E::ts( + "Project ID(s) [%1] already used in profile '%2'.", + [ + 1 => implode(", ", $id_duplicates), + 2 => $profile->getName() + ] + ), + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ); + } + } + + // Validate custom field mapping. + $custom_field_mapping = $this->getAttribute('custom_field_mapping', FALSE); + if ($custom_field_mapping) { + $custom_field_mapping = preg_split('/\r\n|\r|\n/', $custom_field_mapping, -1, PREG_SPLIT_NO_EMPTY); + $parsing_error = new ProfileValidationError( + 'custom_field_mapping', + E::ts('Could not parse custom field mapping.'), + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ); + if (!is_array($custom_field_mapping)) { + throw $parsing_error; + } + foreach ($custom_field_mapping as $custom_field_map) { + $custom_field_map = explode("=", $custom_field_map); + if (count($custom_field_map) !== 2) { + throw $parsing_error; + } + [$twingle_field_name, $custom_field_name] = $custom_field_map; + $custom_field_id = substr($custom_field_name, strlen('custom_')); + + // Check for custom field existence + try { + $custom_field = civicrm_api3( + 'CustomField', 'getsingle', ['id' => $custom_field_id] + ); + } + catch (CiviCRM_API3_Exception $exception) { + throw new ProfileValidationError( + 'custom_field_mapping', + E::ts( + 'Custom field custom_%1 does not exist.', + [1 => $custom_field_id] + ), + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ); + } + + // Only allow custom fields on relevant entities. + try { + civicrm_api3('CustomGroup', 'getsingle', + [ + 'id' => $custom_field['custom_group_id'], + 'extends' => [ + 'IN' => [ + 'Contact', + 'Individual', + 'Organization', + 'Contribution', + 'ContributionRecur', + ], + ], + ]); + } catch (CiviCRM_API3_Exception $exception) { + throw new ProfileValidationError( + 'custom_field_mapping', + E::ts( + 'Custom field custom_%1 is not in a CustomGroup that extends one of the supported CiviCRM entities.', + [1 => $custom_field['id']] + ), + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ); + } + } + } } /** * Persists the profile within the database. * - * @throws \Civi\Twingle\Exceptions\ProfileException + * @throws \CRM_Twingle_Exceptions_ProfileException */ - public function saveProfile(): void { - // make sure it's valid - $this->verifyProfile(); + public function saveProfile() { try { if ($this->id !== NULL) { From 8d1d93d77a01e675ab51717781175349ec995c6e Mon Sep 17 00:00:00 2001 From: Marc Michalsky Date: Thu, 17 Aug 2023 10:41:45 +0200 Subject: [PATCH 2/3] display a warning instead of an error if the project_id is a duplicate --- CRM/Twingle/Form/Profile.php | 8 +++++++- CRM/Twingle/Profile.php | 2 +- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CRM/Twingle/Form/Profile.php b/CRM/Twingle/Form/Profile.php index 0412b1c..245091f 100644 --- a/CRM/Twingle/Form/Profile.php +++ b/CRM/Twingle/Form/Profile.php @@ -568,7 +568,13 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { $profile->validate(); } catch (ProfileValidationError $e) { - $this->setElementError($e->getAffectedFieldName(), $e->getMessage()); + switch ($e->getErrorCode()) { + case ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED: + $this->setElementError($e->getAffectedFieldName(), $e->getMessage()); + break; + case ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_WARNING: + CRM_Core_Session::setStatus($e->getMessage(), E::ts('Warning')); + } } } diff --git a/CRM/Twingle/Profile.php b/CRM/Twingle/Profile.php index dddee4e..35373a1 100644 --- a/CRM/Twingle/Profile.php +++ b/CRM/Twingle/Profile.php @@ -296,7 +296,7 @@ class CRM_Twingle_Profile { 2 => $profile->getName() ] ), - ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_WARNING ); } } From 6114772d07c81ef1a76601996957b73304f6b7b0 Mon Sep 17 00:00:00 2001 From: Jens Schuppe Date: Fri, 5 Apr 2024 13:18:45 +0200 Subject: [PATCH 3/3] PHP Code Sniffer and PHPStan fixes --- CRM/Twingle/Form/Profile.php | 38 ++-- CRM/Twingle/Profile.php | 171 +++++++++++------- Civi/Twingle/Exceptions/BaseException.php | 12 +- .../Exceptions/ProfileValidationError.php | 9 +- 4 files changed, 138 insertions(+), 92 deletions(-) diff --git a/CRM/Twingle/Form/Profile.php b/CRM/Twingle/Form/Profile.php index 245091f..109013f 100644 --- a/CRM/Twingle/Form/Profile.php +++ b/CRM/Twingle/Form/Profile.php @@ -16,8 +16,9 @@ declare(strict_types = 1); use CRM_Twingle_ExtensionUtil as E; -use Civi\Twingle\Exceptions\ProfileException; use Civi\Twingle\Exceptions\BaseException; +use Civi\Twingle\Exceptions\ProfileException; +use Civi\Twingle\Exceptions\ProfileValidationError; /** * Form controller class @@ -168,7 +169,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { switch ($this->_op) { case 'delete': - if ($this->profile) { + if (isset($this->profile)) { CRM_Utils_System::setTitle(E::ts('Delete Twingle API profile %1', [1 => $this->profile->getName()])); $this->addButtons([ [ @@ -185,23 +186,26 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { // Retrieve the source profile name. $source_id = CRM_Utils_Request::retrieve('source_id', 'Int', $this); // When copying without a valid profile id, copy the default profile. - if (!$source_id) { + if (!is_int($source_id)) { $this->profile = CRM_Twingle_Profile::createDefaultProfile(); - } else { + } + 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()); + } + catch (ProfileValidationError $exception) { + if ($exception->getErrorCode() == ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED) { + Civi::log()->error($exception->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()); + } + catch (ProfileException $exception) { + if ($exception->getErrorCode() == ProfileException::ERROR_CODE_PROFILE_NOT_FOUND) { + Civi::log()->error($exception->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; @@ -209,7 +213,10 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { } 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_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; } @@ -218,7 +225,9 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { break; case 'edit': - CRM_Utils_System::setTitle(E::ts('Edit Twingle API profile %1', [1 => $this->profile->getName()])); + CRM_Utils_System::setTitle( + E::ts('Edit Twingle API profile %1', [1 => $this->profile->getName()]) + ); break; case 'create': @@ -554,8 +563,8 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { */ public function validate() { - if (in_array($this->_op, ['create', 'edit', 'copy'])) { - // Create profile with new values + if (in_array($this->_op, ['create', 'edit', 'copy'], TRUE)) { + // Create profile with new values. $profile_values = $this->exportValues(); $profile = new CRM_Twingle_Profile( $profile_values['name'], @@ -572,6 +581,7 @@ class CRM_Twingle_Form_Profile extends CRM_Core_Form { case ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED: $this->setElementError($e->getAffectedFieldName(), $e->getMessage()); break; + case ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_WARNING: CRM_Core_Session::setStatus($e->getMessage(), E::ts('Warning')); } diff --git a/CRM/Twingle/Profile.php b/CRM/Twingle/Profile.php index 35373a1..4e186ad 100644 --- a/CRM/Twingle/Profile.php +++ b/CRM/Twingle/Profile.php @@ -17,6 +17,7 @@ declare(strict_types = 1); use CRM_Twingle_ExtensionUtil as E; use Civi\Twingle\Exceptions\ProfileException as ProfileException; +use Civi\Twingle\Exceptions\ProfileValidationError; /** * Profiles define how incoming submissions from the Twingle API are @@ -25,16 +26,16 @@ use Civi\Twingle\Exceptions\ProfileException as ProfileException; class CRM_Twingle_Profile { /** - * @var int $id + * @var int * The id of the profile. */ - protected $id = NULL; + protected ?int $id; /** - * @var string $name + * @var string * The name of the profile. */ - protected $name; + protected string $name; /** * @var array @@ -99,7 +100,7 @@ class CRM_Twingle_Profile { * @return bool */ public function matches($project_id) { - return in_array($project_id, $this->getProjectIds()); + return in_array($project_id, $this->getProjectIds(), TRUE); } /** @@ -141,7 +142,7 @@ class CRM_Twingle_Profile { * * @return int */ - public function getId() { + public function getId(): ?int { return $this->id; } @@ -150,7 +151,7 @@ class CRM_Twingle_Profile { * * @param int $id */ - public function setId(int $id) { + public function setId(int $id): void { $this->id = $id; } @@ -159,7 +160,7 @@ class CRM_Twingle_Profile { * * @return string */ - public function getName() { + public function getName(): string { return $this->name; } @@ -177,21 +178,21 @@ class CRM_Twingle_Profile { * * @return bool */ - public function is_default() { - return $this->name == 'default'; + public function is_default(): bool { + return $this->name === 'default'; } /** * Retrieves the profile's project IDs. * - * @return array + * @return array */ public function getProjectIds(): array { return array_map( function($project_id) { return trim($project_id); }, - explode(',', $this->getAttribute("selector")) + explode(',', $this->getAttribute('selector') ?? '') ); } @@ -242,14 +243,14 @@ class CRM_Twingle_Profile { * Verifies whether the profile is valid (i.e. consistent and not colliding * with other profiles). * - * @throws \CRM_Twingle_Exceptions_ProfileValidationError + * @throws \Civi\Twingle\Exceptions\ProfileValidationError * @throws \Civi\Core\Exception\DBQueryException * When the profile could not be successfully validated. */ - public function validate() { + public function validate(): void { // Name cannot be empty - if (empty($this->getName())) { + if ('' === $this->getName()) { throw new ProfileValidationError( 'name', E::ts('Profile name cannot be empty.'), @@ -258,7 +259,7 @@ class CRM_Twingle_Profile { } // Restrict profile names to alphanumeric characters, space and the underscore. - $contains_illegal_characters = preg_match("/[^A-Za-z0-9_\s]/", $this->getName()); + $contains_illegal_characters = (1 !== preg_match('/[^A-Za-z0-9_\s]/', $this->getName())); if ($contains_illegal_characters) { throw new ProfileValidationError( 'name', @@ -269,10 +270,11 @@ class CRM_Twingle_Profile { // Check if profile name is already used for other profile $profile_name_duplicates = array_filter( - CRM_Twingle_Profile::getProfiles(), function($profile) { - return $profile->getName() == $this->getName() && $this->getId() != $profile->getId(); - }); - if (!empty($profile_name_duplicates)) { + CRM_Twingle_Profile::getProfiles(), + function($profile) { + return $profile->getName() == $this->getName() && $this->getId() != $profile->getId(); + }); + if ([] !== $profile_name_duplicates) { throw new ProfileValidationError( 'name', E::ts("A profile with the name '%1' already exists.", [1 => $this->getName()]), @@ -283,17 +285,19 @@ class CRM_Twingle_Profile { // Check if project_id is already used in other profile $profiles = $this::getProfiles(); foreach ($profiles as $profile) { - if ($profile->getId() == $this->getId() || $profile->is_default()) continue; + if ($profile->getId() == $this->getId() || $profile->is_default()) { + continue; + }; $project_ids = $this->getProjectIds(); $id_duplicates = array_intersect($profile->getProjectIds(), $project_ids); - if (!empty($id_duplicates)) { + if ([] !== $id_duplicates) { throw new ProfileValidationError( 'selector', E::ts( "Project ID(s) [%1] already used in profile '%2'.", [ - 1 => implode(", ", $id_duplicates), - 2 => $profile->getName() + 1 => implode(', ', $id_duplicates), + 2 => $profile->getName(), ] ), ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_WARNING @@ -302,8 +306,8 @@ class CRM_Twingle_Profile { } // Validate custom field mapping. - $custom_field_mapping = $this->getAttribute('custom_field_mapping', FALSE); - if ($custom_field_mapping) { + $custom_field_mapping = $this->getAttribute('custom_field_mapping'); + if (is_string($custom_field_mapping)) { $custom_field_mapping = preg_split('/\r\n|\r|\n/', $custom_field_mapping, -1, PREG_SPLIT_NO_EMPTY); $parsing_error = new ProfileValidationError( 'custom_field_mapping', @@ -314,7 +318,7 @@ class CRM_Twingle_Profile { throw $parsing_error; } foreach ($custom_field_mapping as $custom_field_map) { - $custom_field_map = explode("=", $custom_field_map); + $custom_field_map = explode('=', $custom_field_map); if (count($custom_field_map) !== 2) { throw $parsing_error; } @@ -323,18 +327,22 @@ class CRM_Twingle_Profile { // Check for custom field existence try { + /** + * @phpstan-var array $custom_field + */ $custom_field = civicrm_api3( 'CustomField', 'getsingle', ['id' => $custom_field_id] ); } - catch (CiviCRM_API3_Exception $exception) { + catch (CRM_Core_Exception $exception) { throw new ProfileValidationError( 'custom_field_mapping', E::ts( 'Custom field custom_%1 does not exist.', [1 => $custom_field_id] ), - ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED, + $exception ); } @@ -353,14 +361,16 @@ class CRM_Twingle_Profile { ], ], ]); - } catch (CiviCRM_API3_Exception $exception) { + } + catch (CRM_Core_Exception $exception) { throw new ProfileValidationError( 'custom_field_mapping', E::ts( 'Custom field custom_%1 is not in a CustomGroup that extends one of the supported CiviCRM entities.', [1 => $custom_field['id']] ), - ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED + ProfileValidationError::ERROR_CODE_PROFILE_VALIDATION_FAILED, + $exception ); } } @@ -370,15 +380,14 @@ class CRM_Twingle_Profile { /** * Persists the profile within the database. * - * @throws \CRM_Twingle_Exceptions_ProfileException + * @throws \Civi\Twingle\Exceptions\ProfileException */ - public function saveProfile() { - + public function saveProfile(): void { try { - if ($this->id !== NULL) { + if (isset($this->id)) { // existing profile -> just update the config CRM_Core_DAO::executeQuery( - "UPDATE civicrm_twingle_profile SET config = %2, name = %3 WHERE id = %1", + 'UPDATE civicrm_twingle_profile SET config = %2, name = %3 WHERE id = %1', [ 1 => [$this->id, 'String'], 2 => [json_encode($this->data), 'String'], @@ -388,17 +397,20 @@ class CRM_Twingle_Profile { 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)", + << [$this->name, 'String'], 2 => [json_encode($this->data), 'String'], ]); } } - catch (Exception $e) { + catch (Exception $exception) { throw new ProfileException( - E::ts("Could not save/update profile: %1", [1 => $e->getMessage()]), - ProfileException::ERROR_CODE_COULD_NOT_SAVE_PROFILE + E::ts('Could not save/update profile: %1', [1 => $exception->getMessage()]), + ProfileException::ERROR_CODE_COULD_NOT_SAVE_PROFILE, + $exception ); } } @@ -408,7 +420,7 @@ class CRM_Twingle_Profile { * * @throws \Civi\Twingle\Exceptions\ProfileException */ - public function deleteProfile() { + public function deleteProfile(): void { // Do only reset default profile if ($this->getName() == 'default') { try { @@ -417,31 +429,31 @@ class CRM_Twingle_Profile { $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) { + CRM_Core_DAO::executeQuery( + 'UPDATE civicrm_twingle_profile SET access_counter = 0, last_access = NULL WHERE id = %1', + [1 => [$this->id, 'Integer']] + ); + } + catch (Exception $exception) { throw new ProfileException( - E::ts("Could not reset default profile: %1", [1 => $e->getMessage()]), - ProfileException::ERROR_CODE_COULD_NOT_RESET_PROFILE + E::ts('Could not reset default profile: %1', [1 => $exception->getMessage()]), + ProfileException::ERROR_CODE_COULD_NOT_RESET_PROFILE, + $exception ); } } else { try { - CRM_Core_DAO::executeQuery("DELETE FROM civicrm_twingle_profile WHERE id = %1", [ - 1 => [ - $this->id, - 'Integer' - ] - ]); - } catch (Exception $e) { + CRM_Core_DAO::executeQuery( + 'DELETE FROM civicrm_twingle_profile WHERE id = %1', + [1 => [$this->id, 'Integer']] + ); + } + catch (Exception $exception) { throw new ProfileException( - E::ts("Could not delete profile: %1", [1 => $e->getMessage()]), - ProfileException::ERROR_CODE_COULD_NOT_DELETE_PROFILE + E::ts('Could not delete profile: %1', [1 => $exception->getMessage()]), + ProfileException::ERROR_CODE_COULD_NOT_DELETE_PROFILE, + $exception ); } } @@ -620,11 +632,20 @@ class CRM_Twingle_Profile { * @throws \Civi\Twingle\Exceptions\ProfileException */ public static function getProfile(int $id = NULL) { - if (!empty($id)) { - $profile_data = CRM_Core_DAO::executeQuery("SELECT id, name, config FROM civicrm_twingle_profile WHERE id = %1", - [1 => [$id, 'Integer']]); + if (isset($id)) { + /** + * @var CRM_Core_DAO $profile_data + */ + $profile_data = CRM_Core_DAO::executeQuery( + 'SELECT id, name, config FROM civicrm_twingle_profile WHERE id = %1', + [1 => [$id, 'Integer']] + ); if ($profile_data->fetch()) { - return new CRM_Twingle_Profile($profile_data->name, json_decode($profile_data->config, 1), (int) $profile_data->id); + return new CRM_Twingle_Profile( + $profile_data->name, + json_decode($profile_data->config, TRUE), + (int) $profile_data->id + ); } } throw new ProfileException('Profile not found.', ProfileException::ERROR_CODE_PROFILE_NOT_FOUND); @@ -634,16 +655,23 @@ class CRM_Twingle_Profile { * Retrieves the list of all profiles persisted within the current CiviCRM * settings, including the default profile. * - * @return array - * profile_name => CRM_Twingle_Profile + * @return array + * An array of profiles with profile IDs as keys and profile objects as values. * @throws \Civi\Core\Exception\DBQueryException */ - public static function getProfiles() { + public static function getProfiles(): array { // todo: cache? $profiles = []; - $profile_data = CRM_Core_DAO::executeQuery("SELECT id, name, config FROM civicrm_twingle_profile"); + /** + * @var CRM_Core_DAO $profile_data + */ + $profile_data = CRM_Core_DAO::executeQuery('SELECT id, name, config FROM civicrm_twingle_profile'); while ($profile_data->fetch()) { - $profiles[$profile_data->id] = new CRM_Twingle_Profile($profile_data->name, json_decode($profile_data->config, 1), (int) $profile_data->id); + $profiles[(int) $profile_data->id] = new CRM_Twingle_Profile( + $profile_data->name, + json_decode($profile_data->config, TRUE), + (int) $profile_data->id + ); } return $profiles; } @@ -651,17 +679,20 @@ class CRM_Twingle_Profile { /** * Get the stats (access_count, last_access) for all twingle profiles * - * @return CRM_Twingle_Profile[] + * @return array> * @throws \Civi\Core\Exception\DBQueryException */ public static function getProfileStats() { $stats = []; + /** + * @var CRM_Core_DAO $profile_data + */ $profile_data = CRM_Core_DAO::executeQuery( 'SELECT name, last_access, access_counter FROM civicrm_twingle_profile' ); while ($profile_data->fetch()) { // phpcs:disable Drupal.Arrays.Array.ArrayIndentation - $stats[$profile_data->name] = [ + $stats[(string) $profile_data->name] = [ 'name' => $profile_data->name, 'last_access' => $profile_data->last_access, 'last_access_txt' => $profile_data->last_access diff --git a/Civi/Twingle/Exceptions/BaseException.php b/Civi/Twingle/Exceptions/BaseException.php index 5aa721e..a5413d5 100644 --- a/Civi/Twingle/Exceptions/BaseException.php +++ b/Civi/Twingle/Exceptions/BaseException.php @@ -28,21 +28,21 @@ use CRM_Twingle_ExtensionUtil as E; class BaseException extends \Exception { /** - * @var int|string + * @var string */ protected $code; protected string $log_message; /** * BaseException Constructor - * @param string|null $message + * @param string $message * Error message - * @param string|null $error_code + * @param string $error_code * A meaningful error code - * @param \Throwable|null $previous + * @param \Throwable $previous * A previously thrown exception to include. */ - public function __construct(?string $message = '', ?string $error_code = '', ?\Throwable $previous = NULL) { + public function __construct(string $message = '', string $error_code = '', \Throwable $previous = NULL) { parent::__construct($message, 1, $previous); $this->log_message = '' !== $message ? E::LONG_NAME . ': ' . $message : ''; $this->code = $error_code; @@ -61,7 +61,7 @@ class BaseException extends \Exception { * @return string */ public function getErrorCode() { - return (string) $this->code; + return $this->code; } } diff --git a/Civi/Twingle/Exceptions/ProfileValidationError.php b/Civi/Twingle/Exceptions/ProfileValidationError.php index da29705..de1b242 100644 --- a/Civi/Twingle/Exceptions/ProfileValidationError.php +++ b/Civi/Twingle/Exceptions/ProfileValidationError.php @@ -38,8 +38,13 @@ class ProfileValidationError extends BaseException { * @param string $error_code * A meaningful error code */ - public function __construct(string $affected_field_name, string $message = '', string $error_code = '') { - parent::__construct($message, $error_code); + public function __construct( + string $affected_field_name, + string $message = '', + string $error_code = '', + ?\Throwable $previous = NULL + ) { + parent::__construct($message, $error_code, $previous); $this->affected_field_name = $affected_field_name; }