Fix Participant batch update to work with multiple value custom fields
[civicrm-core.git] / CRM / Contact / Form / Task / Batch.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
fee14197 4 | CiviCRM version 5 |
6a488035 5 +--------------------------------------------------------------------+
8c9251b3 6 | Copyright CiviCRM LLC (c) 2004-2018 |
6a488035
TO
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 +--------------------------------------------------------------------+
d25dd0ee 26 */
6a488035
TO
27
28/**
29 *
30 * @package CRM
8c9251b3 31 * @copyright CiviCRM LLC (c) 2004-2018
6a488035
TO
32 */
33
34/**
5a409b50 35 * This class provides the functionality for batch profile update.
6a488035
TO
36 */
37class CRM_Contact_Form_Task_Batch extends CRM_Contact_Form_Task {
38
39 /**
fe482240 40 * The title of the group.
6a488035
TO
41 *
42 * @var string
43 */
44 protected $_title;
45
46 /**
fe482240 47 * Maximum contacts that should be allowed to update.
6a488035
TO
48 */
49 protected $_maxContacts = 100;
50
51 /**
fe482240 52 * Maximum profile fields that will be displayed.
6a488035
TO
53 */
54 protected $_maxFields = 9;
55
56 /**
fe482240 57 * Variable to store redirect path.
6a488035
TO
58 */
59 protected $_userContext;
60
61 /**
fe482240 62 * When not to reset sort_name.
6a488035
TO
63 */
64 protected $_preserveDefault = TRUE;
8ef12e64 65
6a488035 66 /**
fe482240 67 * Build all the data structures needed to build the form.
6a488035 68 */
00be9182 69 public function preProcess() {
d424ffde 70 // initialize the task and row fields
6a488035
TO
71 parent::preProcess();
72 }
73
74 /**
fe482240 75 * Build the form object.
6a488035 76 */
00be9182 77 public function buildQuickForm() {
6a488035
TO
78 $ufGroupId = $this->get('ufGroupId');
79
80 if (!$ufGroupId) {
81 CRM_Core_Error::fatal('ufGroupId is missing');
82 }
b581842f 83 $this->_title = ts('Update multiple contacts') . ' - ' . CRM_Core_BAO_UFGroup::getTitle($ufGroupId);
6a488035
TO
84 CRM_Utils_System::setTitle($this->_title);
85
86 $this->addDefaultButtons(ts('Save'));
87 $this->_fields = CRM_Core_BAO_UFGroup::getFields($ufGroupId, FALSE, CRM_Core_Action::VIEW);
88
89 // remove file type field and then limit fields
90 $suppressFields = FALSE;
657c89d9 91 $removehtmlTypes = array('File');
6a488035
TO
92 foreach ($this->_fields as $name => $field) {
93 if ($cfID = CRM_Core_BAO_CustomField::getKeyID($name) &&
94 in_array($this->_fields[$name]['html_type'], $removehtmlTypes)
95 ) {
96 $suppressFields = TRUE;
97 unset($this->_fields[$name]);
98 }
99 }
100
101 //FIX ME: phone ext field is added at the end and it gets removed because of below code
102 //$this->_fields = array_slice($this->_fields, 0, $this->_maxFields);
103
104 $this->addButtons(array(
105 array(
106 'type' => 'submit',
107 'name' => ts('Update Contact(s)'),
108 'isDefault' => TRUE,
109 ),
110 array(
111 'type' => 'cancel',
112 'name' => ts('Cancel'),
113 ),
114 )
115 );
116
6a488035
TO
117 $this->assign('profileTitle', $this->_title);
118 $this->assign('componentIds', $this->_contactIds);
119
120 // if below fields are missing we should not reset sort name / display name
121 // CRM-6794
122 $preserveDefaultsArray = array(
353ffa53
TO
123 'first_name',
124 'last_name',
125 'middle_name',
126 'organization_name',
127 'prefix_id',
128 'suffix_id',
6a488035
TO
129 'household_name',
130 );
8ef12e64 131
6a488035
TO
132 foreach ($this->_contactIds as $contactId) {
133 $profileFields = $this->_fields;
134 CRM_Core_BAO_Address::checkContactSharedAddressFields($profileFields, $contactId);
135 foreach ($profileFields as $name => $field) {
136 CRM_Core_BAO_UFGroup::buildProfile($this, $field, NULL, $contactId);
137
138 if (in_array($field['name'], $preserveDefaultsArray)) {
139 $this->_preserveDefault = FALSE;
140 }
141 }
142 }
8ef12e64 143
6a488035
TO
144 $this->assign('fields', $this->_fields);
145
146 // don't set the status message when form is submitted.
147 $buttonName = $this->controller->getButtonName('submit');
148
149 if ($suppressFields && $buttonName != '_qf_BatchUpdateProfile_next') {
657c89d9 150 CRM_Core_Session::setStatus(ts("File type field(s) in the selected profile are not supported for Update multiple contacts."), ts('Some Fields Excluded'), 'info');
6a488035
TO
151 }
152
153 $this->addDefaultButtons(ts('Update Contacts'));
154 $this->addFormRule(array('CRM_Contact_Form_Task_Batch', 'formRule'));
155 }
156
157 /**
c490a46a 158 * Set default values for the form.
6a488035 159 *
6a488035 160 *
c927c151 161 * @return array
6a488035 162 */
00be9182 163 public function setDefaultValues() {
6a488035 164 if (empty($this->_fields)) {
317fceb4 165 return NULL;
6a488035
TO
166 }
167
168 $defaults = $sortName = array();
169 foreach ($this->_contactIds as $contactId) {
170 $details[$contactId] = array();
171
172 //build sortname
173 $sortName[$contactId] = CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
174 $contactId,
175 'sort_name'
176 );
177
178 CRM_Core_BAO_UFGroup::setProfileDefaults($contactId, $this->_fields, $defaults, FALSE);
179 }
180
181 $this->assign('sortName', $sortName);
8ef12e64 182
6a488035
TO
183 return $defaults;
184 }
185
186 /**
fe482240 187 * Global form rule.
6a488035 188 *
77c5b619
TO
189 * @param array $fields
190 * The input form values.
6a488035 191 *
72b3a70c
CW
192 * @return bool|array
193 * true if no errors, else array of errors
6a488035 194 */
00be9182 195 public static function formRule($fields) {
6a488035
TO
196 $errors = array();
197 $externalIdentifiers = array();
198 foreach ($fields['field'] as $componentId => $field) {
199 foreach ($field as $fieldName => $fieldValue) {
200 if ($fieldName == 'external_identifier') {
201 if (in_array($fieldValue, $externalIdentifiers)) {
7b99ead3 202 $errors["field[$componentId][external_identifier]"] = ts('Duplicate value for External ID.');
6a488035
TO
203 }
204 else {
205 $externalIdentifiers[$componentId] = $fieldValue;
206 }
207 }
208 }
209 }
210
211 return $errors;
212 }
213
214 /**
fe482240 215 * Process the form after the input has been submitted and validated.
6a488035
TO
216 */
217 public function postProcess() {
218 $params = $this->exportValues();
219
3eb99314 220 // @todo extract submit functions &
221 // extend CRM_Event_Form_Task_BatchTest::testSubmit with a data provider to test
222 // handling of custom data, specifically checkbox fields.
353ffa53
TO
223 $ufGroupId = $this->get('ufGroupId');
224 $notify = NULL;
6a488035
TO
225 $inValidSubtypeCnt = 0;
226 //send profile notification email if 'notify' field is set
227 $notify = CRM_Core_DAO::getFieldValue('CRM_Core_DAO_UFGroup', $ufGroupId, 'notify');
228 foreach ($params['field'] as $key => $value) {
229
230 //CRM-5521
231 //validate subtype before updating
a7488080 232 if (!empty($value['contact_sub_type']) && !CRM_Contact_BAO_ContactType::isAllowEdit($key)) {
6a488035
TO
233 unset($value['contact_sub_type']);
234 $inValidSubtypeCnt++;
235 }
236
237 $value['preserveDBName'] = $this->_preserveDefault;
238
239 //parse street address, CRM-7768
240 self::parseStreetAddress($value, $this);
241
4a679824 242 CRM_Contact_BAO_Contact::createProfileContact($value, $this->_fields, $key, NULL, $ufGroupId, NULL, TRUE);
6a488035
TO
243 if ($notify) {
244 $values = CRM_Core_BAO_UFGroup::checkFieldsEmptyValues($ufGroupId, $key, NULL);
245 CRM_Core_BAO_UFGroup::commonSendMail($key, $values);
246 }
247 }
248
249 CRM_Core_Session::setStatus('', ts("Updates Saved"), 'success');
250 if ($inValidSubtypeCnt) {
353ffa53
TO
251 CRM_Core_Session::setStatus(ts('Contact Subtype field of 1 contact has not been updated.', array(
252 'plural' => 'Contact Subtype field of %count contacts has not been updated.',
317fceb4 253 'count' => $inValidSubtypeCnt,
353ffa53 254 )), ts('Invalid Subtype'));
6a488035
TO
255 }
256 }
6a488035 257
afe349ef 258 /**
fe482240 259 * Parse street address.
5a409b50 260 *
77c5b619
TO
261 * @param array $contactValues
262 * Contact values.
263 * @param CRM_Core_Form $form
264 * Form object.
afe349ef 265 */
05d1454b
C
266 public static function parseStreetAddress(&$contactValues, &$form) {
267 if (!is_array($contactValues) || !is_array($form->_fields)) {
6a488035
TO
268 return;
269 }
270
271 static $parseAddress;
272 $addressFldKey = 'street_address';
273 if (!isset($parseAddress)) {
274 $parseAddress = FALSE;
275 foreach ($form->_fields as $key => $fld) {
276 if (strpos($key, $addressFldKey) !== FALSE) {
277 $parseAddress = CRM_Utils_Array::value('street_address_parsing',
278 CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
279 'address_options'
280 ),
281 FALSE
282 );
283 break;
284 }
285 }
286 }
287
288 if (!$parseAddress) {
289 return;
290 }
291
292 $allParseValues = array();
293 foreach ($contactValues as $key => $value) {
294 if (strpos($key, $addressFldKey) !== FALSE) {
295 $locTypeId = substr($key, strlen($addressFldKey) + 1);
296
297 // parse address field.
298 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($value);
299
300 //street address consider to be parsed properly,
301 //If we get street_name and street_number.
8cc574cf 302 if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) {
6a488035
TO
303 $parsedFields = array_fill_keys(array_keys($parsedFields), '');
304 }
305
306 //merge parse values.
307 foreach ($parsedFields as $fldKey => $parseVal) {
308 if ($locTypeId) {
309 $fldKey .= "-{$locTypeId}";
310 }
311 $allParseValues[$fldKey] = $parseVal;
312 }
313 }
314 }
315
316 //finally merge all parse values
317 if (!empty($allParseValues)) {
318 $contactValues += $allParseValues;
319 }
320 }
96025800 321
6a488035 322}