Commit | Line | Data |
---|---|---|
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 | */ |
17 | class 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 = []; |
a61d2ab7 | 37 | $VS = CRM_Core_DAO::VALUE_SEPARATOR; |
63b7d442 | 38 | |
6a488035 | 39 | foreach ($customParams as $tableName => $tables) { |
3d89c1a4 | 40 | foreach ($tables as $fields) { |
353ffa53 | 41 | $hookID = NULL; |
353ffa53 | 42 | $entityID = NULL; |
be2fb01f CW |
43 | $set = []; |
44 | $params = []; | |
353ffa53 | 45 | $count = 1; |
6a488035 | 46 | |
048b069c | 47 | $firstField = reset($fields); |
445bbeed | 48 | $entityID = (int) $firstField['entity_id']; |
048b069c BS |
49 | $isMultiple = $firstField['is_multiple']; |
50 | if (array_key_exists('id', $firstField)) { | |
51 | $sqlOP = "UPDATE $tableName "; | |
52 | $where = " WHERE id = %{$count}"; | |
53 | $params[$count] = [$firstField['id'], 'Integer']; | |
54 | $count++; | |
55 | $hookOP = 'edit'; | |
56 | } | |
57 | else { | |
58 | $sqlOP = "INSERT INTO $tableName "; | |
59 | $where = NULL; | |
60 | $hookOP = 'create'; | |
61 | } | |
62 | ||
445bbeed EM |
63 | CRM_Utils_Hook::customPre( |
64 | $hookOP, | |
65 | (int) $firstField['custom_group_id'], | |
048b069c BS |
66 | $entityID, |
67 | $fields | |
68 | ); | |
69 | ||
70 | foreach ($fields as $field) { | |
6a488035 TO |
71 | // fix the value before we store it |
72 | $value = $field['value']; | |
73 | $type = $field['type']; | |
74 | switch ($type) { | |
75 | case 'StateProvince': | |
76 | $type = 'Integer'; | |
77 | if (is_array($value)) { | |
78 | $value = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $value) . CRM_Core_DAO::VALUE_SEPARATOR; | |
79 | $type = 'String'; | |
80 | } | |
ec28b24d | 81 | elseif (!is_numeric($value) && !strstr($value, CRM_Core_DAO::VALUE_SEPARATOR)) { |
6a488035 TO |
82 | //fix for multi select state, CRM-3437 |
83 | $mulValues = explode(',', $value); | |
be2fb01f | 84 | $validStates = []; |
6a488035 | 85 | foreach ($mulValues as $key => $stateVal) { |
be2fb01f | 86 | $states = []; |
6a488035 TO |
87 | $states['state_province'] = trim($stateVal); |
88 | ||
89 | CRM_Utils_Array::lookupValue($states, 'state_province', | |
90 | CRM_Core_PseudoConstant::stateProvince(), TRUE | |
91 | ); | |
a7488080 | 92 | if (empty($states['state_province_id'])) { |
6a488035 TO |
93 | CRM_Utils_Array::lookupValue($states, 'state_province', |
94 | CRM_Core_PseudoConstant::stateProvinceAbbreviation(), TRUE | |
95 | ); | |
96 | } | |
9c1bc317 | 97 | $validStates[] = $states['state_province_id'] ?? NULL; |
6a488035 TO |
98 | } |
99 | $value = implode(CRM_Core_DAO::VALUE_SEPARATOR, | |
100 | $validStates | |
101 | ); | |
102 | $type = 'String'; | |
103 | } | |
104 | elseif (!$value) { | |
105 | // CRM-3415 | |
106 | // using type of timestamp allows us to sneak in a null into db | |
107 | // gross but effective hack | |
108 | $value = NULL; | |
109 | $type = 'Timestamp'; | |
110 | } | |
ec28b24d | 111 | else { |
112 | $type = 'String'; | |
113 | } | |
6a488035 TO |
114 | break; |
115 | ||
116 | case 'Country': | |
117 | $type = 'Integer'; | |
118 | if (is_array($value)) { | |
119 | $value = CRM_Core_DAO::VALUE_SEPARATOR . implode(CRM_Core_DAO::VALUE_SEPARATOR, $value) . CRM_Core_DAO::VALUE_SEPARATOR; | |
120 | $type = 'String'; | |
121 | } | |
ec28b24d | 122 | elseif (!is_numeric($value) && !strstr($value, CRM_Core_DAO::VALUE_SEPARATOR)) { |
6a488035 TO |
123 | //fix for multi select country, CRM-3437 |
124 | $mulValues = explode(',', $value); | |
be2fb01f | 125 | $validCountries = []; |
6a488035 | 126 | foreach ($mulValues as $key => $countryVal) { |
be2fb01f | 127 | $countries = []; |
6a488035 TO |
128 | $countries['country'] = trim($countryVal); |
129 | CRM_Utils_Array::lookupValue($countries, 'country', | |
130 | CRM_Core_PseudoConstant::country(), TRUE | |
131 | ); | |
a7488080 | 132 | if (empty($countries['country_id'])) { |
6a488035 TO |
133 | CRM_Utils_Array::lookupValue($countries, 'country', |
134 | CRM_Core_PseudoConstant::countryIsoCode(), TRUE | |
135 | ); | |
136 | } | |
9c1bc317 | 137 | $validCountries[] = $countries['country_id'] ?? NULL; |
6a488035 TO |
138 | } |
139 | $value = implode(CRM_Core_DAO::VALUE_SEPARATOR, | |
140 | $validCountries | |
141 | ); | |
142 | $type = 'String'; | |
143 | } | |
144 | elseif (!$value) { | |
145 | // CRM-3415 | |
146 | // using type of timestamp allows us to sneak in a null into db | |
147 | // gross but effective hack | |
148 | $value = NULL; | |
149 | $type = 'Timestamp'; | |
150 | } | |
ec28b24d | 151 | else { |
152 | $type = 'String'; | |
153 | } | |
6a488035 TO |
154 | break; |
155 | ||
156 | case 'File': | |
157 | if (!$field['file_id']) { | |
9d85626b CW |
158 | $value = 'null'; |
159 | break; | |
6a488035 TO |
160 | } |
161 | ||
162 | // need to add/update civicrm_entity_file | |
163 | $entityFileDAO = new CRM_Core_DAO_EntityFile(); | |
164 | $entityFileDAO->file_id = $field['file_id']; | |
165 | $entityFileDAO->find(TRUE); | |
166 | ||
167 | $entityFileDAO->entity_table = $field['table_name']; | |
168 | $entityFileDAO->entity_id = $field['entity_id']; | |
169 | $entityFileDAO->file_id = $field['file_id']; | |
170 | $entityFileDAO->save(); | |
6a488035 TO |
171 | $value = $field['file_id']; |
172 | $type = 'String'; | |
173 | break; | |
174 | ||
175 | case 'Date': | |
176 | $value = CRM_Utils_Date::isoToMysql($value); | |
177 | break; | |
178 | ||
179 | case 'Int': | |
180 | if (is_numeric($value)) { | |
181 | $type = 'Integer'; | |
182 | } | |
183 | else { | |
184 | $type = 'Timestamp'; | |
185 | } | |
186 | break; | |
187 | ||
188 | case 'ContactReference': | |
a61d2ab7 | 189 | if ($value == NULL || $value === '' || $value === $VS . $VS) { |
6a488035 | 190 | $type = 'Timestamp'; |
a61d2ab7 CW |
191 | $value = NULL; |
192 | } | |
193 | elseif (strpos($value, $VS) !== FALSE) { | |
194 | $type = 'String'; | |
195 | // Validate the string contains only integers and value-separators | |
196 | $validChars = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9, $VS]; | |
197 | if (str_replace($validChars, '', $value)) { | |
198 | throw new CRM_Core_Exception('Contact ID must be of type Integer'); | |
199 | } | |
6a488035 TO |
200 | } |
201 | else { | |
202 | $type = 'Integer'; | |
203 | } | |
204 | break; | |
205 | ||
206 | case 'RichTextEditor': | |
207 | $type = 'String'; | |
208 | break; | |
209 | ||
210 | case 'Boolean': | |
211 | //fix for CRM-3290 | |
212 | $value = CRM_Utils_String::strtoboolstr($value); | |
213 | if ($value === FALSE) { | |
214 | $type = 'Timestamp'; | |
215 | } | |
216 | break; | |
217 | ||
218 | default: | |
219 | break; | |
220 | } | |
52fb3f74 | 221 | if ($value === 'null') { |
fd630ef9 | 222 | // when unsetting a value to null, we don't need to validate the type |
223 | // https://projectllr.atlassian.net/browse/VGQBMP-20 | |
224 | $set[$field['column_name']] = $value; | |
f931b74c | 225 | } |
226 | else { | |
fd630ef9 | 227 | $set[$field['column_name']] = "%{$count}"; |
d532d0cf | 228 | // The second parameter is the type of the db field, which |
229 | // would be 'String' for a concatenated set of integers. | |
831545ee | 230 | // However, the god-forsaken timestamp hack also needs to be kept |
231 | // if value is NULL. | |
b99d7139 | 232 | $params[$count] = [$value, ($value && $field['serialize']) ? 'String' : $type]; |
fd630ef9 | 233 | $count++; |
234 | } | |
63b7d442 | 235 | |
9c1bc317 | 236 | $fieldExtends = $field['extends'] ?? NULL; |
63b7d442 | 237 | if ( |
3d89c1a4 EM |
238 | CRM_Utils_Array::value('entity_table', $field) === 'civicrm_contact' |
239 | || $fieldExtends === 'Contact' | |
240 | || $fieldExtends === 'Individual' | |
241 | || $fieldExtends === 'Organization' | |
242 | || $fieldExtends === 'Household' | |
63b7d442 | 243 | ) { |
9c1bc317 | 244 | $paramFieldsExtendContactForEntities[$entityID]['custom_' . CRM_Utils_Array::value('custom_field_id', $field)] = $field['custom_field_id'] ?? NULL; |
63b7d442 | 245 | } |
6a488035 TO |
246 | } |
247 | ||
248 | if (!empty($set)) { | |
be2fb01f | 249 | $setClause = []; |
6a488035 | 250 | foreach ($set as $n => $v) { |
4f426b76 | 251 | $setClause[] = "`$n` = $v"; |
6a488035 TO |
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 | } | |
d532d0cf | 271 | CRM_Core_DAO::executeQuery($query, $params); |
6a488035 TO |
272 | |
273 | CRM_Utils_Hook::custom($hookOP, | |
445bbeed | 274 | (int) $firstField['custom_group_id'], |
6a488035 TO |
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: | |
ac15829d | 337 | throw new CRM_Core_Exception('Invalid Field Type'); |
6a488035 TO |
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'], | |
b99d7139 | 364 | // is_multiple refers to the custom group, serialize refers to the field. |
365 | // @todo is_multiple can be null - does that mean anything different from 0? | |
366 | 'is_multiple' => $customValue['is_multiple'] ?? CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customValue['custom_group_id'], 'is_multiple'), | |
367 | 'serialize' => $customValue['serialize'] ?? (int) CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $customValue['custom_field_id'], 'serialize'), | |
6a488035 | 368 | 'file_id' => $customValue['file_id'], |
be2fb01f | 369 | ]; |
6a488035 | 370 | |
fe482240 | 371 | // Fix Date type to be timestamp, since that is how we store in db. |
6a488035 TO |
372 | if ($cvParam['type'] == 'Date') { |
373 | $cvParam['type'] = 'Timestamp'; | |
374 | } | |
375 | ||
a7488080 | 376 | if (!empty($customValue['id'])) { |
6a488035 TO |
377 | $cvParam['id'] = $customValue['id']; |
378 | } | |
a1fc8261 SL |
379 | elseif (empty($cvParam['is_multiple']) && !empty($entityID)) { |
380 | // dev/core#3000 Ensure that if we are not dealing with multiple record custom data and for some reason have got here without getting the id of the record in the custom table for this entityId let us give it one last shot | |
381 | $rowId = CRM_Core_DAO::singleValueQuery("SELECT id FROM {$cvParam['table_name']} WHERE entity_id = %1", [1 => [$entityID, 'Integer']]); | |
382 | if (!empty($rowId)) { | |
383 | $cvParam['id'] = $rowId; | |
384 | } | |
385 | } | |
6a488035 | 386 | if (!array_key_exists($customValue['table_name'], $cvParams)) { |
be2fb01f | 387 | $cvParams[$customValue['table_name']] = []; |
6a488035 TO |
388 | } |
389 | ||
390 | if (!array_key_exists($index, $cvParams[$customValue['table_name']])) { | |
be2fb01f | 391 | $cvParams[$customValue['table_name']][$index] = []; |
6a488035 TO |
392 | } |
393 | ||
394 | $cvParams[$customValue['table_name']][$index][] = $cvParam; | |
395 | } | |
396 | } | |
397 | if (!empty($cvParams)) { | |
46fe0a66 | 398 | self::create($cvParams, $parentOperation); |
6a488035 TO |
399 | } |
400 | } | |
401 | ||
b5c2afd0 | 402 | /** |
fe482240 EM |
403 | * Post process function. |
404 | * | |
c490a46a | 405 | * @param array $params |
b5c2afd0 | 406 | * @param $entityTable |
100fef9d | 407 | * @param int $entityID |
b5c2afd0 | 408 | * @param $customFieldExtends |
11201e17 | 409 | * @param $parentOperation |
b5c2afd0 | 410 | */ |
11201e17 | 411 | public static function postProcess(&$params, $entityTable, $entityID, $customFieldExtends, $parentOperation = NULL) { |
6a488035 | 412 | $customData = CRM_Core_BAO_CustomField::postProcess($params, |
6a488035 TO |
413 | $entityID, |
414 | $customFieldExtends | |
415 | ); | |
416 | ||
417 | if (!empty($customData)) { | |
11201e17 | 418 | self::store($customData, $entityTable, $entityID, $parentOperation); |
6a488035 TO |
419 | } |
420 | } | |
421 | ||
422 | /** | |
423 | * Return an array of all custom values associated with an entity. | |
424 | * | |
6a0b768e TO |
425 | * @param int $entityID |
426 | * Identification number of the entity. | |
427 | * @param string $entityType | |
428 | * Type of entity that the entityID corresponds to, specified. | |
6a488035 TO |
429 | * as a string with format "'<EntityName>'". Comma separated |
430 | * list may be used to specify OR matches. Allowable values | |
431 | * are enumerated types in civicrm_custom_group.extends field. | |
432 | * Optional. Default value assumes entityID references a | |
433 | * contact entity. | |
6a0b768e TO |
434 | * @param array $fieldIDs |
435 | * Optional list of fieldIDs that we want to retrieve. If this. | |
6a488035 TO |
436 | * is set the entityType is ignored |
437 | * | |
77b97be7 | 438 | * @param bool $formatMultiRecordField |
c693f065 | 439 | * @param array $DTparams - CRM-17810 dataTable params for the multiValued custom fields. |
77b97be7 | 440 | * |
a6c01b45 CW |
441 | * @return array |
442 | * Array of custom values for the entity with key=>value | |
6a488035 TO |
443 | * pairs specified as civicrm_custom_field.id => custom value. |
444 | * Empty array if no custom values found. | |
ac15829d | 445 | * @throws CRM_Core_Exception |
6a488035 | 446 | */ |
f1b78592 | 447 | public static function getEntityValues($entityID, $entityType = NULL, $fieldIDs = NULL, $formatMultiRecordField = FALSE, $DTparams = NULL) { |
6a488035 TO |
448 | if (!$entityID) { |
449 | // adding this here since an empty contact id could have serious repurcussions | |
450 | // like looping forever | |
ac15829d | 451 | throw new CRM_Core_Exception('Please file an issue with the backtrace'); |
6a488035 TO |
452 | return NULL; |
453 | } | |
454 | ||
be2fb01f | 455 | $cond = []; |
6a488035 TO |
456 | if ($entityType) { |
457 | $cond[] = "cg.extends IN ( '$entityType' )"; | |
458 | } | |
459 | if ($fieldIDs && | |
460 | is_array($fieldIDs) | |
461 | ) { | |
462 | $fieldIDList = implode(',', $fieldIDs); | |
463 | $cond[] = "cf.id IN ( $fieldIDList )"; | |
464 | } | |
465 | if (empty($cond)) { | |
f1b78592 CW |
466 | $contactTypes = array_merge(['Contact'], CRM_Contact_BAO_ContactType::basicTypes(TRUE)); |
467 | $cond[] = "cg.extends IN ( '" . implode("', '", $contactTypes) . "' )"; | |
6a488035 TO |
468 | } |
469 | $cond = implode(' AND ', $cond); | |
470 | ||
e87c8fb7 | 471 | $limit = $orderBy = ''; |
c693f065 | 472 | if (!empty($DTparams['rowCount']) && $DTparams['rowCount'] > 0) { |
d51e02d3 | 473 | $limit = " LIMIT " . CRM_Utils_Type::escape($DTparams['offset'], 'Integer') . ", " . CRM_Utils_Type::escape($DTparams['rowCount'], 'Integer'); |
c693f065 | 474 | } |
c693f065 | 475 | if (!empty($DTparams['sort'])) { |
476 | $orderBy = ' ORDER BY ' . CRM_Utils_Type::escape($DTparams['sort'], 'String'); | |
477 | } | |
478 | ||
fe482240 | 479 | // First find all the fields that extend this type of entity. |
6a488035 TO |
480 | $query = " |
481 | SELECT cg.table_name, | |
482 | cg.id as groupID, | |
483 | cg.is_multiple, | |
484 | cf.column_name, | |
34f51a07 N |
485 | cf.id as fieldID, |
486 | cf.data_type as fieldDataType | |
6a488035 TO |
487 | FROM civicrm_custom_group cg, |
488 | civicrm_custom_field cf | |
489 | WHERE cf.custom_group_id = cg.id | |
490 | AND cg.is_active = 1 | |
491 | AND cf.is_active = 1 | |
492 | AND $cond | |
493 | "; | |
494 | $dao = CRM_Core_DAO::executeQuery($query); | |
495 | ||
be2fb01f | 496 | $select = $fields = $isMultiple = []; |
6a488035 TO |
497 | |
498 | while ($dao->fetch()) { | |
499 | if (!array_key_exists($dao->table_name, $select)) { | |
be2fb01f CW |
500 | $fields[$dao->table_name] = []; |
501 | $select[$dao->table_name] = []; | |
6a488035 TO |
502 | } |
503 | $fields[$dao->table_name][] = $dao->fieldID; | |
504 | $select[$dao->table_name][] = "{$dao->column_name} AS custom_{$dao->fieldID}"; | |
63d76404 | 505 | $isMultiple[$dao->table_name] = (bool) $dao->is_multiple; |
77b97be7 | 506 | $file[$dao->table_name][$dao->fieldID] = $dao->fieldDataType; |
6a488035 TO |
507 | } |
508 | ||
be2fb01f | 509 | $result = $sortedResult = []; |
6a488035 | 510 | foreach ($select as $tableName => $clauses) { |
1c66bdc7 | 511 | if (!empty($DTparams['sort'])) { |
512 | $query = CRM_Core_DAO::executeQuery("SELECT id FROM {$tableName} WHERE entity_id = {$entityID}"); | |
513 | $count = 1; | |
514 | while ($query->fetch()) { | |
515 | $sortedResult["{$query->id}"] = $count; | |
516 | $count++; | |
517 | } | |
518 | } | |
519 | ||
c693f065 | 520 | $query = "SELECT SQL_CALC_FOUND_ROWS id, " . implode(', ', $clauses) . " FROM $tableName WHERE entity_id = $entityID {$orderBy} {$limit}"; |
6a488035 | 521 | $dao = CRM_Core_DAO::executeQuery($query); |
e87c8fb7 | 522 | if (!empty($DTparams)) { |
523 | $result['count'] = CRM_Core_DAO::singleValueQuery('SELECT FOUND_ROWS()'); | |
524 | } | |
6a488035 TO |
525 | while ($dao->fetch()) { |
526 | foreach ($fields[$tableName] as $fieldID) { | |
527 | $fieldName = "custom_{$fieldID}"; | |
528 | if ($isMultiple[$tableName]) { | |
529 | if ($formatMultiRecordField) { | |
d8f34a6e | 530 | $result["{$dao->id}"]["{$fieldID}"] = $dao->$fieldName; |
fe482240 EM |
531 | } |
532 | else { | |
6a488035 TO |
533 | $result["{$fieldID}_{$dao->id}"] = $dao->$fieldName; |
534 | } | |
535 | } | |
536 | else { | |
d8f34a6e | 537 | $result[$fieldID] = $dao->$fieldName; |
6a488035 TO |
538 | } |
539 | } | |
540 | } | |
541 | } | |
e525d6af | 542 | if (!empty($sortedResult)) { |
543 | $result['sortedResult'] = $sortedResult; | |
1c66bdc7 | 544 | } |
6a488035 TO |
545 | return $result; |
546 | } | |
547 | ||
548 | /** | |
100fef9d | 549 | * Take in an array of entityID, custom_XXX => value |
6a488035 TO |
550 | * and set the value in the appropriate table. Should also be able |
551 | * to set the value to null. Follows api parameter/return conventions | |
552 | * | |
553 | * @array $params | |
554 | * | |
c490a46a | 555 | * @param array $params |
2a6da8d7 EM |
556 | * |
557 | * @throws Exception | |
6a488035 | 558 | * @return array |
6a488035 | 559 | */ |
00be9182 | 560 | public static function setValues(&$params) { |
d1b0ffad CW |
561 | // For legacy reasons, accept this param in either format |
562 | if (empty($params['entityID']) && !empty($params['entity_id'])) { | |
563 | $params['entityID'] = $params['entity_id']; | |
564 | } | |
6a488035 | 565 | |
d1b0ffad | 566 | if (!isset($params['entityID']) || !CRM_Utils_Type::validate($params['entityID'], 'Integer', FALSE)) { |
e31fc637 | 567 | throw new CRM_Core_Exception(ts('entity_id needs to be set and of type Integer')); |
6a488035 TO |
568 | } |
569 | ||
570 | // first collect all the id/value pairs. The format is: | |
571 | // custom_X => value or custom_X_VALUEID => value (for multiple values), VALUEID == -1, -2 etc for new insertions | |
be2fb01f | 572 | $fieldValues = []; |
6a488035 TO |
573 | foreach ($params as $n => $v) { |
574 | if ($customFieldInfo = CRM_Core_BAO_CustomField::getKeyID($n, TRUE)) { | |
575 | $fieldID = (int ) $customFieldInfo[0]; | |
576 | if (CRM_Utils_Type::escape($fieldID, 'Integer', FALSE) === NULL) { | |
e31fc637 | 577 | throw new CRM_Core_Exception(ts('field ID needs to be of type Integer for index %1', |
be2fb01f | 578 | [1 => $fieldID] |
353ffa53 | 579 | )); |
6a488035 TO |
580 | } |
581 | if (!array_key_exists($fieldID, $fieldValues)) { | |
be2fb01f | 582 | $fieldValues[$fieldID] = []; |
6a488035 TO |
583 | } |
584 | $id = -1; | |
585 | if ($customFieldInfo[1]) { | |
586 | $id = (int ) $customFieldInfo[1]; | |
587 | } | |
be2fb01f | 588 | $fieldValues[$fieldID][] = [ |
6a488035 TO |
589 | 'value' => $v, |
590 | 'id' => $id, | |
be2fb01f | 591 | ]; |
6a488035 TO |
592 | } |
593 | } | |
594 | ||
595 | $fieldIDList = implode(',', array_keys($fieldValues)); | |
596 | ||
597 | // format it so that we can just use create | |
598 | $sql = " | |
599 | SELECT cg.table_name as table_name , | |
600 | cg.id as cg_id , | |
601 | cg.is_multiple as is_multiple, | |
63b7d442 | 602 | cg.extends as extends, |
6a488035 TO |
603 | cf.column_name as column_name, |
604 | cf.id as cf_id , | |
3766bd36 | 605 | cf.html_type as html_type , |
b99d7139 | 606 | cf.data_type as data_type , |
607 | cf.serialize as serialize | |
6a488035 TO |
608 | FROM civicrm_custom_group cg, |
609 | civicrm_custom_field cf | |
610 | WHERE cf.custom_group_id = cg.id | |
611 | AND cf.id IN ( $fieldIDList ) | |
612 | "; | |
613 | ||
614 | $dao = CRM_Core_DAO::executeQuery($sql); | |
be2fb01f | 615 | $cvParams = []; |
6a488035 TO |
616 | |
617 | while ($dao->fetch()) { | |
618 | $dataType = $dao->data_type == 'Date' ? 'Timestamp' : $dao->data_type; | |
619 | foreach ($fieldValues[$dao->cf_id] as $fieldValue) { | |
3766bd36 CW |
620 | // Serialize array values |
621 | if (is_array($fieldValue['value']) && CRM_Core_BAO_CustomField::isSerialized($dao)) { | |
622 | $fieldValue['value'] = CRM_Utils_Array::implodePadded($fieldValue['value']); | |
623 | } | |
6a488035 TO |
624 | // Format null values correctly |
625 | if ($fieldValue['value'] === NULL || $fieldValue['value'] === '') { | |
626 | switch ($dataType) { | |
627 | case 'String': | |
628 | case 'Int': | |
629 | case 'Link': | |
630 | case 'Boolean': | |
631 | $fieldValue['value'] = ''; | |
632 | break; | |
633 | ||
634 | case 'Timestamp': | |
635 | $fieldValue['value'] = NULL; | |
636 | break; | |
637 | ||
638 | case 'StateProvince': | |
639 | case 'Country': | |
640 | case 'Money': | |
641 | case 'Float': | |
2aa397bc | 642 | $fieldValue['value'] = (int) 0; |
6a488035 TO |
643 | break; |
644 | } | |
645 | } | |
646 | // Ensure that value is of the right data type | |
647 | elseif (CRM_Utils_Type::escape($fieldValue['value'], $dataType, FALSE) === NULL) { | |
e31fc637 | 648 | throw new CRM_Core_Exception(ts('value: %1 is not of the right field data type: %2', |
be2fb01f | 649 | [ |
353ffa53 TO |
650 | 1 => $fieldValue['value'], |
651 | 2 => $dao->data_type, | |
be2fb01f | 652 | ] |
353ffa53 | 653 | )); |
6a488035 TO |
654 | } |
655 | ||
be2fb01f | 656 | $cvParam = [ |
6a488035 TO |
657 | 'entity_id' => $params['entityID'], |
658 | 'value' => $fieldValue['value'], | |
659 | 'type' => $dataType, | |
660 | 'custom_field_id' => $dao->cf_id, | |
661 | 'custom_group_id' => $dao->cg_id, | |
662 | 'table_name' => $dao->table_name, | |
663 | 'column_name' => $dao->column_name, | |
664 | 'is_multiple' => $dao->is_multiple, | |
b99d7139 | 665 | 'serialize' => $dao->serialize, |
63b7d442 | 666 | 'extends' => $dao->extends, |
be2fb01f | 667 | ]; |
6a488035 | 668 | |
5013100c | 669 | if (!empty($params['id'])) { |
670 | $cvParam['id'] = $params['id']; | |
671 | } | |
672 | ||
e7dcccf0 CW |
673 | if ($cvParam['type'] == 'File') { |
674 | $cvParam['file_id'] = $fieldValue['value']; | |
675 | } | |
676 | ||
6a488035 | 677 | if (!array_key_exists($dao->table_name, $cvParams)) { |
be2fb01f | 678 | $cvParams[$dao->table_name] = []; |
6a488035 TO |
679 | } |
680 | ||
681 | if (!array_key_exists($fieldValue['id'], $cvParams[$dao->table_name])) { | |
be2fb01f | 682 | $cvParams[$dao->table_name][$fieldValue['id']] = []; |
6a488035 TO |
683 | } |
684 | ||
685 | if ($fieldValue['id'] > 0) { | |
686 | $cvParam['id'] = $fieldValue['id']; | |
687 | } | |
688 | $cvParams[$dao->table_name][$fieldValue['id']][] = $cvParam; | |
689 | } | |
690 | } | |
691 | ||
692 | if (!empty($cvParams)) { | |
693 | self::create($cvParams); | |
be2fb01f | 694 | return ['is_error' => 0, 'result' => 1]; |
6a488035 TO |
695 | } |
696 | ||
e31fc637 | 697 | throw new CRM_Core_Exception(ts('Unknown error')); |
6a488035 TO |
698 | } |
699 | ||
700 | /** | |
100fef9d | 701 | * Take in an array of entityID, custom_ID |
6a488035 TO |
702 | * and gets the value from the appropriate table. |
703 | * | |
704 | * To get the values of custom fields with IDs 13 and 43 for contact ID 1327, use: | |
705 | * $params = array( 'entityID' => 1327, 'custom_13' => 1, 'custom_43' => 1 ); | |
706 | * | |
b44e3f84 | 707 | * Entity Type will be inferred by the custom fields you request |
6a488035 TO |
708 | * Specify $params['entityType'] if you do not supply any custom fields to return |
709 | * and entity type is other than Contact | |
710 | * | |
711 | * @array $params | |
712 | * | |
c490a46a | 713 | * @param array $params |
2a6da8d7 EM |
714 | * |
715 | * @throws Exception | |
6a488035 | 716 | * @return array |
6a488035 | 717 | */ |
f1b78592 | 718 | public static function getValues($params) { |
6a488035 TO |
719 | if (empty($params)) { |
720 | return NULL; | |
721 | } | |
722 | if (!isset($params['entityID']) || | |
723 | CRM_Utils_Type::escape($params['entityID'], | |
724 | 'Integer', FALSE | |
725 | ) === NULL | |
726 | ) { | |
727 | return CRM_Core_Error::createAPIError(ts('entityID needs to be set and of type Integer')); | |
728 | } | |
729 | ||
730 | // first collect all the ids. The format is: | |
731 | // custom_ID | |
be2fb01f | 732 | $fieldIDs = []; |
6a488035 TO |
733 | foreach ($params as $n => $v) { |
734 | $key = $idx = NULL; | |
735 | if (substr($n, 0, 7) == 'custom_') { | |
736 | $idx = substr($n, 7); | |
737 | if (CRM_Utils_Type::escape($idx, 'Integer', FALSE) === NULL) { | |
738 | return CRM_Core_Error::createAPIError(ts('field ID needs to be of type Integer for index %1', | |
be2fb01f | 739 | [1 => $idx] |
353ffa53 | 740 | )); |
6a488035 | 741 | } |
f1b78592 | 742 | $fieldIDs[] = (int) $idx; |
6a488035 TO |
743 | } |
744 | } | |
745 | ||
f1b78592 | 746 | $default = array_merge(['Contact'], CRM_Contact_BAO_ContactType::basicTypes(TRUE)); |
6a488035 TO |
747 | if (!($type = CRM_Utils_Array::value('entityType', $params)) || |
748 | in_array($params['entityType'], $default) | |
749 | ) { | |
750 | $type = NULL; | |
751 | } | |
752 | else { | |
753 | $entities = CRM_Core_SelectValues::customGroupExtends(); | |
754 | if (!array_key_exists($type, $entities)) { | |
755 | if (in_array($type, $entities)) { | |
756 | $type = $entities[$type]; | |
757 | if (in_array($type, $default)) { | |
758 | $type = NULL; | |
759 | } | |
760 | } | |
761 | else { | |
762 | return CRM_Core_Error::createAPIError(ts('Invalid entity type') . ': "' . $type . '"'); | |
763 | } | |
764 | } | |
765 | } | |
766 | ||
767 | $values = self::getEntityValues($params['entityID'], | |
768 | $type, | |
769 | $fieldIDs | |
770 | ); | |
771 | if (empty($values)) { | |
772 | // note that this behaviour is undesirable from an API point of view - it should return an empty array | |
773 | // since this is also called by the merger code & not sure the consequences of changing | |
774 | // are just handling undoing this in the api layer. ie. converting the error back into a success | |
be2fb01f | 775 | $result = [ |
6a488035 TO |
776 | 'is_error' => 1, |
777 | 'error_message' => 'No values found for the specified entity ID and custom field(s).', | |
be2fb01f | 778 | ]; |
6a488035 TO |
779 | return $result; |
780 | } | |
781 | else { | |
be2fb01f | 782 | $result = [ |
6a488035 TO |
783 | 'is_error' => 0, |
784 | 'entityID' => $params['entityID'], | |
be2fb01f | 785 | ]; |
6a488035 TO |
786 | foreach ($values as $id => $value) { |
787 | $result["custom_{$id}"] = $value; | |
788 | } | |
789 | return $result; | |
790 | } | |
791 | } | |
96025800 | 792 | |
6a488035 | 793 | } |