Skip to content

Commit

Permalink
APIv4 - Allow write to contact primary and billing locations
Browse files Browse the repository at this point in the history
Provides symmetry with get operations, allowing email, phone, address & im to be
both read and written to within the contact api.
  • Loading branch information
colemanw committed Jul 9, 2022
1 parent 560d7b9 commit efa4df2
Show file tree
Hide file tree
Showing 4 changed files with 113 additions and 8 deletions.
50 changes: 49 additions & 1 deletion Civi/Api4/Action/Contact/ContactSaveTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,9 @@

namespace Civi\Api4\Action\Contact;

use Civi\Api4\Utils\CoreUtil;
use Civi\Api4\Utils\FormattingUtil;

/**
* Code shared by Contact create/update/save actions
*/
Expand Down Expand Up @@ -40,7 +43,52 @@ protected function write(array $items) {
}
}
}
return parent::write($items);
$saved = parent::write($items);
foreach ($items as $index => $item) {
self::saveLocations($item, $saved[$index]);
}
return $saved;
}

/**
* @param array $params
* @param \CRM_Contact_DAO_Contact $contact
*/
protected function saveLocations(array $params, $contact) {
foreach (['Address', 'Email', 'Phone', 'IM'] as $entity) {
foreach (['primary', 'billing'] as $type) {
$prefix = $type . '_' . strtolower($entity) . '.';
$item = FormattingUtil::filterByPrefix($params, $prefix . '*', '*');
// Not allowed to update by id or alter primary or billing flags
unset($item['id'], $item['is_primary'], $item['is_billing']);
if ($item) {
$labelField = CoreUtil::getInfoItem($entity, 'label_field');
// If NULL was given for the main field (e.g. `email`) then delete the record
if ($labelField && array_key_exists($labelField, $item) && is_null($item[$labelField])) {
civicrm_api4($entity, 'delete', [
'checkPermissions' => FALSE,
'where' => [
['contact_id', '=', $contact->id],
["is_$type", '=', TRUE],
],
]);
}
else {
$item['contact_id'] = $contact->id;
$item["is_$type"] = TRUE;
$saved = civicrm_api4($entity, 'save', [
'checkPermissions' => FALSE,
'records' => [$item],
'match' => ['contact_id', "is_$type"],
])->first();
foreach ($saved as $key => $value) {
$key = $prefix . $key;
$contact->$key = $value;
}
}
}
}
}
}

}
12 changes: 7 additions & 5 deletions Civi/Api4/Generic/Traits/DAOActionTrait.php
Original file line number Diff line number Diff line change
Expand Up @@ -52,13 +52,15 @@ protected function getBaoName() {
* @return array
*/
public function baoToArray($bao, $input) {
$allFields = array_column($bao->fields(), 'name');
$entityFields = array_column($bao->fields(), 'name');
$inputFields = array_map(function($key) {
return explode(':', $key)[0];
}, array_keys($input));
$combinedFields = array_unique(array_merge($entityFields, $inputFields));
if (!empty($this->reload)) {
$inputFields = $allFields;
$bao->find(TRUE);
}
else {
$inputFields = array_keys($input);
// Convert 'null' input to true null
foreach ($inputFields as $key) {
if (($bao->$key ?? NULL) === 'null') {
Expand All @@ -67,8 +69,8 @@ public function baoToArray($bao, $input) {
}
}
$values = [];
foreach ($allFields as $field) {
if (isset($bao->$field) || in_array($field, $inputFields)) {
foreach ($combinedFields as $field) {
if (isset($bao->$field) || in_array($field, $inputFields) || (!empty($this->reload) && in_array($field, $entityFields))) {
$values[$field] = $bao->$field ?? NULL;
}
}
Expand Down
4 changes: 2 additions & 2 deletions tests/phpunit/api/v4/Action/NullValueTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -29,10 +29,10 @@
*/
class NullValueTest extends Api4TestBase implements TransactionalInterface {

public function setUpHeadless() {
public function setUp(): void {
$format = '{contact.first_name}{ }{contact.last_name}';
\Civi::settings()->set('display_name_format', $format);
return parent::setUpHeadless();
parent::setUp();
}

public function testStringNull() {
Expand Down
55 changes: 55 additions & 0 deletions tests/phpunit/api/v4/Entity/ContactJoinTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,12 @@

namespace api\v4\Entity;

use Civi\Api4\Address;
use Civi\Api4\Contact;
use Civi\Api4\Email;
use Civi\Api4\OptionValue;
use api\v4\Api4TestBase;
use Civi\Api4\Phone;

/**
* @group headless
Expand Down Expand Up @@ -101,4 +104,56 @@ public function testJoinToPCMOptionValueWillShowLabel() {
$this->assertEquals($labels, $fetchedContact['preferred_communication_method:label']);
}

public function testCreateWithPrimaryAndBilling() {
$contact = $this->createTestRecord('Contact', [
'primary_email.email' => 'a@test.com',
'billing_email.email' => 'b@test.com',
'billing_address.city' => 'Hello',
'billing_address.state_province_id:abbr' => 'AK',
'billing_address.country_id:abbr' => 'USA',
]);
$addr = Address::get(FALSE)
->addWhere('contact_id', '=', $contact['id'])
->execute();
$this->assertCount(1, $addr);
$this->assertEquals('Hello', $contact['billing_address.city']);
$this->assertEquals(1228, $contact['billing_address.country_id']);
$emails = Email::get(FALSE)
->addWhere('contact_id', '=', $contact['id'])
->execute();
$this->assertCount(2, $emails);
$this->assertEquals('a@test.com', $contact['primary_email.email']);
$this->assertEquals('b@test.com', $contact['billing_email.email']);
}

public function testUpdateDeletePrimaryAndBilling() {
$contact = $this->createTestRecord('Contact', [
'primary_phone.phone' => '12345',
'billing_phone.phone' => '54321',
]);
Contact::update(FALSE)
->addValue('id', $contact['id'])
// Delete primary phone, update billing phone
->addValue('primary_phone.phone', NULL)
->addValue('billing_phone.phone', 99999)
->execute();
$phone = Phone::get(FALSE)
->addWhere('contact_id', '=', $contact['id'])
->execute()
->single();
$this->assertEquals('99999', $phone['phone']);
$this->assertTrue($phone['is_billing']);
// Contact only has one phone now, so it should be auto-set to primary
$this->assertTrue($phone['is_primary']);

$get = Contact::get(FALSE)
->addWhere('id', '=', $contact['id'])
->addSelect('primary_phone.*')
->addSelect('billing_phone.*')
->execute()->single();
$this->assertEquals('99999', $get['primary_phone.phone']);
$this->assertEquals('99999', $get['billing_phone.phone']);
$this->assertEquals($get['primary_phone.id'], $get['billing_phone.id']);
}

}

0 comments on commit efa4df2

Please sign in to comment.