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