Merge pull request #21045 from colemanw/fixSearchKitTaskPermissions
[civicrm-core.git] / CRM / Core / BAO / CustomGroup.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 |
9 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * Business object for managing custom data groups.
20 */
21 class CRM_Core_BAO_CustomGroup extends CRM_Core_DAO_CustomGroup {
22
23 /**
24 * Class constructor.
25 */
26 public function __construct() {
27 parent::__construct();
28 }
29
30 /**
31 * Takes an associative array and creates a custom group object.
32 *
33 * This function is invoked from within the web form layer and also from the api layer
34 *
35 * @param array $params
36 * (reference) an assoc array of name/value pairs.
37 *
38 * @return CRM_Core_DAO_CustomGroup
39 * @throws \Exception
40 */
41 public static function create(&$params) {
42 // create custom group dao, populate fields and then save.
43 $group = new CRM_Core_DAO_CustomGroup();
44 if (isset($params['title'])) {
45 $group->title = $params['title'];
46 }
47
48 $extendsChildType = NULL;
49 // lets allow user to pass direct child type value, CRM-6893
50 if (!empty($params['extends_entity_column_value'])) {
51 $extendsChildType = $params['extends_entity_column_value'];
52 }
53 if (!CRM_Utils_System::isNull($extendsChildType)) {
54 $b = self::getMungedEntity($params['extends'], $params['extends_entity_column_id'] ?? NULL);
55 $registeredSubTypes = self::getSubTypes()[$b];
56 if (is_array($extendsChildType)) {
57 foreach ($extendsChildType as $childType) {
58 if (!array_key_exists($childType, $registeredSubTypes) && !in_array($childType, $registeredSubTypes, TRUE)) {
59 throw new CRM_Core_Exception('Supplied Sub type is not valid for the specified entitiy');
60 }
61 }
62 }
63 else {
64 if (!array_key_exists($extendsChildType, $registeredSubTypes) && !in_array($extendsChildType, $registeredSubTypes, TRUE)) {
65 throw new CRM_Core_Exception('Supplied Sub type is not valid for the specified entitiy');
66 }
67 $extendsChildType = [$extendsChildType];
68 }
69 $extendsChildType = implode(CRM_Core_DAO::VALUE_SEPARATOR, $extendsChildType);
70 if ($params['extends'] == 'Relationship') {
71 $extendsChildType = str_replace(['_a_b', '_b_a'], [
72 '',
73 '',
74 ], $extendsChildType);
75 }
76 if (substr($extendsChildType, 0, 1) != CRM_Core_DAO::VALUE_SEPARATOR) {
77 $extendsChildType = CRM_Core_DAO::VALUE_SEPARATOR . $extendsChildType .
78 CRM_Core_DAO::VALUE_SEPARATOR;
79 }
80 }
81 else {
82 $extendsChildType = 'null';
83 }
84 $group->extends_entity_column_value = $extendsChildType;
85
86 if (isset($params['id'])) {
87 $oldWeight = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $params['id'], 'weight', 'id');
88 }
89 else {
90 $oldWeight = 0;
91 }
92 $group->weight = CRM_Utils_Weight::updateOtherWeights('CRM_Core_DAO_CustomGroup', $oldWeight, CRM_Utils_Array::value('weight', $params, FALSE));
93 $fields = [
94 'style',
95 'collapse_display',
96 'collapse_adv_display',
97 'help_pre',
98 'help_post',
99 'is_active',
100 'is_multiple',
101 'icon',
102 'extends_entity_column_id',
103 'extends',
104 ];
105 $current_db_version = CRM_Core_BAO_Domain::version();
106 $is_public_version = version_compare($current_db_version, '4.7.19', '>=');
107 if ($is_public_version) {
108 $fields[] = 'is_public';
109 }
110 foreach ($fields as $field) {
111 if (isset($params[$field])) {
112 $group->$field = $params[$field];
113 }
114 }
115 $group->max_multiple = isset($params['is_multiple']) ? (isset($params['max_multiple']) &&
116 $params['max_multiple'] >= '0'
117 ) ? $params['max_multiple'] : 'null' : 'null';
118
119 $tableName = $tableNameNeedingIndexUpdate = NULL;
120 if (isset($params['id'])) {
121 $group->id = $params['id'];
122
123 if (isset($params['is_multiple'])) {
124 // check whether custom group was changed from single-valued to multiple-valued
125 $isMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup',
126 $params['id'],
127 'is_multiple'
128 );
129
130 // dev/core#227 Fix issue where is_multiple in params maybe an empty string if checkbox is not rendered on the form.
131 $paramsIsMultiple = empty($params['is_multiple']) ? 0 : 1;
132 if ($paramsIsMultiple != $isMultiple) {
133 $tableNameNeedingIndexUpdate = CRM_Core_DAO::getFieldValue(
134 'CRM_Core_DAO_CustomGroup',
135 $params['id'],
136 'table_name'
137 );
138 }
139 }
140 }
141 else {
142 $group->created_id = $params['created_id'] ?? NULL;
143 $group->created_date = $params['created_date'] ?? NULL;
144
145 // Process name only during create, so it never changes
146 if (!empty($params['name'])) {
147 $group->name = CRM_Utils_String::munge($params['name']);
148 }
149 else {
150 $group->name = CRM_Utils_String::munge($group->title);
151 }
152
153 self::validateCustomGroupName($group);
154
155 if (isset($params['table_name'])) {
156 $tableName = $params['table_name'];
157
158 if (CRM_Core_DAO_AllCoreTables::isCoreTable($tableName)) {
159 // Bad idea. Prevent group creation because it might lead to a broken configuration.
160 throw new CRM_Core_Exception(ts('Cannot create custom table because %1 is already a core table.', ['1' => $tableName]));
161 }
162 }
163 }
164
165 if (array_key_exists('is_reserved', $params)) {
166 $group->is_reserved = $params['is_reserved'] ? 1 : 0;
167 }
168 $op = isset($params['id']) ? 'edit' : 'create';
169 CRM_Utils_Hook::pre($op, 'CustomGroup', CRM_Utils_Array::value('id', $params), $params);
170
171 // enclose the below in a transaction
172 $transaction = new CRM_Core_Transaction();
173
174 $group->save();
175 if (!isset($params['id'])) {
176 if (!isset($params['table_name'])) {
177 $munged_title = strtolower(CRM_Utils_String::munge($group->title, '_', 13));
178 $tableName = "civicrm_value_{$munged_title}_{$group->id}";
179 }
180 $group->table_name = $tableName;
181 CRM_Core_DAO::setFieldValue('CRM_Core_DAO_CustomGroup',
182 $group->id,
183 'table_name',
184 $tableName
185 );
186
187 // now create the table associated with this group
188 self::createTable($group);
189 }
190 elseif ($tableNameNeedingIndexUpdate) {
191 CRM_Core_BAO_SchemaHandler::changeUniqueToIndex($tableNameNeedingIndexUpdate, CRM_Utils_Array::value('is_multiple', $params));
192 }
193
194 if (CRM_Utils_Array::value('overrideFKConstraint', $params) == 1) {
195 $table = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup',
196 $params['id'],
197 'table_name'
198 );
199 CRM_Core_BAO_SchemaHandler::changeFKConstraint($table, self::mapTableName($params['extends']));
200 }
201 $transaction->commit();
202
203 // reset the cache
204 CRM_Utils_System::flushCache();
205
206 if ($tableName) {
207 CRM_Utils_Hook::post('create', 'CustomGroup', $group->id, $group);
208 }
209 else {
210 CRM_Utils_Hook::post('edit', 'CustomGroup', $group->id, $group);
211 }
212
213 return $group;
214 }
215
216 /**
217 * Fetch object based on array of properties.
218 *
219 * @param array $params
220 * (reference ) an assoc array of name/value pairs.
221 * @param array $defaults
222 * (reference ) an assoc array to hold the flattened values.
223 *
224 * @return CRM_Core_DAO_CustomGroup
225 */
226 public static function retrieve(&$params, &$defaults) {
227 return CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_CustomGroup', $params, $defaults);
228 }
229
230 /**
231 * Ensure group name does not conflict with an existing field
232 *
233 * @param CRM_Core_DAO_CustomGroup $group
234 */
235 public static function validateCustomGroupName(CRM_Core_DAO_CustomGroup $group) {
236 $extends = in_array($group->extends, CRM_Contact_BAO_ContactType::basicTypes(TRUE)) ? 'Contact' : $group->extends;
237 $extendsDAO = CRM_Core_DAO_AllCoreTables::getFullName($extends);
238 if ($extendsDAO) {
239 $fields = array_column($extendsDAO::fields(), 'name');
240 if (in_array($group->name, $fields)) {
241 $group->name .= '0';
242 }
243 }
244 }
245
246 /**
247 * Update the is_active flag in the db.
248 *
249 * @param int $id
250 * Id of the database record.
251 * @param bool $is_active
252 * Value we want to set the is_active field.
253 *
254 * @return bool
255 * true if we found and updated the object, else false
256 */
257 public static function setIsActive($id, $is_active) {
258 // reset the cache
259 Civi::cache('fields')->flush();
260 // reset ACL and system caches.
261 CRM_Core_BAO_Cache::resetCaches();
262
263 if (!$is_active) {
264 CRM_Core_BAO_UFField::setUFFieldStatus($id, $is_active);
265 }
266
267 return CRM_Core_DAO::setFieldValue('CRM_Core_DAO_CustomGroup', $id, 'is_active', $is_active);
268 }
269
270 /**
271 * Determine if given entity (sub)type has any custom groups
272 *
273 * @param string $extends
274 * E.g. "Individual", "Activity".
275 * @param int $columnId
276 * E.g. custom-group matching mechanism (usu NULL for matching on sub type-id); see extends_entity_column_id.
277 * @param string $columnValue
278 * E.g. "Student" or "3" or "3\05"; see extends_entity_column_value.
279 *
280 * @return bool
281 */
282 public static function hasCustomGroup($extends, $columnId, $columnValue) {
283 $dao = new CRM_Core_DAO_CustomGroup();
284 $dao->extends = $extends;
285 $dao->extends_entity_column_id = $columnId;
286 $escapedValue = CRM_Core_DAO::VALUE_SEPARATOR . CRM_Core_DAO::escapeString($columnValue) . CRM_Core_DAO::VALUE_SEPARATOR;
287 $dao->whereAdd("extends_entity_column_value LIKE \"%$escapedValue%\"");
288 //$dao->extends_entity_column_value = $columnValue;
289 return (bool) $dao->find();
290 }
291
292 /**
293 * Determine if there are any CustomGroups for the given $activityTypeId.
294 * If none found, create one.
295 *
296 * @param int $activityTypeId
297 *
298 * @return bool
299 * TRUE if a group is found or created; FALSE on error
300 */
301 public static function autoCreateByActivityType($activityTypeId) {
302 if (self::hasCustomGroup('Activity', NULL, $activityTypeId)) {
303 return TRUE;
304 }
305 // everything
306 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE, FALSE);
307 $params = [
308 'version' => 3,
309 'extends' => 'Activity',
310 'extends_entity_column_id' => NULL,
311 'extends_entity_column_value' => CRM_Utils_Array::implodePadded([$activityTypeId]),
312 'title' => ts('%1 Questions', [1 => $activityTypes[$activityTypeId]]),
313 'style' => 'Inline',
314 'is_active' => 1,
315 ];
316 $result = civicrm_api('CustomGroup', 'create', $params);
317 return !$result['is_error'];
318 }
319
320 /**
321 * Get custom groups/fields data for type of entity in a tree structure representing group->field hierarchy
322 * This may also include entity specific data values.
323 *
324 * An array containing all custom groups and their custom fields is returned.
325 *
326 * @param string $entityType
327 * Of the contact whose contact type is needed.
328 * @param array $toReturn
329 * What data should be returned. ['custom_group' => ['id', 'name', etc.], 'custom_field' => ['id', 'label', etc.]]
330 * @param int $entityID
331 * @param int $groupID
332 * @param array $subTypes
333 * @param string $subName
334 * @param bool $fromCache
335 * @param bool $onlySubType
336 * Only return specified subtype or return specified subtype + unrestricted fields.
337 * @param bool $returnAll
338 * Do not restrict by subtype at all. (The parameter feels a bit cludgey but is only used from the
339 * api - through which it is properly tested - so can be refactored with some comfort.)
340 * @param bool|int $checkPermission
341 * Either a CRM_Core_Permission constant or FALSE to disable checks
342 * @param string|int $singleRecord
343 * holds 'new' or id if view/edit/copy form for a single record is being loaded.
344 * @param bool $showPublicOnly
345 *
346 * @return array
347 * Custom field 'tree'.
348 *
349 * The returned array is keyed by group id and has the custom group table fields
350 * and a subkey 'fields' holding the specific custom fields.
351 * If entityId is passed in the fields keys have a subkey 'customValue' which holds custom data
352 * if set for the given entity. This is structured as an array of values with each one having the keys 'id', 'data'
353 *
354 * @todo - review this - It also returns an array called 'info' with tables, select, from, where keys
355 * The reason for the info array in unclear and it could be determined from parsing the group tree after creation
356 * With caching the performance impact would be small & the function would be cleaner
357 *
358 * @throws \CRM_Core_Exception
359 */
360 public static function getTree(
361 $entityType,
362 $toReturn = [],
363 $entityID = NULL,
364 $groupID = NULL,
365 $subTypes = [],
366 $subName = NULL,
367 $fromCache = TRUE,
368 $onlySubType = NULL,
369 $returnAll = FALSE,
370 $checkPermission = CRM_Core_Permission::EDIT,
371 $singleRecord = NULL,
372 $showPublicOnly = FALSE
373 ) {
374 if ($checkPermission === TRUE) {
375 CRM_Core_Error::deprecatedWarning('Unexpected TRUE passed to CustomGroup::getTree $checkPermission param.');
376 $checkPermission = CRM_Core_Permission::EDIT;
377 }
378 if ($entityID) {
379 $entityID = CRM_Utils_Type::escape($entityID, 'Integer');
380 }
381 if (!is_array($subTypes)) {
382 if (empty($subTypes)) {
383 $subTypes = [];
384 }
385 else {
386 if (stristr($subTypes, ',')) {
387 $subTypes = explode(',', $subTypes);
388 }
389 else {
390 $subTypes = explode(CRM_Core_DAO::VALUE_SEPARATOR, trim($subTypes, CRM_Core_DAO::VALUE_SEPARATOR));
391 }
392 }
393 }
394
395 // create a new tree
396
397 // legacy hardcoded list of data to return
398 $tableData = [
399 'custom_field' => [
400 'id',
401 'name',
402 'label',
403 'column_name',
404 'data_type',
405 'html_type',
406 'default_value',
407 'attributes',
408 'is_required',
409 'is_view',
410 'help_pre',
411 'help_post',
412 'options_per_line',
413 'start_date_years',
414 'end_date_years',
415 'date_format',
416 'time_format',
417 'option_group_id',
418 'in_selector',
419 ],
420 'custom_group' => [
421 'id',
422 'name',
423 'table_name',
424 'title',
425 'help_pre',
426 'help_post',
427 'collapse_display',
428 'style',
429 'is_multiple',
430 'extends',
431 'extends_entity_column_id',
432 'extends_entity_column_value',
433 'max_multiple',
434 ],
435 ];
436 $current_db_version = CRM_Core_BAO_Domain::version();
437 $is_public_version = version_compare($current_db_version, '4.7.19', '>=');
438 $serialize_version = version_compare($current_db_version, '5.27.alpha1', '>=');
439 if ($is_public_version) {
440 $tableData['custom_group'][] = 'is_public';
441 }
442 if ($serialize_version) {
443 $tableData['custom_field'][] = 'serialize';
444 }
445 if (!$toReturn || !is_array($toReturn)) {
446 $toReturn = $tableData;
447 }
448 else {
449 // Supply defaults and remove unknown array keys
450 $toReturn = array_intersect_key(array_filter($toReturn) + $tableData, $tableData);
451 // Merge in required fields that we must have
452 $toReturn['custom_field'] = array_unique(array_merge($toReturn['custom_field'], ['id', 'column_name', 'data_type']));
453 $toReturn['custom_group'] = array_unique(array_merge($toReturn['custom_group'], ['id', 'is_multiple', 'table_name', 'name']));
454 // Validate return fields
455 $toReturn['custom_field'] = array_intersect($toReturn['custom_field'], array_keys(CRM_Core_DAO_CustomField::fieldKeys()));
456 $toReturn['custom_group'] = array_intersect($toReturn['custom_group'], array_keys(CRM_Core_DAO_CustomGroup::fieldKeys()));
457 }
458
459 // create select
460 $select = [];
461 foreach ($toReturn as $tableName => $tableColumn) {
462 foreach ($tableColumn as $columnName) {
463 $select[] = "civicrm_{$tableName}.{$columnName} as civicrm_{$tableName}_{$columnName}";
464 }
465 }
466 $strSelect = "SELECT " . implode(', ', $select);
467
468 // from, where, order by
469 $strFrom = "
470 FROM civicrm_custom_group
471 LEFT JOIN civicrm_custom_field ON (civicrm_custom_field.custom_group_id = civicrm_custom_group.id)
472 ";
473
474 // if entity is either individual, organization or household pls get custom groups for 'contact' too.
475 if ($entityType == "Individual" || $entityType == 'Organization' ||
476 $entityType == 'Household'
477 ) {
478 $in = "'$entityType', 'Contact'";
479 }
480 elseif (strpos($entityType, "'") !== FALSE) {
481 // this allows the calling function to send in multiple entity types
482 $in = $entityType;
483 }
484 else {
485 // quote it
486 $in = "'$entityType'";
487 }
488
489 $params = [];
490 $sqlParamKey = 1;
491 $subType = '';
492 if (!empty($subTypes)) {
493 foreach ($subTypes as $key => $subType) {
494 $subTypeClauses[] = self::whereListHas("civicrm_custom_group.extends_entity_column_value", self::validateSubTypeByEntity($entityType, $subType));
495 }
496 $subTypeClause = '(' . implode(' OR ', $subTypeClauses) . ')';
497 if (!$onlySubType) {
498 $subTypeClause = '(' . $subTypeClause . ' OR civicrm_custom_group.extends_entity_column_value IS NULL )';
499 }
500
501 $strWhere = "
502 WHERE civicrm_custom_group.is_active = 1
503 AND civicrm_custom_field.is_active = 1
504 AND civicrm_custom_group.extends IN ($in)
505 AND $subTypeClause
506 ";
507 if ($subName) {
508 $strWhere .= " AND civicrm_custom_group.extends_entity_column_id = %{$sqlParamKey}";
509 $params[$sqlParamKey] = [$subName, 'String'];
510 $sqlParamKey = $sqlParamKey + 1;
511 }
512 }
513 else {
514 $strWhere = "
515 WHERE civicrm_custom_group.is_active = 1
516 AND civicrm_custom_field.is_active = 1
517 AND civicrm_custom_group.extends IN ($in)
518 ";
519 if (!$returnAll) {
520 $strWhere .= "AND civicrm_custom_group.extends_entity_column_value IS NULL";
521 }
522 }
523
524 if ($groupID > 0) {
525 // since we want a specific group id we add it to the where clause
526 $strWhere .= " AND civicrm_custom_group.id = %{$sqlParamKey}";
527 $params[$sqlParamKey] = [$groupID, 'Integer'];
528 }
529 elseif (!$groupID) {
530 // since groupID is false we need to show all Inline groups
531 $strWhere .= " AND civicrm_custom_group.style = 'Inline'";
532 }
533 if ($checkPermission) {
534 // ensure that the user has access to these custom groups
535 $strWhere .= " AND " .
536 CRM_Core_Permission::customGroupClause($checkPermission,
537 'civicrm_custom_group.'
538 );
539 }
540
541 if ($showPublicOnly && $is_public_version) {
542 $strWhere .= "AND civicrm_custom_group.is_public = 1";
543 }
544
545 $orderBy = "
546 ORDER BY civicrm_custom_group.weight,
547 civicrm_custom_group.title,
548 civicrm_custom_field.weight,
549 civicrm_custom_field.label
550 ";
551
552 // final query string
553 $queryString = "$strSelect $strFrom $strWhere $orderBy";
554
555 // lets see if we can retrieve the groupTree from cache
556 $cacheString = $queryString;
557 if ($groupID > 0) {
558 $cacheString .= "_{$groupID}";
559 }
560 else {
561 $cacheString .= "_Inline";
562 }
563
564 $cacheKey = "CRM_Core_DAO_CustomGroup_Query " . md5($cacheString);
565 $multipleFieldGroupCacheKey = "CRM_Core_DAO_CustomGroup_QueryMultipleFields " . md5($cacheString);
566 $cache = CRM_Utils_Cache::singleton();
567 if ($fromCache) {
568 $groupTree = $cache->get($cacheKey);
569 $multipleFieldGroups = $cache->get($multipleFieldGroupCacheKey);
570 }
571
572 if (empty($groupTree)) {
573 list($multipleFieldGroups, $groupTree) = self::buildGroupTree($entityType, $toReturn, $subTypes, $queryString, $params, $subType);
574
575 $cache->set($cacheKey, $groupTree);
576 $cache->set($multipleFieldGroupCacheKey, $multipleFieldGroups);
577 }
578 // entitySelectClauses is an array of select clauses for custom value tables which are not multiple
579 // and have data for the given entities. $entityMultipleSelectClauses is the same for ones with multiple
580 $entitySingleSelectClauses = $entityMultipleSelectClauses = $groupTree['info']['select'] = [];
581 $singleFieldTables = [];
582 // now that we have all the groups and fields, lets get the values
583 // since we need to know the table and field names
584 // add info to groupTree
585
586 if (isset($groupTree['info']) && !empty($groupTree['info']) &&
587 !empty($groupTree['info']['tables']) && $singleRecord != 'new'
588 ) {
589 $select = $from = $where = [];
590 $groupTree['info']['where'] = NULL;
591
592 foreach ($groupTree['info']['tables'] as $table => $fields) {
593 $groupTree['info']['from'][] = $table;
594 $select = [
595 "{$table}.id as {$table}_id",
596 "{$table}.entity_id as {$table}_entity_id",
597 ];
598 foreach ($fields as $column => $dontCare) {
599 $select[] = "{$table}.{$column} as {$table}_{$column}";
600 }
601 $groupTree['info']['select'] = array_merge($groupTree['info']['select'], $select);
602 if ($entityID) {
603 $groupTree['info']['where'][] = "{$table}.entity_id = $entityID";
604 if (in_array($table, $multipleFieldGroups) &&
605 self::customGroupDataExistsForEntity($entityID, $table)
606 ) {
607 $entityMultipleSelectClauses[$table] = $select;
608 }
609 else {
610 $singleFieldTables[] = $table;
611 $entitySingleSelectClauses = array_merge($entitySingleSelectClauses, $select);
612 }
613
614 }
615 }
616 if ($entityID && !empty($singleFieldTables)) {
617 self::buildEntityTreeSingleFields($groupTree, $entityID, $entitySingleSelectClauses, $singleFieldTables);
618 }
619 $multipleFieldTablesWithEntityData = array_keys($entityMultipleSelectClauses);
620 if (!empty($multipleFieldTablesWithEntityData)) {
621 self::buildEntityTreeMultipleFields($groupTree, $entityID, $entityMultipleSelectClauses, $multipleFieldTablesWithEntityData, $singleRecord);
622 }
623
624 }
625 return $groupTree;
626 }
627
628 /**
629 * Clean and validate the filter before it is used in a db query.
630 *
631 * @param string $entityType
632 * @param string $subType
633 *
634 * @return string
635 * @throws \CRM_Core_Exception
636 */
637 protected static function validateSubTypeByEntity($entityType, $subType) {
638 $subType = trim($subType, CRM_Core_DAO::VALUE_SEPARATOR);
639 if (is_numeric($subType)) {
640 return $subType;
641 }
642
643 $contactTypes = CRM_Contact_BAO_ContactType::basicTypeInfo(TRUE);
644 $contactTypes = array_merge($contactTypes, ['Event' => 1]);
645
646 if ($entityType != 'Contact' && !array_key_exists($entityType, $contactTypes)) {
647 throw new CRM_Core_Exception('Invalid Entity Filter');
648 }
649 $subTypes = CRM_Contact_BAO_ContactType::subTypeInfo($entityType, TRUE);
650 $subTypes = array_merge($subTypes, CRM_Event_PseudoConstant::eventType());
651 if (!array_key_exists($subType, $subTypes)) {
652 throw new CRM_Core_Exception('Invalid Filter');
653 }
654 return $subType;
655 }
656
657 /**
658 * Suppose you have a SQL column, $column, which includes a delimited list, and you want
659 * a WHERE condition for rows that include $value. Use whereListHas().
660 *
661 * @param string $column
662 * @param string $value
663 * @param string $delimiter
664 * @return string
665 * SQL condition.
666 */
667 private static function whereListHas($column, $value, $delimiter = CRM_Core_DAO::VALUE_SEPARATOR) {
668 // ?
669 $bareValue = trim($value, $delimiter);
670 $escapedValue = CRM_Utils_Type::escape("%{$delimiter}{$bareValue}{$delimiter}%", 'String', FALSE);
671 return "($column LIKE \"$escapedValue\")";
672 }
673
674 /**
675 * Check whether the custom group has any data for the given entity.
676 *
677 * @param int $entityID
678 * Id of entity for whom we are checking data for.
679 * @param string $table
680 * Table that we are checking.
681 *
682 * @param bool $getCount
683 *
684 * @return bool
685 * does this entity have data in this custom table
686 */
687 public static function customGroupDataExistsForEntity($entityID, $table, $getCount = FALSE) {
688 $query = "
689 SELECT count(id)
690 FROM $table
691 WHERE entity_id = $entityID
692 ";
693 $recordExists = CRM_Core_DAO::singleValueQuery($query);
694 if ($getCount) {
695 return $recordExists;
696 }
697 return (bool) $recordExists;
698 }
699
700 /**
701 * Build the group tree for Custom fields which are not 'is_multiple'
702 *
703 * The combination of all these fields in one query with a 'using' join was not working for
704 * multiple fields. These now have a new behaviour (one at a time) but the single fields still use this
705 * mechanism as it seemed to be acceptable in this context
706 *
707 * @param array $groupTree
708 * (reference) group tree array which is being built.
709 * @param int $entityID
710 * Id of entity for whom the tree is being build up.
711 * @param array $entitySingleSelectClauses
712 * Array of select clauses relevant to the entity.
713 * @param array $singleFieldTablesWithEntityData
714 * Array of tables in which this entity has data.
715 */
716 public static function buildEntityTreeSingleFields(&$groupTree, $entityID, $entitySingleSelectClauses, $singleFieldTablesWithEntityData) {
717 $select = implode(', ', $entitySingleSelectClauses);
718 $fromSQL = " (SELECT $entityID as entity_id ) as first ";
719 foreach ($singleFieldTablesWithEntityData as $table) {
720 $fromSQL .= "\nLEFT JOIN $table USING (entity_id)";
721 }
722
723 $query = "
724 SELECT $select
725 FROM $fromSQL
726 WHERE first.entity_id = $entityID
727 ";
728 self::buildTreeEntityDataFromQuery($groupTree, $query, $singleFieldTablesWithEntityData);
729 }
730
731 /**
732 * Build the group tree for Custom fields which are 'is_multiple'
733 *
734 * This is done one table at a time to avoid Cross-Joins resulting in too many rows being returned
735 *
736 * @param array $groupTree
737 * (reference) group tree array which is being built.
738 * @param int $entityID
739 * Id of entity for whom the tree is being build up.
740 * @param array $entityMultipleSelectClauses
741 * Array of select clauses relevant to the entity.
742 * @param array $multipleFieldTablesWithEntityData
743 * Array of tables in which this entity has data.
744 * @param string|int $singleRecord
745 * holds 'new' or id if view/edit/copy form for a single record is being loaded.
746 */
747 public static function buildEntityTreeMultipleFields(&$groupTree, $entityID, $entityMultipleSelectClauses, $multipleFieldTablesWithEntityData, $singleRecord = NULL) {
748 foreach ($entityMultipleSelectClauses as $table => $selectClauses) {
749 $select = implode(',', $selectClauses);
750 $query = "
751 SELECT $select
752 FROM $table
753 WHERE entity_id = $entityID
754 ";
755 if ($singleRecord) {
756 $offset = $singleRecord - 1;
757 $query .= " LIMIT {$offset}, 1";
758 }
759 self::buildTreeEntityDataFromQuery($groupTree, $query, [$table], $singleRecord);
760 }
761 }
762
763 /**
764 * Build the tree entity data - starting from a query retrieving the custom fields build the group
765 * tree data for the relevant entity (entity is included in the query).
766 *
767 * This function represents shared code between the buildEntityTreeMultipleFields & the buildEntityTreeSingleFields function
768 *
769 * @param array $groupTree
770 * (reference) group tree array which is being built.
771 * @param string $query
772 * @param array $includedTables
773 * Tables to include - required because the function (for historical reasons).
774 * iterates through the group tree
775 * @param string|int $singleRecord
776 * holds 'new' OR id if view/edit/copy form for a single record is being loaded.
777 */
778 public static function buildTreeEntityDataFromQuery(&$groupTree, $query, $includedTables, $singleRecord = NULL) {
779 $dao = CRM_Core_DAO::executeQuery($query);
780 while ($dao->fetch()) {
781 foreach ($groupTree as $groupID => $group) {
782 if ($groupID === 'info') {
783 continue;
784 }
785 $table = $groupTree[$groupID]['table_name'];
786 //working from the groupTree instead of the table list means we have to iterate & exclude.
787 // this could possibly be re-written as other parts of the function have been refactored
788 // for now we just check if the given table is to be included in this function
789 if (!in_array($table, $includedTables)) {
790 continue;
791 }
792 foreach ($group['fields'] as $fieldID => $dontCare) {
793 self::buildCustomFieldData($dao, $groupTree, $table, $groupID, $fieldID, $singleRecord);
794 }
795 }
796 }
797 }
798
799 /**
800 * Build the entity-specific custom data into the group tree on a per-field basis
801 *
802 * @param object $dao
803 * Object representing the custom field to be populated into the groupTree.
804 * @param array $groupTree
805 * (reference) the group tree being build.
806 * @param string $table
807 * Table name.
808 * @param int $groupID
809 * Custom group ID.
810 * @param int $fieldID
811 * Custom field ID.
812 * @param string|int $singleRecord
813 * holds 'new' or id if loading view/edit/copy for a single record.
814 */
815 public static function buildCustomFieldData($dao, &$groupTree, $table, $groupID, $fieldID, $singleRecord = NULL) {
816 $column = $groupTree[$groupID]['fields'][$fieldID]['column_name'];
817 $idName = "{$table}_id";
818 $fieldName = "{$table}_{$column}";
819 $dataType = $groupTree[$groupID]['fields'][$fieldID]['data_type'];
820 if ($dataType == 'File') {
821 if (isset($dao->$fieldName)) {
822 $config = CRM_Core_Config::singleton();
823 $fileDAO = new CRM_Core_DAO_File();
824 $fileDAO->id = $dao->$fieldName;
825
826 if ($fileDAO->find(TRUE)) {
827 $entityIDName = "{$table}_entity_id";
828 $fileHash = CRM_Core_BAO_File::generateFileHash($dao->$entityIDName, $fileDAO->id);
829 $customValue['id'] = $dao->$idName;
830 $customValue['data'] = $fileDAO->uri;
831 $customValue['fid'] = $fileDAO->id;
832 $customValue['fileURL'] = CRM_Utils_System::url('civicrm/file', "reset=1&id={$fileDAO->id}&eid={$dao->$entityIDName}&fcs=$fileHash");
833 $customValue['displayURL'] = NULL;
834 $deleteExtra = ts('Are you sure you want to delete attached file.');
835 $deleteURL = [
836 CRM_Core_Action::DELETE => [
837 'name' => ts('Delete Attached File'),
838 'url' => 'civicrm/file',
839 'qs' => 'reset=1&id=%%id%%&eid=%%eid%%&fid=%%fid%%&action=delete&fcs=%%fcs%%',
840 'extra' => 'onclick = "if (confirm( \'' . $deleteExtra
841 . '\' ) ) this.href+=\'&amp;confirmed=1\'; else return false;"',
842 ],
843 ];
844 $customValue['deleteURL'] = CRM_Core_Action::formLink($deleteURL,
845 CRM_Core_Action::DELETE,
846 [
847 'id' => $fileDAO->id,
848 'eid' => $dao->$entityIDName,
849 'fid' => $fieldID,
850 'fcs' => $fileHash,
851 ],
852 ts('more'),
853 FALSE,
854 'file.manage.delete',
855 'File',
856 $fileDAO->id
857 );
858 $customValue['deleteURLArgs'] = CRM_Core_BAO_File::deleteURLArgs($table, $dao->$entityIDName, $fileDAO->id);
859 $customValue['fileName'] = CRM_Utils_File::cleanFileName(basename($fileDAO->uri));
860 if ($fileDAO->mime_type == "image/jpeg" ||
861 $fileDAO->mime_type == "image/pjpeg" ||
862 $fileDAO->mime_type == "image/gif" ||
863 $fileDAO->mime_type == "image/x-png" ||
864 $fileDAO->mime_type == "image/png"
865 ) {
866 $customValue['displayURL'] = $customValue['fileURL'];
867 $entityId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_EntityFile',
868 $fileDAO->id,
869 'entity_id',
870 'file_id'
871 );
872 $customValue['imageURL'] = str_replace('persist/contribute', 'custom', $config->imageUploadURL) .
873 $fileDAO->uri;
874 list($path) = CRM_Core_BAO_File::path($fileDAO->id, $entityId);
875 if ($path && file_exists($path)) {
876 list($imageWidth, $imageHeight) = getimagesize($path);
877 list($imageThumbWidth, $imageThumbHeight) = CRM_Contact_BAO_Contact::getThumbSize($imageWidth, $imageHeight);
878 $customValue['imageThumbWidth'] = $imageThumbWidth;
879 $customValue['imageThumbHeight'] = $imageThumbHeight;
880 }
881 }
882 }
883 }
884 else {
885 $customValue = [
886 'id' => $dao->$idName,
887 'data' => '',
888 ];
889 }
890 }
891 else {
892 $customValue = [
893 'id' => $dao->$idName,
894 'data' => $dao->$fieldName,
895 ];
896 }
897
898 if (!array_key_exists('customValue', $groupTree[$groupID]['fields'][$fieldID])) {
899 $groupTree[$groupID]['fields'][$fieldID]['customValue'] = [];
900 }
901 if (empty($groupTree[$groupID]['fields'][$fieldID]['customValue']) && !empty($singleRecord)) {
902 $groupTree[$groupID]['fields'][$fieldID]['customValue'] = [$singleRecord => $customValue];
903 }
904 elseif (empty($groupTree[$groupID]['fields'][$fieldID]['customValue'])) {
905 $groupTree[$groupID]['fields'][$fieldID]['customValue'] = [1 => $customValue];
906 }
907 else {
908 $groupTree[$groupID]['fields'][$fieldID]['customValue'][] = $customValue;
909 }
910 }
911
912 /**
913 * Get the group title.
914 *
915 * @param int $id
916 * Id of group.
917 *
918 * @return string
919 * title
920 */
921 public static function getTitle($id) {
922 return CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $id, 'title');
923 }
924
925 /**
926 * Get custom group details for a group.
927 *
928 * An array containing custom group details (including their custom field) is returned.
929 *
930 * @param int $groupId
931 * Group id whose details are needed.
932 * @param bool $searchable
933 * Is this field searchable.
934 * @param array $extends
935 * Which table does it extend if any.
936 *
937 * @param bool $inSelector
938 *
939 * @return array
940 * array consisting of all group and field details
941 */
942 public static function &getGroupDetail($groupId = NULL, $searchable = NULL, &$extends = NULL, $inSelector = NULL) {
943 // create a new tree
944 $groupTree = [];
945
946 // using tableData to build the queryString
947 $tableData = [
948 'civicrm_custom_field' => [
949 'id',
950 'label',
951 'data_type',
952 'html_type',
953 'default_value',
954 'attributes',
955 'is_required',
956 'help_pre',
957 'help_post',
958 'options_per_line',
959 'is_searchable',
960 'start_date_years',
961 'end_date_years',
962 'is_search_range',
963 'date_format',
964 'time_format',
965 'note_columns',
966 'note_rows',
967 'column_name',
968 'is_view',
969 'option_group_id',
970 'in_selector',
971 ],
972 'civicrm_custom_group' => [
973 'id',
974 'name',
975 'title',
976 'help_pre',
977 'help_post',
978 'collapse_display',
979 'collapse_adv_display',
980 'extends',
981 'extends_entity_column_value',
982 'table_name',
983 'is_multiple',
984 ],
985 ];
986
987 // create select
988 $s = [];
989 foreach ($tableData as $tableName => $tableColumn) {
990 foreach ($tableColumn as $columnName) {
991 $s[] = "{$tableName}.{$columnName} as {$tableName}_{$columnName}";
992 }
993 }
994 $select = 'SELECT ' . implode(', ', $s);
995 $params = [];
996 // from, where, order by
997 $from = " FROM civicrm_custom_field, civicrm_custom_group";
998 $where = " WHERE civicrm_custom_field.custom_group_id = civicrm_custom_group.id
999 AND civicrm_custom_group.is_active = 1
1000 AND civicrm_custom_field.is_active = 1 ";
1001 if ($groupId) {
1002 $params[1] = [$groupId, 'Integer'];
1003 $where .= " AND civicrm_custom_group.id = %1";
1004 }
1005
1006 if ($searchable) {
1007 $where .= " AND civicrm_custom_field.is_searchable = 1";
1008 }
1009
1010 if ($inSelector) {
1011 $where .= " AND civicrm_custom_field.in_selector = 1 AND civicrm_custom_group.is_multiple = 1 ";
1012 }
1013
1014 if ($extends) {
1015 $clause = [];
1016 foreach ($extends as $e) {
1017 $clause[] = "civicrm_custom_group.extends = '$e'";
1018 }
1019 $where .= " AND ( " . implode(' OR ', $clause) . " ) ";
1020
1021 //include case activities customdata if case is enabled
1022 if (in_array('Activity', $extends)) {
1023 $extendValues = implode(',', array_keys(CRM_Core_PseudoConstant::activityType(TRUE, TRUE, FALSE, 'label', TRUE)));
1024 $where .= " AND ( civicrm_custom_group.extends_entity_column_value IS NULL OR REPLACE( civicrm_custom_group.extends_entity_column_value, %2, ' ') IN ($extendValues) ) ";
1025 $params[2] = [CRM_Core_DAO::VALUE_SEPARATOR, 'String'];
1026 }
1027 }
1028
1029 // ensure that the user has access to these custom groups
1030 $where .= " AND " .
1031 CRM_Core_Permission::customGroupClause(CRM_Core_Permission::VIEW,
1032 'civicrm_custom_group.'
1033 );
1034
1035 $orderBy = " ORDER BY civicrm_custom_group.weight, civicrm_custom_field.weight";
1036
1037 // final query string
1038 $queryString = $select . $from . $where . $orderBy;
1039
1040 // dummy dao needed
1041 $crmDAO = CRM_Core_DAO::executeQuery($queryString, $params);
1042
1043 // process records
1044 while ($crmDAO->fetch()) {
1045 $groupId = $crmDAO->civicrm_custom_group_id;
1046 $fieldId = $crmDAO->civicrm_custom_field_id;
1047
1048 // create an array for groups if it does not exist
1049 if (!array_key_exists($groupId, $groupTree)) {
1050 $groupTree[$groupId] = [];
1051 $groupTree[$groupId]['id'] = $groupId;
1052
1053 foreach ($tableData['civicrm_custom_group'] as $v) {
1054 $fullField = "civicrm_custom_group_" . $v;
1055
1056 if ($v == 'id' || is_null($crmDAO->$fullField)) {
1057 continue;
1058 }
1059
1060 $groupTree[$groupId][$v] = $crmDAO->$fullField;
1061 }
1062
1063 $groupTree[$groupId]['fields'] = [];
1064 }
1065
1066 // add the fields now (note - the query row will always contain a field)
1067 $groupTree[$groupId]['fields'][$fieldId] = [];
1068 $groupTree[$groupId]['fields'][$fieldId]['id'] = $fieldId;
1069
1070 foreach ($tableData['civicrm_custom_field'] as $v) {
1071 $fullField = "civicrm_custom_field_" . $v;
1072 if ($v == 'id' || is_null($crmDAO->$fullField)) {
1073 continue;
1074 }
1075 $groupTree[$groupId]['fields'][$fieldId][$v] = $crmDAO->$fullField;
1076 }
1077 }
1078
1079 return $groupTree;
1080 }
1081
1082 /**
1083 * @param $entityType
1084 * @param $path
1085 * @param string $cidToken
1086 *
1087 * @return array
1088 */
1089 public static function &getActiveGroups($entityType, $path, $cidToken = '%%cid%%') {
1090 // for Group's
1091 $customGroupDAO = new CRM_Core_DAO_CustomGroup();
1092
1093 // get 'Tab' and 'Tab with table' groups
1094 $customGroupDAO->whereAdd("style IN ('Tab', 'Tab with table')");
1095 $customGroupDAO->whereAdd("is_active = 1");
1096
1097 // add whereAdd for entity type
1098 self::_addWhereAdd($customGroupDAO, $entityType, $cidToken);
1099
1100 $groups = [];
1101
1102 $permissionClause = CRM_Core_Permission::customGroupClause(CRM_Core_Permission::VIEW, NULL, TRUE);
1103 $customGroupDAO->whereAdd($permissionClause);
1104
1105 // order by weight
1106 $customGroupDAO->orderBy('weight');
1107 $customGroupDAO->find();
1108
1109 // process each group with menu tab
1110 while ($customGroupDAO->fetch()) {
1111 $group = [];
1112 $group['id'] = $customGroupDAO->id;
1113 $group['path'] = $path;
1114 $group['title'] = "$customGroupDAO->title";
1115 $group['query'] = "reset=1&gid={$customGroupDAO->id}&cid={$cidToken}";
1116 $group['extra'] = ['gid' => $customGroupDAO->id];
1117 $group['table_name'] = $customGroupDAO->table_name;
1118 $group['is_multiple'] = $customGroupDAO->is_multiple;
1119 $group['icon'] = $customGroupDAO->icon;
1120 $groups[] = $group;
1121 }
1122
1123 return $groups;
1124 }
1125
1126 /**
1127 * Get the table name for the entity type
1128 * currently if entity type is 'Contact', 'Individual', 'Household', 'Organization'
1129 * tableName is 'civicrm_contact'
1130 *
1131 * @param string $entityType
1132 * What entity are we extending here ?.
1133 *
1134 * @return string
1135 *
1136 *
1137 * @see _apachesolr_civiAttachments_dereference_file_parent
1138 */
1139 public static function getTableNameByEntityName($entityType) {
1140 switch ($entityType) {
1141 case 'Contact':
1142 case 'Individual':
1143 case 'Household':
1144 case 'Organization':
1145 return 'civicrm_contact';
1146
1147 default:
1148 return CRM_Core_DAO_AllCoreTables::getTableForEntityName($entityType);
1149 }
1150 }
1151
1152 /**
1153 * Get a list of custom groups which extend a given entity type.
1154 * If there are custom-groups which only apply to certain subtypes,
1155 * those WILL be included.
1156 *
1157 * @param string $entityType
1158 *
1159 * @return CRM_Core_DAO_CustomGroup
1160 */
1161 public static function getAllCustomGroupsByBaseEntity($entityType) {
1162 $customGroupDAO = new CRM_Core_DAO_CustomGroup();
1163 self::_addWhereAdd($customGroupDAO, $entityType, NULL, TRUE);
1164 return $customGroupDAO;
1165 }
1166
1167 /**
1168 * Add the whereAdd clause for the DAO depending on the type of entity
1169 * the custom group is extending.
1170 *
1171 * @param object $customGroupDAO
1172 * @param string $entityType
1173 * What entity are we extending here ?.
1174 *
1175 * @param int $entityID
1176 * @param bool $allSubtypes
1177 */
1178 private static function _addWhereAdd(&$customGroupDAO, $entityType, $entityID = NULL, $allSubtypes = FALSE) {
1179 $addSubtypeClause = FALSE;
1180 // This function isn't really accessible with user data but since the string
1181 // is not passed as a param to the query CRM_Core_DAO::escapeString seems like a harmless
1182 // precaution.
1183 $entityType = CRM_Core_DAO::escapeString($entityType);
1184
1185 switch ($entityType) {
1186 case 'Contact':
1187 // if contact, get all related to contact
1188 $extendList = "'Contact','Individual','Household','Organization'";
1189 $customGroupDAO->whereAdd("extends IN ( $extendList )");
1190 if (!$allSubtypes) {
1191 $addSubtypeClause = TRUE;
1192 }
1193 break;
1194
1195 case 'Individual':
1196 case 'Household':
1197 case 'Organization':
1198 // is I/H/O then get I/H/O and contact
1199 $extendList = "'Contact','$entityType'";
1200 $customGroupDAO->whereAdd("extends IN ( $extendList )");
1201 if (!$allSubtypes) {
1202 $addSubtypeClause = TRUE;
1203 }
1204 break;
1205
1206 default:
1207 $customGroupDAO->whereAdd("extends IN ('$entityType')");
1208 break;
1209 }
1210
1211 if ($addSubtypeClause) {
1212 $csType = is_numeric($entityID) ? CRM_Contact_BAO_Contact::getContactSubType($entityID) : FALSE;
1213
1214 if (!empty($csType)) {
1215 $subtypeClause = [];
1216 foreach ($csType as $subtype) {
1217 $subtype = CRM_Core_DAO::VALUE_SEPARATOR . $subtype .
1218 CRM_Core_DAO::VALUE_SEPARATOR;
1219 $subtypeClause[] = "extends_entity_column_value LIKE '%{$subtype}%'";
1220 }
1221 $subtypeClause[] = "extends_entity_column_value IS NULL";
1222 $customGroupDAO->whereAdd("( " . implode(' OR ', $subtypeClause) .
1223 " )");
1224 }
1225 else {
1226 $customGroupDAO->whereAdd("extends_entity_column_value IS NULL");
1227 }
1228 }
1229 }
1230
1231 /**
1232 * Delete the Custom Group.
1233 *
1234 * @param CRM_Core_BAO_CustomGroup $group
1235 * Custom group object.
1236 * @param bool $force
1237 * whether to force the deletion, even if there are custom fields.
1238 *
1239 * @return bool
1240 * False if field exists for this group, true if group gets deleted.
1241 */
1242 public static function deleteGroup($group, $force = FALSE) {
1243
1244 //check whether this contain any custom fields
1245 $customField = new CRM_Core_DAO_CustomField();
1246 $customField->custom_group_id = $group->id;
1247 $customField->find();
1248
1249 // return early if there are custom fields and we're not
1250 // forcing the delete, otherwise delete the fields one by one
1251 while ($customField->fetch()) {
1252 if (!$force) {
1253 return FALSE;
1254 }
1255 CRM_Core_BAO_CustomField::deleteField($customField);
1256 }
1257
1258 // drop the table associated with this custom group
1259 CRM_Core_BAO_SchemaHandler::dropTable($group->table_name);
1260
1261 //delete custom group
1262 $group->delete();
1263
1264 CRM_Utils_Hook::post('delete', 'CustomGroup', $group->id, $group);
1265
1266 return TRUE;
1267 }
1268
1269 /**
1270 * Delete a record from supplied params.
1271 * API3 calls deleteGroup() which removes the related civicrm_value_X table.
1272 * This function does the same for API4.
1273 *
1274 * @param array $record
1275 * 'id' is required.
1276 * @return CRM_Core_DAO
1277 * @throws CRM_Core_Exception
1278 */
1279 public static function deleteRecord(array $record) {
1280 $table = CRM_Core_DAO::getFieldValue(__CLASS__, $record['id'], 'table_name');
1281 $result = parent::deleteRecord($record);
1282 CRM_Core_BAO_SchemaHandler::dropTable($table);
1283 return $result;
1284 }
1285
1286 /**
1287 * Set defaults.
1288 *
1289 * @param array $groupTree
1290 * @param array $defaults
1291 * @param bool $viewMode
1292 * @param bool $inactiveNeeded
1293 * @param int $action
1294 */
1295 public static function setDefaults(&$groupTree, &$defaults, $viewMode = FALSE, $inactiveNeeded = FALSE, $action = CRM_Core_Action::NONE) {
1296 foreach ($groupTree as $id => $group) {
1297 if (!isset($group['fields'])) {
1298 continue;
1299 }
1300 foreach ($group['fields'] as $field) {
1301 if (isset($field['element_value'])) {
1302 $value = $field['element_value'];
1303 }
1304 elseif (isset($field['default_value']) &&
1305 ($action != CRM_Core_Action::UPDATE ||
1306 // CRM-7548
1307 !array_key_exists('element_value', $field)
1308 )
1309 ) {
1310 $value = $viewMode ? NULL : $field['default_value'];
1311 }
1312 else {
1313 continue;
1314 }
1315
1316 if (empty($field['element_name'])) {
1317 continue;
1318 }
1319
1320 $elementName = $field['element_name'];
1321 $serialize = CRM_Core_BAO_CustomField::isSerialized($field);
1322
1323 if ($serialize) {
1324 if ($field['data_type'] != 'Country' && $field['data_type'] != 'StateProvince' && $field['data_type'] != 'ContactReference') {
1325 $defaults[$elementName] = [];
1326 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($field['id'], $inactiveNeeded);
1327 if ($viewMode) {
1328 $checkedData = explode(CRM_Core_DAO::VALUE_SEPARATOR, substr($value, 1, -1));
1329 if (isset($value)) {
1330 foreach ($customOption as $customValue => $customLabel) {
1331 if (in_array($customValue, $checkedData)) {
1332 if ($field['html_type'] == 'CheckBox') {
1333 $defaults[$elementName][$customValue] = 1;
1334 }
1335 else {
1336 $defaults[$elementName][$customValue] = $customValue;
1337 }
1338 }
1339 else {
1340 $defaults[$elementName][$customValue] = 0;
1341 }
1342 }
1343 }
1344 }
1345 else {
1346 if (isset($field['customValue']['data'])) {
1347 $checkedData = explode(CRM_Core_DAO::VALUE_SEPARATOR, substr($field['customValue']['data'], 1, -1));
1348 foreach ($customOption as $val) {
1349 if (in_array($val['value'], $checkedData)) {
1350 if ($field['html_type'] == 'CheckBox') {
1351 $defaults[$elementName][$val['value']] = 1;
1352 }
1353 else {
1354 $defaults[$elementName][$val['value']] = $val['value'];
1355 }
1356 }
1357 else {
1358 $defaults[$elementName][$val['value']] = 0;
1359 }
1360 }
1361 }
1362 else {
1363 // Values may be "array strings" or actual arrays. Handle both.
1364 if (is_array($value) && count($value)) {
1365 CRM_Utils_Array::formatArrayKeys($value);
1366 $checkedValue = $value;
1367 }
1368 else {
1369 $checkedValue = explode(CRM_Core_DAO::VALUE_SEPARATOR, substr($value, 1, -1));
1370 }
1371 foreach ($customOption as $val) {
1372 if (in_array($val['value'], $checkedValue)) {
1373 if ($field['html_type'] == 'CheckBox') {
1374 $defaults[$elementName][$val['value']] = 1;
1375 }
1376 else {
1377 $defaults[$elementName][$val['value']] = $val['value'];
1378 }
1379 }
1380 }
1381 }
1382 }
1383 }
1384 else {
1385 if (isset($value)) {
1386 // Values may be "array strings" or actual arrays. Handle both.
1387 if (is_array($value) && count($value)) {
1388 CRM_Utils_Array::formatArrayKeys($value);
1389 $checkedValue = $value;
1390 }
1391 // Serialized values from db
1392 elseif ($value === '' || strpos($value, CRM_Core_DAO::VALUE_SEPARATOR) !== FALSE) {
1393 $checkedValue = CRM_Utils_Array::explodePadded($value);
1394 }
1395 // Comma-separated values e.g. from a select2 widget during reload on form error
1396 else {
1397 $checkedValue = explode(',', $value);
1398 }
1399 foreach ($checkedValue as $val) {
1400 if ($val) {
1401 $defaults[$elementName][$val] = $val;
1402 }
1403 }
1404 }
1405 }
1406 }
1407 else {
1408 if ($field['data_type'] == 'Country') {
1409 if ($value) {
1410 $defaults[$elementName] = $value;
1411 }
1412 else {
1413 $config = CRM_Core_Config::singleton();
1414 $defaults[$elementName] = $config->defaultContactCountry;
1415 }
1416 }
1417 else {
1418 if ($field['data_type'] == "Float") {
1419 $defaults[$elementName] = (float) $value;
1420 }
1421 elseif ($field['data_type'] == 'Money' &&
1422 $field['html_type'] == 'Text'
1423 ) {
1424 $defaults[$elementName] = CRM_Utils_Money::formatLocaleNumericRoundedForDefaultCurrency($value);
1425 }
1426 else {
1427 $defaults[$elementName] = $value;
1428 }
1429 }
1430 }
1431 }
1432 }
1433 }
1434
1435 /**
1436 * Old function only called from one place...
1437 * @see CRM_Dedupe_Finder::formatParams
1438 *
1439 * @param array $groupTree
1440 * @param array $params
1441 * @param bool $skipFile
1442 */
1443 public static function postProcess(&$groupTree, &$params, $skipFile = FALSE) {
1444 // Get the Custom form values and groupTree
1445 foreach ($groupTree as $groupID => $group) {
1446 if ($groupID === 'info') {
1447 continue;
1448 }
1449 foreach ($group['fields'] as $field) {
1450 $fieldId = $field['id'];
1451 $serialize = CRM_Core_BAO_CustomField::isSerialized($field);
1452
1453 // Reset all checkbox, radio and multiselect data
1454 if ($field['html_type'] == 'Radio' || $serialize) {
1455 $groupTree[$groupID]['fields'][$fieldId]['customValue']['data'] = 'NULL';
1456 }
1457
1458 $v = NULL;
1459 foreach ($params as $key => $val) {
1460 if (preg_match('/^custom_(\d+)_?(-?\d+)?$/', $key, $match) &&
1461 $match[1] == $field['id']
1462 ) {
1463 $v = $val;
1464 }
1465 }
1466
1467 if (!isset($groupTree[$groupID]['fields'][$fieldId]['customValue'])) {
1468 // field exists in db so populate value from "form".
1469 $groupTree[$groupID]['fields'][$fieldId]['customValue'] = [];
1470 }
1471
1472 // Serialize checkbox and multi-select data (using array keys for checkbox)
1473 if ($serialize) {
1474 $v = ($v && $field['html_type'] === 'Checkbox') ? array_keys($v) : $v;
1475 $v = $v ? CRM_Utils_Array::implodePadded($v) : NULL;
1476 }
1477
1478 switch ($field['html_type']) {
1479
1480 case 'Select Date':
1481 $date = CRM_Utils_Date::processDate($v);
1482 $groupTree[$groupID]['fields'][$fieldId]['customValue']['data'] = $date;
1483 break;
1484
1485 case 'File':
1486 if ($skipFile) {
1487 break;
1488 }
1489
1490 // store the file in d/b
1491 $entityId = explode('=', $groupTree['info']['where'][0]);
1492 $fileParams = ['upload_date' => date('YmdHis')];
1493
1494 if ($groupTree[$groupID]['fields'][$fieldId]['customValue']['fid']) {
1495 $fileParams['id'] = $groupTree[$groupID]['fields'][$fieldId]['customValue']['fid'];
1496 }
1497 if (!empty($v)) {
1498 $fileParams['uri'] = $v['name'];
1499 $fileParams['mime_type'] = $v['type'];
1500 CRM_Core_BAO_File::filePostProcess($v['name'],
1501 $groupTree[$groupID]['fields'][$fieldId]['customValue']['fid'],
1502 $groupTree[$groupID]['table_name'],
1503 trim($entityId[1]),
1504 FALSE,
1505 TRUE,
1506 $fileParams,
1507 'custom_' . $fieldId,
1508 $v['type']
1509 );
1510 }
1511 $defaults = [];
1512 $paramsFile = [
1513 'entity_table' => $groupTree[$groupID]['table_name'],
1514 'entity_id' => $entityId[1],
1515 ];
1516
1517 CRM_Core_DAO::commonRetrieve('CRM_Core_DAO_EntityFile',
1518 $paramsFile,
1519 $defaults
1520 );
1521
1522 $groupTree[$groupID]['fields'][$fieldId]['customValue']['data'] = $defaults['file_id'];
1523 break;
1524
1525 default:
1526 $groupTree[$groupID]['fields'][$fieldId]['customValue']['data'] = $v;
1527 break;
1528 }
1529 }
1530 }
1531 }
1532
1533 /**
1534 * Generic function to build all the form elements for a specific group tree.
1535 *
1536 * @param CRM_Core_Form $form
1537 * The form object.
1538 * @param array $groupTree
1539 * The group tree object.
1540 * @param bool $inactiveNeeded
1541 * Return inactive custom groups.
1542 * @param string $prefix
1543 * Prefix for custom grouptree assigned to template.
1544 *
1545 * @throws \CiviCRM_API3_Exception
1546 */
1547 public static function buildQuickForm(&$form, &$groupTree, $inactiveNeeded = FALSE, $prefix = '') {
1548 $form->assign_by_ref("{$prefix}groupTree", $groupTree);
1549
1550 foreach ($groupTree as $id => $group) {
1551 foreach ($group['fields'] as $field) {
1552 $required = $field['is_required'] ?? NULL;
1553 //fix for CRM-1620
1554 if ($field['data_type'] == 'File') {
1555 if (!empty($field['element_value']['data'])) {
1556 $required = 0;
1557 }
1558 }
1559
1560 $fieldId = $field['id'];
1561 $elementName = $field['element_name'];
1562 CRM_Core_BAO_CustomField::addQuickFormElement($form, $elementName, $fieldId, $required);
1563 if ($form->getAction() == CRM_Core_Action::VIEW) {
1564 $form->getElement($elementName)->freeze();
1565 }
1566 }
1567 }
1568 }
1569
1570 /**
1571 * Extract the get params from the url, validate and store it in session.
1572 *
1573 * @param CRM_Core_Form $form
1574 * The form object.
1575 * @param string $type
1576 * The type of custom group we are using.
1577 *
1578 * @return array
1579 * @throws \CRM_Core_Exception
1580 * @throws \CiviCRM_API3_Exception
1581 */
1582 public static function extractGetParams(&$form, $type) {
1583 if (empty($_GET)) {
1584 return [];
1585 }
1586
1587 $groupTree = CRM_Core_BAO_CustomGroup::getTree($type);
1588 $customValue = [];
1589 $htmlType = [
1590 'CheckBox',
1591 'Multi-Select',
1592 'Select',
1593 'Radio',
1594 ];
1595
1596 foreach ($groupTree as $group) {
1597 if (!isset($group['fields'])) {
1598 continue;
1599 }
1600 foreach ($group['fields'] as $key => $field) {
1601 $fieldName = 'custom_' . $key;
1602 $value = CRM_Utils_Request::retrieve($fieldName, 'String', $form, FALSE, NULL, 'GET');
1603
1604 if ($value) {
1605 $valid = FALSE;
1606 if (!in_array($field['html_type'], $htmlType) ||
1607 $field['data_type'] == 'Boolean'
1608 ) {
1609 $valid = CRM_Core_BAO_CustomValue::typecheck($field['data_type'], $value);
1610 }
1611 if (CRM_Core_BAO_CustomField::isSerialized($field)) {
1612 $value = str_replace("|", ",", $value);
1613 $mulValues = explode(',', $value);
1614 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($key, TRUE);
1615 $val = [];
1616 foreach ($mulValues as $v1) {
1617 foreach ($customOption as $coID => $coValue) {
1618 if (strtolower(trim($coValue['label'])) ==
1619 strtolower(trim($v1))
1620 ) {
1621 $val[$coValue['value']] = 1;
1622 }
1623 }
1624 }
1625 if (!empty($val)) {
1626 $value = $val;
1627 $valid = TRUE;
1628 }
1629 else {
1630 $value = NULL;
1631 }
1632 }
1633 elseif ($field['html_type'] === 'Select' ||
1634 ($field['html_type'] === 'Radio' &&
1635 $field['data_type'] !== 'Boolean'
1636 )
1637 ) {
1638 $customOption = CRM_Core_BAO_CustomOption::getCustomOption($key, TRUE);
1639 foreach ($customOption as $customID => $coValue) {
1640 if (strtolower(trim($coValue['label'])) ==
1641 strtolower(trim($value))
1642 ) {
1643 $value = $coValue['value'];
1644 $valid = TRUE;
1645 }
1646 }
1647 }
1648 elseif ($field['data_type'] === 'Date') {
1649 $valid = CRM_Utils_Rule::date($value);
1650 }
1651
1652 if ($valid) {
1653 $customValue[$fieldName] = $value;
1654 }
1655 }
1656 }
1657 }
1658
1659 return $customValue;
1660 }
1661
1662 /**
1663 * Check the type of custom field type (eg: Used for Individual, Contribution, etc)
1664 * this function is used to get the custom fields of a type (eg: Used for Individual, Contribution, etc )
1665 *
1666 * @param int $customFieldId
1667 * Custom field id.
1668 * @param array $removeCustomFieldTypes
1669 * Remove custom fields of a type eg: array("Individual") ;.
1670 *
1671 * @return bool
1672 * false if it matches else true
1673 *
1674 * @throws \CRM_Core_Exception
1675 */
1676 public static function checkCustomField($customFieldId, &$removeCustomFieldTypes) {
1677 $query = 'SELECT cg.extends as extends
1678 FROM civicrm_custom_group as cg, civicrm_custom_field as cf
1679 WHERE cg.id = cf.custom_group_id
1680 AND cf.id =' .
1681 CRM_Utils_Type::escape($customFieldId, 'Integer');
1682
1683 $extends = CRM_Core_DAO::singleValueQuery($query);
1684
1685 if (in_array($extends, $removeCustomFieldTypes)) {
1686 return FALSE;
1687 }
1688 return TRUE;
1689 }
1690
1691 /**
1692 * @param $table
1693 *
1694 * @return string
1695 * @throws Exception
1696 */
1697 public static function mapTableName($table) {
1698 switch ($table) {
1699 case 'Contact':
1700 case 'Individual':
1701 case 'Household':
1702 case 'Organization':
1703 return 'civicrm_contact';
1704
1705 case 'Activity':
1706 return 'civicrm_activity';
1707
1708 case 'Group':
1709 return 'civicrm_group';
1710
1711 case 'Contribution':
1712 return 'civicrm_contribution';
1713
1714 case 'ContributionRecur':
1715 return 'civicrm_contribution_recur';
1716
1717 case 'Relationship':
1718 return 'civicrm_relationship';
1719
1720 case 'Event':
1721 return 'civicrm_event';
1722
1723 case 'Membership':
1724 return 'civicrm_membership';
1725
1726 case 'Participant':
1727 case 'ParticipantRole':
1728 case 'ParticipantEventName':
1729 case 'ParticipantEventType':
1730 return 'civicrm_participant';
1731
1732 case 'Grant':
1733 return 'civicrm_grant';
1734
1735 case 'Pledge':
1736 return 'civicrm_pledge';
1737
1738 case 'Address':
1739 return 'civicrm_address';
1740
1741 case 'Campaign':
1742 return 'civicrm_campaign';
1743
1744 default:
1745 $query = "
1746 SELECT IF( EXISTS(SELECT name FROM civicrm_contact_type WHERE name like %1), 1, 0 )";
1747 $qParams = [1 => [$table, 'String']];
1748 $result = CRM_Core_DAO::singleValueQuery($query, $qParams);
1749
1750 if ($result) {
1751 return 'civicrm_contact';
1752 }
1753 else {
1754 $extendObjs = CRM_Core_OptionGroup::values('cg_extend_objects', FALSE, FALSE, FALSE, NULL, 'name');
1755 if (array_key_exists($table, $extendObjs)) {
1756 return $extendObjs[$table];
1757 }
1758 throw new CRM_Core_Exception('Unknown error');
1759 }
1760 }
1761 }
1762
1763 /**
1764 * @param $group
1765 *
1766 * @throws \Exception
1767 */
1768 public static function createTable($group) {
1769 $params = [
1770 'name' => $group->table_name,
1771 'is_multiple' => $group->is_multiple ? 1 : 0,
1772 'extends_name' => self::mapTableName($group->extends),
1773 ];
1774
1775 $tableParams = CRM_Core_BAO_CustomField::defaultCustomTableSchema($params);
1776
1777 CRM_Core_BAO_SchemaHandler::createTable($tableParams);
1778 }
1779
1780 /**
1781 * Function returns formatted groupTree, so that form can be easily built in template
1782 *
1783 * @param array $groupTree
1784 * @param int $groupCount
1785 * Group count by default 1, but can vary for multiple value custom data.
1786 * @param \CRM_Core_Form $form
1787 *
1788 * @return array
1789 * @throws \CRM_Core_Exception
1790 */
1791 public static function formatGroupTree(&$groupTree, $groupCount = 1, &$form = NULL) {
1792 $formattedGroupTree = [];
1793 $uploadNames = $formValues = [];
1794
1795 // retrieve qf key from url
1796 $qfKey = CRM_Utils_Request::retrieve('qf', 'String');
1797
1798 // fetch submitted custom field values later use to set as a default values
1799 if ($qfKey) {
1800 $submittedValues = Civi::cache('customData')->get($qfKey);
1801 }
1802
1803 foreach ($groupTree as $key => $value) {
1804 if ($key === 'info') {
1805 continue;
1806 }
1807
1808 // add group information
1809 $formattedGroupTree[$key]['name'] = $value['name'] ?? NULL;
1810 $formattedGroupTree[$key]['title'] = $value['title'] ?? NULL;
1811 $formattedGroupTree[$key]['help_pre'] = $value['help_pre'] ?? NULL;
1812 $formattedGroupTree[$key]['help_post'] = $value['help_post'] ?? NULL;
1813 $formattedGroupTree[$key]['collapse_display'] = $value['collapse_display'] ?? NULL;
1814 $formattedGroupTree[$key]['collapse_adv_display'] = $value['collapse_adv_display'] ?? NULL;
1815 $formattedGroupTree[$key]['style'] = $value['style'] ?? NULL;
1816
1817 // this params needed of bulding multiple values
1818 $formattedGroupTree[$key]['is_multiple'] = $value['is_multiple'] ?? NULL;
1819 $formattedGroupTree[$key]['extends'] = $value['extends'] ?? NULL;
1820 $formattedGroupTree[$key]['extends_entity_column_id'] = $value['extends_entity_column_id'] ?? NULL;
1821 $formattedGroupTree[$key]['extends_entity_column_value'] = $value['extends_entity_column_value'] ?? NULL;
1822 $formattedGroupTree[$key]['subtype'] = $value['subtype'] ?? NULL;
1823 $formattedGroupTree[$key]['max_multiple'] = $value['max_multiple'] ?? NULL;
1824
1825 // add field information
1826 foreach ($value['fields'] as $k => $properties) {
1827 $properties['element_name'] = "custom_{$k}_-{$groupCount}";
1828 if (isset($properties['customValue']) &&
1829 !CRM_Utils_System::isNull($properties['customValue']) &&
1830 !isset($properties['element_value'])
1831 ) {
1832 if (isset($properties['customValue'][$groupCount])) {
1833 $properties['element_name'] = "custom_{$k}_{$properties['customValue'][$groupCount]['id']}";
1834 $formattedGroupTree[$key]['table_id'] = $properties['customValue'][$groupCount]['id'];
1835 if ($properties['data_type'] === 'File') {
1836 $properties['element_value'] = $properties['customValue'][$groupCount];
1837 $uploadNames[] = $properties['element_name'];
1838 }
1839 else {
1840 $properties['element_value'] = $properties['customValue'][$groupCount]['data'];
1841 }
1842 }
1843 }
1844 if ($value = CRM_Utils_Request::retrieve($properties['element_name'], 'String', $form, FALSE, NULL, 'POST')) {
1845 $formValues[$properties['element_name']] = $value;
1846 }
1847 elseif (isset($submittedValues[$properties['element_name']])) {
1848 $properties['element_value'] = $submittedValues[$properties['element_name']];
1849 }
1850 unset($properties['customValue']);
1851 $formattedGroupTree[$key]['fields'][$k] = $properties;
1852 }
1853 }
1854
1855 if ($form) {
1856 if (count($formValues)) {
1857 $qf = $form->get('qfKey');
1858 $form->assign('qfKey', $qf);
1859 Civi::cache('customData')->set($qf, $formValues);
1860 }
1861
1862 // hack for field type File
1863 $formUploadNames = $form->get('uploadNames');
1864 if (is_array($formUploadNames)) {
1865 $uploadNames = array_unique(array_merge($formUploadNames, $uploadNames));
1866 }
1867
1868 $form->set('uploadNames', $uploadNames);
1869 }
1870
1871 return $formattedGroupTree;
1872 }
1873
1874 /**
1875 * Build custom data view.
1876 *
1877 * @param CRM_Core_Form|CRM_Core_Page $form
1878 * Page object.
1879 * @param array $groupTree
1880 * @param bool $returnCount
1881 * True if customValue count needs to be returned.
1882 * @param int $gID
1883 * @param null $prefix
1884 * @param int $customValueId
1885 * @param int $entityId
1886 * @param bool $checkEditPermission
1887 *
1888 * @return array|int
1889 * @throws \CRM_Core_Exception
1890 */
1891 public static function buildCustomDataView(&$form, $groupTree, $returnCount = FALSE, $gID = NULL, $prefix = NULL, $customValueId = NULL, $entityId = NULL, $checkEditPermission = FALSE) {
1892 // Filter out pesky extra info
1893 unset($groupTree['info']);
1894
1895 $details = [];
1896
1897 $editableGroups = [];
1898 if ($checkEditPermission) {
1899 $editableGroups = \CRM_Core_Permission::customGroup(CRM_Core_Permission::EDIT);
1900 }
1901
1902 foreach ($groupTree as $key => $group) {
1903
1904 foreach ($group['fields'] as $k => $properties) {
1905 $groupID = $group['id'];
1906 if (!empty($properties['customValue'])) {
1907 foreach ($properties['customValue'] as $values) {
1908 if (!empty($customValueId) && $customValueId != $values['id']) {
1909 continue;
1910 }
1911 $details[$groupID][$values['id']]['title'] = $group['title'] ?? NULL;
1912 $details[$groupID][$values['id']]['name'] = $group['name'] ?? NULL;
1913 $details[$groupID][$values['id']]['help_pre'] = $group['help_pre'] ?? NULL;
1914 $details[$groupID][$values['id']]['help_post'] = $group['help_post'] ?? NULL;
1915 $details[$groupID][$values['id']]['collapse_display'] = $group['collapse_display'] ?? NULL;
1916 $details[$groupID][$values['id']]['collapse_adv_display'] = $group['collapse_adv_display'] ?? NULL;
1917 $details[$groupID][$values['id']]['style'] = $group['style'] ?? NULL;
1918 $details[$groupID][$values['id']]['fields'][$k] = [
1919 'field_title' => $properties['label'] ?? NULL,
1920 'field_type' => $properties['html_type'] ?? NULL,
1921 'field_data_type' => $properties['data_type'] ?? NULL,
1922 'field_value' => CRM_Core_BAO_CustomField::displayValue($values['data'], $properties['id'], $entityId),
1923 'options_per_line' => $properties['options_per_line'] ?? NULL,
1924 ];
1925 // editable = whether this set contains any non-read-only fields
1926 if (!isset($details[$groupID][$values['id']]['editable'])) {
1927 $details[$groupID][$values['id']]['editable'] = FALSE;
1928 }
1929 if (empty($properties['is_view']) && in_array($key, $editableGroups)) {
1930 $details[$groupID][$values['id']]['editable'] = TRUE;
1931 }
1932 // also return contact reference contact id if user has view all or edit all contacts perm
1933 if ($details[$groupID][$values['id']]['fields'][$k]['field_data_type'] === 'ContactReference'
1934 && CRM_Core_Permission::check([['view all contacts', 'edit all contacts']])
1935 ) {
1936 $details[$groupID][$values['id']]['fields'][$k]['contact_ref_links'] = [];
1937 $path = CRM_Contact_DAO_Contact::getEntityPaths()['view'];
1938 foreach (CRM_Utils_Array::explodePadded($values['data'] ?? []) as $contactId) {
1939 $displayName = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact', $contactId, 'display_name');
1940 if ($displayName) {
1941 $url = CRM_Utils_System::url(str_replace('[id]', $contactId, $path));
1942 $details[$groupID][$values['id']]['fields'][$k]['contact_ref_links'][] = '<a href="' . $url . '" title="' . htmlspecialchars(ts('View Contact')) . '">' .
1943 $displayName . '</a>';
1944 }
1945 }
1946 }
1947 }
1948 }
1949 else {
1950 $details[$groupID][0]['title'] = $group['title'] ?? NULL;
1951 $details[$groupID][0]['name'] = $group['name'] ?? NULL;
1952 $details[$groupID][0]['help_pre'] = $group['help_pre'] ?? NULL;
1953 $details[$groupID][0]['help_post'] = $group['help_post'] ?? NULL;
1954 $details[$groupID][0]['collapse_display'] = $group['collapse_display'] ?? NULL;
1955 $details[$groupID][0]['collapse_adv_display'] = $group['collapse_adv_display'] ?? NULL;
1956 $details[$groupID][0]['style'] = $group['style'] ?? NULL;
1957 $details[$groupID][0]['fields'][$k] = ['field_title' => $properties['label'] ?? NULL];
1958 }
1959 }
1960 }
1961
1962 if ($returnCount) {
1963 // return a single value count if group id is passed to function
1964 // else return a groupId and count mapped array
1965 if (!empty($gID)) {
1966 return count($details[$gID]);
1967 }
1968 else {
1969 $countValue = [];
1970 foreach ($details as $key => $value) {
1971 $countValue[$key] = count($details[$key]);
1972 }
1973 return $countValue;
1974 }
1975 }
1976 else {
1977 $form->assign_by_ref("{$prefix}viewCustomData", $details);
1978 return $details;
1979 }
1980 }
1981
1982 /**
1983 * Get the custom group titles by custom field ids.
1984 *
1985 * @param array $fieldIds
1986 * Array of custom field ids.
1987 *
1988 * @return array|NULL
1989 * array consisting of groups and fields labels with ids.
1990 */
1991 public static function getGroupTitles($fieldIds) {
1992 if (!is_array($fieldIds) && empty($fieldIds)) {
1993 return NULL;
1994 }
1995
1996 $groupLabels = [];
1997 $fIds = "(" . implode(',', $fieldIds) . ")";
1998
1999 $query = "
2000 SELECT civicrm_custom_group.id as groupID, civicrm_custom_group.title as groupTitle,
2001 civicrm_custom_field.label as fieldLabel, civicrm_custom_field.id as fieldID
2002 FROM civicrm_custom_group, civicrm_custom_field
2003 WHERE civicrm_custom_group.id = civicrm_custom_field.custom_group_id
2004 AND civicrm_custom_field.id IN {$fIds}";
2005
2006 $dao = CRM_Core_DAO::executeQuery($query);
2007 while ($dao->fetch()) {
2008 $groupLabels[$dao->fieldID] = [
2009 'fieldID' => $dao->fieldID,
2010 'fieldLabel' => $dao->fieldLabel,
2011 'groupID' => $dao->groupID,
2012 'groupTitle' => $dao->groupTitle,
2013 ];
2014 }
2015
2016 return $groupLabels;
2017 }
2018
2019 public static function dropAllTables() {
2020 $query = "SELECT table_name FROM civicrm_custom_group";
2021 $dao = CRM_Core_DAO::executeQuery($query);
2022
2023 while ($dao->fetch()) {
2024 $query = "DROP TABLE IF EXISTS {$dao->table_name}";
2025 CRM_Core_DAO::executeQuery($query);
2026 }
2027 }
2028
2029 /**
2030 * Check whether custom group is empty or not.
2031 *
2032 * @param int $gID
2033 * Custom group id.
2034 *
2035 * @return bool|NULL
2036 * true if empty otherwise false.
2037 */
2038 public static function isGroupEmpty($gID) {
2039 if (!$gID) {
2040 return NULL;
2041 }
2042
2043 $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup',
2044 $gID,
2045 'table_name'
2046 );
2047
2048 $query = "SELECT count(id) FROM {$tableName} WHERE id IS NOT NULL LIMIT 1";
2049 $value = CRM_Core_DAO::singleValueQuery($query);
2050
2051 if (empty($value)) {
2052 return TRUE;
2053 }
2054
2055 return FALSE;
2056 }
2057
2058 /**
2059 * Get the list of types for objects that a custom group extends to.
2060 *
2061 * @param array $types
2062 * Var which should have the list appended.
2063 *
2064 * @return array
2065 * Array of types.
2066 * @throws \Exception
2067 */
2068 public static function getExtendedObjectTypes(&$types = []) {
2069 static $flag = FALSE, $objTypes = [];
2070
2071 if (!$flag) {
2072 $extendObjs = [];
2073 CRM_Core_OptionValue::getValues(['name' => 'cg_extend_objects'], $extendObjs, 'weight', TRUE);
2074
2075 foreach ($extendObjs as $ovId => $ovValues) {
2076 if ($ovValues['description']) {
2077 // description is expected to be a callback func to subtypes
2078 list($callback, $args) = explode(';', trim($ovValues['description']));
2079
2080 if (empty($args)) {
2081 $args = [];
2082 }
2083
2084 if (!is_array($args)) {
2085 throw new CRM_Core_Exception('Arg is not of type array');
2086 }
2087
2088 list($className) = explode('::', $callback);
2089 require_once str_replace('_', DIRECTORY_SEPARATOR, $className) . '.php';
2090
2091 $objTypes[$ovValues['value']] = call_user_func_array($callback, $args);
2092 }
2093 }
2094 $flag = TRUE;
2095 }
2096
2097 $types = array_merge($types, $objTypes);
2098 return $objTypes;
2099 }
2100
2101 /**
2102 * @param int $customGroupId
2103 * @param int $entityId
2104 *
2105 * @return bool
2106 */
2107 public static function hasReachedMaxLimit($customGroupId, $entityId) {
2108 // check whether the group is multiple
2109 $isMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'is_multiple');
2110 $isMultiple = (bool) $isMultiple;
2111 $hasReachedMax = FALSE;
2112 if ($isMultiple &&
2113 ($maxMultiple = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'max_multiple'))
2114 ) {
2115 if (!$maxMultiple) {
2116 $hasReachedMax = FALSE;
2117 }
2118 else {
2119 $tableName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'table_name');
2120 // count the number of entries for a entity
2121 $sql = "SELECT COUNT(id) FROM {$tableName} WHERE entity_id = %1";
2122 $params = [1 => [$entityId, 'Integer']];
2123 $count = CRM_Core_DAO::singleValueQuery($sql, $params);
2124
2125 if ($count >= $maxMultiple) {
2126 $hasReachedMax = TRUE;
2127 }
2128 }
2129 }
2130 return $hasReachedMax;
2131 }
2132
2133 /**
2134 * @return array
2135 */
2136 public static function getMultipleFieldGroup() {
2137 $multipleGroup = [];
2138 $dao = new CRM_Core_DAO_CustomGroup();
2139 $dao->is_multiple = 1;
2140 $dao->is_active = 1;
2141 $dao->find();
2142 while ($dao->fetch()) {
2143 $multipleGroup[$dao->id] = $dao->title;
2144 }
2145 return $multipleGroup;
2146 }
2147
2148 /**
2149 * Build the metadata tree for the custom group.
2150 *
2151 * @param string $entityType
2152 * @param array $toReturn
2153 * @param array $subTypes
2154 * @param string $queryString
2155 * @param array $params
2156 * @param string $subType
2157 *
2158 * @return array
2159 * @throws \CRM_Core_Exception
2160 */
2161 private static function buildGroupTree($entityType, $toReturn, $subTypes, $queryString, $params, $subType) {
2162 $groupTree = $multipleFieldGroups = [];
2163 $crmDAO = CRM_Core_DAO::executeQuery($queryString, $params);
2164 $customValueTables = [];
2165
2166 // process records
2167 while ($crmDAO->fetch()) {
2168 // get the id's
2169 $groupID = $crmDAO->civicrm_custom_group_id;
2170 $fieldId = $crmDAO->civicrm_custom_field_id;
2171 if ($crmDAO->civicrm_custom_group_is_multiple) {
2172 $multipleFieldGroups[$groupID] = $crmDAO->civicrm_custom_group_table_name;
2173 }
2174 // create an array for groups if it does not exist
2175 if (!array_key_exists($groupID, $groupTree)) {
2176 $groupTree[$groupID] = [];
2177 $groupTree[$groupID]['id'] = $groupID;
2178
2179 // populate the group information
2180 foreach ($toReturn['custom_group'] as $fieldName) {
2181 $fullFieldName = "civicrm_custom_group_$fieldName";
2182 if ($fieldName == 'id' ||
2183 is_null($crmDAO->$fullFieldName)
2184 ) {
2185 continue;
2186 }
2187 // CRM-5507
2188 // This is an old bit of code - per the CRM number & probably does not work reliably if
2189 // that one contact sub-type exists.
2190 if ($fieldName == 'extends_entity_column_value' && !empty($subTypes[0])) {
2191 $groupTree[$groupID]['subtype'] = self::validateSubTypeByEntity($entityType, $subType);
2192 }
2193 $groupTree[$groupID][$fieldName] = $crmDAO->$fullFieldName;
2194 }
2195 $groupTree[$groupID]['fields'] = [];
2196
2197 $customValueTables[$crmDAO->civicrm_custom_group_table_name] = [];
2198 }
2199
2200 // add the fields now (note - the query row will always contain a field)
2201 // we only reset this once, since multiple values come is as multiple rows
2202 if (!array_key_exists($fieldId, $groupTree[$groupID]['fields'])) {
2203 $groupTree[$groupID]['fields'][$fieldId] = [];
2204 }
2205
2206 $customValueTables[$crmDAO->civicrm_custom_group_table_name][$crmDAO->civicrm_custom_field_column_name] = 1;
2207 $groupTree[$groupID]['fields'][$fieldId]['id'] = $fieldId;
2208 // populate information for a custom field
2209 foreach ($toReturn['custom_field'] as $fieldName) {
2210 $fullFieldName = "civicrm_custom_field_$fieldName";
2211 if ($fieldName == 'id' ||
2212 is_null($crmDAO->$fullFieldName)
2213 ) {
2214 continue;
2215 }
2216 $groupTree[$groupID]['fields'][$fieldId][$fieldName] = $crmDAO->$fullFieldName;
2217 }
2218 }
2219
2220 if (!empty($customValueTables)) {
2221 $groupTree['info'] = ['tables' => $customValueTables];
2222 }
2223 return [$multipleFieldGroups, $groupTree];
2224 }
2225
2226 public static function getSubTypes(): array {
2227 $sel2 = [];
2228 $activityType = CRM_Core_PseudoConstant::activityType(FALSE, TRUE, FALSE, 'label', TRUE);
2229
2230 $eventType = CRM_Core_OptionGroup::values('event_type');
2231 $grantType = CRM_Core_OptionGroup::values('grant_type');
2232 $campaignTypes = CRM_Campaign_PseudoConstant::campaignType();
2233 $membershipType = CRM_Member_BAO_MembershipType::getMembershipTypes(FALSE);
2234 $participantRole = CRM_Core_OptionGroup::values('participant_role');
2235
2236 asort($activityType);
2237 asort($eventType);
2238 asort($grantType);
2239 asort($membershipType);
2240 asort($participantRole);
2241
2242 $sel2['Event'] = $eventType;
2243 $sel2['Grant'] = $grantType;
2244 $sel2['Activity'] = $activityType;
2245 $sel2['Campaign'] = $campaignTypes;
2246 $sel2['Membership'] = $membershipType;
2247 $sel2['ParticipantRole'] = $participantRole;
2248 $sel2['ParticipantEventName'] = CRM_Event_PseudoConstant::event(NULL, FALSE, "( is_template IS NULL OR is_template != 1 )");
2249 $sel2['ParticipantEventType'] = $eventType;
2250 $sel2['Contribution'] = CRM_Contribute_PseudoConstant::financialType();
2251 $sel2['Relationship'] = CRM_Custom_Form_Group::getRelationshipTypes();
2252
2253 $sel2['Individual'] = CRM_Contact_BAO_ContactType::subTypePairs('Individual', FALSE, NULL);
2254 $sel2['Household'] = CRM_Contact_BAO_ContactType::subTypePairs('Household', FALSE, NULL);
2255 $sel2['Organization'] = CRM_Contact_BAO_ContactType::subTypePairs('Organization', FALSE, NULL);
2256
2257 CRM_Core_BAO_CustomGroup::getExtendedObjectTypes($sel2);
2258 return $sel2;
2259 }
2260
2261 /**
2262 * Get the munged entity.
2263 *
2264 * This is the entity eg. Relationship or the name of the sub entity
2265 * e.g ParticipantRole.
2266 *
2267 * @param string $extends
2268 * @param int|null $extendsEntityColumn
2269 *
2270 * @return string
2271 */
2272 protected static function getMungedEntity($extends, $extendsEntityColumn = NULL) {
2273 if (!$extendsEntityColumn || $extendsEntityColumn === 'null') {
2274 return $extends;
2275 }
2276 return CRM_Core_OptionGroup::values('custom_data_type', FALSE, FALSE, FALSE, NULL, 'name')[$extendsEntityColumn];
2277 }
2278
2279 /**
2280 * @param int $groupId
2281 * @param int $operation
2282 * @param int|null $userId
2283 */
2284 public static function checkGroupAccess($groupId, $operation = CRM_Core_Permission::EDIT, $userId = NULL): bool {
2285 $allowedGroups = CRM_Core_Permission::customGroup($operation, FALSE, $userId);
2286 return in_array($groupId, $allowedGroups);
2287 }
2288
2289 }