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