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