Merge pull request #16837 from tunbola/case-api-case-clients
[civicrm-core.git] / CRM / Core / BAO / Block.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035 16 *
192d36c5 17 * Add static functions to include some common functionality used across location sub object BAO classes.
6a488035
TO
18 */
19class CRM_Core_BAO_Block {
20
21 /**
fe482240 22 * Fields that are required for a valid block.
518fa0ee 23 * @var array
6a488035 24 */
518fa0ee 25 public static $requiredBlockFields = [
be2fb01f
CW
26 'email' => ['email'],
27 'phone' => ['phone'],
28 'im' => ['name'],
29 'openid' => ['openid'],
30 ];
6a488035
TO
31
32 /**
33 * Given the list of params in the params array, fetch the object
34 * and store the values in the values array
35 *
6a0b768e
TO
36 * @param string $blockName
37 * Name of the above object.
38 * @param array $params
39 * Input parameters to find object.
77b97be7 40 *
a6c01b45 41 * @return array
16b10e64 42 * Array of $block objects.
6a488035 43 */
00be9182 44 public static function &getValues($blockName, $params) {
6a488035
TO
45 if (empty($params)) {
46 return NULL;
47 }
e52506b0 48 $BAOString = 'CRM_Core_BAO_' . $blockName;
481a74f4 49 $block = new $BAOString();
6a488035 50
be2fb01f 51 $blocks = [];
6a488035
TO
52 if (!isset($params['entity_table'])) {
53 $block->contact_id = $params['contact_id'];
54 if (!$block->contact_id) {
55 CRM_Core_Error::fatal();
56 }
57 $blocks = self::retrieveBlock($block, $blockName);
58 }
59 else {
60 $blockIds = self::getBlockIds($blockName, NULL, $params);
61
62 if (empty($blockIds)) {
63 return $blocks;
64 }
65
66 $count = 1;
67 foreach ($blockIds as $blockId) {
481a74f4 68 $block = new $BAOString();
353ffa53
TO
69 $block->id = $blockId['id'];
70 $getBlocks = self::retrieveBlock($block, $blockName);
6a488035
TO
71 $blocks[$count++] = array_pop($getBlocks);
72 }
73 }
74
75 return $blocks;
76 }
77
78 /**
79 * Given the list of params in the params array, fetch the object
80 * and store the values in the values array
81 *
6a0b768e
TO
82 * @param Object $block
83 * Typically a Phone|Email|IM|OpenID object.
84 * @param string $blockName
85 * Name of the above object.
77b97be7 86 *
a6c01b45 87 * @return array
16b10e64 88 * Array of $block objects.
6a488035 89 */
00be9182 90 public static function retrieveBlock(&$block, $blockName) {
6a488035
TO
91 // we first get the primary location due to the order by clause
92 $block->orderBy('is_primary desc, id');
93 $block->find();
94
95 $count = 1;
be2fb01f 96 $blocks = [];
6a488035
TO
97 while ($block->fetch()) {
98 CRM_Core_DAO::storeValues($block, $blocks[$count]);
99 //unset is_primary after first block. Due to some bug in earlier version
100 //there might be more than one primary blocks, hence unset is_primary other than first
101 if ($count > 1) {
102 unset($blocks[$count]['is_primary']);
103 }
104 $count++;
105 }
106
107 return $blocks;
108 }
109
110 /**
fe482240 111 * Check if the current block object has any valid data.
6a488035 112 *
6a0b768e
TO
113 * @param array $blockFields
114 * Array of fields that are of interest for this object.
115 * @param array $params
116 * Associated array of submitted fields.
6a488035 117 *
317fceb4 118 * @return bool
a6c01b45 119 * true if the block has data, otherwise false
6a488035 120 */
00be9182 121 public static function dataExists($blockFields, &$params) {
6a488035
TO
122 foreach ($blockFields as $field) {
123 if (CRM_Utils_System::isNull(CRM_Utils_Array::value($field, $params))) {
124 return FALSE;
125 }
126 }
127 return TRUE;
128 }
129
130 /**
fe482240 131 * Check if the current block exits.
6a488035 132 *
6a0b768e
TO
133 * @param string $blockName
134 * Bloack name.
135 * @param array $params
136 * Associated array of submitted fields.
6a488035 137 *
317fceb4 138 * @return bool
a6c01b45 139 * true if the block exits, otherwise false
6a488035 140 */
00be9182 141 public static function blockExists($blockName, &$params) {
6a488035 142 // return if no data present
a7488080 143 if (empty($params[$blockName]) || !is_array($params[$blockName])) {
6a488035
TO
144 return FALSE;
145 }
146
147 return TRUE;
148 }
149
150 /**
fe482240 151 * Get all block ids for a contact.
6a488035 152 *
6a0b768e
TO
153 * @param string $blockName
154 * Block name.
155 * @param int $contactId
156 * Contact id.
fd31fa4c
EM
157 *
158 * @param null $entityElements
159 * @param bool $updateBlankLocInfo
6a488035 160 *
a6c01b45
CW
161 * @return array
162 * formatted array of block ids
6a488035 163 *
6a488035 164 */
00be9182 165 public static function getBlockIds($blockName, $contactId = NULL, $entityElements = NULL, $updateBlankLocInfo = FALSE) {
be2fb01f 166 $allBlocks = [];
e4f5a5f9 167
6a488035
TO
168 $name = ucfirst($blockName);
169 if ($blockName == 'im') {
170 $name = 'IM';
171 }
172 elseif ($blockName == 'openid') {
173 $name = 'OpenID';
174 }
175
e4f5a5f9 176 $baoString = 'CRM_Core_BAO_' . $name;
6a488035 177 if ($contactId) {
e4f5a5f9 178 //@todo a cleverer way to do this would be to use the same fn name on each
179 // BAO rather than constructing the fn
180 // it would also be easier to grep for
181 // e.g $bao = new $baoString;
182 // $bao->getAllBlocks()
183 $baoFunction = 'all' . $name . 's';
481a74f4 184 $allBlocks = $baoString::$baoFunction($contactId, $updateBlankLocInfo);
6a488035
TO
185 }
186 elseif (!empty($entityElements) && $blockName != 'openid') {
e4f5a5f9 187 $baoFunction = 'allEntity' . $name . 's';
481a74f4 188 $allBlocks = $baoString::$baoFunction($entityElements);
6a488035
TO
189 }
190
191 return $allBlocks;
192 }
193
194 /**
fe482240 195 * Takes an associative array and creates a block.
6a488035 196 *
6a0b768e
TO
197 * @param string $blockName
198 * Block name.
199 * @param array $params
200 * (reference ) an assoc array of name/value pairs.
da6b46f4 201 * @param null $entity
100fef9d 202 * @param int $contactId
da6b46f4 203 *
a6c01b45
CW
204 * @return object
205 * CRM_Core_BAO_Block object on success, null otherwise
6a488035 206 */
00be9182 207 public static function create($blockName, &$params, $entity = NULL, $contactId = NULL) {
6a488035
TO
208 if (!self::blockExists($blockName, $params)) {
209 return NULL;
210 }
211
60fd90d8 212 $name = ucfirst($blockName);
60fd90d8 213 $isPrimary = $isBilling = TRUE;
be2fb01f 214 $entityElements = $blocks = [];
a3b489b9 215 $resetPrimaryId = NULL;
216 $primaryId = FALSE;
6a488035
TO
217
218 if ($entity) {
be2fb01f 219 $entityElements = [
6a488035
TO
220 'entity_table' => $params['entity_table'],
221 'entity_id' => $params['entity_id'],
be2fb01f 222 ];
6a488035
TO
223 }
224 else {
225 $contactId = $params['contact_id'];
226 }
227
60fd90d8 228 $updateBlankLocInfo = CRM_Utils_Array::value('updateBlankLocInfo', $params, FALSE);
a3b489b9 229 $isIdSet = CRM_Utils_Array::value('isIdSet', $params[$blockName], FALSE);
7f660ef3 230
85c882c5 231 //get existing block ids.
232 $blockIds = self::getBlockIds($blockName, $contactId, $entityElements);
a3b489b9 233 foreach ($params[$blockName] as $count => $value) {
9c1bc317 234 $blockId = $value['id'] ?? NULL;
a3b489b9 235 if ($blockId) {
236 if (is_array($blockIds) && array_key_exists($blockId, $blockIds)) {
237 unset($blockIds[$blockId]);
238 }
239 else {
240 unset($value['id']);
60fd90d8 241 }
a3b489b9 242 }
243 //lets allow to update primary w/ more cleanly.
244 if (!$resetPrimaryId && !empty($value['is_primary'])) {
245 $primaryId = TRUE;
246 if (is_array($blockIds)) {
247 foreach ($blockIds as $blockId => $blockValue) {
248 if (!empty($blockValue['is_primary'])) {
249 $resetPrimaryId = $blockId;
250 break;
60fd90d8
DG
251 }
252 }
a3b489b9 253 }
254 if ($resetPrimaryId) {
255 $baoString = 'CRM_Core_BAO_' . $blockName;
256 $block = new $baoString();
257 $block->selectAdd();
258 $block->selectAdd("id, is_primary");
259 $block->id = $resetPrimaryId;
260 if ($block->find(TRUE)) {
261 $block->is_primary = FALSE;
262 $block->save();
6a488035
TO
263 }
264 }
265 }
60fd90d8 266 }
6a488035 267
60fd90d8
DG
268 foreach ($params[$blockName] as $count => $value) {
269 if (!is_array($value)) {
270 continue;
6a488035 271 }
a3b489b9 272 // if in some cases (eg. email used in Online Conribution Page, Profiles, etc.) id is not set
85c882c5 273 // lets try to add using the previous method to avoid any false creation of existing data.
274 foreach ($blockIds as $blockId => $blockValue) {
a3b489b9 275 if (empty($value['id']) && $blockValue['locationTypeId'] == CRM_Utils_Array::value('location_type_id', $value) && !$isIdSet) {
276 $valueId = FALSE;
277 if ($blockName == 'phone') {
9c1bc317 278 $phoneTypeBlockValue = $blockValue['phoneTypeId'] ?? NULL;
a3b489b9 279 if ($phoneTypeBlockValue == CRM_Utils_Array::value('phone_type_id', $value)) {
280 $valueId = TRUE;
281 }
282 }
283 elseif ($blockName == 'im') {
9c1bc317 284 $providerBlockValue = $blockValue['providerId'] ?? NULL;
a3b489b9 285 if (!empty($value['provider_id']) && $providerBlockValue == $value['provider_id']) {
286 $valueId = TRUE;
d0a860ed 287 }
60fd90d8
DG
288 }
289 else {
a3b489b9 290 $valueId = TRUE;
291 }
292 if ($valueId) {
293 $value['id'] = $blockValue['id'];
294 if (!$primaryId && !empty($blockValue['is_primary'])) {
295 $value['is_primary'] = $blockValue['is_primary'];
6a488035 296 }
a3b489b9 297 break;
6a488035
TO
298 }
299 }
300 }
85c882c5 301 $dataExists = self::dataExists(self::$requiredBlockFields[$blockName], $value);
60fd90d8
DG
302 // Note there could be cases when block info already exist ($value[id] is set) for a contact/entity
303 // BUT info is not present at this time, and therefore we should be really careful when deleting the block.
304 // $updateBlankLocInfo will help take appropriate decision. CRM-5969
85c882c5 305 if (!empty($value['id']) && !$dataExists && $updateBlankLocInfo) {
60fd90d8 306 //delete the existing record
be2fb01f 307 self::blockDelete($blockName, ['id' => $value['id']]);
60fd90d8
DG
308 continue;
309 }
85c882c5 310 elseif (!$dataExists) {
60fd90d8 311 continue;
6a488035 312 }
be2fb01f 313 $contactFields = [
85c882c5 314 'contact_id' => $contactId,
6b409353 315 'location_type_id' => $value['location_type_id'] ?? NULL,
be2fb01f 316 ];
6a488035 317
a3b489b9 318 $contactFields['is_primary'] = 0;
60fd90d8
DG
319 if ($isPrimary && !empty($value['is_primary'])) {
320 $contactFields['is_primary'] = $value['is_primary'];
321 $isPrimary = FALSE;
322 }
7f660ef3 323
a3b489b9 324 $contactFields['is_billing'] = 0;
60fd90d8
DG
325 if ($isBilling && !empty($value['is_billing'])) {
326 $contactFields['is_billing'] = $value['is_billing'];
327 $isBilling = FALSE;
328 }
6a488035 329
60fd90d8 330 $blockFields = array_merge($value, $contactFields);
12e2ccdc 331 if ($name === 'Email') {
332 // @todo ideally all would call the api which is our main tested function,
333 // and towards that call the create rather than add which is preferred by the
334 // api. In order to be careful with change only email is swapped over here because it
335 // is specifically tested in testImportParserWithUpdateWithContactID
336 // and the primary handling is otherwise bypassed on importing an email update.
337 $blocks[] = CRM_Core_BAO_Email::create($blockFields);
338 }
339 else {
340 $baoString = 'CRM_Core_BAO_' . $name;
341 $blocks[] = $baoString::add($blockFields);
342 }
60fd90d8
DG
343 }
344
6a488035
TO
345 return $blocks;
346 }
347
348 /**
fe482240 349 * Delete block.
6a488035 350 *
6a0b768e
TO
351 * @param string $blockName
352 * Block name.
353 * @param int $params
354 * Associates array.
6a488035 355 */
00be9182 356 public static function blockDelete($blockName, $params) {
6a488035
TO
357 $name = ucfirst($blockName);
358 if ($blockName == 'im') {
359 $name = 'IM';
360 }
361 elseif ($blockName == 'openid') {
362 $name = 'OpenID';
363 }
364
e4f5a5f9 365 $baoString = 'CRM_Core_DAO_' . $name;
481a74f4 366 $block = new $baoString();
6a488035
TO
367
368 $block->copyValues($params);
d424ffde
CW
369
370 // CRM-11006 add call to pre and post hook for delete action
6a488035
TO
371 CRM_Utils_Hook::pre('delete', $name, $block->id, CRM_Core_DAO::$_nullArray);
372 $block->delete();
373 CRM_Utils_Hook::post('delete', $name, $block->id, $block);
374 }
375
376 /**
377 * Handling for is_primary.
378 * $params is_primary could be
379 * # 1 - find other entries with is_primary = 1 & reset them to 0
380 * # 0 - make sure at least one entry is set to 1
381 * - if no other entry is 1 change to 1
382 * - if one other entry exists change that to 1
383 * - if more than one other entry exists change first one to 1
77b97be7 384 * @fixme - perhaps should choose by location_type
6a488035
TO
385 * # empty - same as 0 as once we have checked first step
386 * we know if it should be 1 or 0
387 *
388 * if $params['id'] is set $params['contact_id'] may need to be retrieved
389 *
77b97be7
EM
390 * @param array $params
391 * @param $class
392 *
393 * @throws API_Exception
6a488035
TO
394 */
395 public static function handlePrimary(&$params, $class) {
37a77d9c
TO
396 $table = CRM_Core_DAO_AllCoreTables::getTableForClass($class);
397 if (!$table) {
398 throw new API_Exception("Failed to locate table for class [$class]");
399 }
d195b60c 400
50fa40e8 401 // contact_id in params might be empty or the string 'null' so cast to integer
2975f0aa 402 $contactId = (int) ($params['contact_id'] ?? 0);
50fa40e8
CW
403 // If id is set & we haven't been passed a contact_id, retrieve it
404 if (!empty($params['id']) && !isset($params['contact_id'])) {
6a488035
TO
405 $entity = new $class();
406 $entity->id = $params['id'];
407 $entity->find(TRUE);
408 $contactId = $entity->contact_id;
409 }
50fa40e8
CW
410 // If entity is not associated with contact, concept of is_primary not relevant
411 if (!$contactId) {
6a488035
TO
412 return;
413 }
414
415 // if params is_primary then set all others to not be primary & exit out
57f8e7f0 416 // if is_primary = 1
a7488080 417 if (!empty($params['is_primary'])) {
6a488035 418 $sql = "UPDATE $table SET is_primary = 0 WHERE contact_id = %1";
be2fb01f 419 $sqlParams = [1 => [$contactId, 'Integer']];
b44e3f84 420 // we don't want to create unnecessary entries in the log_ tables so exclude the one we are working on
9b873358 421 if (!empty($params['id'])) {
6a488035 422 $sql .= " AND id <> %2";
be2fb01f 423 $sqlParams[2] = [$params['id'], 'Integer'];
6a488035
TO
424 }
425 CRM_Core_DAO::executeQuery($sql, $sqlParams);
426 return;
427 }
428
429 //Check what other emails exist for the contact
430 $existingEntities = new $class();
431 $existingEntities->contact_id = $contactId;
432 $existingEntities->orderBy('is_primary DESC');
433 if (!$existingEntities->find(TRUE) || (!empty($params['id']) && $existingEntities->id == $params['id'])) {
434 // ie. if no others is set to be primary then this has to be primary set to 1 so change
435 $params['is_primary'] = 1;
436 return;
437 }
438 else {
439 /*
440 * If the only existing email is the one we are editing then we must set
441 * is_primary to 1
442 * CRM-10451
443 */
481a74f4 444 if ($existingEntities->N == 1 && $existingEntities->id == CRM_Utils_Array::value('id', $params)) {
6a488035
TO
445 $params['is_primary'] = 1;
446 return;
447 }
448
449 if ($existingEntities->is_primary == 1) {
450 return;
451 }
452 // so at this point we are only dealing with ones explicity setting is_primary to 0
453 // since we have reverse sorted by email we can either set the first one to
454 // primary or return if is already is
455 $existingEntities->is_primary = 1;
456 $existingEntities->save();
457 }
458 }
459
460 /**
fe482240 461 * Sort location array so primary element is first.
2a6da8d7 462 *
c490a46a 463 * @param array $locations
6a488035 464 */
9b873358 465 public static function sortPrimaryFirst(&$locations) {
6a488035
TO
466 uasort($locations, 'self::primaryComparison');
467 }
468
2aa397bc
TO
469 /**
470 * compare 2 locations to see which should go first based on is_primary
471 * (sort function for sortPrimaryFirst)
472 * @param array $location1
473 * @param array $location2
317fceb4 474 * @return int
2aa397bc 475 */
9b873358 476 public static function primaryComparison($location1, $location2) {
9c1bc317
CW
477 $l1 = $location1['is_primary'] ?? NULL;
478 $l2 = $location2['is_primary'] ?? NULL;
6a488035
TO
479 if ($l1 == $l2) {
480 return 0;
481 }
482 return ($l1 < $l2) ? -1 : 1;
483 }
96025800 484
6a488035 485}