3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
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 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * form to process actions on the set aspect of Custom Data
21 class CRM_Custom_Form_Group
extends CRM_Core_Form
{
24 * The set id saved to the session for an update.
31 * set is empty or not.
35 protected $_isGroupEmpty = TRUE;
38 * Array of existing subtypes set for a custom set.
42 protected $_subtypes = [];
45 * Set variables up before form is built.
50 public function preProcess() {
51 Civi
::resources()->addScriptFile('civicrm', 'js/jquery/jquery.crmIconPicker.js');
54 $this->_id
= $this->get('id');
56 if ($this->_id
&& $isReserved = CRM_Core_DAO
::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_id
, 'is_reserved', 'id')) {
57 CRM_Core_Error
::statusBounce("You cannot edit the settings of a reserved custom field-set.");
59 // setting title for html page
60 if ($this->_action
== CRM_Core_Action
::UPDATE
) {
61 $title = CRM_Core_BAO_CustomGroup
::getTitle($this->_id
);
62 CRM_Utils_System
::setTitle(ts('Edit %1', [1 => $title]));
64 elseif ($this->_action
== CRM_Core_Action
::VIEW
) {
65 $title = CRM_Core_BAO_CustomGroup
::getTitle($this->_id
);
66 CRM_Utils_System
::setTitle(ts('Preview %1', [1 => $title]));
69 CRM_Utils_System
::setTitle(ts('New Custom Field Set'));
72 if (isset($this->_id
)) {
73 $params = ['id' => $this->_id
];
74 CRM_Core_BAO_CustomGroup
::retrieve($params, $this->_defaults
);
76 $subExtends = $this->_defaults
['extends_entity_column_value'] ??
NULL;
77 if (!empty($subExtends)) {
78 $this->_subtypes
= explode(CRM_Core_DAO
::VALUE_SEPARATOR
, substr($subExtends, 1, -1));
86 * @param array $fields
87 * The input form values.
89 * The uploaded files if any.
94 * true if no errors, else array of errors
96 public static function formRule($fields, $files, $self) {
99 //validate group title as well as name.
100 $title = $fields['title'];
101 $name = CRM_Utils_String
::munge($title, '_', 64);
102 $query = 'select count(*) from civicrm_custom_group where ( name like %1) and id != %2';
103 $grpCnt = CRM_Core_DAO
::singleValueQuery($query, [
104 1 => [$name, 'String'],
105 2 => [(int) $self->_id
, 'Integer'],
108 $errors['title'] = ts('Custom group \'%1\' already exists in Database.', [1 => $title]);
111 if (!empty($fields['extends'][1])) {
112 if (in_array('', $fields['extends'][1]) && count($fields['extends'][1]) > 1) {
113 $errors['extends'] = ts("Cannot combine other option with 'Any'.");
117 if (empty($fields['extends'][0])) {
118 $errors['extends'] = ts("You need to select the type of record that this set of custom fields is applicable for.");
121 $extends = ['Activity', 'Relationship', 'Group', 'Contribution', 'Membership', 'Event', 'Participant'];
122 if (in_array($fields['extends'][0], $extends) && $fields['style'] == 'Tab') {
123 $errors['style'] = ts("Display Style should be Inline for this Class");
124 $self->assign('showStyle', TRUE);
127 if (!empty($fields['is_multiple'])) {
128 $self->assign('showMultiple', TRUE);
131 if (empty($fields['is_multiple']) && $fields['style'] == 'Tab with table') {
132 $errors['style'] = ts("Display Style 'Tab with table' is only supported for multiple-record custom field sets.");
135 //checks the given custom set doesnot start with digit
136 $title = $fields['title'];
137 if (!empty($title)) {
138 // gives the ascii value
139 $asciiValue = ord($title[0]);
140 if ($asciiValue >= 48 && $asciiValue <= 57) {
141 $errors['title'] = ts("Name cannot not start with a digit");
145 return empty($errors) ?
TRUE : $errors;
149 * add the rules (mainly global rules) for form.
150 * All local rules are added near the element
156 public function addRules() {
157 $this->addFormRule(['CRM_Custom_Form_Group', 'formRule'], $this);
161 * Build the form object.
166 public function buildQuickForm() {
167 $this->applyFilter('__ALL__', 'trim');
169 $attributes = CRM_Core_DAO
::getAttribute('CRM_Core_DAO_CustomGroup');
172 $this->add('text', 'title', ts('Set Name'), $attributes['title'], TRUE);
174 //Fix for code alignment, CRM-3058
175 $contactTypes = ['Contact', 'Individual', 'Household', 'Organization'];
176 $this->assign('contactTypes', json_encode($contactTypes));
178 $sel1 = ["" => ts("- select -")] + CRM_Core_SelectValues
::customGroupExtends();
180 $sel2 = CRM_Core_BAO_CustomGroup
::getSubTypes();
182 foreach ($sel2 as $main => $sub) {
183 if (!empty($sel2[$main])) {
190 $cSubTypes = CRM_Core_Component
::contactSubTypes();
192 if (!empty($cSubTypes)) {
193 $contactSubTypes = [];
194 foreach ($cSubTypes as $key => $value) {
195 $contactSubTypes[$key] = $key;
199 ] +
$contactSubTypes;
202 if (!isset($this->_id
)) {
203 $formName = 'document.forms.' . $this->_name
;
205 $js = "<script type='text/javascript'>\n";
206 $js .= "{$formName}['extends_1'].style.display = 'none';\n";
208 $this->assign('initHideBlocks', $js);
212 $sel = &$this->add('hierselect',
216 'name' => 'extends[0]',
217 'style' => 'vertical-align: top;',
221 $sel->setOptions([$sel1, $sel2]);
222 if (is_a($sel->_elements
[1], 'HTML_QuickForm_select')) {
223 // make second selector a multi-select -
224 $sel->_elements
[1]->setMultiple(TRUE);
225 $sel->_elements
[1]->setSize(5);
227 if ($this->_action
== CRM_Core_Action
::UPDATE
) {
228 $subName = $this->_defaults
['extends_entity_column_id'] ??
NULL;
229 if ($this->_defaults
['extends'] == 'Participant') {
231 $this->_defaults
['extends'] = 'ParticipantRole';
233 elseif ($subName == 2) {
234 $this->_defaults
['extends'] = 'ParticipantEventName';
236 elseif ($subName == 3) {
237 $this->_defaults
['extends'] = 'ParticipantEventType';
241 //allow to edit settings if custom set is empty CRM-5258
242 $this->_isGroupEmpty
= CRM_Core_BAO_CustomGroup
::isGroupEmpty($this->_id
);
243 if (!$this->_isGroupEmpty
) {
244 if (!empty($this->_subtypes
)) {
245 // we want to allow adding / updating subtypes for this case,
246 // and therefore freeze the first selector only.
247 $sel->_elements
[0]->freeze();
250 // freeze both the selectors
254 $this->assign('isCustomGroupEmpty', $this->_isGroupEmpty
);
255 $this->assign('gid', $this->_id
);
257 $this->assign('defaultSubtypes', json_encode($this->_subtypes
));
260 $this->add('wysiwyg', 'help_pre', ts('Pre-form Help'), $attributes['help_pre']);
261 $this->add('wysiwyg', 'help_post', ts('Post-form Help'), $attributes['help_post']);
264 $this->add('number', 'weight', ts('Order'), $attributes['weight'], TRUE);
265 $this->addRule('weight', ts('is a numeric field'), 'numeric');
268 $this->add('select', 'style', ts('Display Style'), CRM_Core_SelectValues
::customGroupStyle());
270 $this->add('text', 'icon', ts('Tab icon'), ['class' => 'crm-icon-picker', 'allowClear' => TRUE]);
272 // is this set collapsed or expanded ?
273 $this->addElement('advcheckbox', 'collapse_display', ts('Collapse this set on initial display'));
275 // is this set collapsed or expanded ? in advanced search
276 $this->addElement('advcheckbox', 'collapse_adv_display', ts('Collapse this set in Advanced Search'));
278 // is this set active ?
279 $this->addElement('advcheckbox', 'is_active', ts('Is this Custom Data Set active?'));
281 //Is this set visible on public pages?
282 $this->addElement('advcheckbox', 'is_public', ts('Is this Custom Data Set public?'));
284 // does this set have multiple record?
285 $multiple = $this->addElement('advcheckbox', 'is_multiple',
286 ts('Does this Custom Field Set allow multiple records?'), NULL);
288 // $min_multiple = $this->add('text', 'min_multiple', ts('Minimum number of multiple records'), $attributes['min_multiple'] );
289 // $this->addRule('min_multiple', ts('is a numeric field') , 'numeric');
291 $max_multiple = $this->add('number', 'max_multiple', ts('Maximum number of multiple records'), $attributes['max_multiple']);
292 $this->addRule('max_multiple', ts('is a numeric field'), 'numeric');
294 //allow to edit settings if custom set is empty CRM-5258
295 $this->assign('isGroupEmpty', $this->_isGroupEmpty
);
296 if (!$this->_isGroupEmpty
) {
298 //$min_multiple->freeze();
299 $max_multiple->freeze();
302 $this->assign('showStyle', FALSE);
303 $this->assign('showMultiple', FALSE);
307 'name' => ts('Save'),
308 'spacing' => ' ',
313 'name' => ts('Cancel'),
316 if (!$this->_isGroupEmpty
&& !empty($this->_subtypes
)) {
317 $buttons[0]['class'] = 'crm-warnDataLoss';
319 $this->addButtons($buttons);
321 // TODO: Is this condition ever true? Can this code be removed?
322 if ($this->_action
& CRM_Core_Action
::VIEW
) {
324 $this->addElement('xbutton', 'done', ts('Done'), [
326 'onclick' => "location.href='civicrm/admin/custom/group?reset=1&action=browse'",
332 * Set default values for the form. Note that in edit/view mode
333 * the default values are retrieved from the database
337 * array of default values
339 public function setDefaultValues() {
340 $defaults = &$this->_defaults
;
341 $this->assign('showMaxMultiple', TRUE);
342 if ($this->_action
== CRM_Core_Action
::ADD
) {
343 $defaults['weight'] = CRM_Utils_Weight
::getDefaultWeight('CRM_Core_DAO_CustomGroup');
345 $defaults['is_multiple'] = $defaults['min_multiple'] = 0;
346 $defaults['is_active'] = $defaults['is_public'] = $defaults['collapse_display'] = 1;
347 $defaults['style'] = 'Inline';
349 elseif (empty($defaults['max_multiple']) && !$this->_isGroupEmpty
) {
350 $this->assign('showMaxMultiple', FALSE);
353 if (($this->_action
& CRM_Core_Action
::UPDATE
) && !empty($defaults['is_multiple'])) {
354 $defaults['collapse_display'] = 0;
357 if (isset($defaults['extends'])) {
358 $extends = $defaults['extends'];
359 unset($defaults['extends']);
361 $defaults['extends'][0] = $extends;
363 if (!empty($this->_subtypes
)) {
364 $defaults['extends'][1] = $this->_subtypes
;
367 $defaults['extends'][1] = [0 => ''];
370 if ($extends == 'Relationship' && !empty($this->_subtypes
)) {
371 $relationshipDefaults = [];
372 foreach ($defaults['extends'][1] as $donCare => $rel_type_id) {
373 $relationshipDefaults[] = $rel_type_id;
376 $defaults['extends'][1] = $relationshipDefaults;
389 public function postProcess() {
390 // get the submitted form values.
391 $params = $this->controller
->exportValues('Group');
392 $params['overrideFKConstraint'] = 0;
393 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
394 $params['id'] = $this->_id
;
395 if ($this->_defaults
['extends'][0] != $params['extends'][0]) {
396 $params['overrideFKConstraint'] = 1;
399 if (!empty($this->_subtypes
)) {
400 $subtypesToBeRemoved = [];
401 $subtypesToPreserve = $params['extends'][1];
402 // Don't remove any value if group is extended to -any- subtype
403 if (!empty($subtypesToPreserve[0])) {
404 $subtypesToBeRemoved = array_diff($this->_subtypes
, array_intersect($this->_subtypes
, $subtypesToPreserve));
406 CRM_Contact_BAO_ContactType
::deleteCustomRowsOfSubtype($this->_id
, $subtypesToBeRemoved, $subtypesToPreserve);
409 elseif ($this->_action
& CRM_Core_Action
::ADD
) {
410 //new custom set , so lets set the created_id
411 $session = CRM_Core_Session
::singleton();
412 $params['created_id'] = $session->get('userID');
413 $params['created_date'] = date('YmdHis');
416 $result = civicrm_api3('CustomGroup', 'create', $params);
417 $group = $result['values'][$result['id']];
420 Civi
::cache('fields')->flush();
421 // reset ACL and system caches.
422 CRM_Core_BAO_Cache
::resetCaches();
424 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
425 CRM_Core_Session
::setStatus(ts('Your custom field set \'%1 \' has been saved.', [1 => $group['title']]), ts('Saved'), 'success');
428 // Jump directly to adding a field if popups are disabled
429 $action = CRM_Core_Resources
::singleton()->ajaxPopupsEnabled ?
'' : '/add';
430 $url = CRM_Utils_System
::url("civicrm/admin/custom/group/field$action", 'reset=1&new=1&gid=' . $group['id'] . '&action=' . ($action ?
'add' : 'browse'));
431 CRM_Core_Session
::setStatus(ts("Your custom field set '%1' has been added. You can add custom fields now.",
432 [1 => $group['title']]
433 ), ts('Saved'), 'success');
434 $session = CRM_Core_Session
::singleton();
435 $session->replaceUserContext($url);
438 // prompt Drupal Views users to update $db_prefix in settings.php, if necessary
440 $config = CRM_Core_Config
::singleton();
441 if (is_array($db_prefix) && $config->userSystem
->is_drupal
&& module_exists('views')) {
442 // get table_name for each custom group
444 $sql = "SELECT table_name FROM civicrm_custom_group WHERE is_active = 1";
445 $result = CRM_Core_DAO
::executeQuery($sql);
446 while ($result->fetch()) {
447 $tables[$result->table_name
] = $result->table_name
;
450 // find out which tables are missing from the $db_prefix array
451 $missingTableNames = array_diff_key($tables, $db_prefix);
453 if (!empty($missingTableNames)) {
454 CRM_Core_Session
::setStatus(ts("To ensure that all of your custom data groups are available to Views, you may need to add the following key(s) to the db_prefix array in your settings.php file: '%1'.",
455 [1 => implode(', ', $missingTableNames)]
456 ), ts('Note'), 'info');
462 * Return a formatted list of relationship labels.
465 * Array (int $id => string $label).
467 public static function getRelationshipTypes() {
468 // Note: We include inactive reltypes because we don't want to break custom-data
469 // UI when a reltype is disabled.
470 return CRM_Core_DAO
::executeQuery('
474 WHEN label_a_b is not null AND label_b_a is not null AND label_a_b != label_b_a
475 THEN concat(label_a_b, \' / \', label_b_a)
476 WHEN label_a_b is not null
478 WHEN label_b_a is not null
480 ELSE concat("RelType #", id)
482 FROM civicrm_relationship_type
484 )->fetchMap('id', 'label');