Merge pull request #15042 from civicrm/5.17
[civicrm-core.git] / CRM / Core / BAO / CustomValueTable.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
TO
32 * $Id$
33 *
34 */
35class CRM_Core_BAO_CustomValueTable {
36
b5c2afd0 37 /**
100fef9d 38 * @param array $customParams
46fe0a66 39 * @param string $parentOperation Operation being taken on the parent entity.
40 * If we know the parent entity is doing an insert we can skip the
41 * ON DUPLICATE UPDATE - which improves performance and reduces deadlocks.
42 * - edit
43 * - create
b5c2afd0
EM
44 *
45 * @throws Exception
46 */
46fe0a66 47 public static function create($customParams, $parentOperation = NULL) {
6a488035
TO
48 if (empty($customParams) ||
49 !is_array($customParams)
50 ) {
51 return;
52 }
8ef12e64 53
be2fb01f 54 $paramFieldsExtendContactForEntities = [];
63b7d442 55
6a488035
TO
56 foreach ($customParams as $tableName => $tables) {
57 foreach ($tables as $index => $fields) {
353ffa53
TO
58 $sqlOP = NULL;
59 $hookID = NULL;
60 $hookOP = NULL;
61 $entityID = NULL;
6a488035 62 $isMultiple = FALSE;
be2fb01f
CW
63 $set = [];
64 $params = [];
353ffa53 65 $count = 1;
6a488035
TO
66 foreach ($fields as $field) {
67 if (!$sqlOP) {
353ffa53
TO
68 $entityID = $field['entity_id'];
69 $hookID = $field['custom_group_id'];
6a488035
TO
70 $isMultiple = $field['is_multiple'];
71 if (array_key_exists('id', $field)) {
353ffa53
TO
72 $sqlOP = "UPDATE $tableName ";
73 $where = " WHERE id = %{$count}";
be2fb01f 74 $params[$count] = [$field['id'], 'Integer'];
6a488035
TO
75 $count++;
76 $hookOP = 'edit';
77 }
78 else {
353ffa53
TO
79 $sqlOP = "INSERT INTO $tableName ";
80 $where = NULL;
6a488035
TO
81 $hookOP = 'create';
82 }
83 }
84
85 // fix the value before we store it
86 $value = $field['value'];
87 $type = $field['type'];
88 switch ($type) {
89 case 'StateProvince':
90 $type = 'Integer';
91 if (is_array($value)) {
92 $value = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $value) . CRM_Core_DAO::VALUE_SEPARATOR;
93 $type = 'String';
94 }
ec28b24d 95 elseif (!is_numeric($value) && !strstr($value, CRM_Core_DAO::VALUE_SEPARATOR)) {
6a488035
TO
96 //fix for multi select state, CRM-3437
97 $mulValues = explode(',', $value);
be2fb01f 98 $validStates = [];
6a488035 99 foreach ($mulValues as $key => $stateVal) {
be2fb01f 100 $states = [];
6a488035
TO
101 $states['state_province'] = trim($stateVal);
102
103 CRM_Utils_Array::lookupValue($states, 'state_province',
104 CRM_Core_PseudoConstant::stateProvince(), TRUE
105 );
a7488080 106 if (empty($states['state_province_id'])) {
6a488035
TO
107 CRM_Utils_Array::lookupValue($states, 'state_province',
108 CRM_Core_PseudoConstant::stateProvinceAbbreviation(), TRUE
109 );
110 }
ec28b24d 111 $validStates[] = CRM_Utils_Array::value('state_province_id', $states);
6a488035
TO
112 }
113 $value = implode(CRM_Core_DAO::VALUE_SEPARATOR,
114 $validStates
115 );
116 $type = 'String';
117 }
118 elseif (!$value) {
119 // CRM-3415
120 // using type of timestamp allows us to sneak in a null into db
121 // gross but effective hack
122 $value = NULL;
123 $type = 'Timestamp';
124 }
ec28b24d 125 else {
126 $type = 'String';
127 }
6a488035
TO
128 break;
129
130 case 'Country':
131 $type = 'Integer';
132 if (is_array($value)) {
133 $value = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $value) . CRM_Core_DAO::VALUE_SEPARATOR;
134 $type = 'String';
135 }
ec28b24d 136 elseif (!is_numeric($value) && !strstr($value, CRM_Core_DAO::VALUE_SEPARATOR)) {
6a488035
TO
137 //fix for multi select country, CRM-3437
138 $mulValues = explode(',', $value);
be2fb01f 139 $validCountries = [];
6a488035 140 foreach ($mulValues as $key => $countryVal) {
be2fb01f 141 $countries = [];
6a488035
TO
142 $countries['country'] = trim($countryVal);
143 CRM_Utils_Array::lookupValue($countries, 'country',
144 CRM_Core_PseudoConstant::country(), TRUE
145 );
a7488080 146 if (empty($countries['country_id'])) {
6a488035
TO
147 CRM_Utils_Array::lookupValue($countries, 'country',
148 CRM_Core_PseudoConstant::countryIsoCode(), TRUE
149 );
150 }
8415b361 151 $validCountries[] = CRM_Utils_Array::value('country_id', $countries);
6a488035
TO
152 }
153 $value = implode(CRM_Core_DAO::VALUE_SEPARATOR,
154 $validCountries
155 );
156 $type = 'String';
157 }
158 elseif (!$value) {
159 // CRM-3415
160 // using type of timestamp allows us to sneak in a null into db
161 // gross but effective hack
162 $value = NULL;
163 $type = 'Timestamp';
164 }
ec28b24d 165 else {
166 $type = 'String';
167 }
6a488035
TO
168 break;
169
170 case 'File':
171 if (!$field['file_id']) {
172 CRM_Core_Error::fatal();
173 }
174
175 // need to add/update civicrm_entity_file
176 $entityFileDAO = new CRM_Core_DAO_EntityFile();
177 $entityFileDAO->file_id = $field['file_id'];
178 $entityFileDAO->find(TRUE);
179
180 $entityFileDAO->entity_table = $field['table_name'];
181 $entityFileDAO->entity_id = $field['entity_id'];
182 $entityFileDAO->file_id = $field['file_id'];
183 $entityFileDAO->save();
6a488035
TO
184 $value = $field['file_id'];
185 $type = 'String';
186 break;
187
188 case 'Date':
189 $value = CRM_Utils_Date::isoToMysql($value);
190 break;
191
192 case 'Int':
193 if (is_numeric($value)) {
194 $type = 'Integer';
195 }
196 else {
197 $type = 'Timestamp';
198 }
199 break;
200
201 case 'ContactReference':
202 if ($value == NULL) {
203 $type = 'Timestamp';
204 }
205 else {
206 $type = 'Integer';
207 }
208 break;
209
210 case 'RichTextEditor':
211 $type = 'String';
212 break;
213
214 case 'Boolean':
215 //fix for CRM-3290
216 $value = CRM_Utils_String::strtoboolstr($value);
217 if ($value === FALSE) {
218 $type = 'Timestamp';
219 }
220 break;
221
222 default:
223 break;
224 }
52fb3f74 225 if ($value === 'null') {
fd630ef9 226 // when unsetting a value to null, we don't need to validate the type
227 // https://projectllr.atlassian.net/browse/VGQBMP-20
228 $set[$field['column_name']] = $value;
f931b74c 229 }
230 else {
fd630ef9 231 $set[$field['column_name']] = "%{$count}";
be2fb01f 232 $params[$count] = [$value, $type];
fd630ef9 233 $count++;
234 }
63b7d442
AS
235
236 $fieldExtends = CRM_Utils_Array::value('extends', $field);
237 if (
238 CRM_Utils_Array::value('entity_table', $field) == 'civicrm_contact'
239 || $fieldExtends == 'Contact'
240 || $fieldExtends == 'Individual'
241 || $fieldExtends == 'Organization'
242 || $fieldExtends == 'Household'
243 ) {
244 $paramFieldsExtendContactForEntities[$entityID]['custom_' . CRM_Utils_Array::value('custom_field_id', $field)] = CRM_Utils_Array::value('custom_field_id', $field);
245 }
6a488035
TO
246 }
247
248 if (!empty($set)) {
be2fb01f 249 $setClause = [];
6a488035
TO
250 foreach ($set as $n => $v) {
251 $setClause[] = "$n = $v";
252 }
253 $setClause = implode(',', $setClause);
254 if (!$where) {
255 // do this only for insert
256 $set['entity_id'] = "%{$count}";
be2fb01f 257 $params[$count] = [$entityID, 'Integer'];
6a488035
TO
258 $count++;
259
65aae70e 260 $fieldNames = implode(',', CRM_Utils_Type::escapeAll(array_keys($set), 'MysqlColumnNameOrAlias'));
6a488035 261 $fieldValues = implode(',', array_values($set));
353ffa53 262 $query = "$sqlOP ( $fieldNames ) VALUES ( $fieldValues )";
6a488035 263 // for multiple values we dont do on duplicate key update
46fe0a66 264 if (!$isMultiple && $parentOperation !== 'create') {
6a488035
TO
265 $query .= " ON DUPLICATE KEY UPDATE $setClause";
266 }
267 }
268 else {
269 $query = "$sqlOP SET $setClause $where";
270 }
271 $dao = CRM_Core_DAO::executeQuery($query, $params);
272
273 CRM_Utils_Hook::custom($hookOP,
274 $hookID,
275 $entityID,
276 $fields
277 );
278 }
279 }
280 }
63b7d442
AS
281
282 if (!empty($paramFieldsExtendContactForEntities)) {
be2fb01f 283 CRM_Contact_BAO_Contact::updateGreetingsOnTokenFieldChange($paramFieldsExtendContactForEntities, ['contact_id' => $entityID]);
63b7d442 284 }
6a488035
TO
285 }
286
287 /**
fe482240 288 * Given a field return the mysql data type associated with it.
6a488035 289 *
6a0b768e 290 * @param string $type
fd31fa4c
EM
291 * @param int $maxLength
292 *
72b3a70c
CW
293 * @return string
294 * the mysql data store placeholder
6a488035
TO
295 */
296 public static function fieldToSQLType($type, $maxLength = 255) {
297 if (!isset($maxLength) ||
298 !is_numeric($maxLength) ||
299 $maxLength <= 0
300 ) {
301 $maxLength = 255;
302 }
303
304 switch ($type) {
305 case 'String':
306 case 'Link':
307 return "varchar($maxLength)";
308
309 case 'Boolean':
310 return 'tinyint';
311
312 case 'Int':
313 return 'int';
2aa397bc 314
6a488035
TO
315 // the below three are FK's, and have constraints added to them
316
317 case 'ContactReference':
318 case 'StateProvince':
319 case 'Country':
320 case 'File':
321 return 'int unsigned';
322
323 case 'Float':
324 return 'double';
325
326 case 'Money':
327 return 'decimal(20,2)';
328
329 case 'Memo':
330 case 'RichTextEditor':
331 return 'text';
332
333 case 'Date':
334 return 'datetime';
335
336 default:
337 CRM_Core_Error::fatal();
338 }
339 }
340
b5c2afd0 341 /**
c490a46a 342 * @param array $params
b5c2afd0 343 * @param $entityTable
100fef9d 344 * @param int $entityID
46fe0a66 345 * @param string $parentOperation Operation being taken on the parent entity.
346 * If we know the parent entity is doing an insert we can skip the
347 * ON DUPLICATE UPDATE - which improves performance and reduces deadlocks.
348 * - edit
349 * - create
b5c2afd0 350 */
46fe0a66 351 public static function store($params, $entityTable, $entityID, $parentOperation = NULL) {
be2fb01f 352 $cvParams = [];
6a488035
TO
353 foreach ($params as $fieldID => $param) {
354 foreach ($param as $index => $customValue) {
be2fb01f 355 $cvParam = [
6a488035
TO
356 'entity_table' => $entityTable,
357 'entity_id' => $entityID,
358 'value' => $customValue['value'],
359 'type' => $customValue['type'],
360 'custom_field_id' => $customValue['custom_field_id'],
361 'custom_group_id' => $customValue['custom_group_id'],
362 'table_name' => $customValue['table_name'],
363 'column_name' => $customValue['column_name'],
364 'is_multiple' => CRM_Utils_Array::value('is_multiple', $customValue),
365 'file_id' => $customValue['file_id'],
be2fb01f 366 ];
6a488035 367
fe482240 368 // Fix Date type to be timestamp, since that is how we store in db.
6a488035
TO
369 if ($cvParam['type'] == 'Date') {
370 $cvParam['type'] = 'Timestamp';
371 }
372
a7488080 373 if (!empty($customValue['id'])) {
6a488035
TO
374 $cvParam['id'] = $customValue['id'];
375 }
376 if (!array_key_exists($customValue['table_name'], $cvParams)) {
be2fb01f 377 $cvParams[$customValue['table_name']] = [];
6a488035
TO
378 }
379
380 if (!array_key_exists($index, $cvParams[$customValue['table_name']])) {
be2fb01f 381 $cvParams[$customValue['table_name']][$index] = [];
6a488035
TO
382 }
383
384 $cvParams[$customValue['table_name']][$index][] = $cvParam;
385 }
386 }
387 if (!empty($cvParams)) {
46fe0a66 388 self::create($cvParams, $parentOperation);
6a488035
TO
389 }
390 }
391
b5c2afd0 392 /**
fe482240
EM
393 * Post process function.
394 *
c490a46a 395 * @param array $params
b5c2afd0 396 * @param $entityTable
100fef9d 397 * @param int $entityID
b5c2afd0
EM
398 * @param $customFieldExtends
399 */
5fc3ea24 400 public static function postProcess(&$params, $entityTable, $entityID, $customFieldExtends) {
6a488035 401 $customData = CRM_Core_BAO_CustomField::postProcess($params,
6a488035
TO
402 $entityID,
403 $customFieldExtends
404 );
405
406 if (!empty($customData)) {
407 self::store($customData, $entityTable, $entityID);
408 }
409 }
410
411 /**
412 * Return an array of all custom values associated with an entity.
413 *
6a0b768e
TO
414 * @param int $entityID
415 * Identification number of the entity.
416 * @param string $entityType
417 * Type of entity that the entityID corresponds to, specified.
6a488035
TO
418 * as a string with format "'<EntityName>'". Comma separated
419 * list may be used to specify OR matches. Allowable values
420 * are enumerated types in civicrm_custom_group.extends field.
421 * Optional. Default value assumes entityID references a
422 * contact entity.
6a0b768e
TO
423 * @param array $fieldIDs
424 * Optional list of fieldIDs that we want to retrieve. If this.
6a488035
TO
425 * is set the entityType is ignored
426 *
77b97be7 427 * @param bool $formatMultiRecordField
c693f065 428 * @param array $DTparams - CRM-17810 dataTable params for the multiValued custom fields.
77b97be7 429 *
a6c01b45
CW
430 * @return array
431 * Array of custom values for the entity with key=>value
6a488035
TO
432 * pairs specified as civicrm_custom_field.id => custom value.
433 * Empty array if no custom values found.
6a488035 434 */
c693f065 435 public static function &getEntityValues($entityID, $entityType = NULL, $fieldIDs = NULL, $formatMultiRecordField = FALSE, $DTparams = NULL) {
6a488035
TO
436 if (!$entityID) {
437 // adding this here since an empty contact id could have serious repurcussions
438 // like looping forever
439 CRM_Core_Error::fatal('Please file an issue with the backtrace');
440 return NULL;
441 }
442
be2fb01f 443 $cond = [];
6a488035
TO
444 if ($entityType) {
445 $cond[] = "cg.extends IN ( '$entityType' )";
446 }
447 if ($fieldIDs &&
448 is_array($fieldIDs)
449 ) {
450 $fieldIDList = implode(',', $fieldIDs);
451 $cond[] = "cf.id IN ( $fieldIDList )";
452 }
453 if (empty($cond)) {
454 $cond[] = "cg.extends IN ( 'Contact', 'Individual', 'Household', 'Organization' )";
455 }
456 $cond = implode(' AND ', $cond);
457
e87c8fb7 458 $limit = $orderBy = '';
c693f065 459 if (!empty($DTparams['rowCount']) && $DTparams['rowCount'] > 0) {
d51e02d3 460 $limit = " LIMIT " . CRM_Utils_Type::escape($DTparams['offset'], 'Integer') . ", " . CRM_Utils_Type::escape($DTparams['rowCount'], 'Integer');
c693f065 461 }
c693f065 462 if (!empty($DTparams['sort'])) {
463 $orderBy = ' ORDER BY ' . CRM_Utils_Type::escape($DTparams['sort'], 'String');
464 }
465
fe482240 466 // First find all the fields that extend this type of entity.
6a488035
TO
467 $query = "
468SELECT cg.table_name,
469 cg.id as groupID,
470 cg.is_multiple,
471 cf.column_name,
34f51a07
N
472 cf.id as fieldID,
473 cf.data_type as fieldDataType
6a488035
TO
474FROM civicrm_custom_group cg,
475 civicrm_custom_field cf
476WHERE cf.custom_group_id = cg.id
477AND cg.is_active = 1
478AND cf.is_active = 1
479AND $cond
480";
481 $dao = CRM_Core_DAO::executeQuery($query);
482
be2fb01f 483 $select = $fields = $isMultiple = [];
6a488035
TO
484
485 while ($dao->fetch()) {
486 if (!array_key_exists($dao->table_name, $select)) {
be2fb01f
CW
487 $fields[$dao->table_name] = [];
488 $select[$dao->table_name] = [];
6a488035
TO
489 }
490 $fields[$dao->table_name][] = $dao->fieldID;
491 $select[$dao->table_name][] = "{$dao->column_name} AS custom_{$dao->fieldID}";
492 $isMultiple[$dao->table_name] = $dao->is_multiple ? TRUE : FALSE;
77b97be7 493 $file[$dao->table_name][$dao->fieldID] = $dao->fieldDataType;
6a488035
TO
494 }
495
be2fb01f 496 $result = $sortedResult = [];
6a488035 497 foreach ($select as $tableName => $clauses) {
1c66bdc7 498 if (!empty($DTparams['sort'])) {
499 $query = CRM_Core_DAO::executeQuery("SELECT id FROM {$tableName} WHERE entity_id = {$entityID}");
500 $count = 1;
501 while ($query->fetch()) {
502 $sortedResult["{$query->id}"] = $count;
503 $count++;
504 }
505 }
506
c693f065 507 $query = "SELECT SQL_CALC_FOUND_ROWS id, " . implode(', ', $clauses) . " FROM $tableName WHERE entity_id = $entityID {$orderBy} {$limit}";
6a488035 508 $dao = CRM_Core_DAO::executeQuery($query);
e87c8fb7 509 if (!empty($DTparams)) {
510 $result['count'] = CRM_Core_DAO::singleValueQuery('SELECT FOUND_ROWS()');
511 }
6a488035
TO
512 while ($dao->fetch()) {
513 foreach ($fields[$tableName] as $fieldID) {
514 $fieldName = "custom_{$fieldID}";
515 if ($isMultiple[$tableName]) {
516 if ($formatMultiRecordField) {
d8f34a6e 517 $result["{$dao->id}"]["{$fieldID}"] = $dao->$fieldName;
fe482240
EM
518 }
519 else {
6a488035
TO
520 $result["{$fieldID}_{$dao->id}"] = $dao->$fieldName;
521 }
522 }
523 else {
d8f34a6e 524 $result[$fieldID] = $dao->$fieldName;
6a488035
TO
525 }
526 }
527 }
528 }
e525d6af 529 if (!empty($sortedResult)) {
530 $result['sortedResult'] = $sortedResult;
1c66bdc7 531 }
6a488035
TO
532 return $result;
533 }
534
535 /**
100fef9d 536 * Take in an array of entityID, custom_XXX => value
6a488035
TO
537 * and set the value in the appropriate table. Should also be able
538 * to set the value to null. Follows api parameter/return conventions
539 *
540 * @array $params
541 *
c490a46a 542 * @param array $params
2a6da8d7
EM
543 *
544 * @throws Exception
6a488035 545 * @return array
6a488035 546 */
00be9182 547 public static function setValues(&$params) {
d1b0ffad
CW
548 // For legacy reasons, accept this param in either format
549 if (empty($params['entityID']) && !empty($params['entity_id'])) {
550 $params['entityID'] = $params['entity_id'];
551 }
6a488035 552
d1b0ffad 553 if (!isset($params['entityID']) || !CRM_Utils_Type::validate($params['entityID'], 'Integer', FALSE)) {
fec42980 554 return CRM_Core_Error::createAPIError(ts('entity_id needs to be set and of type Integer'));
6a488035
TO
555 }
556
557 // first collect all the id/value pairs. The format is:
558 // custom_X => value or custom_X_VALUEID => value (for multiple values), VALUEID == -1, -2 etc for new insertions
be2fb01f 559 $fieldValues = [];
6a488035
TO
560 foreach ($params as $n => $v) {
561 if ($customFieldInfo = CRM_Core_BAO_CustomField::getKeyID($n, TRUE)) {
562 $fieldID = (int ) $customFieldInfo[0];
563 if (CRM_Utils_Type::escape($fieldID, 'Integer', FALSE) === NULL) {
564 return CRM_Core_Error::createAPIError(ts('field ID needs to be of type Integer for index %1',
be2fb01f 565 [1 => $fieldID]
353ffa53 566 ));
6a488035
TO
567 }
568 if (!array_key_exists($fieldID, $fieldValues)) {
be2fb01f 569 $fieldValues[$fieldID] = [];
6a488035
TO
570 }
571 $id = -1;
572 if ($customFieldInfo[1]) {
573 $id = (int ) $customFieldInfo[1];
574 }
be2fb01f 575 $fieldValues[$fieldID][] = [
6a488035
TO
576 'value' => $v,
577 'id' => $id,
be2fb01f 578 ];
6a488035
TO
579 }
580 }
581
582 $fieldIDList = implode(',', array_keys($fieldValues));
583
584 // format it so that we can just use create
585 $sql = "
586SELECT cg.table_name as table_name ,
587 cg.id as cg_id ,
588 cg.is_multiple as is_multiple,
63b7d442 589 cg.extends as extends,
6a488035
TO
590 cf.column_name as column_name,
591 cf.id as cf_id ,
592 cf.data_type as data_type
593FROM civicrm_custom_group cg,
594 civicrm_custom_field cf
595WHERE cf.custom_group_id = cg.id
596AND cf.id IN ( $fieldIDList )
597";
598
599 $dao = CRM_Core_DAO::executeQuery($sql);
be2fb01f 600 $cvParams = [];
6a488035
TO
601
602 while ($dao->fetch()) {
603 $dataType = $dao->data_type == 'Date' ? 'Timestamp' : $dao->data_type;
604 foreach ($fieldValues[$dao->cf_id] as $fieldValue) {
605 // Format null values correctly
606 if ($fieldValue['value'] === NULL || $fieldValue['value'] === '') {
607 switch ($dataType) {
608 case 'String':
609 case 'Int':
610 case 'Link':
611 case 'Boolean':
612 $fieldValue['value'] = '';
613 break;
614
615 case 'Timestamp':
616 $fieldValue['value'] = NULL;
617 break;
618
619 case 'StateProvince':
620 case 'Country':
621 case 'Money':
622 case 'Float':
2aa397bc 623 $fieldValue['value'] = (int) 0;
6a488035
TO
624 break;
625 }
626 }
627 // Ensure that value is of the right data type
628 elseif (CRM_Utils_Type::escape($fieldValue['value'], $dataType, FALSE) === NULL) {
629 return CRM_Core_Error::createAPIError(ts('value: %1 is not of the right field data type: %2',
be2fb01f 630 [
353ffa53
TO
631 1 => $fieldValue['value'],
632 2 => $dao->data_type,
be2fb01f 633 ]
353ffa53 634 ));
6a488035
TO
635 }
636
be2fb01f 637 $cvParam = [
6a488035
TO
638 'entity_id' => $params['entityID'],
639 'value' => $fieldValue['value'],
640 'type' => $dataType,
641 'custom_field_id' => $dao->cf_id,
642 'custom_group_id' => $dao->cg_id,
643 'table_name' => $dao->table_name,
644 'column_name' => $dao->column_name,
645 'is_multiple' => $dao->is_multiple,
63b7d442 646 'extends' => $dao->extends,
be2fb01f 647 ];
6a488035 648
5013100c 649 if (!empty($params['id'])) {
650 $cvParam['id'] = $params['id'];
651 }
652
e7dcccf0
CW
653 if ($cvParam['type'] == 'File') {
654 $cvParam['file_id'] = $fieldValue['value'];
655 }
656
6a488035 657 if (!array_key_exists($dao->table_name, $cvParams)) {
be2fb01f 658 $cvParams[$dao->table_name] = [];
6a488035
TO
659 }
660
661 if (!array_key_exists($fieldValue['id'], $cvParams[$dao->table_name])) {
be2fb01f 662 $cvParams[$dao->table_name][$fieldValue['id']] = [];
6a488035
TO
663 }
664
665 if ($fieldValue['id'] > 0) {
666 $cvParam['id'] = $fieldValue['id'];
667 }
668 $cvParams[$dao->table_name][$fieldValue['id']][] = $cvParam;
669 }
670 }
671
672 if (!empty($cvParams)) {
673 self::create($cvParams);
be2fb01f 674 return ['is_error' => 0, 'result' => 1];
6a488035
TO
675 }
676
677 return CRM_Core_Error::createAPIError(ts('Unknown error'));
678 }
679
680 /**
100fef9d 681 * Take in an array of entityID, custom_ID
6a488035
TO
682 * and gets the value from the appropriate table.
683 *
684 * To get the values of custom fields with IDs 13 and 43 for contact ID 1327, use:
685 * $params = array( 'entityID' => 1327, 'custom_13' => 1, 'custom_43' => 1 );
686 *
b44e3f84 687 * Entity Type will be inferred by the custom fields you request
6a488035
TO
688 * Specify $params['entityType'] if you do not supply any custom fields to return
689 * and entity type is other than Contact
690 *
691 * @array $params
692 *
c490a46a 693 * @param array $params
2a6da8d7
EM
694 *
695 * @throws Exception
6a488035 696 * @return array
6a488035 697 */
00be9182 698 public static function &getValues(&$params) {
6a488035
TO
699 if (empty($params)) {
700 return NULL;
701 }
702 if (!isset($params['entityID']) ||
703 CRM_Utils_Type::escape($params['entityID'],
704 'Integer', FALSE
705 ) === NULL
706 ) {
707 return CRM_Core_Error::createAPIError(ts('entityID needs to be set and of type Integer'));
708 }
709
710 // first collect all the ids. The format is:
711 // custom_ID
be2fb01f 712 $fieldIDs = [];
6a488035
TO
713 foreach ($params as $n => $v) {
714 $key = $idx = NULL;
715 if (substr($n, 0, 7) == 'custom_') {
716 $idx = substr($n, 7);
717 if (CRM_Utils_Type::escape($idx, 'Integer', FALSE) === NULL) {
718 return CRM_Core_Error::createAPIError(ts('field ID needs to be of type Integer for index %1',
be2fb01f 719 [1 => $idx]
353ffa53 720 ));
6a488035
TO
721 }
722 $fieldIDs[] = (int ) $idx;
723 }
724 }
725
be2fb01f 726 $default = ['Contact', 'Individual', 'Household', 'Organization'];
6a488035
TO
727 if (!($type = CRM_Utils_Array::value('entityType', $params)) ||
728 in_array($params['entityType'], $default)
729 ) {
730 $type = NULL;
731 }
732 else {
733 $entities = CRM_Core_SelectValues::customGroupExtends();
734 if (!array_key_exists($type, $entities)) {
735 if (in_array($type, $entities)) {
736 $type = $entities[$type];
737 if (in_array($type, $default)) {
738 $type = NULL;
739 }
740 }
741 else {
742 return CRM_Core_Error::createAPIError(ts('Invalid entity type') . ': "' . $type . '"');
743 }
744 }
745 }
746
747 $values = self::getEntityValues($params['entityID'],
748 $type,
749 $fieldIDs
750 );
751 if (empty($values)) {
752 // note that this behaviour is undesirable from an API point of view - it should return an empty array
753 // since this is also called by the merger code & not sure the consequences of changing
754 // are just handling undoing this in the api layer. ie. converting the error back into a success
be2fb01f 755 $result = [
6a488035
TO
756 'is_error' => 1,
757 'error_message' => 'No values found for the specified entity ID and custom field(s).',
be2fb01f 758 ];
6a488035
TO
759 return $result;
760 }
761 else {
be2fb01f 762 $result = [
6a488035
TO
763 'is_error' => 0,
764 'entityID' => $params['entityID'],
be2fb01f 765 ];
6a488035
TO
766 foreach ($values as $id => $value) {
767 $result["custom_{$id}"] = $value;
768 }
769 return $result;
770 }
771 }
96025800 772
6a488035 773}