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