Merge pull request #22719 from eileenmcnaughton/notice_page
[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 (empty($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 $this->applyFilter('__ALL__', 'trim');
151
152 $isReserved = FALSE;
153 if ($this->_id) {
154 $isReserved = (bool) CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'is_reserved');
155 }
156
157 $this->add('text',
158 'label',
159 ts('Label'),
160 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'label'),
161 TRUE
162 );
163
164 if ($this->_gName != 'activity_type') {
165 $this->add('text',
166 'value',
167 ts('Value'),
168 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'value'),
169 TRUE
170 );
171 $this->addRule('value',
172 ts('This Value already exists in the database for this option group. Please select a different Value.'),
173 'optionExists',
174 ['CRM_Core_DAO_OptionValue', $this->_id, $this->_gid, 'value', $this->_domainSpecific]
175 );
176 }
177 else {
178 $this->add('text', 'icon', ts('Icon'), ['class' => 'crm-icon-picker', 'title' => ts('Choose Icon'), 'allowClear' => TRUE]);
179 }
180
181 if (in_array($this->_gName, ['activity_status', 'case_status'])) {
182 $this->add('color', 'color', ts('Color'));
183 }
184
185 if (!in_array($this->_gName, ['email_greeting', 'postal_greeting', 'addressee'])
186 && !$isReserved
187 ) {
188 $this->addRule('label',
189 ts('This Label already exists in the database for this option group. Please select a different Label.'),
190 'optionExists',
191 ['CRM_Core_DAO_OptionValue', $this->_id, $this->_gid, 'label', $this->_domainSpecific]
192 );
193 }
194
195 if ($this->_gName == 'case_status') {
196 $classes = [
197 'Opened' => ts('Opened'),
198 'Closed' => ts('Closed'),
199 ];
200
201 $grouping = $this->add('select',
202 'grouping',
203 ts('Status Class'),
204 $classes
205 );
206 if ($isReserved) {
207 $grouping->freeze();
208 }
209 }
210 // CRM-11516
211 if ($this->_gName == 'payment_instrument') {
212 $accountType = CRM_Core_PseudoConstant::accountOptionValues('financial_account_type', NULL, " AND v.name = 'Asset' ");
213 $financialAccount = CRM_Contribute_PseudoConstant::financialAccount(NULL, key($accountType));
214
215 $this->add('select', 'financial_account_id', ts('Financial Account'),
216 ['' => ts('- select -')] + $financialAccount,
217 TRUE
218 );
219 }
220
221 if ($this->_gName == 'activity_status') {
222 $this->add('select',
223 'filter',
224 ts('Status Type'),
225 [
226 CRM_Activity_BAO_Activity::INCOMPLETE => ts('Incomplete'),
227 CRM_Activity_BAO_Activity::COMPLETED => ts('Completed'),
228 CRM_Activity_BAO_Activity::CANCELLED => ts('Cancelled'),
229 ]
230 );
231 }
232 if ($this->_gName == 'redaction_rule') {
233 $this->add('checkbox',
234 'filter',
235 ts('Regular Expression?')
236 );
237 }
238 if ($this->_gName == 'participant_listing') {
239 $this->add('text',
240 'description',
241 ts('Description'),
242 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'description')
243 );
244 }
245 else {
246 // Hard-coding attributes here since description is still stored as varchar and not text in the schema. dgg
247 $this->add('wysiwyg', 'description',
248 ts('Description'),
249 ['rows' => 4, 'cols' => 80],
250 $this->_gName == 'custom_search'
251 );
252 }
253
254 if ($this->_gName == 'event_badge') {
255 $this->add('text',
256 'name',
257 ts('Class Name'),
258 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'name')
259 );
260 }
261
262 $this->add('number',
263 'weight',
264 ts('Order'),
265 CRM_Core_DAO::getAttribute('CRM_Core_DAO_OptionValue', 'weight'),
266 TRUE
267 );
268 $this->addRule('weight', ts('is a numeric field'), 'numeric');
269
270 // If CiviCase enabled AND "Add" mode OR "edit" mode for non-reserved activities, only allow user to pick Core or CiviCase component.
271 // FIXME: Each component should define whether adding new activity types is allowed.
272 if ($this->_gName == 'activity_type' && CRM_Core_Component::isEnabled("CiviCase") &&
273 (($this->_action & CRM_Core_Action::ADD) || !$isReserved)
274 ) {
275 $caseID = CRM_Core_Component::getComponentID('CiviCase');
276 $components = ['' => ts('Contacts AND Cases'), $caseID => ts('Cases Only')];
277 $this->add('select',
278 'component_id',
279 ts('Component'),
280 $components, FALSE
281 );
282 }
283
284 $enabled = $this->add('checkbox', 'is_active', ts('Enabled?'));
285
286 if ($isReserved) {
287 $enabled->freeze();
288 }
289
290 // fix for CRM-3552, CRM-4575
291 $showIsDefaultGroups = [
292 'email_greeting',
293 'postal_greeting',
294 'addressee',
295 'from_email_address',
296 'case_status',
297 'encounter_medium',
298 'case_type',
299 'payment_instrument',
300 'communication_style',
301 'soft_credit_type',
302 'website_type',
303 ];
304
305 if (in_array($this->_gName, $showIsDefaultGroups)) {
306 $this->assign('showDefault', TRUE);
307 $this->add('checkbox', 'is_default', ts('Default Option?'));
308 }
309
310 // get contact type for which user want to create a new greeting/addressee type, CRM-4575
311 if (in_array($this->_gName, ['email_greeting', 'postal_greeting', 'addressee'])
312 && !$isReserved
313 ) {
314 $values = [
315 1 => ts('Individual'),
316 2 => ts('Household'),
317 3 => ts('Organization'),
318 4 => ts('Multiple Contact Merge'),
319 ];
320 $this->add('select', 'contactOptions', ts('Contact Type'), ['' => '-select-'] + $values, TRUE);
321 $this->assign('showContactFilter', TRUE);
322 }
323
324 if ($this->_gName == 'participant_status') {
325 // For Participant Status options, expose the 'filter' field to track which statuses are "Counted", and the Visibility field
326 $this->add('checkbox', 'filter', ts('Counted?'));
327 $this->add('select', 'visibility_id', ts('Visibility'), CRM_Core_PseudoConstant::visibility());
328 }
329 if ($this->_gName == 'participant_role') {
330 // For Participant Role options, expose the 'filter' field to track which statuses are "Counted"
331 $this->add('checkbox', 'filter', ts('Counted?'));
332 }
333
334 $this->addFormRule(['CRM_Admin_Form_Options', 'formRule'], $this);
335 }
336
337 /**
338 * Global form rule.
339 *
340 * @param array $fields
341 * The input form values.
342 * @param array $files
343 * The uploaded files if any.
344 * @param self $self
345 * Current form object.
346 *
347 * @return array
348 * array of errors / empty array.
349 * @throws \CRM_Core_Exception
350 */
351 public static function formRule($fields, $files, $self) {
352 $errors = [];
353 if ($self->_gName == 'case_status' && empty($fields['grouping'])) {
354 $errors['grouping'] = ts('Status class is a required field');
355 }
356
357 if (in_array($self->_gName, ['email_greeting', 'postal_greeting', 'addressee'])
358 && empty($self->_defaultValues['is_reserved'])
359 ) {
360 $label = $fields['label'];
361 $condition = " AND v.label = '{$label}' ";
362 $values = CRM_Core_OptionGroup::values($self->_gName, FALSE, FALSE, FALSE, $condition, 'filter');
363 $checkContactOptions = TRUE;
364
365 if ($self->_id && ($self->_defaultValues['contactOptions'] == $fields['contactOptions'])) {
366 $checkContactOptions = FALSE;
367 }
368
369 if ($checkContactOptions && in_array($fields['contactOptions'], $values)) {
370 $errors['label'] = ts('This Label already exists in the database for the selected contact type.');
371 }
372 }
373
374 if ($self->_gName == 'from_email_address') {
375 $formEmail = CRM_Utils_Mail::pluckEmailFromHeader($fields['label']);
376 if (!CRM_Utils_Rule::email($formEmail)) {
377 $errors['label'] = ts('Please enter a valid email address.');
378 }
379
380 $formName = explode('"', $fields['label']);
381 if (empty($formName[1]) || count($formName) != 3) {
382 $errors['label'] = ts('Please follow the proper format for From Email Address');
383 }
384 }
385
386 $dataType = self::getOptionGroupDataType($self->_gName);
387 if ($dataType && $self->_gName !== 'activity_type') {
388 $validate = CRM_Utils_Type::validate($fields['value'], $dataType, FALSE);
389 if ($validate === FALSE) {
390 CRM_Core_Session::setStatus(
391 ts('Data Type of the value field for this option value does not match %1.', [1 => $dataType]),
392 ts('Value field Data Type mismatch'));
393 }
394 }
395 return $errors;
396 }
397
398 /**
399 * Get the DataType for a specified Option Group.
400 *
401 * @param string $optionGroupName name of the option group
402 *
403 * @return string|null
404 */
405 public static function getOptionGroupDataType($optionGroupName) {
406 $optionGroupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionGroup', $optionGroupName, 'id', 'name');
407
408 $dataType = CRM_Core_BAO_OptionGroup::getDataType($optionGroupId);
409 return $dataType;
410 }
411
412 /**
413 * Process the form submission.
414 */
415 public function postProcess() {
416 if ($this->_action & CRM_Core_Action::DELETE) {
417 $fieldValues = ['option_group_id' => $this->_gid];
418 CRM_Utils_Weight::delWeight('CRM_Core_DAO_OptionValue', $this->_id, $fieldValues);
419
420 if (CRM_Core_BAO_OptionValue::del($this->_id)) {
421 if ($this->_gName == 'phone_type') {
422 CRM_Core_BAO_Phone::setOptionToNull(CRM_Utils_Array::value('value', $this->_defaultValues));
423 }
424
425 CRM_Core_Session::setStatus(ts('Selected %1 type has been deleted.', [1 => $this->_gLabel]), ts('Record Deleted'), 'success');
426 }
427 else {
428 CRM_Core_Session::setStatus(ts('Selected %1 type has not been deleted.', [1 => $this->_gLabel]), ts('Sorry'), 'error');
429 CRM_Utils_Weight::correctDuplicateWeights('CRM_Core_DAO_OptionValue', $fieldValues);
430 }
431 }
432 else {
433 $params = $this->exportValues();
434
435 // allow multiple defaults within group.
436 $allowMultiDefaults = ['email_greeting', 'postal_greeting', 'addressee', 'from_email_address'];
437 if (in_array($this->_gName, $allowMultiDefaults)) {
438 if ($this->_gName == 'from_email_address') {
439 $params['reset_default_for'] = ['domain_id' => CRM_Core_Config::domainID()];
440 }
441 elseif ($filter = CRM_Utils_Array::value('contactOptions', $params)) {
442 $params['filter'] = $filter;
443 $params['reset_default_for'] = ['filter' => "0, " . $params['filter']];
444 }
445
446 //make sure we only have a single space, CRM-6977 and dev/mail/15
447 if ($this->_gName == 'from_email_address') {
448 $params['label'] = $this->sanitizeFromEmailAddress($params['label']);
449 }
450 }
451
452 // set value of filter if not present in params
453 if ($this->_id && !array_key_exists('filter', $params)) {
454 if ($this->_gName == 'participant_role') {
455 $params['filter'] = 0;
456 }
457 else {
458 $params['filter'] = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_OptionValue', $this->_id, 'filter', 'id');
459 }
460 }
461
462 if (isset($params['color']) && strtolower($params['color']) == '#ffffff') {
463 $params['color'] = 'null';
464 }
465
466 $optionValue = CRM_Core_OptionValue::addOptionValue($params, $this->_gName, $this->_action, $this->_id);
467
468 CRM_Core_Session::setStatus(ts('The %1 \'%2\' has been saved.', [
469 1 => $this->_gLabel,
470 2 => $optionValue->label,
471 ]), ts('Saved'), 'success');
472
473 $this->ajaxResponse['optionValue'] = $optionValue->toArray();
474 }
475 }
476
477 public function sanitizeFromEmailAddress($email) {
478 preg_match("/^\"(.*)\" *<([^@>]*@[^@>]*)>$/", $email, $parts);
479 return "\"{$parts[1]}\" <$parts[2]>";
480 }
481
482 }