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