Provides symmetry with get operations, allowing email, phone, address & im to be
both read and written to within the contact api.
namespace Civi\Api4\Action\Contact;
+use Civi\Api4\Utils\CoreUtil;
+use Civi\Api4\Utils\FormattingUtil;
+
/**
* Code shared by Contact create/update/save actions
*/
}
}
}
- 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 = strtolower($entity) . '_' . $type . '.';
+ $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;
+ }
+ }
+ }
+ }
+ }
}
}
* @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') {
}
}
$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;
}
}
*/
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() {
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
$this->assertEquals($labels, $fetchedContact['preferred_communication_method:label']);
}
+ public function testCreateWithPrimaryAndBilling() {
+ $contact = $this->createTestRecord('Contact', [
+ 'email_primary.email' => 'a@test.com',
+ 'email_billing.email' => 'b@test.com',
+ 'address_billing.city' => 'Hello',
+ 'address_billing.state_province_id:abbr' => 'AK',
+ 'address_billing.country_id:abbr' => 'USA',
+ ]);
+ $addr = Address::get(FALSE)
+ ->addWhere('contact_id', '=', $contact['id'])
+ ->execute();
+ $this->assertCount(1, $addr);
+ $this->assertEquals('Hello', $contact['address_billing.city']);
+ $this->assertEquals(1228, $contact['address_billing.country_id']);
+ $emails = Email::get(FALSE)
+ ->addWhere('contact_id', '=', $contact['id'])
+ ->execute();
+ $this->assertCount(2, $emails);
+ $this->assertEquals('a@test.com', $contact['email_primary.email']);
+ $this->assertEquals('b@test.com', $contact['email_billing.email']);
+ }
+
+ public function testUpdateDeletePrimaryAndBilling() {
+ $contact = $this->createTestRecord('Contact', [
+ 'phone_primary.phone' => '12345',
+ 'phone_billing.phone' => '54321',
+ ]);
+ Contact::update(FALSE)
+ ->addValue('id', $contact['id'])
+ // Delete primary phone, update billing phone
+ ->addValue('phone_primary.phone', NULL)
+ ->addValue('phone_billing.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('phone_primary.*')
+ ->addSelect('phone_billing.*')
+ ->execute()->single();
+ $this->assertEquals('99999', $get['phone_primary.phone']);
+ $this->assertEquals('99999', $get['phone_billing.phone']);
+ $this->assertEquals($get['phone_primary.id'], $get['phone_billing.id']);
+ }
+
}