Merge pull request #15821 from seamuslee001/dev_core_183_custom_group
[civicrm-core.git] / CRM / Custom / Form / Group.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
bc77d7c0 4 | Copyright CiviCRM LLC. All rights reserved. |
6a488035 5 | |
bc77d7c0
TO
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
6a488035 9 +--------------------------------------------------------------------+
d25dd0ee 10 */
6a488035
TO
11
12/**
13 *
14 * @package CRM
ca5cec67 15 * @copyright CiviCRM LLC https://civicrm.org/licensing
6a488035
TO
16 * $Id$
17 *
18 */
19
20/**
21 * form to process actions on the set aspect of Custom Data
22 */
23class CRM_Custom_Form_Group extends CRM_Core_Form {
24
25 /**
fe482240 26 * The set id saved to the session for an update.
6a488035
TO
27 *
28 * @var int
6a488035
TO
29 */
30 protected $_id;
31
32 /**
fe482240 33 * set is empty or not.
6a488035
TO
34 *
35 * @var bool
6a488035
TO
36 */
37 protected $_isGroupEmpty = TRUE;
38
39 /**
fe482240 40 * Array of existing subtypes set for a custom set.
6a488035
TO
41 *
42 * @var array
6a488035 43 */
be2fb01f 44 protected $_subtypes = [];
6a488035 45
6a488035 46 /**
fe482240 47 * Set variables up before form is built.
6a488035 48 *
6a488035
TO
49 *
50 * @return void
6a488035
TO
51 */
52 public function preProcess() {
53 // current set id
54 $this->_id = $this->get('id');
55
148c89c8 56 if ($this->_id && $isReserved = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $this->_id, 'is_reserved', 'id')) {
e89941dc 57 CRM_Core_Error::fatal("You cannot edit the settings of a reserved custom field-set.");
d06700a7 58 }
6a488035
TO
59 // setting title for html page
60 if ($this->_action == CRM_Core_Action::UPDATE) {
61 $title = CRM_Core_BAO_CustomGroup::getTitle($this->_id);
be2fb01f 62 CRM_Utils_System::setTitle(ts('Edit %1', [1 => $title]));
6a488035
TO
63 }
64 elseif ($this->_action == CRM_Core_Action::VIEW) {
65 $title = CRM_Core_BAO_CustomGroup::getTitle($this->_id);
be2fb01f 66 CRM_Utils_System::setTitle(ts('Preview %1', [1 => $title]));
6a488035
TO
67 }
68 else {
69 CRM_Utils_System::setTitle(ts('New Custom Field Set'));
70 }
71
72 if (isset($this->_id)) {
be2fb01f 73 $params = ['id' => $this->_id];
6a488035
TO
74 CRM_Core_BAO_CustomGroup::retrieve($params, $this->_defaults);
75
76 $subExtends = CRM_Utils_Array::value('extends_entity_column_value', $this->_defaults);
77 if (!empty($subExtends)) {
78 $this->_subtypes = explode(CRM_Core_DAO::VALUE_SEPARATOR, substr($subExtends, 1, -1));
79 }
80 }
81 }
82
83 /**
fe482240 84 * Global form rule.
6a488035 85 *
c4ca4892
TO
86 * @param array $fields
87 * The input form values.
88 * @param array $files
89 * The uploaded files if any.
dd244018
EM
90 * @param $self
91 *
6a488035 92 *
72b3a70c
CW
93 * @return bool|array
94 * true if no errors, else array of errors
6a488035 95 */
00be9182 96 public static function formRule($fields, $files, $self) {
be2fb01f 97 $errors = [];
6a488035
TO
98
99 //validate group title as well as name.
353ffa53
TO
100 $title = $fields['title'];
101 $name = CRM_Utils_String::munge($title, '_', 64);
9d5dc965 102 $query = 'select count(*) from civicrm_custom_group where ( name like %1) and id != %2';
be2fb01f
CW
103 $grpCnt = CRM_Core_DAO::singleValueQuery($query, [
104 1 => [$name, 'String'],
105 2 => [(int) $self->_id, 'Integer'],
106 ]);
6a488035 107 if ($grpCnt) {
be2fb01f 108 $errors['title'] = ts('Custom group \'%1\' already exists in Database.', [1 => $title]);
6a488035
TO
109 }
110
a7488080 111 if (!empty($fields['extends'][1])) {
6a488035
TO
112 if (in_array('', $fields['extends'][1]) && count($fields['extends'][1]) > 1) {
113 $errors['extends'] = ts("Cannot combine other option with 'Any'.");
114 }
115 }
116
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.");
119 }
120
be2fb01f 121 $extends = ['Activity', 'Relationship', 'Group', 'Contribution', 'Membership', 'Event', 'Participant'];
6a488035
TO
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);
125 }
126
a7488080 127 if (!empty($fields['is_multiple'])) {
e8646905 128 $self->assign('showMultiple', TRUE);
6a488035
TO
129 }
130
b05a0fb6 131 if (empty($fields['is_multiple']) && $fields['style'] == 'Tab with table') {
e232751e 132 $errors['style'] = ts("Display Style 'Tab with table' is only supported for multiple-record custom field sets.");
b05a0fb6
PJ
133 }
134
6a488035
TO
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) {
5ab78070 141 $errors['title'] = ts("Name cannot not start with a digit");
6a488035
TO
142 }
143 }
144
145 return empty($errors) ? TRUE : $errors;
146 }
147
148 /**
dc195289 149 * add the rules (mainly global rules) for form.
6a488035
TO
150 * All local rules are added near the element
151 *
6a488035
TO
152 *
153 * @return void
6a488035
TO
154 * @see valid_date
155 */
00be9182 156 public function addRules() {
be2fb01f 157 $this->addFormRule(['CRM_Custom_Form_Group', 'formRule'], $this);
6a488035
TO
158 }
159
160 /**
fe482240 161 * Build the form object.
6a488035 162 *
6a488035
TO
163 *
164 * @return void
6a488035
TO
165 */
166 public function buildQuickForm() {
167 $this->applyFilter('__ALL__', 'trim');
168
169 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_CustomGroup');
170
171 //title
172 $this->add('text', 'title', ts('Set Name'), $attributes['title'], TRUE);
173
174 //Fix for code alignment, CRM-3058
be2fb01f 175 $contactTypes = ['Contact', 'Individual', 'Household', 'Organization'];
6a488035
TO
176 $this->assign('contactTypes', json_encode($contactTypes));
177
be2fb01f
CW
178 $sel1 = ["" => ts("- select -")] + CRM_Core_SelectValues::customGroupExtends();
179 $sel2 = [];
6a488035
TO
180 $activityType = CRM_Core_PseudoConstant::activityType(FALSE, TRUE, FALSE, 'label', TRUE);
181
353ffa53
TO
182 $eventType = CRM_Core_OptionGroup::values('event_type');
183 $grantType = CRM_Core_OptionGroup::values('grant_type');
184 $campaignTypes = CRM_Campaign_PseudoConstant::campaignType();
185 $membershipType = CRM_Member_BAO_MembershipType::getMembershipTypes(FALSE);
6a488035 186 $participantRole = CRM_Core_OptionGroup::values('participant_role');
6a488035
TO
187
188 ksort($sel1);
189 asort($activityType);
190 asort($eventType);
191 asort($grantType);
192 asort($membershipType);
193 asort($participantRole);
6a488035
TO
194
195 $sel2['Event'] = $eventType;
196 $sel2['Grant'] = $grantType;
197 $sel2['Activity'] = $activityType;
198 $sel2['Campaign'] = $campaignTypes;
199 $sel2['Membership'] = $membershipType;
200 $sel2['ParticipantRole'] = $participantRole;
201 $sel2['ParticipantEventName'] = CRM_Event_PseudoConstant::event(NULL, FALSE, "( is_template IS NULL OR is_template != 1 )");
202 $sel2['ParticipantEventType'] = $eventType;
879db7b0 203 $sel2['Contribution'] = CRM_Contribute_PseudoConstant::financialType();
a9c1ff43 204 $sel2['Relationship'] = self::getRelationshipTypes();
6a488035
TO
205
206 $sel2['Individual'] = CRM_Contact_BAO_ContactType::subTypePairs('Individual', FALSE, NULL);
207 $sel2['Household'] = CRM_Contact_BAO_ContactType::subTypePairs('Household', FALSE, NULL);
208 $sel2['Organization'] = CRM_Contact_BAO_ContactType::subTypePairs('Organization', FALSE, NULL);
209
210 CRM_Core_BAO_CustomGroup::getExtendedObjectTypes($sel2);
211
212 foreach ($sel2 as $main => $sub) {
213 if (!empty($sel2[$main])) {
be2fb01f 214 $sel2[$main] = [
a9c1ff43 215 '' => ts("- Any -"),
be2fb01f 216 ] + $sel2[$main];
6a488035
TO
217 }
218 }
219
220 $cSubTypes = CRM_Core_Component::contactSubTypes();
221
222 if (!empty($cSubTypes)) {
be2fb01f 223 $contactSubTypes = [];
6a488035
TO
224 foreach ($cSubTypes as $key => $value) {
225 $contactSubTypes[$key] = $key;
226 }
be2fb01f 227 $sel2['Contact'] = [
b6b6d6cc 228 "" => ("- Any -"),
be2fb01f 229 ] + $contactSubTypes;
6a488035
TO
230 }
231 else {
232 if (!isset($this->_id)) {
233 $formName = 'document.forms.' . $this->_name;
234
235 $js = "<script type='text/javascript'>\n";
236 $js .= "{$formName}['extends_1'].style.display = 'none';\n";
237 $js .= "</script>";
238 $this->assign('initHideBlocks', $js);
239 }
240 }
241
242 $sel = &$this->add('hierselect',
243 'extends',
244 ts('Used For'),
be2fb01f 245 [
6a488035 246 'name' => 'extends[0]',
21dfd5f5 247 'style' => 'vertical-align: top;',
be2fb01f 248 ],
6a488035
TO
249 TRUE
250 );
be2fb01f 251 $sel->setOptions([$sel1, $sel2]);
6a488035
TO
252 if (is_a($sel->_elements[1], 'HTML_QuickForm_select')) {
253 // make second selector a multi-select -
254 $sel->_elements[1]->setMultiple(TRUE);
255 $sel->_elements[1]->setSize(5);
256 }
257 if ($this->_action == CRM_Core_Action::UPDATE) {
258 $subName = CRM_Utils_Array::value('extends_entity_column_id', $this->_defaults);
259 if ($this->_defaults['extends'] == 'Participant') {
260 if ($subName == 1) {
261 $this->_defaults['extends'] = 'ParticipantRole';
262 }
263 elseif ($subName == 2) {
264 $this->_defaults['extends'] = 'ParticipantEventName';
265 }
266 elseif ($subName == 3) {
267 $this->_defaults['extends'] = 'ParticipantEventType';
268 }
269 }
270
271 //allow to edit settings if custom set is empty CRM-5258
272 $this->_isGroupEmpty = CRM_Core_BAO_CustomGroup::isGroupEmpty($this->_id);
273 if (!$this->_isGroupEmpty) {
274 if (!empty($this->_subtypes)) {
275 // we want to allow adding / updating subtypes for this case,
276 // and therefore freeze the first selector only.
277 $sel->_elements[0]->freeze();
278 }
279 else {
280 // freeze both the selectors
281 $sel->freeze();
282 }
283 }
284 $this->assign('isCustomGroupEmpty', $this->_isGroupEmpty);
285 $this->assign('gid', $this->_id);
286 }
287 $this->assign('defaultSubtypes', json_encode($this->_subtypes));
288
289 // help text
5d51a2f9
CW
290 $this->add('wysiwyg', 'help_pre', ts('Pre-form Help'), $attributes['help_pre']);
291 $this->add('wysiwyg', 'help_post', ts('Post-form Help'), $attributes['help_post']);
6a488035
TO
292
293 // weight
f20d2b0d 294 $this->add('number', 'weight', ts('Order'), $attributes['weight'], TRUE);
6a488035
TO
295 $this->addRule('weight', ts('is a numeric field'), 'numeric');
296
297 // display style
298 $this->add('select', 'style', ts('Display Style'), CRM_Core_SelectValues::customGroupStyle());
299
300 // is this set collapsed or expanded ?
cbaf6620 301 $this->addElement('advcheckbox', 'collapse_display', ts('Collapse this set on initial display'));
6a488035
TO
302
303 // is this set collapsed or expanded ? in advanced search
cbaf6620 304 $this->addElement('advcheckbox', 'collapse_adv_display', ts('Collapse this set in Advanced Search'));
6a488035
TO
305
306 // is this set active ?
cbaf6620 307 $this->addElement('advcheckbox', 'is_active', ts('Is this Custom Data Set active?'));
6a488035 308
460931ca
JP
309 //Is this set visible on public pages?
310 $this->addElement('advcheckbox', 'is_public', ts('Is this Custom Data Set public?'));
311
6a488035 312 // does this set have multiple record?
cbaf6620 313 $multiple = $this->addElement('advcheckbox', 'is_multiple',
d9aa50a7 314 ts('Does this Custom Field Set allow multiple records?'), NULL);
6a488035
TO
315
316 // $min_multiple = $this->add('text', 'min_multiple', ts('Minimum number of multiple records'), $attributes['min_multiple'] );
317 // $this->addRule('min_multiple', ts('is a numeric field') , 'numeric');
318
319 $max_multiple = $this->add('text', 'max_multiple', ts('Maximum number of multiple records'), $attributes['max_multiple']);
320 $this->addRule('max_multiple', ts('is a numeric field'), 'numeric');
321
322 //allow to edit settings if custom set is empty CRM-5258
323 $this->assign('isGroupEmpty', $this->_isGroupEmpty);
324 if (!$this->_isGroupEmpty) {
325 $multiple->freeze();
326 //$min_multiple->freeze();
327 $max_multiple->freeze();
328 }
329
330 $this->assign('showStyle', FALSE);
331 $this->assign('showMultiple', FALSE);
be2fb01f
CW
332 $buttons = [
333 [
6a488035
TO
334 'type' => 'next',
335 'name' => ts('Save'),
336 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
337 'isDefault' => TRUE,
be2fb01f
CW
338 ],
339 [
6a488035
TO
340 'type' => 'cancel',
341 'name' => ts('Cancel'),
be2fb01f
CW
342 ],
343 ];
6a488035 344 if (!$this->_isGroupEmpty && !empty($this->_subtypes)) {
b01812e5 345 $buttons[0]['class'] = 'crm-warnDataLoss';
6a488035
TO
346 }
347 $this->addButtons($buttons);
348
b6b6d6cc 349 // TODO: Is this condition ever true? Can this code be removed?
6a488035
TO
350 if ($this->_action & CRM_Core_Action::VIEW) {
351 $this->freeze();
be2fb01f 352 $this->addElement('button', 'done', ts('Done'), ['onclick' => "location.href='civicrm/admin/custom/group?reset=1&action=browse'"]);
6a488035
TO
353 }
354 }
355
356 /**
c490a46a 357 * Set default values for the form. Note that in edit/view mode
6a488035
TO
358 * the default values are retrieved from the database
359 *
6a488035 360 *
a6c01b45
CW
361 * @return array
362 * array of default values
6a488035 363 */
00be9182 364 public function setDefaultValues() {
6a488035
TO
365 $defaults = &$this->_defaults;
366 $this->assign('showMaxMultiple', TRUE);
367 if ($this->_action == CRM_Core_Action::ADD) {
368 $defaults['weight'] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_CustomGroup');
369
370 $defaults['is_multiple'] = $defaults['min_multiple'] = 0;
460931ca 371 $defaults['is_active'] = $defaults['is_public'] = $defaults['collapse_display'] = 1;
6a488035
TO
372 $defaults['style'] = 'Inline';
373 }
a7488080 374 elseif (empty($defaults['max_multiple']) && !$this->_isGroupEmpty) {
6a488035
TO
375 $this->assign('showMaxMultiple', FALSE);
376 }
377
b05a0fb6 378 if (($this->_action & CRM_Core_Action::UPDATE) && !empty($defaults['is_multiple'])) {
5a205b89
PJ
379 $defaults['collapse_display'] = 0;
380 }
381
6a488035
TO
382 if (isset($defaults['extends'])) {
383 $extends = $defaults['extends'];
384 unset($defaults['extends']);
385
386 $defaults['extends'][0] = $extends;
387
388 if (!empty($this->_subtypes)) {
389 $defaults['extends'][1] = $this->_subtypes;
390 }
391 else {
be2fb01f 392 $defaults['extends'][1] = [0 => ''];
6a488035
TO
393 }
394
6a488035 395 if ($extends == 'Relationship' && !empty($this->_subtypes)) {
be2fb01f 396 $relationshipDefaults = [];
6a488035
TO
397 foreach ($defaults['extends'][1] as $donCare => $rel_type_id) {
398 $relationshipDefaults[] = $rel_type_id;
399 }
400
401 $defaults['extends'][1] = $relationshipDefaults;
402 }
403 }
404
405 return $defaults;
406 }
407
408 /**
fe482240 409 * Process the form.
6a488035 410 *
6a488035
TO
411 *
412 * @return void
6a488035
TO
413 */
414 public function postProcess() {
415 // get the submitted form values.
416 $params = $this->controller->exportValues('Group');
417 $params['overrideFKConstraint'] = 0;
418 if ($this->_action & CRM_Core_Action::UPDATE) {
419 $params['id'] = $this->_id;
420 if ($this->_defaults['extends'][0] != $params['extends'][0]) {
421 $params['overrideFKConstraint'] = 1;
422 }
423
424 if (!empty($this->_subtypes)) {
be2fb01f 425 $subtypesToBeRemoved = [];
00aca323 426 $subtypesToPreserve = $params['extends'][1];
5737ecff 427 // Don't remove any value if group is extended to -any- subtype
b27a1338 428 if (!empty($subtypesToPreserve[0])) {
5737ecff 429 $subtypesToBeRemoved = array_diff($this->_subtypes, array_intersect($this->_subtypes, $subtypesToPreserve));
430 }
00aca323 431 CRM_Contact_BAO_ContactType::deleteCustomRowsOfSubtype($this->_id, $subtypesToBeRemoved, $subtypesToPreserve);
6a488035
TO
432 }
433 }
434 elseif ($this->_action & CRM_Core_Action::ADD) {
435 //new custom set , so lets set the created_id
436 $session = CRM_Core_Session::singleton();
437 $params['created_id'] = $session->get('userID');
438 $params['created_date'] = date('YmdHis');
439 }
440
441 $group = CRM_Core_BAO_CustomGroup::create($params);
442
443 // reset the cache
9cdf85c1 444 Civi::cache('fields')->flush();
5e601882
SL
445 // reset ACL and system caches.
446 CRM_Core_BAO_Cache::resetCaches();
6a488035
TO
447
448 if ($this->_action & CRM_Core_Action::UPDATE) {
be2fb01f 449 CRM_Core_Session::setStatus(ts('Your custom field set \'%1 \' has been saved.', [1 => $group->title]), ts('Saved'), 'success');
6a488035
TO
450 }
451 else {
704f21c0
CW
452 // Jump directly to adding a field if popups are disabled
453 $action = CRM_Core_Resources::singleton()->ajaxPopupsEnabled ? '' : '/add';
6f231148 454 $url = CRM_Utils_System::url("civicrm/admin/custom/group/field$action", 'reset=1&new=1&gid=' . $group->id . '&action=' . ($action ? 'add' : 'browse'));
6a488035 455 CRM_Core_Session::setStatus(ts("Your custom field set '%1' has been added. You can add custom fields now.",
be2fb01f 456 [1 => $group->title]
353ffa53 457 ), ts('Saved'), 'success');
6a488035
TO
458 $session = CRM_Core_Session::singleton();
459 $session->replaceUserContext($url);
460 }
461
462 // prompt Drupal Views users to update $db_prefix in settings.php, if necessary
463 global $db_prefix;
464 $config = CRM_Core_Config::singleton();
465 if (is_array($db_prefix) && $config->userSystem->is_drupal && module_exists('views')) {
466 // get table_name for each custom group
be2fb01f 467 $tables = [];
353ffa53 468 $sql = "SELECT table_name FROM civicrm_custom_group WHERE is_active = 1";
6a488035
TO
469 $result = CRM_Core_DAO::executeQuery($sql);
470 while ($result->fetch()) {
471 $tables[$result->table_name] = $result->table_name;
472 }
473
474 // find out which tables are missing from the $db_prefix array
475 $missingTableNames = array_diff_key($tables, $db_prefix);
476
477 if (!empty($missingTableNames)) {
478 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'.",
be2fb01f 479 [1 => implode(', ', $missingTableNames)]
353ffa53 480 ), ts('Note'), 'info');
6a488035
TO
481 }
482 }
483 }
484
e0ef6999 485 /**
a9c1ff43 486 * Return a formatted list of relationship labels.
e0ef6999 487 *
a6c01b45 488 * @return array
a9c1ff43 489 * Array (int $id => string $label).
e0ef6999 490 */
a9c1ff43
TO
491 public static function getRelationshipTypes() {
492 // Note: We include inactive reltypes because we don't want to break custom-data
493 // UI when a reltype is disabled.
494 return CRM_Core_DAO::executeQuery('
495 SELECT
496 id,
497 (CASE 1
498 WHEN label_a_b is not null AND label_b_a is not null AND label_a_b != label_b_a
499 THEN concat(label_a_b, \' / \', label_b_a)
500 WHEN label_a_b is not null
501 THEN label_a_b
502 WHEN label_b_a is not null
503 THEN label_b_a
504 ELSE concat("RelType #", id)
505 END) as label
506 FROM civicrm_relationship_type
507 '
508 )->fetchMap('id', 'label');
6a488035 509 }
96025800 510
6a488035 511}