CRM-10551: Fix code style, use more semantic variable names
[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 */
62 public static function &getValues($blockName, $params) {
63 if (empty($params)) {
64 return NULL;
65 }
66 $BAOString = 'CRM_Core_BAO_' . $blockName;
67 $block = new $BAOString();
68
69 $blocks = array();
70 if (!isset($params['entity_table'])) {
71 $block->contact_id = $params['contact_id'];
72 if (!$block->contact_id) {
73 CRM_Core_Error::fatal();
74 }
75 $blocks = self::retrieveBlock($block, $blockName);
76 }
77 else {
78 $blockIds = self::getBlockIds($blockName, NULL, $params);
79
80 if (empty($blockIds)) {
81 return $blocks;
82 }
83
84 $count = 1;
85 foreach ($blockIds as $blockId) {
86 $block = new $BAOString();
87 $block->id = $blockId['id'];
88 $getBlocks = self::retrieveBlock($block, $blockName);
89 $blocks[$count++] = array_pop($getBlocks);
90 }
91 }
92
93 return $blocks;
94 }
95
96 /**
97 * Given the list of params in the params array, fetch the object
98 * and store the values in the values array
99 *
100 * @param Object $block
101 * Typically a Phone|Email|IM|OpenID object.
102 * @param string $blockName
103 * Name of the above object.
104 *
105 * @return array
106 * Array of $block objects.
107 */
108 public static function retrieveBlock(&$block, $blockName) {
109 // we first get the primary location due to the order by clause
110 $block->orderBy('is_primary desc, id');
111 $block->find();
112
113 $count = 1;
114 $blocks = array();
115 while ($block->fetch()) {
116 CRM_Core_DAO::storeValues($block, $blocks[$count]);
117 //unset is_primary after first block. Due to some bug in earlier version
118 //there might be more than one primary blocks, hence unset is_primary other than first
119 if ($count > 1) {
120 unset($blocks[$count]['is_primary']);
121 }
122 $count++;
123 }
124
125 return $blocks;
126 }
127
128 /**
129 * Check if the current block object has any valid data.
130 *
131 * @param array $blockFields
132 * Array of fields that are of interest for this object.
133 * @param array $params
134 * Associated array of submitted fields.
135 *
136 * @return bool
137 * true if the block has data, otherwise false
138 */
139 public 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
152 * Bloack name.
153 * @param array $params
154 * Associated array of submitted fields.
155 *
156 * @return bool
157 * true if the block exits, otherwise false
158 */
159 public static function blockExists($blockName, &$params) {
160 // return if no data present
161 if (empty($params[$blockName]) || !is_array($params[$blockName])) {
162 return FALSE;
163 }
164
165 return TRUE;
166 }
167
168 /**
169 * Get all block ids for a contact.
170 *
171 * @param string $blockName
172 * Block name.
173 * @param int $contactId
174 * Contact id.
175 *
176 * @param null $entityElements
177 * @param bool $updateBlankLocInfo
178 *
179 * @return array
180 * formatted array of block ids
181 *
182 */
183 public static function getBlockIds($blockName, $contactId = NULL, $entityElements = NULL, $updateBlankLocInfo = FALSE) {
184 $allBlocks = array();
185
186 $name = ucfirst($blockName);
187 if ($blockName == 'im') {
188 $name = 'IM';
189 }
190 elseif ($blockName == 'openid') {
191 $name = 'OpenID';
192 }
193
194 $baoString = 'CRM_Core_BAO_' . $name;
195 if ($contactId) {
196 //@todo a cleverer way to do this would be to use the same fn name on each
197 // BAO rather than constructing the fn
198 // it would also be easier to grep for
199 // e.g $bao = new $baoString;
200 // $bao->getAllBlocks()
201 $baoFunction = 'all' . $name . 's';
202 $allBlocks = $baoString::$baoFunction($contactId, $updateBlankLocInfo);
203 }
204 elseif (!empty($entityElements) && $blockName != 'openid') {
205 $baoFunction = 'allEntity' . $name . 's';
206 $allBlocks = $baoString::$baoFunction($entityElements);
207 }
208
209 return $allBlocks;
210 }
211
212 /**
213 * Takes an associative array and creates a block.
214 *
215 * @param string $blockName
216 * Block name.
217 * @param array $params
218 * (reference ) an assoc array of name/value pairs.
219 * @param null $entity
220 * @param int $contactId
221 *
222 * @return object
223 * CRM_Core_BAO_Block object on success, null otherwise
224 */
225 public static function create($blockName, &$params, $entity = NULL, $contactId = NULL) {
226
227 // @todo Consistant variable names, eg: locationTypeId / location_type_id
228
229 if (!self::blockExists($blockName, $params)) {
230 return NULL;
231 }
232
233 // Set up required information / defaults
234 $entityElements = $blocks = array();
235 $reset_primary = $primary_set = $billing_set = FALSE;
236 $contact_id = NULL;
237
238 $bao_string = 'CRM_Core_BAO_' . ucfirst($blockName);
239 $updateBlankLocInfo = CRM_Utils_Array::value('updateBlankLocInfo', $params, FALSE);
240
241 if ($entity) {
242 $entityElements = array(
243 'entity_table' => $params['entity_table'],
244 'entity_id' => $params['entity_id'],
245 );
246 }
247 else {
248 $contact_id = $params['contact_id'];
249 }
250
251 // Get current and submitted values
252 $existing_values = self::getBlockIds($blockName, $contact_id, $entityElements, $updateBlankLocInfo);
253 $submitted_values = $params[$blockName];
254
255 // For each submitted value
256 foreach ($submitted_values as $count => $submitted_value) {
257
258 // Set the contact ID
259 $submitted_value['contact_id'] = $contact_id;
260
261 // If this is a primary value, and we haven't unset a primary value yet, and there are values on the contact
262 // Then unset any primary value currently on the Contact
263 if (!empty($submitted_value['is_primary']) && !$reset_primary && is_array($existing_values)) {
264 foreach ($existing_values as $existing_value_id => $existing_value) {
265 if (!empty($existing_value['is_primary'])) {
266
267 // @todo Can we refactor this?
268 $block = new $bao_string();
269 $block->selectAdd();
270 $block->selectAdd("id, is_primary");
271 $block->id = $existing_value['id'];
272 if ($block->find(TRUE)) {
273 $block->is_primary = FALSE;
274 $block->save();
275 }
276 $block->free();
277
278 // Stop looping since we found a match
279 $reset_primary = TRUE;
280 break;
281 }
282 }
283 }
284
285 // If there is already an ID passed in
286 if (!empty($submitted_value['id'])) {
287 // If the ID already exists on the contact
288 // Then we don't want to match on it later, so unset it
289 if (array_key_exists($submitted_value['id'], $existing_values)) {
290 unset($existing_values[$existing_value_id]);
291 }
292 // Otherwise it is a new value, ignore the passed in ID
293 else {
294 unset($submitted_value['id']);
295 }
296 }
297
298 // Otherwise, if there was no ID passed in
299 // Loop through the current values, and find the first match on location type
300 else {
301 foreach ($existing_values as $existing_value_id => $existing_value) {
302 if ($existing_value['locationTypeId'] == $submitted_value['location_type_id']) {
303
304 // Also require a match on 'type id' for phone and IM blocks
305 $match_found = FALSE;
306
307 if ($blockName == 'phone') {
308 if (CRM_Utils_Array::value('phoneTypeId', $existing_value) == CRM_Utils_Array::value('phone_type_id', $submitted_value)) {
309 $match_found = TRUE;
310 }
311 }
312 elseif ($blockName == 'im') {
313 if (CRM_Utils_Array::value('providerId', $existing_value) == CRM_Utils_Array::value('provider_id', $submitted_value)) {
314 $match_found = TRUE;
315 }
316 }
317 else {
318 $match_found = TRUE;
319 }
320
321 // If we found a match
322 if ($match_found) {
323 // Match up the ID
324 $submitted_value['id'] = $existing_value['id'];
325 // If the submitted value is not primary, but the matched value is
326 // Then set the submitted value to be primary
327 if (empty($submitted_value['is_primary']) && !empty($existing_value['is_primary'])) {
328 $submitted_value['is_primary'] = 1;
329 }
330 // Remove the original value from the array so we don't match on it again
331 unset($existing_values[$existing_value_id]);
332 break;
333 }
334 }
335 }
336 }
337
338 // Check if data exists in the input
339 $data_exists = self::dataExists(self::$requiredBlockFields[$blockName], $submitted_value);
340
341 // If there is data
342 if ($data_exists) {
343
344 // Ensure there is only one primary / billing block
345 // "There can be only one"
346 if (!$primary_set && !empty($submitted_value['is_primary'])) {
347 $submitted_value['is_primary'] = 1;
348 $primary_set = TRUE;
349 }
350 else {
351 $contactFields['is_primary'] = 0;
352 }
353
354 if (!$billing_set && !empty($submitted_value['is_billing'])) {
355 $submitted_value['is_billing'] = 1;
356 $billing_set = TRUE;
357 }
358 else {
359 $contactFields['is_billing'] = 0;
360 }
361
362 // Add the value to the list of blocks
363 $blocks[] = $bao_string::add($submitted_value);
364 }
365
366 // Otherwise, if there is no data, and there is an ID, and we are deleting 'blanked' values
367 // Then delete it
368 elseif (!empty($submitted_value['id']) && $updateBlankLocInfo) {
369 self::blockDelete($blockName, array('id' => $submitted_value['id']));
370 }
371
372 // Otherwise we ignore it
373 else {
374 }
375
376 }
377
378 return $blocks;
379 }
380
381 /**
382 * Delete block.
383 *
384 * @param string $blockName
385 * Block name.
386 * @param int $params
387 * Associates array.
388 *
389 * @return void
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 CRM_Utils_Hook::pre('delete', $name, $block->id, CRM_Core_DAO::$_nullArray);
407 $block->delete();
408 CRM_Utils_Hook::post('delete', $name, $block->id, $block);
409 }
410
411 /**
412 * Handling for is_primary.
413 * $params is_primary could be
414 * # 1 - find other entries with is_primary = 1 & reset them to 0
415 * # 0 - make sure at least one entry is set to 1
416 * - if no other entry is 1 change to 1
417 * - if one other entry exists change that to 1
418 * - if more than one other entry exists change first one to 1
419 * @fixme - perhaps should choose by location_type
420 * # empty - same as 0 as once we have checked first step
421 * we know if it should be 1 or 0
422 *
423 * if $params['id'] is set $params['contact_id'] may need to be retrieved
424 *
425 * @param array $params
426 * @param $class
427 *
428 * @throws API_Exception
429 */
430 public static function handlePrimary(&$params, $class) {
431 $table = CRM_Core_DAO_AllCoreTables::getTableForClass($class);
432 if (!$table) {
433 throw new API_Exception("Failed to locate table for class [$class]");
434 }
435
436 // contact_id in params might be empty or the string 'null' so cast to integer
437 $contactId = (int) CRM_Utils_Array::value('contact_id', $params);
438 // If id is set & we haven't been passed a contact_id, retrieve it
439 if (!empty($params['id']) && !isset($params['contact_id'])) {
440 $entity = new $class();
441 $entity->id = $params['id'];
442 $entity->find(TRUE);
443 $contactId = $entity->contact_id;
444 }
445 // If entity is not associated with contact, concept of is_primary not relevant
446 if (!$contactId) {
447 return;
448 }
449
450 // if params is_primary then set all others to not be primary & exit out
451 if (!empty($params['is_primary'])) {
452 $sql = "UPDATE $table SET is_primary = 0 WHERE contact_id = %1";
453 $sqlParams = array(1 => array($contactId, 'Integer'));
454 // we don't want to create unecessary entries in the log_ tables so exclude the one we are working on
455 if (!empty($params['id'])) {
456 $sql .= " AND id <> %2";
457 $sqlParams[2] = array($params['id'], 'Integer');
458 }
459 CRM_Core_DAO::executeQuery($sql, $sqlParams);
460 return;
461 }
462
463 //Check what other emails exist for the contact
464 $existingEntities = new $class();
465 $existingEntities->contact_id = $contactId;
466 $existingEntities->orderBy('is_primary DESC');
467 if (!$existingEntities->find(TRUE) || (!empty($params['id']) && $existingEntities->id == $params['id'])) {
468 // ie. if no others is set to be primary then this has to be primary set to 1 so change
469 $params['is_primary'] = 1;
470 return;
471 }
472 else {
473 /*
474 * If the only existing email is the one we are editing then we must set
475 * is_primary to 1
476 * CRM-10451
477 */
478 if ($existingEntities->N == 1 && $existingEntities->id == CRM_Utils_Array::value('id', $params)) {
479 $params['is_primary'] = 1;
480 return;
481 }
482
483 if ($existingEntities->is_primary == 1) {
484 return;
485 }
486 // so at this point we are only dealing with ones explicity setting is_primary to 0
487 // since we have reverse sorted by email we can either set the first one to
488 // primary or return if is already is
489 $existingEntities->is_primary = 1;
490 $existingEntities->save();
491 }
492 }
493
494 /**
495 * Sort location array so primary element is first.
496 *
497 * @param array $locations
498 */
499 public static function sortPrimaryFirst(&$locations) {
500 uasort($locations, 'self::primaryComparison');
501 }
502
503 /**
504 * compare 2 locations to see which should go first based on is_primary
505 * (sort function for sortPrimaryFirst)
506 * @param array $location1
507 * @param array $location2
508 * @return int
509 */
510 public static function primaryComparison($location1, $location2) {
511 $l1 = CRM_Utils_Array::value('is_primary', $location1);
512 $l2 = CRM_Utils_Array::value('is_primary', $location2);
513 if ($l1 == $l2) {
514 return 0;
515 }
516 return ($l1 < $l2) ? -1 : 1;
517 }
518
519 }