Merge pull request #4837 from eileenmcnaughton/CRM-15779
[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
328 if ($contactSubType = CRM_Utils_Array::value( 'contact_sub_type', $_POST)) {
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 :
361 str_replace( CRM_Core_DAO::VALUE_SEPARATOR, ',', trim($this->_contactSubType, CRM_Core_DAO::VALUE_SEPARATOR));
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
432 CRM_Contact_Form_Edit_Address::setDefaultValues( $defaults, $this );
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
TO
454 * primary location, default country
455 *
456 */
00be9182 457 public function blockSetDefaults(&$defaults) {
b2b0530a 458 $locationTypeKeys = array_filter(array_keys(CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id')), 'is_int');
6a488035
TO
459 sort($locationTypeKeys);
460
461 // get the default location type
462 $locationType = CRM_Core_BAO_LocationType::getDefault();
463
464 // unset primary location type
465 $primaryLocationTypeIdKey = CRM_Utils_Array::key($locationType->id, $locationTypeKeys);
466 unset($locationTypeKeys[$primaryLocationTypeIdKey]);
467
468 // reset the array sequence
469 $locationTypeKeys = array_values($locationTypeKeys);
470
471 // get default phone and im provider id.
472 $defPhoneTypeId = key(CRM_Core_OptionGroup::values('phone_type', FALSE, FALSE, FALSE, ' AND is_default = 1'));
473 $defIMProviderId = key(CRM_Core_OptionGroup::values('instant_messenger_service',
474 FALSE, FALSE, FALSE, ' AND is_default = 1'
475 ));
1d477756
PN
476 $defWebsiteTypeId = key(CRM_Core_OptionGroup::values('website_type',
477 FALSE, FALSE, FALSE, ' AND is_default = 1'
478 ));
6a488035
TO
479
480 $allBlocks = $this->_blocks;
481 if (array_key_exists('Address', $this->_editOptions)) {
482 $allBlocks['Address'] = $this->_editOptions['Address'];
483 }
484
485 $config = CRM_Core_Config::singleton();
486 foreach ($allBlocks as $blockName => $label) {
487 $name = strtolower($blockName);
488 $hasPrimary = $updateMode = FALSE;
489
490 // user is in update mode.
491 if (array_key_exists($name, $defaults) &&
492 !CRM_Utils_System::isNull($defaults[$name])
493 ) {
494 $updateMode = TRUE;
495 }
496
497 for ($instance = 1; $instance <= $this->get($blockName . '_Block_Count'); $instance++) {
498 // make we require one primary block, CRM-5505
499 if ($updateMode) {
500 if (!$hasPrimary) {
501 $hasPrimary =
502 CRM_Utils_Array::value(
503 'is_primary',
504 CRM_Utils_Array::value($instance, $defaults[$name])
505 );
506 }
507 continue;
508 }
509
510 //set location to primary for first one.
511 if ($instance == 1) {
512 $hasPrimary = TRUE;
513 $defaults[$name][$instance]['is_primary'] = TRUE;
514 $defaults[$name][$instance]['location_type_id'] = $locationType->id;
515 }
516 else {
517 $locTypeId = isset($locationTypeKeys[$instance - 1]) ? $locationTypeKeys[$instance - 1] : $locationType->id;
518 $defaults[$name][$instance]['location_type_id'] = $locTypeId;
519 }
520
521 //set default country
522 if ($name == 'address' && $config->defaultContactCountry) {
523 $defaults[$name][$instance]['country_id'] = $config->defaultContactCountry;
524 }
525
526 //set default state/province
527 if ($name == 'address' && $config->defaultContactStateProvince) {
528 $defaults[$name][$instance]['state_province_id'] = $config->defaultContactStateProvince;
529 }
530
531 //set default phone type.
532 if ($name == 'phone' && $defPhoneTypeId) {
533 $defaults[$name][$instance]['phone_type_id'] = $defPhoneTypeId;
534 }
1d477756
PN
535 //set default website type.
536 if ($name == 'website' && $defWebsiteTypeId) {
537 $defaults[$name][$instance]['website_type_id'] = $defWebsiteTypeId;
538 }
6a488035
TO
539
540 //set default im provider.
541 if ($name == 'im' && $defIMProviderId) {
542 $defaults[$name][$instance]['provider_id'] = $defIMProviderId;
543 }
544 }
545
546 if (!$hasPrimary) {
547 $defaults[$name][1]['is_primary'] = TRUE;
548 }
549 }
6a488035
TO
550 }
551
552 /**
553 * This function is used to add the rules (mainly global rules) for form.
554 * All local rules are added near the element
555 *
355ba699 556 * @return void
6a488035
TO
557 * @see valid_date
558 */
00be9182 559 public function addRules() {
6a488035
TO
560 // skip adding formRules when custom data is build
561 if ($this->_addBlockName || ($this->_action & CRM_Core_Action::DELETE)) {
562 return;
563 }
564
565 $this->addFormRule(array('CRM_Contact_Form_Edit_' . $this->_contactType, 'formRule'), $this->_contactId);
566
567 // Call Locking check if editing existing contact
568 if ($this->_contactId) {
569 $this->addFormRule(array('CRM_Contact_Form_Edit_Lock', 'formRule'), $this->_contactId);
570 }
571
572 if (array_key_exists('Address', $this->_editOptions)) {
ac79e2f5 573 $this->addFormRule(array('CRM_Contact_Form_Edit_Address', 'formRule'), $this);
6a488035
TO
574 }
575
576 if (array_key_exists('CommunicationPreferences', $this->_editOptions)) {
577 $this->addFormRule(array('CRM_Contact_Form_Edit_CommunicationPreferences', 'formRule'), $this);
578 }
579 }
580
581 /**
100fef9d 582 * Global validation rules for the form
6a488035 583 *
77b97be7
EM
584 * @param array $fields posted values of the form
585 * @param array $errors list of errors to be posted back to the form
586 * @param int $contactId contact id if doing update.
6a488035 587 *
77b97be7 588 * @return bool $primaryID email/openId@static
6a488035 589 */
00be9182 590 public static function formRule($fields, &$errors, $contactId = NULL) {
6a488035
TO
591 $config = CRM_Core_Config::singleton();
592
593 // validations.
594 //1. for each block only single value can be marked as is_primary = true.
595 //2. location type id should be present if block data present.
596 //3. check open id across db and other each block for duplicate.
597 //4. at least one location should be primary.
598 //5. also get primaryID from email or open id block.
599
600 // take the location blocks.
601 $blocks = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
602 'contact_edit_options', TRUE, NULL,
603 FALSE, 'name', TRUE, 'AND v.filter = 1'
604 );
605
606 $otherEditOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
607 'contact_edit_options', TRUE, NULL,
608 FALSE, 'name', TRUE, 'AND v.filter = 0'
609 );
610 //get address block inside.
611 if (array_key_exists('Address', $otherEditOptions)) {
612 $blocks['Address'] = $otherEditOptions['Address'];
613 }
614
615 $openIds = array();
616 $primaryID = FALSE;
617 foreach ($blocks as $name => $label) {
618 $hasData = $hasPrimary = array();
619 $name = strtolower($name);
a7488080 620 if (!empty($fields[$name]) && is_array($fields[$name])) {
6a488035
TO
621 foreach ($fields[$name] as $instance => $blockValues) {
622 $dataExists = self::blockDataExists($blockValues);
623
624 if (!$dataExists && $name == 'address') {
625 $dataExists = CRM_Utils_Array::value('use_shared_address', $fields['address'][$instance]);
626 }
627
628 if ($dataExists) {
629 // skip remaining checks for website
630 if ($name == 'website') {
631 continue;
632 }
633
634 $hasData[] = $instance;
a7488080 635 if (!empty($blockValues['is_primary'])) {
6a488035
TO
636 $hasPrimary[] = $instance;
637 if (!$primaryID &&
638 in_array($name, array(
8cc574cf 639 'email', 'openid')) && !empty($blockValues[$name])) {
6a488035
TO
640 $primaryID = $blockValues[$name];
641 }
642 }
643
a7488080 644 if (empty($blockValues['location_type_id'])) {
6a488035
TO
645 $errors["{$name}[$instance][location_type_id]"] = ts('The Location Type should be set if there is %1 information.', array(1 => $label));
646 }
647 }
648
8cc574cf 649 if ($name == 'openid' && !empty($blockValues[$name])) {
6a488035
TO
650 $oid = new CRM_Core_DAO_OpenID();
651 $oid->openid = $openIds[$instance] = CRM_Utils_Array::value($name, $blockValues);
652 $cid = isset($contactId) ? $contactId : 0;
653 if ($oid->find(TRUE) && ($oid->contact_id != $cid)) {
654 $errors["{$name}[$instance][openid]"] = ts('%1 already exist.', array(1 => $blocks['OpenID']));
655 }
656 }
657 }
658
659 if (empty($hasPrimary) && !empty($hasData)) {
660 $errors["{$name}[1][is_primary]"] = ts('One %1 should be marked as primary.', array(1 => $label));
661 }
662
663 if (count($hasPrimary) > 1) {
664 $errors["{$name}[" . array_pop($hasPrimary) . "][is_primary]"] = ts('Only one %1 can be marked as primary.',
665 array(1 => $label)
666 );
667 }
668 }
669 }
670
671 //do validations for all opend ids they should be distinct.
672 if (!empty($openIds) && (count(array_unique($openIds)) != count($openIds))) {
673 foreach ($openIds as $instance => $value) {
674 if (!array_key_exists($instance, array_unique($openIds))) {
675 $errors["openid[$instance][openid]"] = ts('%1 already used.', array(1 => $blocks['OpenID']));
676 }
677 }
678 }
679
680 // street number should be digit + suffix, CRM-5450
681 $parseStreetAddress = CRM_Utils_Array::value('street_address_parsing',
682 CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
683 'address_options'
684 )
685 );
686 if ($parseStreetAddress) {
687 if (isset($fields['address']) &&
688 is_array($fields['address'])
689 ) {
690 $invalidStreetNumbers = array();
691 foreach ($fields['address'] as $cnt => $address) {
692 if ($streetNumber = CRM_Utils_Array::value('street_number', $address)) {
693 $parsedAddress = CRM_Core_BAO_Address::parseStreetAddress($address['street_number']);
a7488080 694 if (empty($parsedAddress['street_number'])) {
6a488035
TO
695 $invalidStreetNumbers[] = $cnt;
696 }
697 }
698 }
699
700 if (!empty($invalidStreetNumbers)) {
701 $first = $invalidStreetNumbers[0];
702 foreach ($invalidStreetNumbers as & $num) $num = CRM_Contact_Form_Contact::ordinalNumber($num);
703 $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)));
704 }
705 }
706 }
707
708 return $primaryID;
709 }
710
711 /**
c490a46a 712 * Build the form object
6a488035 713 *
355ba699 714 * @return void
6a488035
TO
715 */
716 public function buildQuickForm() {
717 //load form for child blocks
718 if ($this->_addBlockName) {
150f50c1
CW
719 $className = 'CRM_Contact_Form_Edit_' . $this->_addBlockName;
720 return $className::buildQuickForm($this);
6a488035
TO
721 }
722
723 if ($this->_action == CRM_Core_Action::UPDATE) {
724 $deleteExtra = ts('Are you sure you want to delete contact image.');
725 $deleteURL = array(
726 CRM_Core_Action::DELETE =>
727 array(
728 'name' => ts('Delete Contact Image'),
729 'url' => 'civicrm/contact/image',
730 'qs' => 'reset=1&cid=%%id%%&action=delete',
731 'extra' =>
732 'onclick = "if (confirm( \'' . $deleteExtra . '\' ) ) this.href+=\'&amp;confirmed=1\'; else return false;"',
733 ),
734 );
735 $deleteURL = CRM_Core_Action::formLink($deleteURL,
736 CRM_Core_Action::DELETE,
737 array(
738 'id' => $this->_contactId,
87dab4a4
AH
739 ),
740 ts('more'),
741 FALSE,
742 'contact.image.delete',
743 'Contact',
744 $this->_contactId
6a488035
TO
745 );
746 $this->assign('deleteURL', $deleteURL);
747 }
748
749 //build contact type specific fields
150f50c1
CW
750 $className = 'CRM_Contact_Form_Edit_' . $this->_contactType;
751 $className::buildQuickForm($this);
6a488035
TO
752
753 // build Custom data if Custom data present in edit option
754 $buildCustomData = 'noCustomDataPresent';
755 if (array_key_exists('CustomData', $this->_editOptions)) {
756 $buildCustomData = "customDataPresent";
757 }
758
759 // subtype is a common field. lets keep it here
ab345ca5 760 $subtypes = CRM_Contact_BAO_Contact::buildOptions('contact_sub_type', 'create', array('contact_type' => $this->_contactType));
6a488035
TO
761 if (!empty($subtypes)) {
762 $sel = $this->add('select', 'contact_sub_type', ts('Contact Type'),
763 $subtypes, FALSE,
764 array(
765 'id' => 'contact_sub_type',
766 'multiple' => 'multiple',
ab345ca5 767 'class' => $buildCustomData . ' crm-select2',
6a488035
TO
768 )
769 );
770 }
771
772 // build edit blocks ( custom data, demographics, communication preference, notes, tags and groups )
773 foreach ($this->_editOptions as $name => $label) {
774 if ($name == 'Address') {
775 $this->_blocks['Address'] = $this->_editOptions['Address'];
776 continue;
777 }
c18f95b7
PJ
778 if ($name == 'TagsAndGroups') {
779 continue;
780 }
150f50c1
CW
781 $className = 'CRM_Contact_Form_Edit_' . $name;
782 $className::buildQuickForm($this);
6a488035
TO
783 }
784
c18f95b7
PJ
785 // build tags and groups
786 CRM_Contact_Form_Edit_TagsAndGroups::buildQuickForm($this, 0, CRM_Contact_Form_Edit_TagsAndGroups::ALL,
ab345ca5 787 FALSE, NULL, 'Group(s)', 'Tag(s)', NULL, 'select');
c18f95b7 788
6a488035
TO
789 // build location blocks.
790 CRM_Contact_Form_Edit_Lock::buildQuickForm($this);
791 CRM_Contact_Form_Location::buildQuickForm($this);
792
793 // add attachment
794 $this->addElement('file', 'image_URL', ts('Browse/Upload Image'), 'size=30 maxlength=60');
795 $this->addUploadElement('image_URL');
796
797 // add the dedupe button
798 $this->addElement('submit',
799 $this->_dedupeButtonName,
800 ts('Check for Matching Contact(s)')
801 );
802 $this->addElement('submit',
803 $this->_duplicateButtonName,
804 ts('Save Matching Contact')
805 );
806 $this->addElement('submit',
807 $this->getButtonName('next', 'sharedHouseholdDuplicate'),
808 ts('Save With Duplicate Household')
809 );
810
811 $buttons = array(
812 array(
813 'type' => 'upload',
814 'name' => ts('Save'),
815 'subName' => 'view',
816 'isDefault' => TRUE,
817 ),
1870cae9
CW
818 );
819 if (CRM_Core_Permission::check('add contacts')) {
820 $buttons[] = array(
6a488035
TO
821 'type' => 'upload',
822 'name' => ts('Save and New'),
823 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
824 'subName' => 'new',
1870cae9
CW
825 );
826 }
827 $buttons[] = array(
828 'type' => 'cancel',
829 'name' => ts('Cancel'),
6a488035
TO
830 );
831
a7488080 832 if (!empty($this->_values['contact_sub_type'])) {
6a488035
TO
833 $this->_oldSubtypes = explode(CRM_Core_DAO::VALUE_SEPARATOR,
834 trim($this->_values['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR)
835 );
836 }
837 $this->assign('oldSubtypes', json_encode($this->_oldSubtypes));
838
839 $this->addButtons($buttons);
840 }
841
842 /**
843 * Form submission of new/edit contact is processed.
844 *
6a488035 845 *
355ba699 846 * @return void
6a488035
TO
847 */
848 public function postProcess() {
849 // check if dedupe button, if so return.
850 $buttonName = $this->controller->getButtonName();
851 if ($buttonName == $this->_dedupeButtonName) {
852 return;
853 }
854
855 //get the submitted values in an array
856 $params = $this->controller->exportValues($this->_name);
857
c18f95b7
PJ
858 $group = CRM_Utils_Array::value('group', $params);
859 if ($group && is_array($group)) {
860 unset($params['group']);
861 foreach ($group as $key => $value) {
862 $params['group'][$value] = 1;
863 }
864 }
865
6a488035
TO
866 CRM_Contact_BAO_Contact_Optimizer::edit( $params, $this->_preEditValues );
867
a7488080 868 if (!empty($params['image_URL'])) {
6a488035
TO
869 CRM_Contact_BAO_Contact::processImageParams($params);
870 }
871
8cc574cf 872 if (is_numeric(CRM_Utils_Array::value('current_employer_id', $params)) && !empty($params['current_employer'])) {
6a488035
TO
873 $params['current_employer'] = $params['current_employer_id'];
874 }
875
876 // don't carry current_employer_id field,
877 // since we don't want to directly update DAO object without
878 // handling related business logic ( eg related membership )
879 if (isset($params['current_employer_id'])) {
880 unset($params['current_employer_id']);
881 }
882
883 $params['contact_type'] = $this->_contactType;
884 if (empty($params['contact_sub_type']) && $this->_isContactSubType) {
885 $params['contact_sub_type'] = array($this->_contactSubType);
886 }
887
888 if ($this->_contactId) {
889 $params['contact_id'] = $this->_contactId;
890 }
891
892 //make deceased date null when is_deceased = false
8cc574cf 893 if ($this->_contactType == 'Individual' && !empty($this->_editOptions['Demographics']) && empty($params['is_deceased'])) {
6a488035
TO
894 $params['is_deceased'] = FALSE;
895 $params['deceased_date'] = NULL;
896 }
897
898 if (isset($params['contact_id'])) {
899 // process membership status for deceased contact
900 $deceasedParams = array('contact_id' => CRM_Utils_Array::value('contact_id', $params),
901 'is_deceased' => CRM_Utils_Array::value('is_deceased', $params, FALSE),
902 'deceased_date' => CRM_Utils_Array::value('deceased_date', $params, NULL),
903 );
904 $updateMembershipMsg = $this->updateMembershipStatus($deceasedParams);
905 }
906
907 // action is taken depending upon the mode
908 if ($this->_action & CRM_Core_Action::UPDATE) {
909 CRM_Utils_Hook::pre('edit', $params['contact_type'], $params['contact_id'], $params);
910 }
911 else {
912 CRM_Utils_Hook::pre('create', $params['contact_type'], NULL, $params);
913 }
914
915 $customFields = CRM_Core_BAO_CustomField::getFields($params['contact_type'], FALSE, TRUE);
916
917 //CRM-5143
918 //if subtype is set, send subtype as extend to validate subtype customfield
919 $customFieldExtends = (CRM_Utils_Array::value('contact_sub_type', $params)) ? $params['contact_sub_type'] : $params['contact_type'];
920
921 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
922 $customFields,
923 $this->_contactId,
924 $customFieldExtends,
925 TRUE
926 );
927 if ($this->_contactId && !empty($this->_oldSubtypes)) {
928 CRM_Contact_BAO_ContactType::deleteCustomSetForSubtypeMigration($this->_contactId,
929 $params['contact_type'],
930 $this->_oldSubtypes,
931 $params['contact_sub_type']
932 );
933 }
934
935 if (array_key_exists('CommunicationPreferences', $this->_editOptions)) {
936 // this is a chekbox, so mark false if we dont get a POST value
937 $params['is_opt_out'] = CRM_Utils_Array::value('is_opt_out', $params, FALSE);
938 }
939
940 // process shared contact address.
941 CRM_Contact_BAO_Contact_Utils::processSharedAddress($params['address']);
942
943 if (!array_key_exists('TagsAndGroups', $this->_editOptions)) {
944 unset($params['group']);
945 }
946
a7488080 947 if (!empty($params['contact_id']) && ($this->_action & CRM_Core_Action::UPDATE)) {
6a488035 948 // figure out which all groups are intended to be removed
c18f95b7
PJ
949 $contactGroupList = CRM_Contact_BAO_GroupContact::getContactGroup($params['contact_id'], 'Added');
950 if (is_array($contactGroupList)) {
951 foreach ($contactGroupList as $key) {
8cc574cf 952 if ((!array_key_exists($key['group_id'], $params['group']) || $params['group'][$key['group_id']] != 1) && empty($key['is_hidden'])) {
c18f95b7 953 $params['group'][$key['group_id']] = -1;
6a488035
TO
954 }
955 }
956 }
957 }
958
959 // parse street address, CRM-5450
960 $parseStatusMsg = NULL;
961 if ($this->_parseStreetAddress) {
962 $parseResult = self::parseAddress($params);
963 $parseStatusMsg = self::parseAddressStatusMsg($parseResult);
964 }
965
966 // Allow un-setting of location info, CRM-5969
967 $params['updateBlankLocInfo'] = TRUE;
968
969 $contact = CRM_Contact_BAO_Contact::create($params, TRUE, FALSE, TRUE);
970
971 // status message
972 if ($this->_contactId) {
973 $message = ts('%1 has been updated.', array(1 => $contact->display_name));
974 }
975 else {
976 $message = ts('%1 has been created.', array(1 => $contact->display_name));
977 }
978
979 // set the contact ID
980 $this->_contactId = $contact->id;
981
982 if (array_key_exists('TagsAndGroups', $this->_editOptions)) {
983 //add contact to tags
984 CRM_Core_BAO_EntityTag::create($params['tag'], 'civicrm_contact', $params['contact_id']);
985
986 //save free tags
987 if (isset($params['contact_taglist']) && !empty($params['contact_taglist'])) {
988 CRM_Core_Form_Tag::postProcess($params['contact_taglist'], $params['contact_id'], 'civicrm_contact', $this);
989 }
990 }
991
992 if (!empty($parseStatusMsg)) {
993 $message .= "<br />$parseStatusMsg";
994 }
995 if (!empty($updateMembershipMsg)) {
996 $message .= "<br />$updateMembershipMsg";
997 }
998
999 $session = CRM_Core_Session::singleton();
1000 $session->setStatus($message, ts('Contact Saved'), 'success');
1001
1002 // add the recently viewed contact
1003 $recentOther = array();
1004 if (($session->get('userID') == $contact->id) ||
1005 CRM_Contact_BAO_Contact_Permission::allow($contact->id, CRM_Core_Permission::EDIT)
1006 ) {
1007 $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/add', 'reset=1&action=update&cid=' . $contact->id);
1008 }
1009
1010 if (($session->get('userID') != $this->_contactId) && CRM_Core_Permission::check('delete contacts')) {
1011 $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/delete', 'reset=1&delete=1&cid=' . $contact->id);
1012 }
1013
1014 CRM_Utils_Recent::add($contact->display_name,
1015 CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $contact->id),
1016 $contact->id,
1017 $this->_contactType,
1018 $contact->id,
1019 $contact->display_name,
1020 $recentOther
1021 );
1022
1023 // here we replace the user context with the url to view this contact
1024 $buttonName = $this->controller->getButtonName();
1025 if ($buttonName == $this->getButtonName('upload', 'new')) {
1026 $resetStr = "reset=1&ct={$contact->contact_type}";
1027 $resetStr .= $this->_contactSubType ? "&cst={$this->_contactSubType}" : '';
1028 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/add', $resetStr));
1029 }
1030 else {
1031 $context = CRM_Utils_Request::retrieve('context', 'String', $this);
1032 $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this);
1033 //validate the qfKey
1034 $urlParams = 'reset=1&cid=' . $contact->id;
1035 if ($context) {
1036 $urlParams .= "&context=$context";
1037 }
1038 if (CRM_Utils_Rule::qfKey($qfKey)) {
1039 $urlParams .= "&key=$qfKey";
1040 }
1041
1042 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view', $urlParams));
1043 }
1044
1045 // now invoke the post hook
1046 if ($this->_action & CRM_Core_Action::UPDATE) {
1047 CRM_Utils_Hook::post('edit', $params['contact_type'], $contact->id, $contact);
1048 }
1049 else {
1050 CRM_Utils_Hook::post('create', $params['contact_type'], $contact->id, $contact);
1051 }
1052 }
1053
1054 /**
100fef9d 1055 * Is there any real significant data in the hierarchical location array
6a488035
TO
1056 *
1057 * @param array $fields the hierarchical value representation of this location
1058 *
1059 * @return boolean true if data exists, false otherwise
1060 * @static
6a488035 1061 */
00be9182 1062 public static function blockDataExists(&$fields) {
6a488035
TO
1063 if (!is_array($fields)) {
1064 return FALSE;
1065 }
1066
1067 static $skipFields = array('location_type_id', 'is_primary', 'phone_type_id', 'provider_id', 'country_id', 'website_type_id', 'master_id');
1068 foreach ($fields as $name => $value) {
1069 $skipField = FALSE;
1070 foreach ($skipFields as $skip) {
1071 if (strpos("[$skip]", $name) !== FALSE) {
1072 if ($name == 'phone') {
1073 continue;
1074 }
1075 $skipField = TRUE;
1076 break;
1077 }
1078 }
1079 if ($skipField) {
1080 continue;
1081 }
1082 if (is_array($value)) {
1083 if (self::blockDataExists($value)) {
1084 return TRUE;
1085 }
1086 }
1087 else {
1088 if (!empty($value)) {
1089 return TRUE;
1090 }
1091 }
1092 }
1093
1094 return FALSE;
1095 }
1096
1097 /**
100fef9d 1098 * That checks for duplicate contacts
6a488035 1099 *
fd31fa4c
EM
1100 * @param array $fields fields array which are submitted
1101 * @param $errors
1102 * @param int $contactID contact id
1103 * @param string $contactType contact type
1104 *
6a488035 1105 */
00be9182 1106 public static function checkDuplicateContacts(&$fields, &$errors, $contactID, $contactType) {
6a488035 1107 // if this is a forced save, ignore find duplicate rule
a7488080 1108 if (empty($fields['_qf_Contact_upload_duplicate'])) {
6a488035
TO
1109
1110 $dedupeParams = CRM_Dedupe_Finder::formatParams($fields, $contactType);
1111 $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, $contactType, 'Supervised', array($contactID));
1112 if ($ids) {
1113
1114 $contactLinks = CRM_Contact_BAO_Contact_Utils::formatContactIDSToLinks($ids, TRUE, TRUE, $contactID);
1115
1116 $duplicateContactsLinks = '<div class="matching-contacts-found">';
1117 $duplicateContactsLinks .= ts('One matching contact was found. ', array('count' => count($contactLinks['rows']), 'plural' => '%count matching contacts were found.<br />'));
1118 if ($contactLinks['msg'] == 'view') {
1119 $duplicateContactsLinks .= ts('You can View the existing contact', array('count' => count($contactLinks['rows']), 'plural' => 'You can View the existing contacts'));
1120 }
1121 else {
1122 $duplicateContactsLinks .= ts('You can View or Edit the existing contact', array('count' => count($contactLinks['rows']), 'plural' => 'You can View or Edit the existing contacts'));
1123 }
1124 if ($contactLinks['msg'] == 'merge') {
1125 // We should also get a merge link if this is for an existing contact
1126 $duplicateContactsLinks .= ts(', or Merge this contact with an existing contact');
1127 }
1128 $duplicateContactsLinks .= '.';
1129 $duplicateContactsLinks .= '</div>';
1130 $duplicateContactsLinks .= '<table class="matching-contacts-actions">';
1131 $row = '';
1132 for ($i = 0; $i < count($contactLinks['rows']); $i++) {
1133 $row .= ' <tr> ';
1134 $row .= ' <td class="matching-contacts-name"> ';
1135 $row .= $contactLinks['rows'][$i]['display_name'];
1136 $row .= ' </td>';
1137 $row .= ' <td class="matching-contacts-email"> ';
1138 $row .= $contactLinks['rows'][$i]['primary_email'];
1139 $row .= ' </td>';
1140 $row .= ' <td class="action-items"> ';
1141 $row .= $contactLinks['rows'][$i]['view'];
1142 $row .= $contactLinks['rows'][$i]['edit'];
1143 $row .= CRM_Utils_Array::value('merge', $contactLinks['rows'][$i]);
1144 $row .= ' </td>';
1145 $row .= ' </tr> ';
1146 }
1147
1148 $duplicateContactsLinks .= $row . '</table>';
1149 $duplicateContactsLinks .= ts("If you're sure this record is not a duplicate, click the 'Save Matching Contact' button below.");
1150
1151 $errors['_qf_default'] = $duplicateContactsLinks;
1152
1153
1154
1155 // let smarty know that there are duplicates
1156 $template = CRM_Core_Smarty::singleton();
1157 $template->assign('isDuplicate', 1);
1158 }
a7488080 1159 elseif (!empty($fields['_qf_Contact_refresh_dedupe'])) {
6a488035
TO
1160 // add a session message for no matching contacts
1161 CRM_Core_Session::setStatus(ts('No matching contact found.'), ts('None Found'), 'info');
1162 }
1163 }
1164 }
1165
86538308
EM
1166 /**
1167 * Use the form name to create the tpl file name
1168 *
1169 * @return string
86538308 1170 */
00be9182 1171 public function getTemplateFileName() {
6a488035
TO
1172 if ($this->_contactSubType) {
1173 $templateFile = "CRM/Contact/Form/Edit/SubType/{$this->_contactSubType}.tpl";
1174 $template = CRM_Core_Form::getTemplate();
1175 if ($template->template_exists($templateFile)) {
1176 return $templateFile;
1177 }
1178 }
1179 return parent::getTemplateFileName();
1180 }
1181
1182 /**
1183 * Parse all address blocks present in given params
77b97be7
EM
1184 * and return parse result for all address blocks,
1185 * This function either parse street address in to child
1186 * elements or build street address from child elements.
1187 *
c490a46a 1188 * @param $params array of key value consist of address blocks.
77b97be7
EM
1189 *
1190 * @return array $parseSuccess as array of sucess/fails for each address block@static
1191 */
00be9182 1192 public function parseAddress(&$params) {
6a488035
TO
1193 $parseSuccess = $parsedFields = array();
1194 if (!is_array($params['address']) ||
1195 CRM_Utils_System::isNull($params['address'])
1196 ) {
1197 return $parseSuccess;
1198 }
1199
1200 foreach ($params['address'] as $instance => & $address) {
1201 $buildStreetAddress = FALSE;
1202 $parseFieldName = 'street_address';
1203 foreach (array(
1204 'street_number', 'street_name', 'street_unit') as $fld) {
a7488080 1205 if (!empty($address[$fld])) {
6a488035
TO
1206 $parseFieldName = 'street_number';
1207 $buildStreetAddress = TRUE;
1208 break;
1209 }
1210 }
1211
1212 // main parse string.
1213 $parseString = CRM_Utils_Array::value($parseFieldName, $address);
1214
1215 // parse address field.
1216 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($parseString);
1217
1218 if ($buildStreetAddress) {
1219 //hack to ignore spaces between number and suffix.
1220 //here user gives input as street_number so it has to
1221 //be street_number and street_number_suffix, but
1222 //due to spaces though preg detect string as street_name
1223 //consider it as 'street_number_suffix'.
1224 $suffix = $parsedFields['street_number_suffix'];
1225 if (!$suffix) {
1226 $suffix = $parsedFields['street_name'];
1227 }
1228 $address['street_number_suffix'] = $suffix;
1229 $address['street_number'] = $parsedFields['street_number'];
1230
1231 $streetAddress = NULL;
1232 foreach (array(
1233 'street_number', 'street_number_suffix', 'street_name', 'street_unit') as $fld) {
1234 if (in_array($fld, array(
1235 'street_name', 'street_unit'))) {
1236 $streetAddress .= ' ';
1237 }
1238 $streetAddress .= CRM_Utils_Array::value($fld, $address);
1239 }
1240 $address['street_address'] = trim($streetAddress);
1241 $parseSuccess[$instance] = TRUE;
1242 }
1243 else {
1244 $success = TRUE;
1245 // consider address is automatically parseable,
1246 // when we should found street_number and street_name
8cc574cf 1247 if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) {
6a488035
TO
1248 $success = FALSE;
1249 }
1250
1251 // check for original street address string.
1252 if (empty($parseString)) {
1253 $success = TRUE;
1254 }
1255
1256 $parseSuccess[$instance] = $success;
1257
1258 // we do not reset element values, but keep what we've parsed
1259 // in case of partial matches: CRM-8378
1260
1261 // merge parse address in to main address block.
1262 $address = array_merge($address, $parsedFields);
1263 }
1264 }
1265
1266 return $parseSuccess;
1267 }
1268
1269 /**
100fef9d 1270 * Check parse result and if some address block fails then this
6a488035
TO
1271 * function return the status message for all address blocks.
1272 *
1273 * @param $parseResult an array of address blk instance and its status.
1274 *
77b97be7 1275 * @return null|string $statusMsg string status message for all address blocks.@static
6a488035 1276 */
00be9182 1277 public static function parseAddressStatusMsg($parseResult) {
6a488035
TO
1278 $statusMsg = NULL;
1279 if (!is_array($parseResult) || empty($parseResult)) {
1280 return $statusMsg;
1281 }
1282
1283 $parseFails = array();
1284 foreach ($parseResult as $instance => $success) {
1285 if (!$success) {
1286 $parseFails[] = self::ordinalNumber($instance);
1287 }
1288 }
1289
1290 if (!empty($parseFails)) {
1291 $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.",
1292 array(1 => implode(', ', $parseFails))
1293 );
1294 }
1295
1296 return $statusMsg;
1297 }
1298
1299 /**
1300 * Convert normal number to ordinal number format.
1301 * like 1 => 1st, 2 => 2nd and so on...
1302 *
1303 * @param $number int number to convert in to ordinal number.
1304 *
1305 * @return ordinal number for given number.
1306 * @static
1307 */
00be9182 1308 public static function ordinalNumber($number) {
6a488035
TO
1309 if (empty($number)) {
1310 return NULL;
1311 }
1312
1313 $str = 'th';
1314 switch (floor($number / 10) % 10) {
1315 case 1:
1316 default:
1317 switch ($number % 10) {
1318 case 1:
1319 $str = 'st';
1320 break;
1321
1322 case 2:
1323 $str = 'nd';
1324 break;
1325
1326 case 3:
1327 $str = 'rd';
1328 break;
1329 }
1330 }
1331
1332 return "$number$str";
1333 }
1334
1335 /**
1336 * Update membership status to deceased
1337 * function return the status message for updated membership.
1338 *
1339 * @param $deceasedParams array having contact id and deceased value.
1340 *
77b97be7 1341 * @return null|string $updateMembershipMsg string status message for updated membership.
6a488035 1342 */
00be9182 1343 public function updateMembershipStatus($deceasedParams) {
6a488035
TO
1344 $updateMembershipMsg = NULL;
1345 $contactId = CRM_Utils_Array::value('contact_id', $deceasedParams);
1346 $deceasedDate = CRM_Utils_Array::value('deceased_date', $deceasedParams);
1347
1348 // process to set membership status to deceased for both active/inactive membership
1349 if ($contactId &&
8cc574cf 1350 $this->_contactType == 'Individual' && !empty($deceasedParams['is_deceased'])) {
6a488035
TO
1351
1352 $session = CRM_Core_Session::singleton();
1353 $userId = $session->get('userID');
1354 if (!$userId) {
1355 $userId = $contactId;
1356 }
1357
1358
1359 // get deceased status id
1360 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1361 $deceasedStatusId = array_search('Deceased', $allStatus);
1362 if (!$deceasedStatusId) {
1363 return $updateMembershipMsg;
1364 }
1365
1366 $today = time();
1367 if ($deceasedDate && strtotime($deceasedDate) > $today) {
1368 return $updateMembershipMsg;
1369 }
1370
1371 // get non deceased membership
1372 $dao = new CRM_Member_DAO_Membership();
1373 $dao->contact_id = $contactId;
1374 $dao->whereAdd("status_id != $deceasedStatusId");
1375 $dao->find();
1376 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name');
1377 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1378 $memCount = 0;
1379 while ($dao->fetch()) {
1380 // update status to deceased (for both active/inactive membership )
1381 CRM_Core_DAO::setFieldValue('CRM_Member_DAO_Membership', $dao->id,
1382 'status_id', $deceasedStatusId
1383 );
1384
1385 // add membership log
1386 $membershipLog = array(
1387 'membership_id' => $dao->id,
1388 'status_id' => $deceasedStatusId,
1389 'start_date' => CRM_Utils_Date::isoToMysql($dao->start_date),
1390 'end_date' => CRM_Utils_Date::isoToMysql($dao->end_date),
1391 'modified_id' => $userId,
1392 'modified_date' => date('Ymd'),
1393 'membership_type_id' => $dao->membership_type_id,
1394 'max_related' => $dao->max_related,
1395 );
1396
1397
1398 CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
1399
1400 //create activity when membership status is changed
1401 $activityParam = array(
1402 'subject' => "Status changed from {$allStatus[$dao->status_id]} to {$allStatus[$deceasedStatusId]}",
1403 'source_contact_id' => $userId,
1404 'target_contact_id' => $dao->contact_id,
1405 'source_record_id' => $dao->id,
1406 'activity_type_id' => array_search('Change Membership Status', $activityTypes),
1407 'status_id' => 2,
1408 'version' => 3,
1409 'priority_id' => 2,
1410 'activity_date_time' => date('Y-m-d H:i:s'),
1411 'is_auto' => 0,
1412 'is_current_revision' => 1,
1413 'is_deleted' => 0,
1414 );
1415 $activityResult = civicrm_api('activity', 'create', $activityParam);
1416
1417 $memCount++;
1418 }
1419
1420 // set status msg
1421 if ($memCount) {
1422 $updateMembershipMsg = ts("%1 Current membership(s) for this contact have been set to 'Deceased' status.",
1423 array(1 => $memCount)
1424 );
1425 }
1426 }
1427
1428 return $updateMembershipMsg;
1429 }
1430}