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