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