Merge pull request #23016 from pradpnayak/optionValue
[civicrm-core.git] / CRM / Admin / Form / Options.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 * This class generates form components for Options.
20 */
21 class CRM_Admin_Form_Options extends CRM_Admin_Form {
22
23 /**
24 * The option group name.
25 *
26 * @var array
27 */
28 protected $_gName;
29
30 /**
31 * The option group name in display format (capitalized, without underscores...etc)
32 *
33 * @var array
34 */
35 protected $_gLabel;
36
37 /**
38 * Is this Option Group Domain Specific
39 * @var bool
40 */
41 protected $_domainSpecific = FALSE;
42
43 /**
44 * @var bool
45 */
46 public $submitOnce = TRUE;
47
48 /**
49 * Pre-process
50 */
51 public function preProcess() {
52 parent::preProcess();
53 $session = CRM_Core_Session::singleton();
54 if (!$this->_gName && !empty($this->urlPath[3])) {
55 $this->_gName = $this->urlPath[3];
56 }
57 if (!$this->_gName && !empty($_GET['gid'])) {
58 $this->_gName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', (int) $_GET['gid'], 'name');
59 }
60 if ($this->_gName) {
61 $this->set('gName', $this->_gName);
62 }
63 else {
64 $this->_gName = $this->get('gName');
65 }
66 $this->_gid = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup',
67 $this->_gName,
68 'id',
69 'name'
70 );
71 $this->_gLabel = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $this->_gid, 'title');
72 $this->_domainSpecific = CRM_Core_OptionGroup::isDomainOptionGroup($this->_gName);
73 $url = "civicrm/admin/options/{$this->_gName}";
74 $params = "reset=1";
75
76 if (($this->_action & CRM_Core_Action::DELETE) &&
77 in_array($this->_gName, ['email_greeting', 'postal_greeting', 'addressee'])
78 ) {
79 // Don't allow delete if the option value belongs to addressee, postal or email greetings and is in use.
80 $findValue = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'value');
81 $queryParam = [1 => [$findValue, 'Integer']];
82 $columnName = $this->_gName . "_id";
83 $sql = "SELECT count(id) FROM civicrm_contact WHERE " . $columnName . " = %1";
84 $isInUse = CRM_Core_DAO::singleValueQuery($sql, $queryParam);
85 if ($isInUse) {
86 $scriptURL = "<a href='" . CRM_Utils_System::docURL2('Update Greetings and Address Data for Contacts', TRUE, NULL, NULL, NULL, "wiki") . "'>" . ts('Learn more about a script that can automatically update contact addressee and greeting options.') . "</a>";
87 CRM_Core_Session::setStatus(ts('The selected %1 option has <strong>not been deleted</strong> because it is currently in use. Please update these contacts to use a different format before deleting this option. %2', [
88 1 => $this->_gLabel,
89 2 => $scriptURL,
90 ]), ts('Sorry'), 'error');
91 $redirect = CRM_Utils_System::url($url, $params);
92 CRM_Utils_System::redirect($redirect);
93 }
94 }
95
96 $session->pushUserContext(CRM_Utils_System::url($url, $params));
97 $this->assign('id', $this->_id);
98
99 if ($this->_id && CRM_Core_OptionGroup::isDomainOptionGroup($this->_gName)) {
100 $domainID = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'domain_id', 'id');
101 if (CRM_Core_Config::domainID() != $domainID) {
102 CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.'));
103 }
104 }
105 }
106
107 /**
108 * Set default values for the form.
109 */
110 public function setDefaultValues() {
111 $defaults = parent::setDefaultValues();
112
113 // Default weight & value
114 $fieldValues = ['option_group_id' => $this->_gid];
115 foreach (['weight', 'value'] as $field) {
116 if (!isset($defaults[$field]) || $defaults[$field] === '') {
117 $defaults[$field] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_OptionValue', $fieldValues, $field);
118 }
119 }
120
121 // setDefault of contact types for email greeting, postal greeting, addressee, CRM-4575
122 if (in_array($this->_gName, [
123 'email_greeting',
124 'postal_greeting',
125 'addressee',
126 ])) {
127 $defaults['contactOptions'] = (CRM_Utils_Array::value('filter', $defaults)) ? $defaults['filter'] : NULL;
128 }
129 // CRM-11516
130 if ($this->_gName == 'payment_instrument' && $this->_id) {
131 $defaults['financial_account_id'] = CRM_Contribute_PseudoConstant::getRelationalFinancialAccount($this->_id, NULL, 'civicrm_option_value');
132 }
133 if (empty($this->_id) || !CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'color')) {
134 $defaults['color'] = '#ffffff';
135 }
136 return $defaults;
137 }
138
139 /**
140 * Build the form object.
141 */
142 public function buildQuickForm() {
143 parent::buildQuickForm();
144 $this->setPageTitle(ts('%1 Option', [1 => $this->_gLabel]));
145
146 if ($this->_action & CRM_Core_Action::DELETE) {
147 return;
148 }
149
150 $optionGroup = \Civi\Api4\OptionGroup::get(FALSE)
151 ->addWhere('id', '=', $this->_gid)
152 ->execute()->first();
153
154 $this->applyFilter('__ALL__', 'trim');
155
156 $isReserved = FALSE;
157 if ($this->_id) {
158 $isReserved = (bool) CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'is_reserved');
159 }
160
161 $this->add('text',
162 'label',
163 ts('Label'),
164 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'label'),
165 TRUE
166 );
167
168 if ($this->_gName != 'activity_type') {
169 $this->add('text',
170 'value',
171 ts('Value'),
172 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'value'),
173 TRUE
174 );
175 $this->addRule('value',
176 ts('This Value already exists in the database for this option group. Please select a different Value.'),
177 'optionExists',
178 ['CRM_Core_DAO_OptionValue', $this->_id, $this->_gid, 'value', $this->_domainSpecific]
179 );
180 }
181
182 // Add icon & color if this option group supports it.
183 if ($optionGroup['option_value_fields'] && in_array('icon', $optionGroup['option_value_fields'])) {
184 $this->add('text', 'icon', ts('Icon'), ['class' => 'crm-icon-picker', 'title' => ts('Choose Icon'), 'allowClear' => TRUE]);
185 }
186 if ($optionGroup['option_value_fields'] && in_array('color', $optionGroup['option_value_fields'])) {
187 $this->add('color', 'color', ts('Color'));
188 }
189
190 if (!in_array($this->_gName, ['email_greeting', 'postal_greeting', 'addressee'])
191 && !$isReserved
192 ) {
193 $this->addRule('label',
194 ts('This Label already exists in the database for this option group. Please select a different Label.'),
195 'optionExists',
196 ['CRM_Core_DAO_OptionValue', $this->_id, $this->_gid, 'label', $this->_domainSpecific]
197 );
198 }
199
200 if ($this->_gName == 'case_status') {
201 $classes = [
202 'Opened' => ts('Opened'),
203 'Closed' => ts('Closed'),
204 ];
205
206 $grouping = $this->add('select',
207 'grouping',
208 ts('Status Class'),
209 $classes
210 );
211 if ($isReserved) {
212 $grouping->freeze();
213 }
214 }
215 // CRM-11516
216 if ($this->_gName == 'payment_instrument') {
217 $accountType = CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name = 'Asset' ");
218 $financialAccount = CRM_Contribute_PseudoConstant::financialAccount(NULL, key($accountType));
219
220 $this->add('select', 'financial_account_id', ts('Financial Account'),
221 ['' => ts('- select -')] + $financialAccount,
222 TRUE
223 );
224 }
225
226 if ($this->_gName == 'activity_status') {
227 $this->add('select',
228 'filter',
229 ts('Status Type'),
230 [
231 CRM_Activity_BAO_Activity::INCOMPLETE => ts('Incomplete'),
232 CRM_Activity_BAO_Activity::COMPLETED => ts('Completed'),
233 CRM_Activity_BAO_Activity::CANCELLED => ts('Cancelled'),
234 ]
235 );
236 }
237 if ($this->_gName == 'redaction_rule') {
238 $this->add('checkbox',
239 'filter',
240 ts('Regular Expression?')
241 );
242 }
243 if ($this->_gName == 'participant_listing') {
244 $this->add('text',
245 'description',
246 ts('Description'),
247 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'description')
248 );
249 }
250 else {
251 // Hard-coding attributes here since description is still stored as varchar and not text in the schema. dgg
252 $this->add('wysiwyg', 'description',
253 ts('Description'),
254 ['rows' => 4, 'cols' => 80],
255 $this->_gName == 'custom_search'
256 );
257 }
258
259 if ($this->_gName == 'event_badge') {
260 $this->add('text',
261 'name',
262 ts('Class Name'),
263 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'name')
264 );
265 }
266
267 $this->add('number',
268 'weight',
269 ts('Order'),
270 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'weight'),
271 TRUE
272 );
273 $this->addRule('weight', ts('is a numeric field'), 'numeric');
274
275 // If CiviCase enabled AND "Add" mode OR "edit" mode for non-reserved activities, only allow user to pick Core or CiviCase component.
276 // FIXME: Each component should define whether adding new activity types is allowed.
277 if ($this->_gName == 'activity_type' && CRM_Core_Component::isEnabled("CiviCase") &&
278 (($this->_action & CRM_Core_Action::ADD) || !$isReserved)
279 ) {
280 $caseID = CRM_Core_Component::getComponentID('CiviCase');
281 $components = ['' => ts('Contacts AND Cases'), $caseID => ts('Cases Only')];
282 $this->add('select',
283 'component_id',
284 ts('Component'),
285 $components, FALSE
286 );
287 }
288
289 $enabled = $this->add('checkbox', 'is_active', ts('Enabled?'));
290
291 if ($isReserved) {
292 $enabled->freeze();
293 }
294
295 // fix for CRM-3552, CRM-4575
296 $showIsDefaultGroups = [
297 'email_greeting',
298 'postal_greeting',
299 'addressee',
300 'from_email_address',
301 'case_status',
302 'encounter_medium',
303 'case_type',
304 'payment_instrument',
305 'communication_style',
306 'soft_credit_type',
307 'website_type',
308 ];
309
310 if (in_array($this->_gName, $showIsDefaultGroups)) {
311 $this->assign('showDefault', TRUE);
312 $this->add('checkbox', 'is_default', ts('Default Option?'));
313 }
314
315 // get contact type for which user want to create a new greeting/addressee type, CRM-4575
316 if (in_array($this->_gName, ['email_greeting', 'postal_greeting', 'addressee'])
317 && !$isReserved
318 ) {
319 $values = [
320 1 => ts('Individual'),
321 2 => ts('Household'),
322 3 => ts('Organization'),
323 4 => ts('Multiple Contact Merge'),
324 ];
325 $this->add('select', 'contactOptions', ts('Contact Type'), ['' => '-select-'] + $values, TRUE);
326 $this->assign('showContactFilter', TRUE);
327 }
328
329 if ($this->_gName == 'participant_status') {
330 // For Participant Status options, expose the 'filter' field to track which statuses are "Counted", and the Visibility field
331 $this->add('checkbox', 'filter', ts('Counted?'));
332 $this->add('select', 'visibility_id', ts('Visibility'), CRM_Core_PseudoConstant::visibility());
333 }
334 if ($this->_gName == 'participant_role') {
335 // For Participant Role options, expose the 'filter' field to track which statuses are "Counted"
336 $this->add('checkbox', 'filter', ts('Counted?'));
337 }
338
339 $this->addFormRule(['CRM_Admin_Form_Options', 'formRule'], $this);
340 }
341
342 /**
343 * Global form rule.
344 *
345 * @param array $fields
346 * The input form values.
347 * @param array $files
348 * The uploaded files if any.
349 * @param self $self
350 * Current form object.
351 *
352 * @return array
353 * array of errors / empty array.
354 * @throws \CRM_Core_Exception
355 */
356 public static function formRule($fields, $files, $self) {
357 $errors = [];
358 if ($self->_gName == 'case_status' && empty($fields['grouping'])) {
359 $errors['grouping'] = ts('Status class is a required field');
360 }
361
362 if (in_array($self->_gName, ['email_greeting', 'postal_greeting', 'addressee'])
363 && empty($self->_defaultValues['is_reserved'])
364 ) {
365 $label = $fields['label'];
366 $condition = " AND v.label = '{$label}' ";
367 $values = CRM_Core_OptionGroup::values($self->_gName, FALSE, FALSE, FALSE, $condition, 'filter');
368 $checkContactOptions = TRUE;
369
370 if ($self->_id && ($self->_defaultValues['contactOptions'] == $fields['contactOptions'])) {
371 $checkContactOptions = FALSE;
372 }
373
374 if ($checkContactOptions && in_array($fields['contactOptions'], $values)) {
375 $errors['label'] = ts('This Label already exists in the database for the selected contact type.');
376 }
377 }
378
379 if ($self->_gName == 'from_email_address') {
380 $formEmail = CRM_Utils_Mail::pluckEmailFromHeader($fields['label']);
381 if (!CRM_Utils_Rule::email($formEmail)) {
382 $errors['label'] = ts('Please enter a valid email address.');
383 }
384
385 $formName = explode('"', $fields['label']);
386 if (empty($formName[1]) || count($formName) != 3) {
387 $errors['label'] = ts('Please follow the proper format for From Email Address');
388 }
389 }
390
391 $dataType = self::getOptionGroupDataType($self->_gName);
392 if ($dataType && $self->_gName !== 'activity_type') {
393 $validate = CRM_Utils_Type::validate($fields['value'], $dataType, FALSE);
394 if ($validate === FALSE) {
395 CRM_Core_Session::setStatus(
396 ts('Data Type of the value field for this option value does not match %1.', [1 => $dataType]),
397 ts('Value field Data Type mismatch'));
398 }
399 }
400 return $errors;
401 }
402
403 /**
404 * Get the DataType for a specified Option Group.
405 *
406 * @param string $optionGroupName name of the option group
407 *
408 * @return string|null
409 */
410 public static function getOptionGroupDataType($optionGroupName) {
411 $optionGroupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $optionGroupName, 'id', 'name');
412
413 $dataType = CRM_Core_BAO_OptionGroup::getDataType($optionGroupId);
414 return $dataType;
415 }
416
417 /**
418 * Process the form submission.
419 */
420 public function postProcess() {
421 if ($this->_action & CRM_Core_Action::DELETE) {
422 $fieldValues = ['option_group_id' => $this->_gid];
423 CRM_Utils_Weight::delWeight('CRM_Core_DAO_OptionValue', $this->_id, $fieldValues);
424
425 if (CRM_Core_BAO_OptionValue::del($this->_id)) {
426 if ($this->_gName == 'phone_type') {
427 CRM_Core_BAO_Phone::setOptionToNull(CRM_Utils_Array::value('value', $this->_defaultValues));
428 }
429
430 CRM_Core_Session::setStatus(ts('Selected %1 type has been deleted.', [1 => $this->_gLabel]), ts('Record Deleted'), 'success');
431 }
432 else {
433 CRM_Core_Session::setStatus(ts('Selected %1 type has not been deleted.', [1 => $this->_gLabel]), ts('Sorry'), 'error');
434 CRM_Utils_Weight::correctDuplicateWeights('CRM_Core_DAO_OptionValue', $fieldValues);
435 }
436 }
437 else {
438 $params = $this->exportValues();
439
440 // allow multiple defaults within group.
441 $allowMultiDefaults = ['email_greeting', 'postal_greeting', 'addressee', 'from_email_address'];
442 if (in_array($this->_gName, $allowMultiDefaults)) {
443 if ($this->_gName == 'from_email_address') {
444 $params['reset_default_for'] = ['domain_id' => CRM_Core_Config::domainID()];
445 }
446 elseif ($filter = CRM_Utils_Array::value('contactOptions', $params)) {
447 $params['filter'] = $filter;
448 $params['reset_default_for'] = ['filter' => "0, " . $params['filter']];
449 }
450
451 //make sure we only have a single space, CRM-6977 and dev/mail/15
452 if ($this->_gName == 'from_email_address') {
453 $params['label'] = $this->sanitizeFromEmailAddress($params['label']);
454 }
455 }
456
457 // set value of filter if not present in params
458 if ($this->_id && !array_key_exists('filter', $params)) {
459 if ($this->_gName == 'participant_role') {
460 $params['filter'] = 0;
461 }
462 else {
463 $params['filter'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'filter', 'id');
464 }
465 }
466
467 if (isset($params['color']) && strtolower($params['color']) == '#ffffff') {
468 $params['color'] = 'null';
469 }
470
471 $optionValue = CRM_Core_OptionValue::addOptionValue($params, $this->_gName, $this->_action, $this->_id);
472
473 CRM_Core_Session::setStatus(ts('The %1 \'%2\' has been saved.', [
474 1 => $this->_gLabel,
475 2 => $optionValue->label,
476 ]), ts('Saved'), 'success');
477
478 $this->ajaxResponse['optionValue'] = $optionValue->toArray();
479 }
480 }
481
482 public function sanitizeFromEmailAddress($email) {
483 preg_match("/^\"(.*)\" *<([^@>]*@[^@>]*)>$/", $email, $parts);
484 return "\"{$parts[1]}\" <$parts[2]>";
485 }
486
487 }