Merge pull request #4606 from johanv/CRM-15636-price_set_event_and_contribution
[civicrm-core.git] / CRM / UF / Form / Field.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.6 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
32 * $Id$
33 *
34 */
35
36 /**
37 * form to process actions on the field aspect of Custom
38 */
39 class CRM_UF_Form_Field extends CRM_Core_Form {
40
41 /**
42 * The uf group id saved to the session for an update
43 *
44 * @var int
45 */
46 protected $_gid;
47
48 /**
49 * The field id, used when editing the field
50 *
51 * @var int
52 */
53 protected $_id;
54
55 /**
56 * The set of fields that we can view/edit in the user field framework
57 *
58 * @var array
59 */
60 protected $_fields;
61
62 /**
63 * The title for field
64 *
65 * @var int
66 */
67 protected $_title;
68
69 /**
70 * The set of fields sent to the select element
71 *
72 * @var array
73 */
74 protected $_selectFields;
75
76 /**
77 * To store fields with if locationtype exits status
78 *
79 * @var array
80 */
81 protected $_hasLocationTypes;
82
83 /**
84 * Is this profile has searchable field
85 * or is any field having in selector true.
86 *
87 * @var boolean.
88 */
89 protected $_hasSearchableORInSelector;
90
91 /**
92 * Set variables up before form is built
93 *
94 * @return void
95 */
96 public function preProcess() {
97 $this->_gid = CRM_Utils_Request::retrieve('gid', 'Positive', $this);
98 $this->_id = CRM_Utils_Request::retrieve('id', 'Positive', $this);
99 if ($this->_gid) {
100 $this->_title = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $this->_gid, 'title');
101
102 $this->setPageTitle(ts('Profile Field'));
103
104 $url = CRM_Utils_System::url('civicrm/admin/uf/group/field',
105 "reset=1&action=browse&gid={$this->_gid}"
106 );
107
108 $session = CRM_Core_Session::singleton();
109 $session->pushUserContext($url);
110 $breadCrumb = array(array('title' => ts('CiviCRM Profile Fields'),
111 'url' => $url,
112 ));
113 CRM_Utils_System::appendBreadCrumb($breadCrumb);
114 }
115
116 $showBestResult = CRM_Utils_Request::retrieve('sbr', 'Positive', CRM_Core_DAO::$_nullArray);
117 if ($showBestResult) {
118 $this->assign('showBestResult', $showBestResult);
119 }
120
121 $this->_fields = CRM_Contact_BAO_Contact::importableFields('All', TRUE, TRUE, TRUE, TRUE, TRUE);
122 $this->_fields = array_merge(CRM_Activity_BAO_Activity::exportableFields('Activity'), $this->_fields);
123
124 //unset campaign related fields.
125 if (isset($this->_fields['activity_campaign_id'])) {
126 $this->_fields['activity_campaign_id']['title'] = ts('Campaign');
127 if (isset($this->_fields['activity_campaign'])) {
128 unset($this->_fields['activity_campaign']);
129 }
130 }
131
132 if (CRM_Core_Permission::access('CiviContribute')) {
133 $this->_fields = array_merge(CRM_Contribute_BAO_Contribution::getContributionFields(FALSE), $this->_fields);
134 $this->_fields = array_merge(CRM_Core_BAO_UFField::getContribBatchEntryFields(), $this->_fields);
135 }
136
137 if (CRM_Core_Permission::access('CiviMember')) {
138 $this->_fields = array_merge(CRM_Member_BAO_Membership::getMembershipFields(), $this->_fields);
139 }
140
141 if (CRM_Core_Permission::access('CiviEvent')) {
142 $this->_fields = array_merge(CRM_Event_BAO_Query::getParticipantFields(), $this->_fields);
143 }
144
145 if (CRM_Core_Permission::access('CiviCase')) {
146 $this->_fields = array_merge(CRM_Case_BAO_Query::getFields(), $this->_fields);
147 }
148
149 $this->_fields = array_merge($this->_fields, CRM_Contact_BAO_Query_Hook::singleton()->getFields());
150
151 $this->_selectFields = array();
152 foreach ($this->_fields as $name => $field) {
153 // lets skip note for now since we dont support it
154 if ($name == 'note') {
155 continue;
156 }
157 $this->_selectFields[$name] = $field['title'];
158 $this->_hasLocationTypes[$name] = CRM_Utils_Array::value('hasLocationType', $field);
159 }
160
161 // lets add group, tag and current_employer to this list
162 $this->_selectFields['group'] = ts('Group(s)');
163 $this->_selectFields['tag'] = ts('Tag(s)');
164 $this->_selectFields['current_employer'] = ts('Current Employer');
165 $this->_selectFields['phone_and_ext'] = ts('Phone and Extension');
166
167 //CRM-4363 check for in selector or searchable fields.
168 $this->_hasSearchableORInSelector = CRM_Core_BAO_UFField::checkSearchableORInSelector($this->_gid);
169
170 $this->assign('fieldId', $this->_id);
171 if ($this->_id) {
172 $fieldTitle = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFField', $this->_id, 'label');
173 $this->assign('fieldTitle', $fieldTitle);
174 }
175 }
176
177 /**
178 * Build the form object
179 *
180 * @return void
181 */
182 public function buildQuickForm() {
183 if ($this->_action & CRM_Core_Action::DELETE) {
184 $this->addButtons(array(
185 array(
186 'type' => 'next',
187 'name' => ts('Delete Profile Field'),
188 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
189 'isDefault' => TRUE,
190 ),
191 array(
192 'type' => 'cancel',
193 'name' => ts('Cancel'),
194 ),
195 )
196 );
197 return;
198 }
199
200 if (isset($this->_id)) {
201 $params = array('id' => $this->_id);
202 CRM_Core_BAO_UFField::retrieve($params, $defaults);
203
204 // set it to null if so (avoids crappy E_NOTICE errors below
205 $defaults['location_type_id'] = CRM_Utils_Array::value('location_type_id', $defaults);
206
207 $specialFields = CRM_Core_BAO_UFGroup::getLocationFields();
208
209 if (!$defaults['location_type_id'] &&
210 $defaults["field_type"] != "Formatting" &&
211 in_array($defaults['field_name'], $specialFields)
212 ) {
213 $defaults['location_type_id'] = 0;
214 }
215
216 $defaults['field_name'] = array(
217 $defaults['field_type'],
218 ($defaults['field_type'] == "Formatting" ? "" : $defaults['field_name']),
219 ($defaults['field_name'] == "url") ? $defaults['website_type_id'] : $defaults['location_type_id'],
220 CRM_Utils_Array::value('phone_type_id', $defaults),
221 );
222 $this->_gid = $defaults['uf_group_id'];
223 }
224 else {
225 $defaults['is_active'] = 1;
226 }
227
228 $otherModules = array_values(CRM_Core_BAO_UFGroup::getUFJoinRecord($this->_gid));
229 $this->assign('otherModules', $otherModules);
230
231 if ($this->_action & CRM_Core_Action::ADD) {
232 $fieldValues = array('uf_group_id' => $this->_gid);
233 $defaults['weight'] = CRM_Utils_Weight::getDefaultWeight('CRM_Core_DAO_UFField', $fieldValues);
234 }
235
236 // lets trim all the whitespace
237 $this->applyFilter('__ALL__', 'trim');
238
239 //hidden field to catch the group id in profile
240 $this->add('hidden', 'group_id', $this->_gid);
241
242 //hidden field to catch the field id in profile
243 $this->add('hidden', 'field_id', $this->_id);
244
245 $fields = CRM_Core_BAO_UFField::getAvailableFields($this->_gid, $defaults);
246
247 $noSearchable = $hasWebsiteTypes = array();
248 $addressCustomFields = array_keys(CRM_Core_BAO_CustomField::getFieldsForImport('Address'));
249
250 foreach ($fields as $key => $value) {
251 foreach ($value as $key1 => $value1) {
252 //CRM-2676, replacing the conflict for same custom field name from different custom group.
253 if ($customFieldId = CRM_Core_BAO_CustomField::getKeyID($key1)) {
254 $customGroupId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomField', $customFieldId, 'custom_group_id');
255 $customGroupName = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_CustomGroup', $customGroupId, 'title');
256 $this->_mapperFields[$key][$key1] = $value1['title'] . ' :: ' . $customGroupName;
257 if (in_array($key1, $addressCustomFields)) {
258 $noSearchable[] = $value1['title'] . ' :: ' . $customGroupName;
259 }
260 }
261 else {
262 $this->_mapperFields[$key][$key1] = $value1['title'];
263 }
264 $hasLocationTypes[$key][$key1] = CRM_Utils_Array::value('hasLocationType', $value1);
265 $hasWebsiteTypes[$key][$key1] = CRM_Utils_Array::value('hasWebsiteType', $value1);
266 // hide the 'is searchable' field for 'File' custom data
267 if (isset($value1['data_type']) &&
268 isset($value1['html_type']) &&
269 (($value1['data_type'] == 'File' && $value1['html_type'] == 'File')
270 || ($value1['data_type'] == 'Link' && $value1['html_type'] == 'Link')
271 )
272 ) {
273 if (!in_array($value1['title'], $noSearchable)) {
274 $noSearchable[] = $value1['title'];
275 }
276 }
277 }
278 }
279 $this->assign('noSearchable', $noSearchable);
280
281 $this->_location_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id');
282 $defaultLocationType = CRM_Core_BAO_LocationType::getDefault();
283 $this->_website_types = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Website', 'website_type_id');
284
285 /**
286 * FIXME: dirty hack to make the default option show up first. This
287 * avoids a mozilla browser bug with defaults on dynamically constructed
288 * selector widgets.
289 */
290 if ($defaultLocationType) {
291 $defaultLocation = $this->_location_types[$defaultLocationType->id];
292 unset($this->_location_types[$defaultLocationType->id]);
293 $this->_location_types = array(
294 $defaultLocationType->id => $defaultLocation) + $this->_location_types;
295 }
296
297 $this->_location_types = array('Primary') + $this->_location_types;
298
299 // since we need a hierarchical list to display contact types & subtypes,
300 // this is what we going to display in first selector
301 $contactTypes = CRM_Contact_BAO_ContactType::getSelectElements(FALSE, FALSE);
302 unset($contactTypes['']);
303
304 $contactTypes = !empty($contactTypes) ? array('Contact' => 'Contacts') + $contactTypes : array();
305 $sel1 = array('' => '- select -') + $contactTypes;
306
307 if (!empty($fields['Activity'])) {
308 $sel1['Activity'] = 'Activity';
309 }
310
311 if (CRM_Core_Permission::access('CiviEvent')) {
312 $sel1['Participant'] = 'Participants';
313 }
314
315 if (!empty($fields['Contribution'])) {
316 $sel1['Contribution'] = 'Contributions';
317 }
318
319 if (!empty($fields['Membership'])) {
320 $sel1['Membership'] = 'Membership';
321 }
322
323 if (!empty($fields['Case'])) {
324 $sel1['Case'] = 'Case';
325 }
326
327 if (!empty($fields['Formatting'])) {
328 $sel1['Formatting'] = 'Formatting';
329 }
330
331 foreach ($sel1 as $key => $sel) {
332 if ($key) {
333 $sel2[$key] = $this->_mapperFields[$key];
334 }
335 }
336 $sel3[''] = NULL;
337 $phoneTypes = CRM_Core_PseudoConstant::get('CRM_Core_DAO_Phone', 'phone_type_id');
338 ksort($phoneTypes);
339
340 foreach ($sel1 as $k => $sel) {
341 if ($k) {
342 foreach ($this->_location_types as $key => $value) {
343 $sel4[$k]['phone'][$key] = &$phoneTypes;
344 $sel4[$k]['phone_and_ext'][$key] = &$phoneTypes;
345 }
346 }
347 }
348
349 foreach ($sel1 as $k => $sel) {
350 if ($k) {
351 if (is_array($this->_mapperFields[$k])) {
352 foreach ($this->_mapperFields[$k] as $key => $value) {
353 if ($hasLocationTypes[$k][$key]) {
354 $sel3[$k][$key] = $this->_location_types;
355 }
356 elseif ($hasWebsiteTypes[$k][$key]) {
357 $sel3[$k][$key] = $this->_website_types;
358 }
359 else {
360 $sel3[$key] = NULL;
361 }
362 }
363 }
364 }
365 }
366
367 $this->_defaults = array();
368 $js = "<script type='text/javascript'>\n";
369 $formName = "document.{$this->_name}";
370
371 $alreadyMixProfile = FALSE;
372 if (CRM_Core_BAO_UFField::checkProfileType($this->_gid)) {
373 $alreadyMixProfile = TRUE;
374 }
375 $this->assign('alreadyMixProfile', $alreadyMixProfile);
376
377 $sel = &$this->addElement('hierselect', 'field_name', ts('Field Name'));
378
379 $formValues = $this->exportValues();
380
381 if (empty($formValues)) {
382 for ($k = 1; $k < 4; $k++) {
383 if (!isset($defaults['field_name'][$k])) {
384 $js .= "{$formName}['field_name[$k]'].style.display = 'none';\n";
385 }
386 }
387 }
388 else {
389 if (!empty($formValues['field_name'])) {
390 for ($key = 1; $key < 4; $key++) {
391 if (!isset($formValues['field_name'][$key])) {
392 $js .= "{$formName}['field_name[$key]'].style.display = 'none';\n";
393 }
394 else {
395 $js .= "{$formName}['field_name[$key]'].style.display = '';\n";
396 }
397 }
398 }
399 else {
400 for ($k = 1; $k < 4; $k++) {
401 if (!isset($defaults['field_name'][$k])) {
402 $js .= "{$formName}['field_name[$k]'].style.display = 'none';\n";
403 }
404 }
405 }
406 }
407
408 foreach ($sel2 as $k => $v) {
409 if (is_array($sel2[$k])) {
410 asort($sel2[$k]);
411 }
412 }
413
414 $sel->setOptions(array($sel1, $sel2, $sel3, $sel4));
415
416 // proper interpretation of spec in CRM-8732
417 if (!isset($this->_id) && in_array('Search Profile', $otherModules)) {
418 $defaults['visibility'] = 'Public Pages and Listings';
419 }
420
421 $js .= "</script>\n";
422 $this->assign('initHideBoxes', $js);
423
424 $this->add('select',
425 'visibility',
426 ts('Visibility'),
427 CRM_Core_SelectValues::ufVisibility(),
428 TRUE,
429 array('onChange' => "showHideSeletorSearch(this.value);")
430 );
431
432 //CRM-4363
433 $js = array('onChange' => "mixProfile();");
434 // should the field appear in selectors (as a column)?
435 $this->add('checkbox', 'in_selector', ts('Results Column?'), NULL, NULL, $js);
436 $this->add('checkbox', 'is_searchable', ts('Searchable?'), NULL, NULL, $js);
437
438 $attributes = CRM_Core_DAO::getAttribute('CRM_Core_DAO_UFField');
439
440 // weight
441 $this->add('text', 'weight', ts('Order'), $attributes['weight'], TRUE);
442 $this->addRule('weight', ts('is a numeric field'), 'numeric');
443
444 $this->add('textarea', 'help_pre', ts('Field Pre Help'), $attributes['help_pre']);
445 $this->add('textarea', 'help_post', ts('Field Post Help'), $attributes['help_post']);
446
447 $this->add('checkbox', 'is_required', ts('Required?'));
448
449 $this->add('checkbox', 'is_multi_summary', ts('Include in multi-record listing?'));
450 $this->add('checkbox', 'is_active', ts('Active?'));
451 $this->add('checkbox', 'is_view', ts('View Only?'));
452
453 // $this->add( 'checkbox', 'is_registration', ts( 'Display in Registration Form?' ) );
454 //$this->add( 'checkbox', 'is_match' , ts( 'Key to Match Contacts?' ) );
455
456 $this->add('text', 'label', ts('Field Label'), $attributes['label']);
457
458 $js = NULL;
459 if ($this->_hasSearchableORInSelector) {
460 $js = array('onclick' => "return verify( );");
461 }
462
463 // add buttons
464 $this->addButtons(array(
465 array(
466 'type' => 'next',
467 'name' => ts('Save'),
468 'isDefault' => TRUE,
469 'js' => $js,
470 ),
471 array(
472 'type' => 'next',
473 'name' => ts('Save and New'),
474 'subName' => 'new',
475 'js' => $js,
476 ),
477 array(
478 'type' => 'cancel',
479 'name' => ts('Cancel'),
480 ),
481 )
482 );
483
484 $this->addFormRule(array('CRM_UF_Form_Field', 'formRule'), $this);
485
486 // if view mode pls freeze it with the done button.
487 if ($this->_action & CRM_Core_Action::VIEW) {
488 $this->freeze();
489 $this->addElement('button', 'done', ts('Done'),
490 array('onclick' => "location.href='civicrm/admin/uf/group/field?reset=1&action=browse&gid=" . $this->_gid . "'")
491 );
492 }
493
494 $this->setDefaults($defaults);
495 }
496
497 /**
498 * Process the form
499 *
500 * @return void
501 */
502 public function postProcess() {
503 $ids = array('uf_group' => $this->_gid);
504 if ($this->_action & CRM_Core_Action::DELETE) {
505 $fieldValues = array('uf_group_id' => $this->_gid);
506 CRM_Utils_Weight::delWeight('CRM_Core_DAO_UFField', $this->_id, $fieldValues);
507 $deleted = CRM_Core_BAO_UFField::del($this->_id);
508
509 //update group_type every time. CRM-3608
510 if ($this->_gid && $deleted) {
511 //get the profile type.
512 $fieldsType = CRM_Core_BAO_UFGroup::calculateGroupType($this->_gid, TRUE);
513 CRM_Core_BAO_UFGroup::updateGroupTypes($this->_gid, $fieldsType);
514 }
515
516 CRM_Core_Session::setStatus(ts('Selected Profile Field has been deleted.'), ts('Profile Field Deleted'), 'success');
517 return;
518 }
519
520 // store the submitted values in an array
521 $params = $this->controller->exportValues('Field');
522 if ($params['visibility'] == 'User and User Admin Only') {
523 $params['is_searchable'] = $params['in_selector'] = 0;
524 }
525
526 if ($this->_action & CRM_Core_Action::UPDATE) {
527 $ids['uf_field'] = $this->_id;
528 }
529
530
531 $name = NULL;
532 if (isset($params['field_name'][1]) && isset($this->_selectFields[$params['field_name'][1]])) {
533 // we dont get a name for a html formatting element
534 $name = $this->_selectFields[$params['field_name'][1]];
535 }
536
537 //Hack for Formatting Field Name
538 if ($params['field_name'][0] == 'Formatting') {
539 $params['field_name'][1] = 'formatting_' . rand(1000, 9999);
540 }
541
542 //check for duplicate fields
543 if ($params["field_name"][0] != "Formatting" && CRM_Core_BAO_UFField::duplicateField($params, $ids)) {
544 CRM_Core_Session::setStatus(ts('The selected field already exists in this profile.'), ts('Field Not Added'), 'error');
545 return;
546 }
547 else {
548 $params['weight'] = CRM_Core_BAO_UFField::autoWeight($params);
549 $ufField = CRM_Core_BAO_UFField::add($params, $ids);
550
551 //reset other field is searchable and in selector settings, CRM-4363
552 if ($this->_hasSearchableORInSelector &&
553 in_array($ufField->field_type, array('Participant', 'Contribution', 'Membership', 'Activity', 'Case'))
554 ) {
555 CRM_Core_BAO_UFField::resetInSelectorANDSearchable($this->_gid);
556 }
557
558 $config = CRM_Core_Config::singleton();
559 $showBestResult = FALSE;
560 if (in_array($ufField->field_name, array(
561 'country', 'state_province')) && count($config->countryLimit) > 1) {
562 // get state or country field weight if exists
563 $field = 'state_province';
564 if ($ufField->field_name == 'state_province') {
565 $field = 'country';
566 }
567 $ufFieldDAO = new CRM_Core_DAO_UFField();
568 $ufFieldDAO->field_name = $field;
569 $ufFieldDAO->location_type_id = $ufField->location_type_id;
570 $ufFieldDAO->uf_group_id = $ufField->uf_group_id;
571
572 if ($ufFieldDAO->find(TRUE)) {
573 if ($field == 'country' && $ufFieldDAO->weight > $ufField->weight) {
574 $showBestResult = TRUE;
575 }
576 elseif ($field == 'state_province' && $ufFieldDAO->weight < $ufField->weight) {
577 $showBestResult = TRUE;
578 }
579 }
580 }
581
582 //update group_type every time. CRM-3608
583 if ($this->_gid && is_a($ufField, 'CRM_Core_DAO_UFField')) {
584 // get the profile type.
585 $fieldsType = CRM_Core_BAO_UFGroup::calculateGroupType($this->_gid, TRUE);
586 CRM_Core_BAO_UFGroup::updateGroupTypes($this->_gid, $fieldsType);
587 }
588 CRM_Core_Session::setStatus(ts('Your CiviCRM Profile Field \'%1\' has been saved to \'%2\'.',
589 array(1 => $name, 2 => $this->_title)
590 ), ts('Profile Field Saved'), 'success');
591 }
592 $buttonName = $this->controller->getButtonName();
593
594 $session = CRM_Core_Session::singleton();
595 if ($buttonName == $this->getButtonName('next', 'new')) {
596 CRM_Core_Session::setStatus(ts(' You can add another profile field.'), '', 'info');
597 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field/add',
598 "reset=1&action=add&gid={$this->_gid}&sbr={$showBestResult}"
599 ));
600 }
601 else {
602 $session->replaceUserContext(CRM_Utils_System::url('civicrm/admin/uf/group/field',
603 "reset=1&action=browse&gid={$this->_gid}"
604 ));
605 $session->set('showBestResult', $showBestResult);
606 }
607 }
608
609 /**
610 * Validation rule for subtype.
611 *
612 * @param array $groupType contains all groupTypes.
613 *
614 * @param string $fieldType type of field.
615 *
616 * @param array $errors
617 *
618 * @return array list of errors to be posted back to the form
619 * @static
620 */
621 public static function formRuleSubType($fieldType, $groupType, $errors) {
622 if (in_array($fieldType, array(
623 'Participant', 'Contribution', 'Membership', 'Activity'))) {
624 $individualSubTypes = CRM_Contact_BAO_ContactType::subTypes('Individual');
625 foreach ($groupType as $value) {
626 if (!in_array($value, $individualSubTypes) &&
627 !in_array($value, array(
628 'Participant', 'Contribution', 'Membership',
629 'Individual', 'Contact', 'Activity',
630 ))
631 ) {
632 $errors['field_name'] = ts('Cannot add or update profile field "%1" with combination of Household or Organization or any subtypes of Household or Organisation.', array(1 => $fieldType));
633 break;
634 }
635 }
636 }
637 else {
638 $basicType = CRM_Contact_BAO_ContactType::getBasicType($groupType);
639 if ($basicType) {
640 if (!is_array($basicType)) {
641 $basicType = array($basicType);
642 }
643 if (!in_array($fieldType, $basicType)) {
644 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of subtype other than "%1".',
645 array(1 => $fieldType)
646 );
647 }
648 }
649 }
650 }
651
652 /**
653 * Validation rule for custom data extends entity column values.
654 *
655 * @param Object $customField Custom field
656 * @param Integer $gid Group Id.
657 * @param String $fieldType Group type of the field
658 * @param Array $errors Collect errors
659 *
660 * @return Array list of errors to be posted back to the form
661 * @static
662 */
663 public static function formRuleCustomDataExtentColumnValue($customField, $gid, $fieldType, &$errors) {
664 // fix me : check object $customField
665 if (in_array($fieldType, array(
666 'Participant', 'Contribution', 'Membership', 'Activity', 'Case'))) {
667 $params = array('id' => $customField->custom_group_id);
668 $customGroup = array();
669 CRM_Core_BAO_CustomGroup::retrieve($params, $customGroup);
670 if (($fieldType != CRM_Utils_Array::value('extends', $customGroup)) || empty($customGroup['extends_entity_column_value'])) {
671 return $errors;
672 }
673
674 $extendsColumnValues = array();
675 foreach (explode(CRM_Core_DAO::VALUE_SEPARATOR, $customGroup['extends_entity_column_value']) as $val) {
676 if ($val) {
677 $extendsColumnValues[] = $val;
678 }
679 }
680
681 if (empty($extendsColumnValues)) {
682 return $errors;
683 }
684
685 $fieldTypeValues = CRM_Core_BAO_UFGroup::groupTypeValues($gid, $fieldType);
686 if (empty($fieldTypeValues[$fieldType])) {
687 return;
688 }
689
690 $disallowedTypes = array_diff($extendsColumnValues, $fieldTypeValues[$fieldType]);
691 if (!empty($disallowedTypes)) {
692 $errors['field_name'] = ts('Profile is already having custom fields extending different group types, you can not add or update this custom field.');
693 }
694 }
695 }
696
697 /**
698 * Validation rule to prevent multiple fields of primary location type within the same communication type.
699 *
700 * @param Array $fields Submitted fields
701 * @param String $profileFieldName Group Id.
702 * @param Array $groupFields List of fields already in the group
703 * @param Array $errors Collect errors
704 *
705 * @static
706 */
707 public static function formRulePrimaryCheck($fields, $profileFieldName, $groupFields, &$errors) {
708 //FIXME: This may need to also apply to website fields if they are refactored to allow more than one per profile
709 $checkPrimary = array('phone' => 'civicrm_phone.phone', 'phone_and_ext' => 'civicrm_phone.phone');
710 $whereCheck = NULL;
711 $primaryOfSameTypeFound = NULL;
712 $fieldID = empty($fields['field_id']) ? 0 : $fields['field_id'];
713 // Is this a primary location type field of interest
714 if (array_key_exists($profileFieldName, $checkPrimary)) {
715 $whereCheck = $checkPrimary[$profileFieldName];
716 }
717 $potentialLocationType = CRM_Utils_Array::value(2, $fields['field_name']);
718
719 if ($whereCheck && $potentialLocationType == 0) {
720 $primaryOfSameTypeFound = '';
721
722 foreach ($groupFields as $groupField) {
723 // if it is a phone
724 if ($groupField['where'] == $whereCheck && is_null($groupField['location_type_id']) && $groupField['field_id'] != $fieldID) {
725 $primaryOfSameTypeFound = $groupField['title'];
726 break;
727 }
728 }
729 if ($primaryOfSameTypeFound) {
730 $errors['field_name'] = ts('You have already added a primary location field of this type: %1', $primaryOfSameTypeFound);
731 }
732 }
733 }
734
735 /**
736 * Global validation rules for the form
737 *
738 * @param array $fields posted values of the form
739 *
740 * @param $files
741 * @param $self
742 *
743 * @return array list of errors to be posted back to the form
744 * @static
745 */
746 public static function formRule($fields, $files, $self) {
747 $is_required = CRM_Utils_Array::value('is_required', $fields, FALSE);
748 $is_registration = CRM_Utils_Array::value('is_registration', $fields, FALSE);
749 $is_view = CRM_Utils_Array::value('is_view', $fields, FALSE);
750 $in_selector = CRM_Utils_Array::value('in_selector', $fields, FALSE);
751 $is_active = CRM_Utils_Array::value('is_active', $fields, FALSE);
752
753 $errors = array();
754 if ($is_view && $is_registration) {
755 $errors['is_registration'] = ts('View Only cannot be selected if this field is to be included on the registration form');
756 }
757 if ($is_view && $is_required) {
758 $errors['is_view'] = ts('A View Only field cannot be required');
759 }
760
761 $entityName = $fields['field_name'][0];
762 if (!$entityName) {
763 $errors['field_name'] = ts('Please select a field name');
764 }
765
766 if ($in_selector && in_array($entityName, array(
767 'Contribution', 'Participant', 'Membership', 'Activity'))) {
768 $errors['in_selector'] = ts("'In Selector' cannot be checked for %1 fields.", array(1 => $entityName));
769 }
770
771 $isCustomField = FALSE;
772 $profileFieldName = CRM_Utils_Array::value(1, $fields['field_name']);
773 if ($profileFieldName) {
774 //get custom field id
775 $customFieldId = explode('_', $profileFieldName);
776 if ($customFieldId[0] == 'custom') {
777 $customField = new CRM_Core_DAO_CustomField();
778 $customField->id = $customFieldId[1];
779 $customField->find(TRUE);
780 $isCustomField = TRUE;
781 if (!empty($fields['field_id']) && !$customField->is_active && $is_active) {
782 $errors['field_name'] = ts('Cannot set this field "Active" since the selected custom field is disabled.');
783 }
784
785 //check if profile already has a different multi-record custom set field configured
786 $customGroupId = CRM_Core_BAO_CustomField::isMultiRecordField($profileFieldName);
787 if ($customGroupId) {
788 if ($profileMultiRecordCustomGid = CRM_Core_BAO_UFField::checkMultiRecordFieldExists($self->_gid)) {
789 if ($customGroupId != $profileMultiRecordCustomGid) {
790 $errors['field_name'] = ts("You cannot configure multi-record custom fields belonging to different custom sets in one profile");
791 }
792 }
793 }
794 }
795 }
796
797 // Get list of fields already in the group
798 $groupFields = CRM_Core_BAO_UFGroup::getFields($fields['group_id'], FALSE, NULL, NULL, NULL, TRUE, NULL, TRUE);
799 // Check if we already added a primary field of the same communication type
800 self::formRulePrimaryCheck($fields, $profileFieldName, $groupFields, $errors);
801
802 //check profile is configured for double option process
803 //adding group field, email field should be present in the group
804 //fixed for issue CRM-2861 & CRM-4153
805 if (CRM_Core_BAO_UFGroup::isProfileDoubleOptin()) {
806 if (CRM_Utils_Array::value(1, $fields['field_name']) == 'group') {
807 $dao = new CRM_Core_BAO_UFField();
808 $dao->uf_group_id = $fields['group_id'];
809 $dao->find();
810 $emailField = FALSE;
811 while ($dao->fetch()) {
812 //check email field is present in the group
813 if ($dao->field_name == 'email') {
814 $emailField = TRUE;
815 break;
816 }
817 }
818
819 if (!$emailField) {
820 $disableSettingURL = CRM_Utils_System::url(
821 'civicrm/admin/setting/preferences/mailing',
822 'reset=1'
823 );
824
825 $errors['field_name'] = ts('Your site is currently configured to require double-opt in when users join (subscribe) to Group(s) via a Profile form. In this mode, you need to include an Email field in a Profile BEFORE you can add the Group(s) field. This ensures that an opt-in confirmation email can be sent. Your site administrator can disable double opt-in on the civimail admin settings: <em>%1</em>', array(1 => $disableSettingURL));
826 }
827 }
828 }
829
830 //fix for CRM-3037
831 $fieldType = $fields['field_name'][0];
832
833 //get the group type.
834 $groupType = CRM_Core_BAO_UFGroup::calculateGroupType($self->_gid, FALSE, CRM_Utils_Array::value('field_id', $fields));
835
836 switch ($fieldType) {
837 case 'Contact':
838 self::formRuleSubType($fieldType, $groupType, $errors);
839 break;
840
841 case 'Individual':
842 if (in_array('Activity', $groupType) ||
843 in_array('Household', $groupType) ||
844 in_array('Organization', $groupType)
845 ) {
846
847 //CRM-7603 - need to support activity + individual.
848 //$errors['field_name'] =
849 //ts( 'Cannot add or update profile field type Individual with combination of Household or Organization or Activity' );
850 if (in_array('Household', $groupType) ||
851 in_array('Organization', $groupType)
852 ) {
853 $errors['field_name'] = ts('Cannot add or update profile field type Individual with combination of Household or Organization');
854 }
855 }
856 else {
857 self::formRuleSubType($fieldType, $groupType, $errors);
858 }
859 break;
860
861 case 'Household':
862 if (in_array('Activity', $groupType) || in_array('Individual', $groupType) || in_array('Organization', $groupType)) {
863 $errors['field_name'] = ts('Cannot add or update profile field type Household with combination of Individual or Organization or Activity');
864 }
865 else {
866 self::formRuleSubType($fieldType, $groupType, $errors);
867 }
868 break;
869
870 case 'Organization':
871 if (in_array('Activity', $groupType) || in_array('Household', $groupType) || in_array('Individual', $groupType)) {
872 $errors['field_name'] = ts('Cannot add or update profile field type Organization with combination of Household or Individual or Activity');
873 }
874 else {
875 self::formRuleSubType($fieldType, $groupType, $errors);
876 }
877 break;
878
879 case 'Activity':
880 if (in_array('Individual', $groupType) ||
881 in_array('Membership', $groupType) ||
882 in_array('Contribution', $groupType) ||
883 in_array('Organization', $groupType) ||
884 in_array('Household', $groupType) ||
885 in_array('Participant', $groupType)
886 ) {
887
888 //CRM-7603 - need to support activity + contact type.
889 //$errors['field_name'] =
890 //ts( 'Cannot add or update profile field type Activity with combination Participant or Membership or Contribution or Household or Organization or Individual' );
891 if (in_array('Membership', $groupType) ||
892 in_array('Contribution', $groupType) ||
893 in_array('Participant', $groupType)
894 ) {
895 $errors['field_name'] = ts('Cannot add or update profile field type Activity with combination Participant or Membership or Contribution');
896 }
897 }
898 else {
899 self::formRuleSubType($fieldType, $groupType, $errors);
900 }
901
902 if ($isCustomField && !isset($errors['field_name'])) {
903 self::formRuleCustomDataExtentColumnValue($customField, $self->_gid, $fieldType, $errors);
904 }
905 break;
906
907 case 'Participant':
908 if (in_array('Membership', $groupType) || in_array('Contribution', $groupType)
909 || in_array('Organization', $groupType) || in_array('Household', $groupType) || in_array('Activity', $groupType)
910 ) {
911 $errors['field_name'] = ts('Cannot add or update profile field type Participant with combination of Activity or Membership or Contribution or Household or Organization.');
912 }
913 else {
914 self::formRuleSubType($fieldType, $groupType, $errors);
915 }
916 break;
917
918 case 'Contribution':
919 //special case where in we allow contribution + oganization fields, for on behalf feature
920 $profileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup',
921 'on_behalf_organization', 'id', 'name'
922 );
923
924 if (in_array('Participant', $groupType) || in_array('Membership', $groupType)
925 || ($profileId != $self->_gid && in_array('Organization', $groupType)) || in_array('Household', $groupType) || in_array('Activity', $groupType)
926 ) {
927 $errors['field_name'] = ts('Cannot add or update profile field type Contribution with combination of Activity or Membership or Participant or Household or Organization');
928 }
929 else {
930 self::formRuleSubType($fieldType, $groupType, $errors);
931 }
932 break;
933
934 case 'Membership':
935 //special case where in we allow contribution + oganization fields, for on behalf feature
936 $profileId = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup',
937 'on_behalf_organization', 'id', 'name'
938 );
939
940 if (in_array('Participant', $groupType) || in_array('Contribution', $groupType)
941 || ($profileId != $self->_gid && in_array('Organization', $groupType)) || in_array('Household', $groupType) || in_array('Activity', $groupType)
942 ) {
943 $errors['field_name'] = ts('Cannot add or update profile field type Membership with combination of Activity or Participant or Contribution or Household or Organization');
944 }
945 else {
946 self::formRuleSubType($fieldType, $groupType, $errors);
947 }
948 break;
949
950 default:
951 $profileType = CRM_Core_BAO_UFField::getProfileType($fields['group_id'], TRUE, FALSE, TRUE);
952 if (CRM_Contact_BAO_ContactType::isaSubType($fieldType)) {
953 if (CRM_Contact_BAO_ContactType::isaSubType($profileType)) {
954 if ($fieldType != $profileType) {
955 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', array(1 => $fieldType, 2 => $profileType));
956 }
957 }
958 else {
959 $basicType = CRM_Contact_BAO_ContactType::getBasicType($fieldType);
960 if ($profileType &&
961 $profileType != $basicType &&
962 $profileType != 'Contact'
963 ) {
964 $errors['field_name'] = ts('Cannot add or update profile field type "%1" with combination of "%2".', array(1 => $fieldType, 2 => $profileType));
965 }
966 }
967 }
968 elseif (
969 CRM_Utils_Array::value(1, $fields['field_name']) == 'contact_sub_type' &&
970 !in_array($profileType, array('Individual', 'Household', 'Organization')) &&
971 !in_array($profileType, CRM_Contact_BAO_ContactType::subTypes())
972 ) {
973 $errors['field_name'] = ts('Cannot add or update profile field Contact Subtype as profile type is not one of Individual, Household or Organization.');
974 }
975 }
976 return empty($errors) ? TRUE : $errors;
977 }
978 }