Merge pull request #4895 from colemanw/INFRA-132
[civicrm-core.git] / CRM / Contact / Form / Contact.php
CommitLineData
6a488035
TO
1<?php
2/*
3 +--------------------------------------------------------------------+
39de6fd5 4 | CiviCRM version 4.6 |
6a488035 5 +--------------------------------------------------------------------+
06b69b18 6 | Copyright CiviCRM LLC (c) 2004-2014 |
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 +--------------------------------------------------------------------+
26*/
27
28/**
29 *
30 * @package CRM
06b69b18 31 * @copyright CiviCRM LLC (c) 2004-2014
6a488035
TO
32 * $Id$
33 *
34 */
35
36/**
37 * This class generates form components generic to all the contact types.
38 *
39 * It delegates the work to lower level subclasses and integrates the changes
40 * back in. It also uses a lot of functionality with the CRM API's, so any change
41 * made here could potentially affect the API etc. Be careful, be aware, use unit tests.
42 *
43 */
44class CRM_Contact_Form_Contact extends CRM_Core_Form {
45
46 /**
47 * The contact type of the form
48 *
49 * @var string
50 */
51 public $_contactType;
52
53 /**
54 * The contact type of the form
55 *
56 * @var string
57 */
58 public $_contactSubType;
59
60 /**
61 * The contact id, used when editing the form
62 *
63 * @var int
64 */
65 public $_contactId;
66
67 /**
100fef9d 68 * The default group id passed in via the url
6a488035
TO
69 *
70 * @var int
71 */
72 public $_gid;
73
74 /**
100fef9d 75 * The default tag id passed in via the url
6a488035
TO
76 *
77 * @var int
78 */
79 public $_tid;
80
81 /**
100fef9d 82 * Name of de-dupe button
6a488035
TO
83 *
84 * @var string
6a488035
TO
85 */
86 protected $_dedupeButtonName;
87
88 /**
100fef9d 89 * Name of optional save duplicate button
6a488035
TO
90 *
91 * @var string
6a488035
TO
92 */
93 protected $_duplicateButtonName;
94
95 protected $_editOptions = array();
96
97 protected $_oldSubtypes = array();
98
99 public $_blocks;
100
101 public $_values = array();
102
103 public $_action;
104
105 public $_customValueCount;
106 /**
107 * The array of greetings with option group and filed names
108 *
109 * @var array
110 */
111 public $_greetings;
112
113 /**
114 * Do we want to parse street address.
115 */
116 public $_parseStreetAddress;
117
118 /**
100fef9d 119 * Check contact has a subtype or not
6a488035
TO
120 */
121 public $_isContactSubType;
122
123 /**
124 * Lets keep a cache of all the values that we retrieved.
125 * THis is an attempt to avoid the number of update statements
126 * during the write phase
127 */
128 public $_preEditValues;
129
130 /**
100fef9d 131 * Build all the data structures needed to build the form
6a488035
TO
132 *
133 * @return void
6a488035 134 */
00be9182 135 public function preProcess() {
6a488035
TO
136 $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this, FALSE, 'add');
137
138 $this->_dedupeButtonName = $this->getButtonName('refresh', 'dedupe');
139 $this->_duplicateButtonName = $this->getButtonName('upload', 'duplicate');
140
141 $session = CRM_Core_Session::singleton();
142 if ($this->_action == CRM_Core_Action::ADD) {
143 // check for add contacts permissions
144 if (!CRM_Core_Permission::check('add contacts')) {
145 CRM_Utils_System::permissionDenied();
146 CRM_Utils_System::civiExit();
147 }
148 $this->_contactType = CRM_Utils_Request::retrieve('ct', 'String',
149 $this, TRUE, NULL, 'REQUEST'
150 );
151 if (!in_array($this->_contactType,
152 array('Individual', 'Household', 'Organization')
153 )) {
154 CRM_Core_Error::statusBounce(ts('Could not get a contact id and/or contact type'));
155 }
156
157 $this->_isContactSubType = FALSE;
158 if ($this->_contactSubType = CRM_Utils_Request::retrieve('cst', 'String', $this)) {
159 $this->_isContactSubType = TRUE;
160 }
161
162 if (
163 $this->_contactSubType &&
164 !(CRM_Contact_BAO_ContactType::isExtendsContactType($this->_contactSubType, $this->_contactType, TRUE))) {
165 CRM_Core_Error::statusBounce(ts("Could not get a valid contact subtype for contact type '%1'", array(1 => $this->_contactType)));
166 }
167
168 $this->_gid = CRM_Utils_Request::retrieve('gid', 'Integer',
169 CRM_Core_DAO::$_nullObject,
170 FALSE, NULL, 'GET'
171 );
172 $this->_tid = CRM_Utils_Request::retrieve('tid', 'Integer',
173 CRM_Core_DAO::$_nullObject,
174 FALSE, NULL, 'GET'
175 );
176 $typeLabel = CRM_Contact_BAO_ContactType::contactTypePairs(TRUE, $this->_contactSubType ?
177 $this->_contactSubType : $this->_contactType
178 );
179 $typeLabel = implode(' / ', $typeLabel);
180
181 CRM_Utils_System::setTitle(ts('New %1', array(1 => $typeLabel)));
182 $session->pushUserContext(CRM_Utils_System::url('civicrm/dashboard', 'reset=1'));
183 $this->_contactId = NULL;
184 }
185 else {
186 //update mode
187 if (!$this->_contactId) {
188 $this->_contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE);
189 }
190
191 if ($this->_contactId) {
192 $defaults = array();
193 $params = array('id' => $this->_contactId);
194 $returnProperities = array('id', 'contact_type', 'contact_sub_type', 'modified_date');
195 CRM_Core_DAO::commonRetrieve('CRM_Contact_DAO_Contact', $params, $defaults, $returnProperities);
196
a7488080 197 if (empty($defaults['id'])) {
6a488035
TO
198 CRM_Core_Error::statusBounce(ts('A Contact with that ID does not exist: %1', array(1 => $this->_contactId)));
199 }
200
201 $this->_contactType = CRM_Utils_Array::value('contact_type', $defaults);
202 $this->_contactSubType = CRM_Utils_Array::value('contact_sub_type', $defaults);
203
204 // check for permissions
205 $session = CRM_Core_Session::singleton();
ad623fd4 206 if (!CRM_Contact_BAO_Contact_Permission::allow($this->_contactId, CRM_Core_Permission::EDIT)) {
6a488035
TO
207 CRM_Core_Error::statusBounce(ts('You do not have the necessary permission to edit this contact.'));
208 }
209
210 $displayName = CRM_Contact_BAO_Contact::displayName($this->_contactId);
211 $displayName = ts('Edit %1', array(1 => $displayName));
8ef12e64 212
6a488035
TO
213 // Check if this is default domain contact CRM-10482
214 if (CRM_Contact_BAO_Contact::checkDomainContact($this->_contactId)) {
215 $displayName .= ' (' . ts('default organization') . ')';
216 }
8ef12e64 217
6a488035
TO
218 // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container
219 CRM_Utils_System::setTitle($displayName);
220 $context = CRM_Utils_Request::retrieve('context', 'String', $this);
221 $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this);
222
223 $urlParams = 'reset=1&cid=' . $this->_contactId;
224 if ($context) {
225 $urlParams .= "&context=$context";
226 }
227
228 if (CRM_Utils_Rule::qfKey($qfKey)) {
229
230 $urlParams .= "&key=$qfKey";
231
232 }
233 $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/view', $urlParams));
234
235 $values = $this->get('values');
236 // get contact values.
237 if (!empty($values)) {
238 $this->_values = $values;
239 }
240 else {
241 $params = array(
242 'id' => $this->_contactId,
243 'contact_id' => $this->_contactId,
244 'noRelationships' => TRUE,
245 'noNotes' => TRUE,
246 'noGroups' => TRUE,
247 );
248
249 $contact = CRM_Contact_BAO_Contact::retrieve($params, $this->_values, TRUE);
250 $this->set('values', $this->_values);
251 }
252 }
253 else {
254 CRM_Core_Error::statusBounce(ts('Could not get a contact_id and/or contact_type'));
255 }
256 }
257
258 // parse street address, CRM-5450
259 $this->_parseStreetAddress = $this->get('parseStreetAddress');
260 if (!isset($this->_parseStreetAddress)) {
261 $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
262 'address_options'
263 );
264 $this->_parseStreetAddress = FALSE;
8cc574cf 265 if (!empty($addressOptions['street_address']) && !empty($addressOptions['street_address_parsing'])) {
6a488035
TO
266 $this->_parseStreetAddress = TRUE;
267 }
268 $this->set('parseStreetAddress', $this->_parseStreetAddress);
269 }
270 $this->assign('parseStreetAddress', $this->_parseStreetAddress);
271
272 $this->_editOptions = $this->get('contactEditOptions');
273 if (CRM_Utils_System::isNull($this->_editOptions)) {
274 $this->_editOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
275 'contact_edit_options', TRUE, NULL,
276 FALSE, 'name', TRUE, 'AND v.filter = 0'
277 );
278 $this->set('contactEditOptions', $this->_editOptions);
279 }
280
281 // build demographics only for Individual contact type
282 if ($this->_contactType != 'Individual' &&
283 array_key_exists('Demographics', $this->_editOptions)
284 ) {
285 unset($this->_editOptions['Demographics']);
286 }
287
288 // in update mode don't show notes
289 if ($this->_contactId && array_key_exists('Notes', $this->_editOptions)) {
290 unset($this->_editOptions['Notes']);
291 }
292
293 $this->assign('editOptions', $this->_editOptions);
294 $this->assign('contactType', $this->_contactType);
295 $this->assign('contactSubType', $this->_contactSubType);
296
297 //build contact subtype form element, CRM-6864
298 $buildContactSubType = TRUE;
299 if ($this->_contactSubType && ($this->_action & CRM_Core_Action::ADD)) {
300 $buildContactSubType = FALSE;
301 }
302 $this->assign('buildContactSubType', $buildContactSubType);
303
304 // get the location blocks.
305 $this->_blocks = $this->get('blocks');
306 if (CRM_Utils_System::isNull($this->_blocks)) {
307 $this->_blocks = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
308 'contact_edit_options', TRUE, NULL,
309 FALSE, 'name', TRUE, 'AND v.filter = 1'
310 );
311 $this->set('blocks', $this->_blocks);
312 }
313 $this->assign('blocks', $this->_blocks);
314
315 // this is needed for custom data.
316 $this->assign('entityID', $this->_contactId);
317
318 // also keep the convention.
319 $this->assign('contactId', $this->_contactId);
320
321 // location blocks.
322 CRM_Contact_Form_Location::preProcess($this);
323
324 // retain the multiple count custom fields value
a7488080 325 if (!empty($_POST['hidden_custom'])) {
6a488035
TO
326 $customGroupCount = CRM_Utils_Array::value('hidden_custom_group_count', $_POST);
327
481a74f4 328 if ($contactSubType = CRM_Utils_Array::value('contact_sub_type', $_POST)) {
6a488035
TO
329 $paramSubType = implode(',', $contactSubType);
330 }
331
332 $this->_getCachedTree = FALSE;
333 unset($customGroupCount[0]);
334 foreach ($customGroupCount as $groupID => $groupCount) {
335 if ($groupCount > 1) {
336 $this->set('groupID', $groupID);
337 //loop the group
338 for ($i = 0; $i <= $groupCount; $i++) {
339 CRM_Custom_Form_CustomData::preProcess($this, NULL, $contactSubType,
340 $i, $this->_contactType
341 );
342 CRM_Contact_Form_Edit_CustomData::buildQuickForm($this);
343 }
344 }
345 }
346
347 //reset all the ajax stuff, for normal processing
348 if (isset($this->_groupTree)) {
349 $this->_groupTree = NULL;
350 }
351 $this->set('groupID', NULL);
352 $this->_getCachedTree = TRUE;
353 }
354
355 // execute preProcess dynamically by js else execute normal preProcess
356 if (array_key_exists('CustomData', $this->_editOptions)) {
357 //assign a parameter to pass for sub type multivalue
358 //custom field to load
359 if ($this->_contactSubType || isset($paramSubType)) {
360 $paramSubType = (isset($paramSubType)) ? $paramSubType :
481a74f4 361 str_replace(CRM_Core_DAO::VALUE_SEPARATOR, ',', trim($this->_contactSubType, CRM_Core_DAO::VALUE_SEPARATOR));
6a488035
TO
362
363 $this->assign('paramSubType', $paramSubType);
364 }
365
366 if (CRM_Utils_Request::retrieve('type', 'String', CRM_Core_DAO::$_nullObject)) {
367 CRM_Contact_Form_Edit_CustomData::preProcess($this);
368 }
369 else {
370 $contactSubType = $this->_contactSubType;
371 // need contact sub type to build related grouptree array during post process
a7488080 372 if (!empty($_POST['contact_sub_type'])) {
6a488035
TO
373 $contactSubType = $_POST['contact_sub_type'];
374 }
375 //only custom data has preprocess hence directly call it
376 CRM_Custom_Form_CustomData::preProcess($this, NULL, $contactSubType,
377 1, $this->_contactType, $this->_contactId
378 );
379 $this->assign('customValueCount', $this->_customValueCount);
380 }
381 }
382 }
383
384 /**
c490a46a 385 * Set default values for the form. Note that in edit/view mode
6a488035
TO
386 * the default values are retrieved from the database
387 *
6a488035 388 *
355ba699 389 * @return void
6a488035 390 */
00be9182 391 public function setDefaultValues() {
6a488035
TO
392 $defaults = $this->_values;
393 $params = array();
394
395 if ($this->_action & CRM_Core_Action::ADD) {
396 if (array_key_exists('TagsAndGroups', $this->_editOptions)) {
397 // set group and tag defaults if any
398 if ($this->_gid) {
c18f95b7 399 $defaults['group'][] = $this->_gid;
6a488035
TO
400 }
401 if ($this->_tid) {
402 $defaults['tag'][$this->_tid] = 1;
403 }
404 }
405 if ($this->_contactSubType) {
406 $defaults['contact_sub_type'] = $this->_contactSubType;
407 }
408 }
409 else {
6a488035
TO
410 foreach ($defaults['email'] as $dontCare => & $val) {
411 if (isset($val['signature_text'])) {
412 $val['signature_text_hidden'] = $val['signature_text'];
413 }
414 if (isset($val['signature_html'])) {
415 $val['signature_html_hidden'] = $val['signature_html'];
416 }
417 }
418
a7488080 419 if (!empty($defaults['contact_sub_type'])) {
6a488035
TO
420 $defaults['contact_sub_type'] = $this->_oldSubtypes;
421 }
422 }
6a488035
TO
423 // set defaults for blocks ( custom data, address, communication preference, notes, tags and groups )
424 foreach ($this->_editOptions as $name => $label) {
150f50c1
CW
425 if (!in_array($name, array('Address', 'Notes'))) {
426 $className = 'CRM_Contact_Form_Edit_' . $name;
427 $className::setDefaultValues($this, $defaults);
6a488035
TO
428 }
429 }
430
431 //set address block defaults
481a74f4 432 CRM_Contact_Form_Edit_Address::setDefaultValues($defaults, $this);
6a488035 433
f2a84e5c 434
a7488080 435 if (!empty($defaults['image_URL'])) {
77d45291 436 list($imageWidth, $imageHeight) = getimagesize(CRM_Utils_String::unstupifyUrl($defaults['image_URL']));
6a488035
TO
437 list($imageThumbWidth, $imageThumbHeight) = CRM_Contact_BAO_Contact::getThumbSize($imageWidth, $imageHeight);
438 $this->assign('imageWidth', $imageWidth);
439 $this->assign('imageHeight', $imageHeight);
440 $this->assign('imageThumbWidth', $imageThumbWidth);
441 $this->assign('imageThumbHeight', $imageThumbHeight);
442 $this->assign('imageURL', $defaults['image_URL']);
443 }
444
445 //set location type and country to default for each block
446 $this->blockSetDefaults($defaults);
447
448 $this->_preEditValues = $defaults;
449 return $defaults;
450 }
451
452 /**
100fef9d 453 * Do the set default related to location type id,
6a488035 454 * primary location, default country
6a488035 455 */
00be9182 456 public function blockSetDefaults(&$defaults) {
b2b0530a 457 $locationTypeKeys = array_filter(array_keys(CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id')), 'is_int');
6a488035
TO
458 sort($locationTypeKeys);
459
460 // get the default location type
461 $locationType = CRM_Core_BAO_LocationType::getDefault();
462
463 // unset primary location type
464 $primaryLocationTypeIdKey = CRM_Utils_Array::key($locationType->id, $locationTypeKeys);
465 unset($locationTypeKeys[$primaryLocationTypeIdKey]);
466
467 // reset the array sequence
468 $locationTypeKeys = array_values($locationTypeKeys);
469
470 // get default phone and im provider id.
471 $defPhoneTypeId = key(CRM_Core_OptionGroup::values('phone_type', FALSE, FALSE, FALSE, ' AND is_default = 1'));
472 $defIMProviderId = key(CRM_Core_OptionGroup::values('instant_messenger_service',
473 FALSE, FALSE, FALSE, ' AND is_default = 1'
474 ));
1d477756
PN
475 $defWebsiteTypeId = key(CRM_Core_OptionGroup::values('website_type',
476 FALSE, FALSE, FALSE, ' AND is_default = 1'
477 ));
6a488035
TO
478
479 $allBlocks = $this->_blocks;
480 if (array_key_exists('Address', $this->_editOptions)) {
481 $allBlocks['Address'] = $this->_editOptions['Address'];
482 }
483
484 $config = CRM_Core_Config::singleton();
485 foreach ($allBlocks as $blockName => $label) {
486 $name = strtolower($blockName);
487 $hasPrimary = $updateMode = FALSE;
488
489 // user is in update mode.
490 if (array_key_exists($name, $defaults) &&
491 !CRM_Utils_System::isNull($defaults[$name])
492 ) {
493 $updateMode = TRUE;
494 }
495
496 for ($instance = 1; $instance <= $this->get($blockName . '_Block_Count'); $instance++) {
497 // make we require one primary block, CRM-5505
498 if ($updateMode) {
499 if (!$hasPrimary) {
500 $hasPrimary =
501 CRM_Utils_Array::value(
502 'is_primary',
503 CRM_Utils_Array::value($instance, $defaults[$name])
504 );
505 }
506 continue;
507 }
508
509 //set location to primary for first one.
510 if ($instance == 1) {
511 $hasPrimary = TRUE;
512 $defaults[$name][$instance]['is_primary'] = TRUE;
513 $defaults[$name][$instance]['location_type_id'] = $locationType->id;
514 }
515 else {
516 $locTypeId = isset($locationTypeKeys[$instance - 1]) ? $locationTypeKeys[$instance - 1] : $locationType->id;
517 $defaults[$name][$instance]['location_type_id'] = $locTypeId;
518 }
519
520 //set default country
521 if ($name == 'address' && $config->defaultContactCountry) {
522 $defaults[$name][$instance]['country_id'] = $config->defaultContactCountry;
523 }
524
525 //set default state/province
526 if ($name == 'address' && $config->defaultContactStateProvince) {
527 $defaults[$name][$instance]['state_province_id'] = $config->defaultContactStateProvince;
528 }
529
530 //set default phone type.
531 if ($name == 'phone' && $defPhoneTypeId) {
532 $defaults[$name][$instance]['phone_type_id'] = $defPhoneTypeId;
533 }
1d477756
PN
534 //set default website type.
535 if ($name == 'website' && $defWebsiteTypeId) {
536 $defaults[$name][$instance]['website_type_id'] = $defWebsiteTypeId;
537 }
6a488035
TO
538
539 //set default im provider.
540 if ($name == 'im' && $defIMProviderId) {
541 $defaults[$name][$instance]['provider_id'] = $defIMProviderId;
542 }
543 }
544
545 if (!$hasPrimary) {
546 $defaults[$name][1]['is_primary'] = TRUE;
547 }
548 }
6a488035
TO
549 }
550
551 /**
dc195289 552 * add the rules (mainly global rules) for form.
6a488035
TO
553 * All local rules are added near the element
554 *
355ba699 555 * @return void
6a488035
TO
556 * @see valid_date
557 */
00be9182 558 public function addRules() {
6a488035
TO
559 // skip adding formRules when custom data is build
560 if ($this->_addBlockName || ($this->_action & CRM_Core_Action::DELETE)) {
561 return;
562 }
563
564 $this->addFormRule(array('CRM_Contact_Form_Edit_' . $this->_contactType, 'formRule'), $this->_contactId);
565
566 // Call Locking check if editing existing contact
567 if ($this->_contactId) {
568 $this->addFormRule(array('CRM_Contact_Form_Edit_Lock', 'formRule'), $this->_contactId);
569 }
570
571 if (array_key_exists('Address', $this->_editOptions)) {
ac79e2f5 572 $this->addFormRule(array('CRM_Contact_Form_Edit_Address', 'formRule'), $this);
6a488035
TO
573 }
574
575 if (array_key_exists('CommunicationPreferences', $this->_editOptions)) {
576 $this->addFormRule(array('CRM_Contact_Form_Edit_CommunicationPreferences', 'formRule'), $this);
577 }
578 }
579
580 /**
100fef9d 581 * Global validation rules for the form
6a488035 582 *
77c5b619
TO
583 * @param array $fields
584 * Posted values of the form.
585 * @param array $errors
586 * List of errors to be posted back to the form.
587 * @param int $contactId
588 * Contact id if doing update.
6a488035 589 *
a6c01b45
CW
590 * @return bool
591 * email/openId
e0b82b44 592 * @static
6a488035 593 */
00be9182 594 public static function formRule($fields, &$errors, $contactId = NULL) {
6a488035
TO
595 $config = CRM_Core_Config::singleton();
596
597 // validations.
598 //1. for each block only single value can be marked as is_primary = true.
599 //2. location type id should be present if block data present.
600 //3. check open id across db and other each block for duplicate.
601 //4. at least one location should be primary.
602 //5. also get primaryID from email or open id block.
603
604 // take the location blocks.
605 $blocks = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
606 'contact_edit_options', TRUE, NULL,
607 FALSE, 'name', TRUE, 'AND v.filter = 1'
608 );
609
610 $otherEditOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
611 'contact_edit_options', TRUE, NULL,
612 FALSE, 'name', TRUE, 'AND v.filter = 0'
613 );
614 //get address block inside.
615 if (array_key_exists('Address', $otherEditOptions)) {
616 $blocks['Address'] = $otherEditOptions['Address'];
617 }
618
619 $openIds = array();
620 $primaryID = FALSE;
621 foreach ($blocks as $name => $label) {
622 $hasData = $hasPrimary = array();
623 $name = strtolower($name);
a7488080 624 if (!empty($fields[$name]) && is_array($fields[$name])) {
6a488035
TO
625 foreach ($fields[$name] as $instance => $blockValues) {
626 $dataExists = self::blockDataExists($blockValues);
627
628 if (!$dataExists && $name == 'address') {
629 $dataExists = CRM_Utils_Array::value('use_shared_address', $fields['address'][$instance]);
630 }
631
632 if ($dataExists) {
633 // skip remaining checks for website
634 if ($name == 'website') {
635 continue;
636 }
637
638 $hasData[] = $instance;
a7488080 639 if (!empty($blockValues['is_primary'])) {
6a488035
TO
640 $hasPrimary[] = $instance;
641 if (!$primaryID &&
642 in_array($name, array(
8cc574cf 643 'email', 'openid')) && !empty($blockValues[$name])) {
6a488035
TO
644 $primaryID = $blockValues[$name];
645 }
646 }
647
a7488080 648 if (empty($blockValues['location_type_id'])) {
6a488035
TO
649 $errors["{$name}[$instance][location_type_id]"] = ts('The Location Type should be set if there is %1 information.', array(1 => $label));
650 }
651 }
652
8cc574cf 653 if ($name == 'openid' && !empty($blockValues[$name])) {
6a488035
TO
654 $oid = new CRM_Core_DAO_OpenID();
655 $oid->openid = $openIds[$instance] = CRM_Utils_Array::value($name, $blockValues);
656 $cid = isset($contactId) ? $contactId : 0;
657 if ($oid->find(TRUE) && ($oid->contact_id != $cid)) {
658 $errors["{$name}[$instance][openid]"] = ts('%1 already exist.', array(1 => $blocks['OpenID']));
659 }
660 }
661 }
662
663 if (empty($hasPrimary) && !empty($hasData)) {
664 $errors["{$name}[1][is_primary]"] = ts('One %1 should be marked as primary.', array(1 => $label));
665 }
666
667 if (count($hasPrimary) > 1) {
668 $errors["{$name}[" . array_pop($hasPrimary) . "][is_primary]"] = ts('Only one %1 can be marked as primary.',
669 array(1 => $label)
670 );
671 }
672 }
673 }
674
675 //do validations for all opend ids they should be distinct.
676 if (!empty($openIds) && (count(array_unique($openIds)) != count($openIds))) {
677 foreach ($openIds as $instance => $value) {
678 if (!array_key_exists($instance, array_unique($openIds))) {
679 $errors["openid[$instance][openid]"] = ts('%1 already used.', array(1 => $blocks['OpenID']));
680 }
681 }
682 }
683
684 // street number should be digit + suffix, CRM-5450
685 $parseStreetAddress = CRM_Utils_Array::value('street_address_parsing',
686 CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
687 'address_options'
688 )
689 );
690 if ($parseStreetAddress) {
691 if (isset($fields['address']) &&
692 is_array($fields['address'])
693 ) {
694 $invalidStreetNumbers = array();
695 foreach ($fields['address'] as $cnt => $address) {
696 if ($streetNumber = CRM_Utils_Array::value('street_number', $address)) {
697 $parsedAddress = CRM_Core_BAO_Address::parseStreetAddress($address['street_number']);
a7488080 698 if (empty($parsedAddress['street_number'])) {
6a488035
TO
699 $invalidStreetNumbers[] = $cnt;
700 }
701 }
702 }
703
704 if (!empty($invalidStreetNumbers)) {
705 $first = $invalidStreetNumbers[0];
706 foreach ($invalidStreetNumbers as & $num) $num = CRM_Contact_Form_Contact::ordinalNumber($num);
707 $errors["address[$first][street_number]"] = ts('The street number you entered for the %1 address block(s) is not in an expected format. Street numbers may include numeric digit(s) followed by other characters. You can still enter the complete street address (unparsed) by clicking "Edit Complete Street Address".', array(1 => implode(', ', $invalidStreetNumbers)));
708 }
709 }
710 }
711
712 return $primaryID;
713 }
714
715 /**
c490a46a 716 * Build the form object
6a488035 717 *
355ba699 718 * @return void
6a488035
TO
719 */
720 public function buildQuickForm() {
721 //load form for child blocks
722 if ($this->_addBlockName) {
150f50c1
CW
723 $className = 'CRM_Contact_Form_Edit_' . $this->_addBlockName;
724 return $className::buildQuickForm($this);
6a488035
TO
725 }
726
727 if ($this->_action == CRM_Core_Action::UPDATE) {
728 $deleteExtra = ts('Are you sure you want to delete contact image.');
729 $deleteURL = array(
730 CRM_Core_Action::DELETE =>
731 array(
732 'name' => ts('Delete Contact Image'),
733 'url' => 'civicrm/contact/image',
734 'qs' => 'reset=1&cid=%%id%%&action=delete',
735 'extra' =>
736 'onclick = "if (confirm( \'' . $deleteExtra . '\' ) ) this.href+=\'&amp;confirmed=1\'; else return false;"',
737 ),
738 );
739 $deleteURL = CRM_Core_Action::formLink($deleteURL,
740 CRM_Core_Action::DELETE,
741 array(
742 'id' => $this->_contactId,
87dab4a4
AH
743 ),
744 ts('more'),
745 FALSE,
746 'contact.image.delete',
747 'Contact',
748 $this->_contactId
6a488035
TO
749 );
750 $this->assign('deleteURL', $deleteURL);
751 }
752
753 //build contact type specific fields
150f50c1
CW
754 $className = 'CRM_Contact_Form_Edit_' . $this->_contactType;
755 $className::buildQuickForm($this);
6a488035
TO
756
757 // build Custom data if Custom data present in edit option
758 $buildCustomData = 'noCustomDataPresent';
759 if (array_key_exists('CustomData', $this->_editOptions)) {
760 $buildCustomData = "customDataPresent";
761 }
762
763 // subtype is a common field. lets keep it here
ab345ca5 764 $subtypes = CRM_Contact_BAO_Contact::buildOptions('contact_sub_type', 'create', array('contact_type' => $this->_contactType));
6a488035
TO
765 if (!empty($subtypes)) {
766 $sel = $this->add('select', 'contact_sub_type', ts('Contact Type'),
767 $subtypes, FALSE,
768 array(
769 'id' => 'contact_sub_type',
770 'multiple' => 'multiple',
ab345ca5 771 'class' => $buildCustomData . ' crm-select2',
6a488035
TO
772 )
773 );
774 }
775
776 // build edit blocks ( custom data, demographics, communication preference, notes, tags and groups )
777 foreach ($this->_editOptions as $name => $label) {
778 if ($name == 'Address') {
779 $this->_blocks['Address'] = $this->_editOptions['Address'];
780 continue;
781 }
c18f95b7
PJ
782 if ($name == 'TagsAndGroups') {
783 continue;
784 }
150f50c1
CW
785 $className = 'CRM_Contact_Form_Edit_' . $name;
786 $className::buildQuickForm($this);
6a488035
TO
787 }
788
c18f95b7
PJ
789 // build tags and groups
790 CRM_Contact_Form_Edit_TagsAndGroups::buildQuickForm($this, 0, CRM_Contact_Form_Edit_TagsAndGroups::ALL,
ab345ca5 791 FALSE, NULL, 'Group(s)', 'Tag(s)', NULL, 'select');
c18f95b7 792
6a488035
TO
793 // build location blocks.
794 CRM_Contact_Form_Edit_Lock::buildQuickForm($this);
795 CRM_Contact_Form_Location::buildQuickForm($this);
796
797 // add attachment
798 $this->addElement('file', 'image_URL', ts('Browse/Upload Image'), 'size=30 maxlength=60');
799 $this->addUploadElement('image_URL');
800
801 // add the dedupe button
802 $this->addElement('submit',
803 $this->_dedupeButtonName,
804 ts('Check for Matching Contact(s)')
805 );
806 $this->addElement('submit',
807 $this->_duplicateButtonName,
808 ts('Save Matching Contact')
809 );
810 $this->addElement('submit',
811 $this->getButtonName('next', 'sharedHouseholdDuplicate'),
812 ts('Save With Duplicate Household')
813 );
814
815 $buttons = array(
816 array(
817 'type' => 'upload',
818 'name' => ts('Save'),
819 'subName' => 'view',
820 'isDefault' => TRUE,
821 ),
1870cae9
CW
822 );
823 if (CRM_Core_Permission::check('add contacts')) {
824 $buttons[] = array(
6a488035
TO
825 'type' => 'upload',
826 'name' => ts('Save and New'),
827 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
828 'subName' => 'new',
1870cae9
CW
829 );
830 }
831 $buttons[] = array(
832 'type' => 'cancel',
833 'name' => ts('Cancel'),
6a488035
TO
834 );
835
a7488080 836 if (!empty($this->_values['contact_sub_type'])) {
6a488035
TO
837 $this->_oldSubtypes = explode(CRM_Core_DAO::VALUE_SEPARATOR,
838 trim($this->_values['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR)
839 );
840 }
841 $this->assign('oldSubtypes', json_encode($this->_oldSubtypes));
842
843 $this->addButtons($buttons);
844 }
845
846 /**
847 * Form submission of new/edit contact is processed.
848 *
6a488035 849 *
355ba699 850 * @return void
6a488035
TO
851 */
852 public function postProcess() {
853 // check if dedupe button, if so return.
854 $buttonName = $this->controller->getButtonName();
855 if ($buttonName == $this->_dedupeButtonName) {
856 return;
857 }
858
859 //get the submitted values in an array
860 $params = $this->controller->exportValues($this->_name);
861
c18f95b7
PJ
862 $group = CRM_Utils_Array::value('group', $params);
863 if ($group && is_array($group)) {
864 unset($params['group']);
865 foreach ($group as $key => $value) {
866 $params['group'][$value] = 1;
867 }
868 }
869
481a74f4 870 CRM_Contact_BAO_Contact_Optimizer::edit($params, $this->_preEditValues);
6a488035 871
a7488080 872 if (!empty($params['image_URL'])) {
6a488035
TO
873 CRM_Contact_BAO_Contact::processImageParams($params);
874 }
875
8cc574cf 876 if (is_numeric(CRM_Utils_Array::value('current_employer_id', $params)) && !empty($params['current_employer'])) {
6a488035
TO
877 $params['current_employer'] = $params['current_employer_id'];
878 }
879
880 // don't carry current_employer_id field,
881 // since we don't want to directly update DAO object without
882 // handling related business logic ( eg related membership )
883 if (isset($params['current_employer_id'])) {
884 unset($params['current_employer_id']);
885 }
886
887 $params['contact_type'] = $this->_contactType;
888 if (empty($params['contact_sub_type']) && $this->_isContactSubType) {
889 $params['contact_sub_type'] = array($this->_contactSubType);
890 }
891
892 if ($this->_contactId) {
893 $params['contact_id'] = $this->_contactId;
894 }
895
896 //make deceased date null when is_deceased = false
8cc574cf 897 if ($this->_contactType == 'Individual' && !empty($this->_editOptions['Demographics']) && empty($params['is_deceased'])) {
6a488035
TO
898 $params['is_deceased'] = FALSE;
899 $params['deceased_date'] = NULL;
900 }
901
902 if (isset($params['contact_id'])) {
903 // process membership status for deceased contact
904 $deceasedParams = array('contact_id' => CRM_Utils_Array::value('contact_id', $params),
905 'is_deceased' => CRM_Utils_Array::value('is_deceased', $params, FALSE),
906 'deceased_date' => CRM_Utils_Array::value('deceased_date', $params, NULL),
907 );
908 $updateMembershipMsg = $this->updateMembershipStatus($deceasedParams);
909 }
910
911 // action is taken depending upon the mode
912 if ($this->_action & CRM_Core_Action::UPDATE) {
913 CRM_Utils_Hook::pre('edit', $params['contact_type'], $params['contact_id'], $params);
914 }
915 else {
916 CRM_Utils_Hook::pre('create', $params['contact_type'], NULL, $params);
917 }
918
919 $customFields = CRM_Core_BAO_CustomField::getFields($params['contact_type'], FALSE, TRUE);
920
921 //CRM-5143
922 //if subtype is set, send subtype as extend to validate subtype customfield
923 $customFieldExtends = (CRM_Utils_Array::value('contact_sub_type', $params)) ? $params['contact_sub_type'] : $params['contact_type'];
924
925 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
926 $customFields,
927 $this->_contactId,
928 $customFieldExtends,
929 TRUE
930 );
931 if ($this->_contactId && !empty($this->_oldSubtypes)) {
932 CRM_Contact_BAO_ContactType::deleteCustomSetForSubtypeMigration($this->_contactId,
933 $params['contact_type'],
934 $this->_oldSubtypes,
935 $params['contact_sub_type']
936 );
937 }
938
939 if (array_key_exists('CommunicationPreferences', $this->_editOptions)) {
940 // this is a chekbox, so mark false if we dont get a POST value
941 $params['is_opt_out'] = CRM_Utils_Array::value('is_opt_out', $params, FALSE);
942 }
943
944 // process shared contact address.
945 CRM_Contact_BAO_Contact_Utils::processSharedAddress($params['address']);
946
947 if (!array_key_exists('TagsAndGroups', $this->_editOptions)) {
948 unset($params['group']);
949 }
950
a7488080 951 if (!empty($params['contact_id']) && ($this->_action & CRM_Core_Action::UPDATE)) {
6a488035 952 // figure out which all groups are intended to be removed
c18f95b7
PJ
953 $contactGroupList = CRM_Contact_BAO_GroupContact::getContactGroup($params['contact_id'], 'Added');
954 if (is_array($contactGroupList)) {
955 foreach ($contactGroupList as $key) {
8cc574cf 956 if ((!array_key_exists($key['group_id'], $params['group']) || $params['group'][$key['group_id']] != 1) && empty($key['is_hidden'])) {
c18f95b7 957 $params['group'][$key['group_id']] = -1;
6a488035
TO
958 }
959 }
960 }
961 }
962
963 // parse street address, CRM-5450
964 $parseStatusMsg = NULL;
965 if ($this->_parseStreetAddress) {
966 $parseResult = self::parseAddress($params);
967 $parseStatusMsg = self::parseAddressStatusMsg($parseResult);
968 }
969
970 // Allow un-setting of location info, CRM-5969
971 $params['updateBlankLocInfo'] = TRUE;
972
973 $contact = CRM_Contact_BAO_Contact::create($params, TRUE, FALSE, TRUE);
974
975 // status message
976 if ($this->_contactId) {
977 $message = ts('%1 has been updated.', array(1 => $contact->display_name));
978 }
979 else {
980 $message = ts('%1 has been created.', array(1 => $contact->display_name));
981 }
982
983 // set the contact ID
984 $this->_contactId = $contact->id;
985
986 if (array_key_exists('TagsAndGroups', $this->_editOptions)) {
987 //add contact to tags
988 CRM_Core_BAO_EntityTag::create($params['tag'], 'civicrm_contact', $params['contact_id']);
989
990 //save free tags
991 if (isset($params['contact_taglist']) && !empty($params['contact_taglist'])) {
992 CRM_Core_Form_Tag::postProcess($params['contact_taglist'], $params['contact_id'], 'civicrm_contact', $this);
993 }
994 }
995
996 if (!empty($parseStatusMsg)) {
997 $message .= "<br />$parseStatusMsg";
998 }
999 if (!empty($updateMembershipMsg)) {
1000 $message .= "<br />$updateMembershipMsg";
1001 }
1002
1003 $session = CRM_Core_Session::singleton();
1004 $session->setStatus($message, ts('Contact Saved'), 'success');
1005
1006 // add the recently viewed contact
1007 $recentOther = array();
1008 if (($session->get('userID') == $contact->id) ||
1009 CRM_Contact_BAO_Contact_Permission::allow($contact->id, CRM_Core_Permission::EDIT)
1010 ) {
1011 $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/add', 'reset=1&action=update&cid=' . $contact->id);
1012 }
1013
1014 if (($session->get('userID') != $this->_contactId) && CRM_Core_Permission::check('delete contacts')) {
1015 $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/delete', 'reset=1&delete=1&cid=' . $contact->id);
1016 }
1017
1018 CRM_Utils_Recent::add($contact->display_name,
1019 CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $contact->id),
1020 $contact->id,
1021 $this->_contactType,
1022 $contact->id,
1023 $contact->display_name,
1024 $recentOther
1025 );
1026
1027 // here we replace the user context with the url to view this contact
1028 $buttonName = $this->controller->getButtonName();
1029 if ($buttonName == $this->getButtonName('upload', 'new')) {
1030 $resetStr = "reset=1&ct={$contact->contact_type}";
1031 $resetStr .= $this->_contactSubType ? "&cst={$this->_contactSubType}" : '';
1032 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/add', $resetStr));
1033 }
1034 else {
1035 $context = CRM_Utils_Request::retrieve('context', 'String', $this);
1036 $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this);
1037 //validate the qfKey
1038 $urlParams = 'reset=1&cid=' . $contact->id;
1039 if ($context) {
1040 $urlParams .= "&context=$context";
1041 }
1042 if (CRM_Utils_Rule::qfKey($qfKey)) {
1043 $urlParams .= "&key=$qfKey";
1044 }
1045
1046 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view', $urlParams));
1047 }
1048
1049 // now invoke the post hook
1050 if ($this->_action & CRM_Core_Action::UPDATE) {
1051 CRM_Utils_Hook::post('edit', $params['contact_type'], $contact->id, $contact);
1052 }
1053 else {
1054 CRM_Utils_Hook::post('create', $params['contact_type'], $contact->id, $contact);
1055 }
1056 }
1057
1058 /**
100fef9d 1059 * Is there any real significant data in the hierarchical location array
6a488035 1060 *
77c5b619
TO
1061 * @param array $fields
1062 * The hierarchical value representation of this location.
6a488035 1063 *
a6c01b45
CW
1064 * @return boolean
1065 * true if data exists, false otherwise
6a488035 1066 * @static
6a488035 1067 */
00be9182 1068 public static function blockDataExists(&$fields) {
6a488035
TO
1069 if (!is_array($fields)) {
1070 return FALSE;
1071 }
1072
1073 static $skipFields = array('location_type_id', 'is_primary', 'phone_type_id', 'provider_id', 'country_id', 'website_type_id', 'master_id');
1074 foreach ($fields as $name => $value) {
1075 $skipField = FALSE;
1076 foreach ($skipFields as $skip) {
1077 if (strpos("[$skip]", $name) !== FALSE) {
1078 if ($name == 'phone') {
1079 continue;
1080 }
1081 $skipField = TRUE;
1082 break;
1083 }
1084 }
1085 if ($skipField) {
1086 continue;
1087 }
1088 if (is_array($value)) {
1089 if (self::blockDataExists($value)) {
1090 return TRUE;
1091 }
1092 }
1093 else {
1094 if (!empty($value)) {
1095 return TRUE;
1096 }
1097 }
1098 }
1099
1100 return FALSE;
1101 }
1102
1103 /**
100fef9d 1104 * That checks for duplicate contacts
6a488035 1105 *
77c5b619
TO
1106 * @param array $fields
1107 * Fields array which are submitted.
fd31fa4c 1108 * @param $errors
77c5b619
TO
1109 * @param int $contactID
1110 * Contact id.
1111 * @param string $contactType
1112 * Contact type.
6a488035 1113 */
00be9182 1114 public static function checkDuplicateContacts(&$fields, &$errors, $contactID, $contactType) {
6a488035 1115 // if this is a forced save, ignore find duplicate rule
a7488080 1116 if (empty($fields['_qf_Contact_upload_duplicate'])) {
6a488035
TO
1117
1118 $dedupeParams = CRM_Dedupe_Finder::formatParams($fields, $contactType);
1119 $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, $contactType, 'Supervised', array($contactID));
1120 if ($ids) {
1121
1122 $contactLinks = CRM_Contact_BAO_Contact_Utils::formatContactIDSToLinks($ids, TRUE, TRUE, $contactID);
1123
1124 $duplicateContactsLinks = '<div class="matching-contacts-found">';
1125 $duplicateContactsLinks .= ts('One matching contact was found. ', array('count' => count($contactLinks['rows']), 'plural' => '%count matching contacts were found.<br />'));
1126 if ($contactLinks['msg'] == 'view') {
1127 $duplicateContactsLinks .= ts('You can View the existing contact', array('count' => count($contactLinks['rows']), 'plural' => 'You can View the existing contacts'));
1128 }
1129 else {
1130 $duplicateContactsLinks .= ts('You can View or Edit the existing contact', array('count' => count($contactLinks['rows']), 'plural' => 'You can View or Edit the existing contacts'));
1131 }
1132 if ($contactLinks['msg'] == 'merge') {
1133 // We should also get a merge link if this is for an existing contact
1134 $duplicateContactsLinks .= ts(', or Merge this contact with an existing contact');
1135 }
1136 $duplicateContactsLinks .= '.';
1137 $duplicateContactsLinks .= '</div>';
1138 $duplicateContactsLinks .= '<table class="matching-contacts-actions">';
1139 $row = '';
1140 for ($i = 0; $i < count($contactLinks['rows']); $i++) {
1141 $row .= ' <tr> ';
1142 $row .= ' <td class="matching-contacts-name"> ';
1143 $row .= $contactLinks['rows'][$i]['display_name'];
1144 $row .= ' </td>';
1145 $row .= ' <td class="matching-contacts-email"> ';
1146 $row .= $contactLinks['rows'][$i]['primary_email'];
1147 $row .= ' </td>';
1148 $row .= ' <td class="action-items"> ';
1149 $row .= $contactLinks['rows'][$i]['view'];
1150 $row .= $contactLinks['rows'][$i]['edit'];
1151 $row .= CRM_Utils_Array::value('merge', $contactLinks['rows'][$i]);
1152 $row .= ' </td>';
1153 $row .= ' </tr> ';
1154 }
1155
1156 $duplicateContactsLinks .= $row . '</table>';
1157 $duplicateContactsLinks .= ts("If you're sure this record is not a duplicate, click the 'Save Matching Contact' button below.");
1158
1159 $errors['_qf_default'] = $duplicateContactsLinks;
1160
1161
1162
1163 // let smarty know that there are duplicates
1164 $template = CRM_Core_Smarty::singleton();
1165 $template->assign('isDuplicate', 1);
1166 }
a7488080 1167 elseif (!empty($fields['_qf_Contact_refresh_dedupe'])) {
6a488035
TO
1168 // add a session message for no matching contacts
1169 CRM_Core_Session::setStatus(ts('No matching contact found.'), ts('None Found'), 'info');
1170 }
1171 }
1172 }
1173
86538308
EM
1174 /**
1175 * Use the form name to create the tpl file name
1176 *
1177 * @return string
86538308 1178 */
00be9182 1179 public function getTemplateFileName() {
6a488035
TO
1180 if ($this->_contactSubType) {
1181 $templateFile = "CRM/Contact/Form/Edit/SubType/{$this->_contactSubType}.tpl";
1182 $template = CRM_Core_Form::getTemplate();
1183 if ($template->template_exists($templateFile)) {
1184 return $templateFile;
1185 }
1186 }
1187 return parent::getTemplateFileName();
1188 }
1189
1190 /**
1191 * Parse all address blocks present in given params
77b97be7
EM
1192 * and return parse result for all address blocks,
1193 * This function either parse street address in to child
1194 * elements or build street address from child elements.
1195 *
5a4f6742
CW
1196 * @param array $params
1197 * of key value consist of address blocks.
77b97be7 1198 *
a6c01b45
CW
1199 * @return array
1200 * as array of sucess/fails for each address block
e0b82b44 1201 * @static
77b97be7 1202 */
00be9182 1203 public function parseAddress(&$params) {
6a488035
TO
1204 $parseSuccess = $parsedFields = array();
1205 if (!is_array($params['address']) ||
1206 CRM_Utils_System::isNull($params['address'])
1207 ) {
1208 return $parseSuccess;
1209 }
1210
1211 foreach ($params['address'] as $instance => & $address) {
1212 $buildStreetAddress = FALSE;
1213 $parseFieldName = 'street_address';
1214 foreach (array(
1215 'street_number', 'street_name', 'street_unit') as $fld) {
a7488080 1216 if (!empty($address[$fld])) {
6a488035
TO
1217 $parseFieldName = 'street_number';
1218 $buildStreetAddress = TRUE;
1219 break;
1220 }
1221 }
1222
1223 // main parse string.
1224 $parseString = CRM_Utils_Array::value($parseFieldName, $address);
1225
1226 // parse address field.
1227 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($parseString);
1228
1229 if ($buildStreetAddress) {
1230 //hack to ignore spaces between number and suffix.
1231 //here user gives input as street_number so it has to
1232 //be street_number and street_number_suffix, but
1233 //due to spaces though preg detect string as street_name
1234 //consider it as 'street_number_suffix'.
1235 $suffix = $parsedFields['street_number_suffix'];
1236 if (!$suffix) {
1237 $suffix = $parsedFields['street_name'];
1238 }
1239 $address['street_number_suffix'] = $suffix;
1240 $address['street_number'] = $parsedFields['street_number'];
1241
1242 $streetAddress = NULL;
1243 foreach (array(
1244 'street_number', 'street_number_suffix', 'street_name', 'street_unit') as $fld) {
1245 if (in_array($fld, array(
1246 'street_name', 'street_unit'))) {
1247 $streetAddress .= ' ';
1248 }
1249 $streetAddress .= CRM_Utils_Array::value($fld, $address);
1250 }
1251 $address['street_address'] = trim($streetAddress);
1252 $parseSuccess[$instance] = TRUE;
1253 }
1254 else {
1255 $success = TRUE;
1256 // consider address is automatically parseable,
1257 // when we should found street_number and street_name
8cc574cf 1258 if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) {
6a488035
TO
1259 $success = FALSE;
1260 }
1261
1262 // check for original street address string.
1263 if (empty($parseString)) {
1264 $success = TRUE;
1265 }
1266
1267 $parseSuccess[$instance] = $success;
1268
1269 // we do not reset element values, but keep what we've parsed
1270 // in case of partial matches: CRM-8378
1271
1272 // merge parse address in to main address block.
1273 $address = array_merge($address, $parsedFields);
1274 }
1275 }
1276
1277 return $parseSuccess;
1278 }
1279
1280 /**
100fef9d 1281 * Check parse result and if some address block fails then this
6a488035
TO
1282 * function return the status message for all address blocks.
1283 *
5a4f6742 1284 * @param array $parseResult
77c5b619 1285 * An array of address blk instance and its status.
6a488035 1286 *
e0b82b44
CW
1287 * @return null|string $statusMsg string status message for all address blocks.
1288 * @static
6a488035 1289 */
00be9182 1290 public static function parseAddressStatusMsg($parseResult) {
6a488035
TO
1291 $statusMsg = NULL;
1292 if (!is_array($parseResult) || empty($parseResult)) {
1293 return $statusMsg;
1294 }
1295
1296 $parseFails = array();
1297 foreach ($parseResult as $instance => $success) {
1298 if (!$success) {
1299 $parseFails[] = self::ordinalNumber($instance);
1300 }
1301 }
1302
1303 if (!empty($parseFails)) {
1304 $statusMsg = ts("Complete street address(es) have been saved. However we were unable to split the address in the %1 address block(s) into address elements (street number, street name, street unit) due to an unrecognized address format. You can set the address elements manually by clicking 'Edit Address Elements' next to the Street Address field while in edit mode.",
1305 array(1 => implode(', ', $parseFails))
1306 );
1307 }
1308
1309 return $statusMsg;
1310 }
1311
1312 /**
1313 * Convert normal number to ordinal number format.
1314 * like 1 => 1st, 2 => 2nd and so on...
1315 *
5a4f6742
CW
1316 * @param int $number
1317 * number to convert in to ordinal number.
6a488035
TO
1318 *
1319 * @return ordinal number for given number.
1320 * @static
1321 */
00be9182 1322 public static function ordinalNumber($number) {
6a488035
TO
1323 if (empty($number)) {
1324 return NULL;
1325 }
1326
1327 $str = 'th';
1328 switch (floor($number / 10) % 10) {
1329 case 1:
1330 default:
1331 switch ($number % 10) {
1332 case 1:
1333 $str = 'st';
1334 break;
1335
1336 case 2:
1337 $str = 'nd';
1338 break;
1339
1340 case 3:
1341 $str = 'rd';
1342 break;
1343 }
1344 }
1345
1346 return "$number$str";
1347 }
1348
1349 /**
1350 * Update membership status to deceased
1351 * function return the status message for updated membership.
1352 *
5a4f6742
CW
1353 * @param array $deceasedParams
1354 * having contact id and deceased value.
6a488035 1355 *
77b97be7 1356 * @return null|string $updateMembershipMsg string status message for updated membership.
6a488035 1357 */
00be9182 1358 public function updateMembershipStatus($deceasedParams) {
6a488035
TO
1359 $updateMembershipMsg = NULL;
1360 $contactId = CRM_Utils_Array::value('contact_id', $deceasedParams);
1361 $deceasedDate = CRM_Utils_Array::value('deceased_date', $deceasedParams);
1362
1363 // process to set membership status to deceased for both active/inactive membership
1364 if ($contactId &&
8cc574cf 1365 $this->_contactType == 'Individual' && !empty($deceasedParams['is_deceased'])) {
6a488035
TO
1366
1367 $session = CRM_Core_Session::singleton();
1368 $userId = $session->get('userID');
1369 if (!$userId) {
1370 $userId = $contactId;
1371 }
1372
1373
1374 // get deceased status id
1375 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1376 $deceasedStatusId = array_search('Deceased', $allStatus);
1377 if (!$deceasedStatusId) {
1378 return $updateMembershipMsg;
1379 }
1380
1381 $today = time();
1382 if ($deceasedDate && strtotime($deceasedDate) > $today) {
1383 return $updateMembershipMsg;
1384 }
1385
1386 // get non deceased membership
1387 $dao = new CRM_Member_DAO_Membership();
1388 $dao->contact_id = $contactId;
1389 $dao->whereAdd("status_id != $deceasedStatusId");
1390 $dao->find();
1391 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name');
1392 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1393 $memCount = 0;
1394 while ($dao->fetch()) {
1395 // update status to deceased (for both active/inactive membership )
1396 CRM_Core_DAO::setFieldValue('CRM_Member_DAO_Membership', $dao->id,
1397 'status_id', $deceasedStatusId
1398 );
1399
1400 // add membership log
1401 $membershipLog = array(
1402 'membership_id' => $dao->id,
1403 'status_id' => $deceasedStatusId,
1404 'start_date' => CRM_Utils_Date::isoToMysql($dao->start_date),
1405 'end_date' => CRM_Utils_Date::isoToMysql($dao->end_date),
1406 'modified_id' => $userId,
1407 'modified_date' => date('Ymd'),
1408 'membership_type_id' => $dao->membership_type_id,
1409 'max_related' => $dao->max_related,
1410 );
1411
1412
1413 CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
1414
1415 //create activity when membership status is changed
1416 $activityParam = array(
1417 'subject' => "Status changed from {$allStatus[$dao->status_id]} to {$allStatus[$deceasedStatusId]}",
1418 'source_contact_id' => $userId,
1419 'target_contact_id' => $dao->contact_id,
1420 'source_record_id' => $dao->id,
1421 'activity_type_id' => array_search('Change Membership Status', $activityTypes),
1422 'status_id' => 2,
1423 'version' => 3,
1424 'priority_id' => 2,
1425 'activity_date_time' => date('Y-m-d H:i:s'),
1426 'is_auto' => 0,
1427 'is_current_revision' => 1,
1428 'is_deleted' => 0,
1429 );
1430 $activityResult = civicrm_api('activity', 'create', $activityParam);
1431
1432 $memCount++;
1433 }
1434
1435 // set status msg
1436 if ($memCount) {
1437 $updateMembershipMsg = ts("%1 Current membership(s) for this contact have been set to 'Deceased' status.",
1438 array(1 => $memCount)
1439 );
1440 }
1441 }
1442
1443 return $updateMembershipMsg;
1444 }
1445}