-
-
Notifications
You must be signed in to change notification settings - Fork 825
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
DomainEmailAddress - Add legacy API adapter
Wraps the OptionValue api (v3 and v4) to provide backward compatibility for accessing DomainEmailAddress via the OptionValue api.
- Loading branch information
Showing
11 changed files
with
469 additions
and
86 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
230 changes: 230 additions & 0 deletions
230
Civi/API/Subscriber/DomainEmailLegacyOptionValueAdapter.php
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,230 @@ | ||
<?php | ||
/* | ||
+--------------------------------------------------------------------+ | ||
| Copyright CiviCRM LLC. All rights reserved. | | ||
| | | ||
| This work is published under the GNU AGPLv3 license with some | | ||
| permitted exceptions and without any warranty. For full license | | ||
| and copyright information, see https://civicrm.org/licensing | | ||
+--------------------------------------------------------------------+ | ||
*/ | ||
|
||
namespace Civi\API\Subscriber; | ||
|
||
use Civi\Api4\Generic\AbstractAction; | ||
use Civi\Core\Service\AutoSubscriber; | ||
use CRM_Core_Config; | ||
|
||
/** | ||
* Wraps the OptionValue api (v3 and v4) to provide backward compatibility | ||
* with the DomainEmail entity (formerly the `from_email_address` option group). | ||
*/ | ||
class DomainEmailLegacyOptionValueAdapter extends AutoSubscriber { | ||
|
||
private static $apiIds = []; | ||
|
||
const CONCAT_LABEL = 'CONCAT(\'"\', name, \'" <\', email, \'>\')'; | ||
|
||
/** | ||
* @return array | ||
*/ | ||
public static function getSubscribedEvents() { | ||
return [ | ||
'civi.api.prepare' => [ | ||
['onApiPrepare', 2000], | ||
], | ||
'civi.api.respond' => [ | ||
['onApiRespond', 2000], | ||
], | ||
]; | ||
} | ||
|
||
public function onApiPrepare(\Civi\API\Event\PrepareEvent $event) { | ||
$apiRequest = $event->getApiRequest(); | ||
if ($apiRequest['entity'] !== 'OptionValue') { | ||
return; | ||
} | ||
if ($apiRequest['version'] == 3 && $this->isApi3FromEmailOptionValueRequest($apiRequest)) { | ||
$this->preprocessApi3DomainEmailOptionValues($apiRequest); | ||
$event->setApiRequest($apiRequest); | ||
} | ||
elseif ($apiRequest['version'] == 4 && $this->isApi4FromEmailOptionValueRequest($apiRequest)) { | ||
$this->preprocessApi4DomainEmailOptionValues($apiRequest); | ||
} | ||
} | ||
|
||
public function onApiRespond(\Civi\API\Event\RespondEvent $event) { | ||
$apiRequest = $event->getApiRequest(); | ||
// If ID was previously stashed by preprocessApi3DomainEmailOptionValues | ||
if (isset(self::$apiIds[$apiRequest['id']])) { | ||
unset(self::$apiIds[$apiRequest['id']]); | ||
$apiResponse = $event->getResponse(); | ||
$this->postprocessApi3DomainEmailOptionValues($apiRequest, $apiResponse); | ||
$event->setResponse($apiResponse); | ||
} | ||
} | ||
|
||
/** | ||
* @param array $apiRequest | ||
* @return bool | ||
*/ | ||
private function isApi3FromEmailOptionValueRequest($apiRequest): bool { | ||
// In api3, 'create' also means 'update'; this supports both. | ||
// This also effectively supports `getsingle` and `getvalue` because they wrap `get`. | ||
// However, 'delete' is not supported because the params don't include option_group_id so we have no way to target them. | ||
$supportedActions = ['create', 'get']; | ||
if (!in_array($apiRequest['action'], $supportedActions, TRUE)) { | ||
return FALSE; | ||
} | ||
$apiParams = $apiRequest['params']; | ||
if (($apiParams['option_group_id'] ?? NULL) === 'from_email_address') { | ||
return TRUE; | ||
} | ||
// Update actions are tricky because they probably won't pass option_group_id in the params | ||
// So check if the label looks like a well-formed email, and if so, see if the id matches a record in civicrm_domain_email_address | ||
if ($apiRequest['action'] === 'create' && !empty($apiParams['id']) && !empty($apiParams['label'])) { | ||
$pattern = '/^"[^"]+" <[^\s@<>]+@[^\s@<>]+\.[^\s@<>]+>$/'; | ||
if (preg_match($pattern, $apiParams['label'])) { | ||
return (bool) \CRM_Core_DAO::singleValueQuery("SELECT COUNT(*) FROM civicrm_domain_email_address WHERE id = %1", [ | ||
1 => [$apiParams['id'], 'Integer'], | ||
]); | ||
} | ||
} | ||
return FALSE; | ||
} | ||
|
||
private function preprocessApi3DomainEmailOptionValues(&$apiRequest) { | ||
// Register request id for postprocessing | ||
self::$apiIds[$apiRequest['id']] = TRUE; | ||
// Modify internal variables of the api request... don't try this at home | ||
$apiRequest['function'] = str_replace('option_value', 'domain_email_address', $apiRequest['function']); | ||
// Switch request to use DomainEmailAddress entity | ||
$apiRequest['entity'] = 'DomainEmailAddress'; | ||
$apiParams = &$apiRequest['params']; | ||
unset($apiParams['option_group_id']); | ||
if (!empty($apiParams['return'])) { | ||
if (!is_array($apiParams['return'])) { | ||
$apiParams['return'] = array_map('trim', explode(',', $apiParams['return'])); | ||
} | ||
$apiParams['return'][] = 'id'; | ||
$apiParams['return'][] = 'name'; | ||
$apiParams['return'][] = 'email'; | ||
} | ||
if (!empty($apiParams['value'])) { | ||
$apiParams['id'] = $apiParams['value']; | ||
} | ||
if (isset($apiParams['name']) && !isset($apiParams['label'])) { | ||
$apiParams['label'] = $apiParams['name']; | ||
} | ||
unset($apiParams['option_group_id'], $apiParams['name'], $apiParams['value']); | ||
if (isset($apiParams['label'])) { | ||
$apiParams['email'] = \CRM_Utils_Mail::pluckEmailFromHeader(rtrim($apiParams['label'])); | ||
$apiParams['name'] = trim(explode('"', $apiParams['label'])[1]); | ||
} | ||
if ($apiRequest['action'] === 'create' && !isset($apiParams['id']) && !isset($apiParams['domain_id'])) { | ||
$apiParams['domain_id'] = CRM_Core_Config::domainID(); | ||
} | ||
// Convert chains | ||
foreach (array_keys($apiParams) as $key) { | ||
if (str_starts_with(strtolower($key), 'api.option_value.') || str_starts_with(strtolower($key), 'api.optionvalue.')) { | ||
$action = explode('.', $key)[2]; | ||
$apiParams["api.domain_email_address.$action"] = $apiParams[$key]; | ||
unset($apiParams[$key]); | ||
} | ||
} | ||
} | ||
|
||
private function postprocessApi3DomainEmailOptionValues(array $apiRequest, array &$apiResult) { | ||
if (isset($apiResult['values']) && is_array($apiResult['values'])) { | ||
foreach ($apiResult['values'] as &$value) { | ||
if (isset($value['id'])) { | ||
$value['value'] = $value['id']; | ||
} | ||
if (isset($value['name']) && isset($value['email'])) { | ||
$value['label'] = \CRM_Utils_Mail::formatFromAddress($value); | ||
$value['name'] = $value['label']; | ||
} | ||
} | ||
} | ||
} | ||
|
||
/** | ||
* @param \Civi\Api4\Generic\AbstractAction $apiRequest | ||
* @return bool | ||
*/ | ||
private function isApi4FromEmailOptionValueRequest($apiRequest): bool { | ||
$supportedActions = ['create', 'get', 'update', 'delete']; | ||
$action = $apiRequest->getActionName(); | ||
if (!in_array($action, $supportedActions, TRUE)) { | ||
return FALSE; | ||
} | ||
if ($apiRequest->reflect()->hasProperty('where')) { | ||
foreach ($apiRequest->getWhere() as $clause) { | ||
if ($clause[0] === 'option_group_id:name' || $clause[0] === 'option_group_id.name') { | ||
if ($clause[1] === '=' && $clause[2] === 'from_email_address') { | ||
return TRUE; | ||
} | ||
} | ||
} | ||
} | ||
if ($action === 'create' || $action === 'update') { | ||
$values = $apiRequest->getValues(); | ||
if (($values['option_group_id:name'] ?? $values['option_group_id.name'] ?? NULL) === 'from_email_address') { | ||
return TRUE; | ||
} | ||
} | ||
return FALSE; | ||
} | ||
|
||
private function preprocessApi4DomainEmailOptionValues(AbstractAction $apiRequest) { | ||
$action = $apiRequest->getActionName(); | ||
// Modify internal variables of the api request... don't try this at home | ||
$reflection = $apiRequest->reflect(); | ||
$entityNameProperty = $reflection->getProperty('_entityName'); | ||
$entityNameProperty->setAccessible(TRUE); | ||
$entityNameProperty->setValue($apiRequest, 'DomainEmailAddress'); | ||
// Also reset $_entityFields | ||
$entityFieldsProperty = $reflection->getProperty('_entityFields'); | ||
$entityFieldsProperty->setAccessible(TRUE); | ||
$entityFieldsProperty->setValue($apiRequest, NULL); | ||
if ($reflection->hasProperty('where')) { | ||
$where = $apiRequest->getWhere(); | ||
foreach ($where as $index => $clause) { | ||
if ($clause[0] === 'option_group_id:name' || $clause[0] === 'option_group_id.name') { | ||
unset($where[$index]); | ||
} | ||
if ($clause[0] === 'value') { | ||
$where[$index][0] = 'id'; | ||
} | ||
if ($clause[0] === 'label') { | ||
$where[$index][0] = self::CONCAT_LABEL; | ||
} | ||
} | ||
$apiRequest->setWhere(array_values($where)); | ||
} | ||
if ($action === 'get') { | ||
$select = $apiRequest->getSelect() ?: ['*']; | ||
// Remove all non-supported fields from select clause | ||
$allowedFields = array_keys(\Civi::entity('DomainEmailAddress')->getFields()); | ||
$allowedFields[] = '*'; | ||
$select = array_intersect($select, $allowedFields); | ||
$select[] = self::CONCAT_LABEL . ' AS label'; | ||
$select[] = '(id) AS value'; | ||
$apiRequest->setSelect($select); | ||
} | ||
if (in_array($action, ['create', 'update'], TRUE)) { | ||
$values = $apiRequest->getValues(); | ||
if (isset($values['value']) && $action === 'update' && !isset($values['id'])) { | ||
$values['id'] = $values['value']; | ||
} | ||
$values['label'] ??= $values['name'] ?? NULL; | ||
if (isset($values['label'])) { | ||
$values['email'] = \CRM_Utils_Mail::pluckEmailFromHeader(rtrim($values['label'])); | ||
$values['name'] = trim(explode('"', $values['label'])[1]); | ||
} | ||
unset($values['label'], $values['value'], $values['option_group_id:name'], $values['option_group_id.name']); | ||
$apiRequest->setValues($values); | ||
} | ||
} | ||
|
||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
<?php | ||
/* | ||
+--------------------------------------------------------------------+ | ||
| Copyright CiviCRM LLC. All rights reserved. | | ||
| | | ||
| This work is published under the GNU AGPLv3 license with some | | ||
| permitted exceptions and without any warranty. For full license | | ||
| and copyright information, see https://civicrm.org/licensing | | ||
+--------------------------------------------------------------------+ | ||
*/ | ||
|
||
/** | ||
* APIv3 for CiviCRM DomainEmailAddress, mostly for the sake of backward-compatability. | ||
* | ||
* @see \Civi\API\Subscriber\DomainEmailLegacyOptionValueAdapter | ||
* | ||
* @package CiviCRM_APIv3 | ||
*/ | ||
|
||
/** | ||
* Add or update a DomainEmailAddress. | ||
* | ||
* @param array $params | ||
* | ||
* @return array | ||
*/ | ||
function civicrm_api3_domain_email_address_create($params) { | ||
return _civicrm_api3_basic_create(_civicrm_api3_get_BAO(__FUNCTION__), $params); | ||
} | ||
|
||
/** | ||
* Deletes an existing DomainEmailAddress. | ||
* | ||
* @param array $params | ||
* | ||
* @return array | ||
*/ | ||
function civicrm_api3_domain_email_address_delete($params) { | ||
return _civicrm_api3_basic_delete(_civicrm_api3_get_BAO(__FUNCTION__), $params); | ||
} | ||
|
||
/** | ||
* Retrieve one or more DomainEmailAddress. | ||
* | ||
* @param array $params | ||
* | ||
* @return array | ||
*/ | ||
function civicrm_api3_domain_email_address_get($params) { | ||
return _civicrm_api3_basic_get(_civicrm_api3_get_BAO(__FUNCTION__), $params); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -10,7 +10,7 @@ | |
*/ | ||
|
||
use Civi\Api4\Activity; | ||
use Civi\Api4\OptionValue; | ||
use Civi\Api4\DomainEmailAddress; | ||
|
||
/** | ||
* Test class for CRM_Contact_Form_Task_Email. | ||
|
@@ -27,9 +27,9 @@ protected function setUp(): void { | |
$this->individualCreate(['first_name' => 'Antonia', 'last_name' => 'D`souza']); | ||
$this->individualCreate(['first_name' => 'Anthony', 'last_name' => 'Collins']); | ||
|
||
$this->createTestEntity('OptionValue', [ | ||
'label' => '"Seamus Lee" <[email protected]>', | ||
'option_group_id:name' => 'from_email_address', | ||
$this->createTestEntity('DomainEmailAddress', [ | ||
'name' => 'Seamus Lee', | ||
'email' => '[email protected]', | ||
], 'aussie'); | ||
} | ||
|
||
|
@@ -40,8 +40,8 @@ protected function setUp(): void { | |
*/ | ||
public function tearDown(): void { | ||
Civi::settings()->set('allow_mail_from_logged_in_contact', 0); | ||
if (!empty($this->ids['OptionValue'])) { | ||
OptionValue::delete(FALSE)->addWhere('id', 'IN', $this->ids['OptionValue'])->execute(); | ||
if (!empty($this->ids['DomainEmailAddress'])) { | ||
DomainEmailAddress::delete(FALSE)->addWhere('id', 'IN', $this->ids['DomainEmailAddress'])->execute(); | ||
} | ||
parent::tearDown(); | ||
} | ||
|
@@ -52,11 +52,7 @@ public function tearDown(): void { | |
public function testDomainEmailGeneration(): void { | ||
$emails = CRM_Core_BAO_Email::domainEmails(); | ||
$this->assertNotEmpty($emails); | ||
$optionValue = $this->callAPISuccess('OptionValue', 'Get', [ | ||
'id' => $this->ids['OptionValue']['aussie'], | ||
]); | ||
$this->assertArrayHasKey('"Seamus Lee" <[email protected]>', $emails); | ||
$this->assertEquals('"Seamus Lee" <[email protected]>', $optionValue['values'][$this->ids['OptionValue']['aussie']]['label']); | ||
} | ||
|
||
/** | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -54,6 +54,9 @@ public function testHandlingForMultiDefaultOptions(): void { | |
* | ||
* The from_email_address supports a single default per domain. | ||
* | ||
* Note: This accesses the DomainEmailAddress entity through the OptionValue api, using the legacy adapter: | ||
* @see \Civi\API\Subscriber\DomainEmailLegacyOptionValueAdapter | ||
* | ||
* @throws \CRM_Core_Exception | ||
*/ | ||
public function testDefaultHandlingForFromEmailAddress(): void { | ||
|
@@ -63,8 +66,7 @@ public function testDefaultHandlingForFromEmailAddress(): void { | |
->setValues([ | ||
'option_group_id:name' => 'from_email_address', | ||
'is_default' => TRUE, | ||
'label' => '[email protected]', | ||
'name' => '[email protected]', | ||
'label' => '"Test Email" <[email protected]>', | ||
'value' => 3, | ||
]) | ||
->execute(); | ||
|
Oops, something went wrong.