Merge pull request #24115 from kcristiano/5.52-token
[civicrm-core.git] / CRM / Custom / Form / Group.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 * form to process actions on the set aspect of Custom Data
20 */
21 class CRM_Custom_Form_Group extends CRM_Core_Form {
22
23 /**
24 * The set id saved to the session for an update.
25 *
26 * @var int
27 */
28 protected $_id;
29
30 /**
31 * set is empty or not.
32 *
33 * @var bool
34 */
35 protected $_isGroupEmpty = TRUE;
36
37 /**
38 * Array of existing subtypes set for a custom set.
39 *
40 * @var array
41 */
42 protected $_subtypes = [];
43
44 /**
45 * Set variables up before form is built.
46 *
47 *
48 * @return void
49 */
50 public function preProcess() {
51 Civi::resources()->addScriptFile('civicrm', 'js/jquery/jquery.crmIconPicker.js');
52
53 // current set id
54 $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this);
55 $this->setAction($this->_id ? CRM_Core_Action::UPDATE : CRM_Core_Action::ADD);
56
57 if ($this->_id && CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_id, 'is_reserved', 'id')) {
58 CRM_Core_Error::statusBounce("You cannot edit the settings of a reserved custom field-set.");
59 }
60
61 if ($this->_id) {
62 $title = CRM_Core_BAO_CustomGroup::getTitle($this->_id);
63 $this->setTitle(ts('Edit %1', [1 => $title]));
64 $params = ['id' => $this->_id];
65 CRM_Core_BAO_CustomGroup::retrieve($params, $this->_defaults);
66
67 $subExtends = $this->_defaults['extends_entity_column_value'] ?? NULL;
68 if (!empty($subExtends)) {
69 $this->_subtypes = explode(CRM_Core_DAO::VALUE_SEPARATOR, substr($subExtends, 1, -1));
70 }
71 }
72 else {
73 $this->setTitle(ts('New Custom Field Set'));
74 }
75 }
76
77 /**
78 * Global form rule.
79 *
80 * @param array $fields
81 * The input form values.
82 * @param array $files
83 * The uploaded files if any.
84 * @param self $self
85 *
86 *
87 * @return bool|array
88 * true if no errors, else array of errors
89 */
90 public static function formRule($fields, $files, $self) {
91 $errors = [];
92
93 //validate group title as well as name.
94 $title = $fields['title'];
95 $name = CRM_Utils_String::munge($title, '_', 64);
96 $query = 'select count(*) from civicrm_custom_group where ( name like %1) and id != %2';
97 $grpCnt = CRM_Core_DAO::singleValueQuery($query, [
98 1 => [$name, 'String'],
99 2 => [(int) $self->_id, 'Integer'],
100 ]);
101 if ($grpCnt) {
102 $errors['title'] = ts('Custom group \'%1\' already exists in Database.', [1 => $title]);
103 }
104
105 if (!empty($fields['extends'][1])) {
106 if (in_array('', $fields['extends'][1]) && count($fields['extends'][1]) > 1) {
107 $errors['extends'] = ts("Cannot combine other option with 'Any'.");
108 }
109 }
110
111 if (empty($fields['extends'][0])) {
112 $errors['extends'] = ts("You need to select the type of record that this set of custom fields is applicable for.");
113 }
114
115 $extends = ['Activity', 'Relationship', 'Group', 'Contribution', 'Membership', 'Event', 'Participant'];
116 if (in_array($fields['extends'][0], $extends) && $fields['style'] == 'Tab') {
117 $errors['style'] = ts("Display Style should be Inline for this Class");
118 $self->assign('showStyle', TRUE);
119 }
120
121 if (!empty($fields['is_multiple'])) {
122 $self->assign('showMultiple', TRUE);
123 }
124
125 if (empty($fields['is_multiple']) && $fields['style'] == 'Tab with table') {
126 $errors['style'] = ts("Display Style 'Tab with table' is only supported for multiple-record custom field sets.");
127 }
128
129 //checks the given custom set doesnot start with digit
130 $title = $fields['title'];
131 if (!empty($title)) {
132 // gives the ascii value
133 $asciiValue = ord($title[0]);
134 if ($asciiValue >= 48 && $asciiValue <= 57) {
135 $errors['title'] = ts("Name cannot not start with a digit");
136 }
137 }
138
139 return empty($errors) ? TRUE : $errors;
140 }
141
142 /**
143 * add the rules (mainly global rules) for form.
144 * All local rules are added near the element
145 *
146 *
147 * @return void
148 * @see valid_date
149 */
150 public function addRules() {
151 $this->addFormRule(['CRM_Custom_Form_Group', 'formRule'], $this);
152 }
153
154 /**
155 * Build the form object.
156 *
157 *
158 * @return void
159 */
160 public function buildQuickForm() {
161 $this->applyFilter('__ALL__', 'trim');
162
163 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_CustomGroup');
164
165 //title
166 $this->add('text', 'title', ts('Set Name'), $attributes['title'], TRUE);
167
168 //Fix for code alignment, CRM-3058
169 $contactTypes = array_merge(['Contact'], CRM_Contact_BAO_ContactType::basicTypes());
170 $this->assign('contactTypes', json_encode($contactTypes));
171
172 $sel1 = ["" => ts("- select -")] + CRM_Core_SelectValues::customGroupExtends();
173 ksort($sel1);
174 $sel2 = CRM_Core_BAO_CustomGroup::getSubTypes();
175
176 foreach ($sel2 as $main => $sub) {
177 if (!empty($sel2[$main])) {
178 $sel2[$main] = [
179 '' => ts("- Any -"),
180 ] + $sel2[$main];
181 }
182 }
183
184 if (!isset($this->_id)) {
185 $formName = 'document.forms.' . $this->_name;
186
187 $js = "<script type='text/javascript'>\n";
188 $js .= "{$formName}['extends_1'].style.display = 'none';\n";
189 $js .= "</script>";
190 $this->assign('initHideBlocks', $js);
191 }
192
193 $sel = &$this->add('hierselect',
194 'extends',
195 ts('Used For'),
196 [
197 'name' => 'extends[0]',
198 'style' => 'vertical-align: top;',
199 ],
200 TRUE
201 );
202 $sel->setOptions([$sel1, $sel2]);
203 if (is_a($sel->_elements[1], 'HTML_QuickForm_select')) {
204 // make second selector a multi-select -
205 $sel->_elements[1]->setMultiple(TRUE);
206 $sel->_elements[1]->setSize(5);
207 }
208 if ($this->_action == CRM_Core_Action::UPDATE) {
209 $subName = $this->_defaults['extends_entity_column_id'] ?? NULL;
210 if ($this->_defaults['extends'] == 'Participant') {
211 if ($subName == 1) {
212 $this->_defaults['extends'] = 'ParticipantRole';
213 }
214 elseif ($subName == 2) {
215 $this->_defaults['extends'] = 'ParticipantEventName';
216 }
217 elseif ($subName == 3) {
218 $this->_defaults['extends'] = 'ParticipantEventType';
219 }
220 }
221
222 //allow to edit settings if custom set is empty CRM-5258
223 $this->_isGroupEmpty = CRM_Core_BAO_CustomGroup::isGroupEmpty($this->_id);
224 if (!$this->_isGroupEmpty) {
225 if (!empty($this->_subtypes)) {
226 // we want to allow adding / updating subtypes for this case,
227 // and therefore freeze the first selector only.
228 $sel->_elements[0]->freeze();
229 }
230 else {
231 // freeze both the selectors
232 $sel->freeze();
233 }
234 }
235 $this->assign('isCustomGroupEmpty', $this->_isGroupEmpty);
236 $this->assign('gid', $this->_id);
237 }
238 $this->assign('defaultSubtypes', json_encode($this->_subtypes));
239
240 // help text
241 $this->add('wysiwyg', 'help_pre', ts('Pre-form Help'), $attributes['help_pre']);
242 $this->add('wysiwyg', 'help_post', ts('Post-form Help'), $attributes['help_post']);
243
244 // weight
245 $this->add('number', 'weight', ts('Order'), $attributes['weight'], TRUE);
246 $this->addRule('weight', ts('is a numeric field'), 'numeric');
247
248 // display style
249 $this->add('select', 'style', ts('Display Style'), CRM_Core_SelectValues::customGroupStyle());
250
251 $this->add('text', 'icon', ts('Tab icon'), ['class' => 'crm-icon-picker', 'allowClear' => TRUE]);
252
253 // is this set collapsed or expanded ?
254 $this->addElement('advcheckbox', 'collapse_display', ts('Collapse this set on initial display'));
255
256 // is this set collapsed or expanded ? in advanced search
257 $this->addElement('advcheckbox', 'collapse_adv_display', ts('Collapse this set in Advanced Search'));
258
259 // is this set active ?
260 $this->addElement('advcheckbox', 'is_active', ts('Is this Custom Data Set active?'));
261
262 //Is this set visible on public pages?
263 $this->addElement('advcheckbox', 'is_public', ts('Is this Custom Data Set public?'));
264
265 // does this set have multiple record?
266 $multiple = $this->addElement('advcheckbox', 'is_multiple',
267 ts('Does this Custom Field Set allow multiple records?'), NULL);
268
269 // $min_multiple = $this->add('text', 'min_multiple', ts('Minimum number of multiple records'), $attributes['min_multiple'] );
270 // $this->addRule('min_multiple', ts('is a numeric field') , 'numeric');
271
272 $max_multiple = $this->add('number', 'max_multiple', ts('Maximum number of multiple records'), $attributes['max_multiple']);
273 $this->addRule('max_multiple', ts('is a numeric field'), 'numeric');
274
275 //allow to edit settings if custom set is empty CRM-5258
276 $this->assign('isGroupEmpty', $this->_isGroupEmpty);
277 if (!$this->_isGroupEmpty) {
278 $multiple->freeze();
279 //$min_multiple->freeze();
280 $max_multiple->freeze();
281 }
282
283 $this->assign('showStyle', FALSE);
284 $this->assign('showMultiple', FALSE);
285 $buttons = [
286 [
287 'type' => 'next',
288 'name' => ts('Save'),
289 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
290 'isDefault' => TRUE,
291 ],
292 [
293 'type' => 'cancel',
294 'name' => ts('Cancel'),
295 ],
296 ];
297 if (!$this->_isGroupEmpty && !empty($this->_subtypes)) {
298 $buttons[0]['class'] = 'crm-warnDataLoss';
299 }
300 $this->addButtons($buttons);
301 }
302
303 /**
304 * Set default values for the form. Note that in edit/view mode
305 * the default values are retrieved from the database
306 *
307 *
308 * @return array
309 * array of default values
310 */
311 public function setDefaultValues() {
312 $defaults = &$this->_defaults;
313 $this->assign('showMaxMultiple', TRUE);
314 if ($this->_action == CRM_Core_Action::ADD) {
315 $defaults['weight'] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_CustomGroup');
316
317 $defaults['is_multiple'] = $defaults['min_multiple'] = 0;
318 $defaults['is_active'] = $defaults['is_public'] = $defaults['collapse_adv_display'] = 1;
319 $defaults['style'] = 'Inline';
320 }
321 elseif (empty($defaults['max_multiple']) && !$this->_isGroupEmpty) {
322 $this->assign('showMaxMultiple', FALSE);
323 }
324
325 if (($this->_action & CRM_Core_Action::UPDATE) && !empty($defaults['is_multiple'])) {
326 $defaults['collapse_display'] = 0;
327 }
328
329 if (isset($defaults['extends'])) {
330 $extends = $defaults['extends'];
331 unset($defaults['extends']);
332
333 $defaults['extends'][0] = $extends;
334
335 if (!empty($this->_subtypes)) {
336 $defaults['extends'][1] = $this->_subtypes;
337 }
338 else {
339 $defaults['extends'][1] = [0 => ''];
340 }
341
342 if ($extends == 'Relationship' && !empty($this->_subtypes)) {
343 $relationshipDefaults = [];
344 foreach ($defaults['extends'][1] as $donCare => $rel_type_id) {
345 $relationshipDefaults[] = $rel_type_id;
346 }
347
348 $defaults['extends'][1] = $relationshipDefaults;
349 }
350 }
351
352 return $defaults;
353 }
354
355 /**
356 * Process the form.
357 *
358 *
359 * @return void
360 */
361 public function postProcess() {
362 // get the submitted form values.
363 $params = $this->controller->exportValues('Group');
364 $params['overrideFKConstraint'] = 0;
365 if ($this->_action & CRM_Core_Action::UPDATE) {
366 $params['id'] = $this->_id;
367 if ($this->_defaults['extends'][0] != $params['extends'][0]) {
368 $params['overrideFKConstraint'] = 1;
369 }
370
371 if (!empty($this->_subtypes)) {
372 $subtypesToBeRemoved = [];
373 $subtypesToPreserve = $params['extends'][1];
374 // Don't remove any value if group is extended to -any- subtype
375 if (!empty($subtypesToPreserve[0])) {
376 $subtypesToBeRemoved = array_diff($this->_subtypes, array_intersect($this->_subtypes, $subtypesToPreserve));
377 }
378 CRM_Contact_BAO_ContactType::deleteCustomRowsOfSubtype($this->_id, $subtypesToBeRemoved, $subtypesToPreserve);
379 }
380 }
381 elseif ($this->_action & CRM_Core_Action::ADD) {
382 //new custom set , so lets set the created_id
383 $session = CRM_Core_Session::singleton();
384 $params['created_id'] = $session->get('userID');
385 $params['created_date'] = date('YmdHis');
386 }
387
388 $result = civicrm_api3('CustomGroup', 'create', $params);
389 $group = $result['values'][$result['id']];
390
391 // reset the cache
392 Civi::cache('fields')->flush();
393 // reset ACL and system caches.
394 CRM_Core_BAO_Cache::resetCaches();
395
396 if ($this->_action & CRM_Core_Action::UPDATE) {
397 CRM_Core_Session::setStatus(ts('Your custom field set \'%1 \' has been saved.', [1 => $group['title']]), ts('Saved'), 'success');
398 }
399 else {
400 // Jump directly to adding a field if popups are disabled
401 $action = CRM_Core_Resources::singleton()->ajaxPopupsEnabled ? '' : '/add';
402 $url = CRM_Utils_System::url("civicrm/admin/custom/group/field$action", 'reset=1&new=1&gid=' . $group['id']);
403 CRM_Core_Session::setStatus(ts("Your custom field set '%1' has been added. You can add custom fields now.",
404 [1 => $group['title']]
405 ), ts('Saved'), 'success');
406 $session = CRM_Core_Session::singleton();
407 $session->replaceUserContext($url);
408 }
409
410 // prompt Drupal Views users to update $db_prefix in settings.php, if necessary
411 global $db_prefix;
412 $config = CRM_Core_Config::singleton();
413 if (is_array($db_prefix) && $config->userSystem->is_drupal && module_exists('views')) {
414 // get table_name for each custom group
415 $tables = [];
416 $sql = "SELECT table_name FROM civicrm_custom_group WHERE is_active = 1";
417 $result = CRM_Core_DAO::executeQuery($sql);
418 while ($result->fetch()) {
419 $tables[$result->table_name] = $result->table_name;
420 }
421
422 // find out which tables are missing from the $db_prefix array
423 $missingTableNames = array_diff_key($tables, $db_prefix);
424
425 if (!empty($missingTableNames)) {
426 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'.",
427 [1 => implode(', ', $missingTableNames)]
428 ), ts('Note'), 'info');
429 }
430 }
431 }
432
433 /**
434 * Return a formatted list of relationship labels.
435 *
436 * @return array
437 * Array (int $id => string $label).
438 */
439 public static function getRelationshipTypes() {
440 // Note: We include inactive reltypes because we don't want to break custom-data
441 // UI when a reltype is disabled.
442 return CRM_Core_DAO::executeQuery('
443 SELECT
444 id,
445 (CASE 1
446 WHEN label_a_b is not null AND label_b_a is not null AND label_a_b != label_b_a
447 THEN concat(label_a_b, \' / \', label_b_a)
448 WHEN label_a_b is not null
449 THEN label_a_b
450 WHEN label_b_a is not null
451 THEN label_b_a
452 ELSE concat("RelType #", id)
453 END) as label
454 FROM civicrm_relationship_type
455 '
456 )->fetchMap('id', 'label');
457 }
458
459 }