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