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