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