Commit 30ce03ec authored by colemanw's avatar colemanw Committed by GitHub

Merge pull request #11019 from DanielvV/CRM-21214

CRM-21214 - Fix address sharing
parents e0b780c5 68499476
......@@ -800,7 +800,7 @@ INNER JOIN civicrm_contact contact_target ON ( contact_target.id = act.contact_i
* Function handles shared contact address processing.
* In this function we just modify submitted values so that new address created for the user
* has same address as shared contact address. We copy the address so that search etc will be
* much efficient.
* much more efficient.
*
* @param array $address
* This is associated array which contains submitted form values.
......@@ -810,20 +810,20 @@ INNER JOIN civicrm_contact contact_target ON ( contact_target.id = act.contact_i
return;
}
// Sharing contact address during create mode is pretty straight forward.
// In update mode we should check following:
// - We should check if user has uncheck shared contact address
// - If yes then unset the master_id or may be just delete the address that copied master
// Normal update process will automatically create new address with submitted values
// In create mode sharing a contact's address is pretty straight forward.
// In update mode we should check if the user stops sharing. If yes:
// - Set the master_id to an empty value
// Normal update process will automatically create new address with submitted values
// 1. loop through entire subnitted address array
$masterAddress = array();
// 1. loop through entire submitted address array
$skipFields = array('is_primary', 'location_type_id', 'is_billing', 'master_id');
foreach ($address as & $values) {
// 2. check if master id exists, if not continue
if (empty($values['master_id']) || empty($values['use_shared_address'])) {
// we should unset master id when use uncheck share address for existing address
$values['master_id'] = 'null';
// 2. check if "Use another contact's address" is checked, if not continue
// Additionally, if master_id is set (address was shared), set master_id to empty value.
if (empty($values['use_shared_address'])) {
if (!empty($values['master_id'])) {
$values['master_id'] = '';
}
continue;
}
......
......@@ -152,6 +152,11 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
CRM_Core_BAO_Block::handlePrimary($params, get_class());
}
// (prevent chaining 1 and 3) CRM-21214
if (CRM_Utils_Array::value('master_id', $params)) {
self::fixSharedAddress($params);
}
$address->copyValues($params);
$address->save();
......@@ -171,15 +176,11 @@ class CRM_Core_BAO_Address extends CRM_Core_DAO_Address {
CRM_Core_BAO_CustomValueTable::store($addressCustom, 'civicrm_address', $address->id);
}
//call the function to sync shared address
// call the function to sync shared address and create relationships
// if address is already shared, share master_id with all children and update relationships accordingly
// (prevent chaining 2) CRM-21214
self::processSharedAddress($address->id, $params);
// call the function to create shared relationships
// we only create create relationship if address is shared by Individual
if (!CRM_Utils_System::isNull($address->master_id)) {
self::processSharedAddressRelationship($address->master_id, $params);
}
// lets call the post hook only after we've done all the follow on processing
CRM_Utils_Hook::post($hook, 'Address', $address->id, $address);
}
......@@ -995,6 +996,27 @@ SELECT is_primary,
}
}
/**
* Fix the shared address if address is already shared
* or if address will be shared with itself.
*
* @param array $params
* Associated array of address params.
*/
public static function fixSharedAddress(&$params) {
// if address master address is shared, use its master (prevent chaining 1) CRM-21214
$masterMasterId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_Address', $params['master_id'], 'master_id');
if ($masterMasterId > 0) {
$params['master_id'] = $masterMasterId;
}
// prevent an endless chain between two shared addresses (prevent chaining 3) CRM-21214
if (CRM_Utils_Array::value('id', $params) == $params['master_id']) {
$params['master_id'] = NULL;
CRM_Core_Session::setStatus(ts("You can't connect an address to itself"), '', 'warning');
}
}
/**
* Update the shared addresses if master address is modified.
*
......@@ -1004,17 +1026,29 @@ SELECT is_primary,
* Associated array of address params.
*/
public static function processSharedAddress($addressId, $params) {
$query = 'SELECT id FROM civicrm_address WHERE master_id = %1';
$query = 'SELECT id, contact_id FROM civicrm_address WHERE master_id = %1';
$dao = CRM_Core_DAO::executeQuery($query, array(1 => array($addressId, 'Integer')));
// unset contact id
$skipFields = array('is_primary', 'location_type_id', 'is_billing', 'master_id', 'contact_id');
$skipFields = array('is_primary', 'location_type_id', 'is_billing', 'contact_id');
if (CRM_Utils_Array::value('master_id', $params)) {
// call the function to create a relationship for the new shared address
self::processSharedAddressRelationship($params['master_id'], $params['contact_id']);
}
else {
// else no new shares will be created, only update shared addresses
$skipFields[] = 'master_id';
}
foreach ($skipFields as $value) {
unset($params[$value]);
}
$addressDAO = new CRM_Core_DAO_Address();
while ($dao->fetch()) {
// call the function to update the relationship
if (CRM_Utils_Array::value('master_id', $params)) {
self::processSharedAddressRelationship($params['master_id'], $dao->contact_id);
}
$addressDAO->copyValues($params);
$addressDAO->id = $dao->id;
$addressDAO->save();
......@@ -1098,18 +1132,17 @@ SELECT is_primary,
/**
* Create relationship between contacts who share an address.
*
* Note that currently we create relationship only for Individual contacts
* Individual + Household and Individual + Orgnization
* Note that currently we create relationship between
* Individual + Household and Individual + Organization
*
* @param int $masterAddressId
* Master address id.
* @param array $params
* Associated array of submitted values.
* @param int $currentContactId
* Current contact id.
*/
public static function processSharedAddressRelationship($masterAddressId, $params) {
public static function processSharedAddressRelationship($masterAddressId, $currentContactId) {
// get the contact type of contact being edited / created
$currentContactType = CRM_Contact_BAO_Contact::getContactType($params['contact_id']);
$currentContactId = $params['contact_id'];
$currentContactType = CRM_Contact_BAO_Contact::getContactType($currentContactId);
// if current contact is not of type individual return
if ($currentContactType != 'Individual') {
......@@ -1125,8 +1158,7 @@ SELECT is_primary,
$dao = CRM_Core_DAO::executeQuery($query, array(1 => array($masterAddressId, 'Integer')));
$dao->fetch();
// if current contact is not of type individual return, since we don't create relationship between
// 2 individuals
// master address contact needs to be Household or Organization, otherwise return
if ($dao->contact_type == 'Individual') {
return;
}
......
......@@ -330,4 +330,165 @@ class CRM_Core_BAO_AddressTest extends CiviUnitTestCase {
$this->assertNotContains('street_number_suffix', $parsedStreetAddress);
}
/**
* CRM-21214 - Ensure all child addresses are updated correctly - 1.
* 1. First, create three contacts: A, B, and C
* 2. Create an address for contact A
* 3. Use contact A's address for contact B
* 4. Use contact B's address for contact C
* 5. Change contact A's address
* Address of Contact C should reflect contact A's address change
* Also, Contact C's address' master_id should be Contact A's address id.
*/
public function testSharedAddressChaining1() {
$contactIdA = $this->individualCreate(array(), 0);
$contactIdB = $this->individualCreate(array(), 1);
$contactIdC = $this->individualCreate(array(), 2);
$addressParamsA = array(
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'contact_id' => $contactIdA,
);
$addAddressA = CRM_Core_BAO_Address::add($addressParamsA, FALSE);
$addressParamsB = array(
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'master_id' => $addAddressA->id,
'contact_id' => $contactIdB,
);
$addAddressB = CRM_Core_BAO_Address::add($addressParamsB, FALSE);
$addressParamsC = array(
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'master_id' => $addAddressB->id,
'contact_id' => $contactIdC,
);
$addAddressC = CRM_Core_BAO_Address::add($addressParamsC, FALSE);
$updatedAddressParamsA = array(
'id' => $addAddressA->id,
'street_address' => '1313 New Address Lane',
'location_type_id' => '1',
'is_primary' => '1',
'contact_id' => $contactIdA,
);
$updatedAddressA = CRM_Core_BAO_Address::add($updatedAddressParamsA, FALSE);
// CRM-21214 - Has Address C been updated with Address A's new values?
$newAddressC = new CRM_Core_DAO_Address();
$newAddressC->id = $addAddressC->id;
$newAddressC->find(TRUE);
$newAddressC->fetch(TRUE);
$this->assertEquals($updatedAddressA->street_address, $newAddressC->street_address);
$this->assertEquals($updatedAddressA->id, $newAddressC->master_id);
}
/**
* CRM-21214 - Ensure all child addresses are updated correctly - 2.
* 1. First, create three contacts: A, B, and C
* 2. Create an address for contact A and B
* 3. Use contact A's address for contact C
* 4. Use contact B's address for contact A
* 5. Change contact B's address
* Address of Contact C should reflect contact B's address change
* Also, Contact C's address' master_id should be Contact B's address id.
*/
public function testSharedAddressChaining2() {
$contactIdA = $this->individualCreate(array(), 0);
$contactIdB = $this->individualCreate(array(), 1);
$contactIdC = $this->individualCreate(array(), 2);
$addressParamsA = array(
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'contact_id' => $contactIdA,
);
$addAddressA = CRM_Core_BAO_Address::add($addressParamsA, FALSE);
$addressParamsB = array(
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'contact_id' => $contactIdB,
);
$addAddressB = CRM_Core_BAO_Address::add($addressParamsB, FALSE);
$addressParamsC = array(
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'master_id' => $addAddressA->id,
'contact_id' => $contactIdC,
);
$addAddressC = CRM_Core_BAO_Address::add($addressParamsC, FALSE);
$updatedAddressParamsA = array(
'id' => $addAddressA->id,
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'master_id' => $addAddressB->id,
'contact_id' => $contactIdA,
);
$updatedAddressA = CRM_Core_BAO_Address::add($updatedAddressParamsA, FALSE);
$updatedAddressParamsB = array(
'id' => $addAddressB->id,
'street_address' => '1313 New Address Lane',
'location_type_id' => '1',
'is_primary' => '1',
'contact_id' => $contactIdB,
);
$updatedAddressB = CRM_Core_BAO_Address::add($updatedAddressParamsB, FALSE);
// CRM-21214 - Has Address C been updated with Address B's new values?
$newAddressC = new CRM_Core_DAO_Address();
$newAddressC->id = $addAddressC->id;
$newAddressC->find(TRUE);
$newAddressC->fetch(TRUE);
$this->assertEquals($updatedAddressB->street_address, $newAddressC->street_address);
$this->assertEquals($updatedAddressB->id, $newAddressC->master_id);
}
/**
* CRM-21214 - Ensure all child addresses are updated correctly - 3.
* 1. First, create a contact: A
* 2. Create an address for contact A
* 3. Use contact A's address for contact A's address
* An error should be given, and master_id should remain the same.
*/
public function testSharedAddressChaining3() {
$contactIdA = $this->individualCreate(array(), 0);
$addressParamsA = array(
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'contact_id' => $contactIdA,
);
$addAddressA = CRM_Core_BAO_Address::add($addressParamsA, FALSE);
$updatedAddressParamsA = array(
'id' => $addAddressA->id,
'street_address' => '123 Fake St.',
'location_type_id' => '1',
'is_primary' => '1',
'master_id' => $addAddressA->id,
'contact_id' => $contactIdA,
);
$updatedAddressA = CRM_Core_BAO_Address::add($updatedAddressParamsA, FALSE);
// CRM-21214 - AdressA shouldn't be master of itself.
$this->assertEmpty($updatedAddressA->master_id);
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment