Merge pull request #3966 from eileenmcnaughton/CRM-15168-all-functions
[civicrm-core.git] / CRM / Contact / Form / Contact.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 4.5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2014 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
10 | CiviCRM is free software; you can copy, modify, and distribute it |
11 | under the terms of the GNU Affero General Public License |
12 | Version 3, 19 November 2007 and the CiviCRM Licensing Exception. |
13 | |
14 | CiviCRM is distributed in the hope that it will be useful, but |
15 | WITHOUT ANY WARRANTY; without even the implied warranty of |
16 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. |
17 | See the GNU Affero General Public License for more details. |
18 | |
19 | You should have received a copy of the GNU Affero General Public |
20 | License and the CiviCRM Licensing Exception along |
21 | with this program; if not, contact CiviCRM LLC |
22 | at info[AT]civicrm[DOT]org. If you have questions about the |
23 | GNU Affero General Public License or the licensing of CiviCRM, |
24 | see the CiviCRM license FAQ at http://civicrm.org/licensing |
25 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2014
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 */
44 class 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
200 if (empty($defaults['id'])) {
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();
209 if (!CRM_Contact_BAO_Contact_Permission::allow($this->_contactId, CRM_Core_Permission::EDIT)) {
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));
215
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 }
220
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;
268 if (!empty($addressOptions['street_address']) && !empty($addressOptions['street_address_parsing'])) {
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
328 if (!empty($_POST['hidden_custom'])) {
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
375 if (!empty($_POST['contact_sub_type'])) {
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 *
393 * @return void
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) {
403 $defaults['group'][] = $this->_gid;
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 {
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
423 if (!empty($defaults['contact_sub_type'])) {
424 $defaults['contact_sub_type'] = $this->_oldSubtypes;
425 }
426 }
427 // set defaults for blocks ( custom data, address, communication preference, notes, tags and groups )
428 foreach ($this->_editOptions as $name => $label) {
429 if (!in_array($name, array('Address', 'Notes'))) {
430 $className = 'CRM_Contact_Form_Edit_' . $name;
431 $className::setDefaultValues($this, $defaults);
432 }
433 }
434
435 //set address block defaults
436 CRM_Contact_Form_Edit_Address::setDefaultValues( $defaults, $this );
437
438
439 if (!empty($defaults['image_URL'])) {
440 list($imageWidth, $imageHeight) = getimagesize(CRM_Utils_String::unstupifyUrl($defaults['image_URL']));
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 /**
457 * do the set default related to location type id,
458 * primary location, default country
459 *
460 */
461 function blockSetDefaults(&$defaults) {
462 $locationTypeKeys = array_filter(array_keys(CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id')), 'is_int');
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 ));
480 $defWebsiteTypeId = key(CRM_Core_OptionGroup::values('website_type',
481 FALSE, FALSE, FALSE, ' AND is_default = 1'
482 ));
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 }
539 //set default website type.
540 if ($name == 'website' && $defWebsiteTypeId) {
541 $defaults[$name][$instance]['website_type_id'] = $defWebsiteTypeId;
542 }
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 }
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 *
560 * @return void
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)) {
578 $this->addFormRule(array('CRM_Contact_Form_Edit_Address', 'formRule'), $this);
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 /**
587 * global validation rules for the form
588 *
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.
592 *
593 * @return bool $primaryID email/openId@static
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);
626 if (!empty($fields[$name]) && is_array($fields[$name])) {
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;
641 if (!empty($blockValues['is_primary'])) {
642 $hasPrimary[] = $instance;
643 if (!$primaryID &&
644 in_array($name, array(
645 'email', 'openid')) && !empty($blockValues[$name])) {
646 $primaryID = $blockValues[$name];
647 }
648 }
649
650 if (empty($blockValues['location_type_id'])) {
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
655 if ($name == 'openid' && !empty($blockValues[$name])) {
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']);
700 if (empty($parsedAddress['street_number'])) {
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 /**
718 * Function to actually build the form
719 *
720 * @return void
721 * @access public
722 */
723 public function buildQuickForm() {
724 //load form for child blocks
725 if ($this->_addBlockName) {
726 $className = 'CRM_Contact_Form_Edit_' . $this->_addBlockName;
727 return $className::buildQuickForm($this);
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,
746 ),
747 ts('more'),
748 FALSE,
749 'contact.image.delete',
750 'Contact',
751 $this->_contactId
752 );
753 $this->assign('deleteURL', $deleteURL);
754 }
755
756 //build contact type specific fields
757 $className = 'CRM_Contact_Form_Edit_' . $this->_contactType;
758 $className::buildQuickForm($this);
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
767 $subtypes = CRM_Contact_BAO_Contact::buildOptions('contact_sub_type', 'create', array('contact_type' => $this->_contactType));
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',
774 'class' => $buildCustomData . ' crm-select2',
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 }
785 if ($name == 'TagsAndGroups') {
786 continue;
787 }
788 $className = 'CRM_Contact_Form_Edit_' . $name;
789 $className::buildQuickForm($this);
790 }
791
792 // build tags and groups
793 CRM_Contact_Form_Edit_TagsAndGroups::buildQuickForm($this, 0, CRM_Contact_Form_Edit_TagsAndGroups::ALL,
794 FALSE, NULL, 'Group(s)', 'Tag(s)', NULL, 'select');
795
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 ),
825 array(
826 'type' => 'upload',
827 'name' => ts('Save and New'),
828 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
829 'subName' => 'new',
830 ),
831 array(
832 'type' => 'cancel',
833 'name' => ts('Cancel'),
834 ),
835 );
836
837 if (!empty($this->_values['contact_sub_type'])) {
838 $this->_oldSubtypes = explode(CRM_Core_DAO::VALUE_SEPARATOR,
839 trim($this->_values['contact_sub_type'], CRM_Core_DAO::VALUE_SEPARATOR)
840 );
841 }
842 $this->assign('oldSubtypes', json_encode($this->_oldSubtypes));
843
844 $this->addButtons($buttons);
845 }
846
847 /**
848 * Form submission of new/edit contact is processed.
849 *
850 * @access public
851 *
852 * @return void
853 */
854 public function postProcess() {
855 // check if dedupe button, if so return.
856 $buttonName = $this->controller->getButtonName();
857 if ($buttonName == $this->_dedupeButtonName) {
858 return;
859 }
860
861 //get the submitted values in an array
862 $params = $this->controller->exportValues($this->_name);
863
864 $group = CRM_Utils_Array::value('group', $params);
865 if ($group && is_array($group)) {
866 unset($params['group']);
867 foreach ($group as $key => $value) {
868 $params['group'][$value] = 1;
869 }
870 }
871
872 CRM_Contact_BAO_Contact_Optimizer::edit( $params, $this->_preEditValues );
873
874 if (!empty($params['image_URL'])) {
875 CRM_Contact_BAO_Contact::processImageParams($params);
876 }
877
878 if (is_numeric(CRM_Utils_Array::value('current_employer_id', $params)) && !empty($params['current_employer'])) {
879 $params['current_employer'] = $params['current_employer_id'];
880 }
881
882 // don't carry current_employer_id field,
883 // since we don't want to directly update DAO object without
884 // handling related business logic ( eg related membership )
885 if (isset($params['current_employer_id'])) {
886 unset($params['current_employer_id']);
887 }
888
889 $params['contact_type'] = $this->_contactType;
890 if (empty($params['contact_sub_type']) && $this->_isContactSubType) {
891 $params['contact_sub_type'] = array($this->_contactSubType);
892 }
893
894 if ($this->_contactId) {
895 $params['contact_id'] = $this->_contactId;
896 }
897
898 //make deceased date null when is_deceased = false
899 if ($this->_contactType == 'Individual' && !empty($this->_editOptions['Demographics']) && empty($params['is_deceased'])) {
900 $params['is_deceased'] = FALSE;
901 $params['deceased_date'] = NULL;
902 }
903
904 if (isset($params['contact_id'])) {
905 // process membership status for deceased contact
906 $deceasedParams = array('contact_id' => CRM_Utils_Array::value('contact_id', $params),
907 'is_deceased' => CRM_Utils_Array::value('is_deceased', $params, FALSE),
908 'deceased_date' => CRM_Utils_Array::value('deceased_date', $params, NULL),
909 );
910 $updateMembershipMsg = $this->updateMembershipStatus($deceasedParams);
911 }
912
913 // action is taken depending upon the mode
914 if ($this->_action & CRM_Core_Action::UPDATE) {
915 CRM_Utils_Hook::pre('edit', $params['contact_type'], $params['contact_id'], $params);
916 }
917 else {
918 CRM_Utils_Hook::pre('create', $params['contact_type'], NULL, $params);
919 }
920
921 $customFields = CRM_Core_BAO_CustomField::getFields($params['contact_type'], FALSE, TRUE);
922
923 //CRM-5143
924 //if subtype is set, send subtype as extend to validate subtype customfield
925 $customFieldExtends = (CRM_Utils_Array::value('contact_sub_type', $params)) ? $params['contact_sub_type'] : $params['contact_type'];
926
927 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
928 $customFields,
929 $this->_contactId,
930 $customFieldExtends,
931 TRUE
932 );
933 if ($this->_contactId && !empty($this->_oldSubtypes)) {
934 CRM_Contact_BAO_ContactType::deleteCustomSetForSubtypeMigration($this->_contactId,
935 $params['contact_type'],
936 $this->_oldSubtypes,
937 $params['contact_sub_type']
938 );
939 }
940
941 if (array_key_exists('CommunicationPreferences', $this->_editOptions)) {
942 // this is a chekbox, so mark false if we dont get a POST value
943 $params['is_opt_out'] = CRM_Utils_Array::value('is_opt_out', $params, FALSE);
944 }
945
946 // process shared contact address.
947 CRM_Contact_BAO_Contact_Utils::processSharedAddress($params['address']);
948
949 if (!array_key_exists('TagsAndGroups', $this->_editOptions)) {
950 unset($params['group']);
951 }
952
953 if (!empty($params['contact_id']) && ($this->_action & CRM_Core_Action::UPDATE)) {
954 // figure out which all groups are intended to be removed
955 $contactGroupList = CRM_Contact_BAO_GroupContact::getContactGroup($params['contact_id'], 'Added');
956 if (is_array($contactGroupList)) {
957 foreach ($contactGroupList as $key) {
958 if ((!array_key_exists($key['group_id'], $params['group']) || $params['group'][$key['group_id']] != 1) && empty($key['is_hidden'])) {
959 $params['group'][$key['group_id']] = -1;
960 }
961 }
962 }
963 }
964
965 // parse street address, CRM-5450
966 $parseStatusMsg = NULL;
967 if ($this->_parseStreetAddress) {
968 $parseResult = self::parseAddress($params);
969 $parseStatusMsg = self::parseAddressStatusMsg($parseResult);
970 }
971
972 // Allow un-setting of location info, CRM-5969
973 $params['updateBlankLocInfo'] = TRUE;
974
975 $contact = CRM_Contact_BAO_Contact::create($params, TRUE, FALSE, TRUE);
976
977 // status message
978 if ($this->_contactId) {
979 $message = ts('%1 has been updated.', array(1 => $contact->display_name));
980 }
981 else {
982 $message = ts('%1 has been created.', array(1 => $contact->display_name));
983 }
984
985 // set the contact ID
986 $this->_contactId = $contact->id;
987
988 if (array_key_exists('TagsAndGroups', $this->_editOptions)) {
989 //add contact to tags
990 CRM_Core_BAO_EntityTag::create($params['tag'], 'civicrm_contact', $params['contact_id']);
991
992 //save free tags
993 if (isset($params['contact_taglist']) && !empty($params['contact_taglist'])) {
994 CRM_Core_Form_Tag::postProcess($params['contact_taglist'], $params['contact_id'], 'civicrm_contact', $this);
995 }
996 }
997
998 if (!empty($parseStatusMsg)) {
999 $message .= "<br />$parseStatusMsg";
1000 }
1001 if (!empty($updateMembershipMsg)) {
1002 $message .= "<br />$updateMembershipMsg";
1003 }
1004
1005 $session = CRM_Core_Session::singleton();
1006 $session->setStatus($message, ts('Contact Saved'), 'success');
1007
1008 // add the recently viewed contact
1009 $recentOther = array();
1010 if (($session->get('userID') == $contact->id) ||
1011 CRM_Contact_BAO_Contact_Permission::allow($contact->id, CRM_Core_Permission::EDIT)
1012 ) {
1013 $recentOther['editUrl'] = CRM_Utils_System::url('civicrm/contact/add', 'reset=1&action=update&cid=' . $contact->id);
1014 }
1015
1016 if (($session->get('userID') != $this->_contactId) && CRM_Core_Permission::check('delete contacts')) {
1017 $recentOther['deleteUrl'] = CRM_Utils_System::url('civicrm/contact/view/delete', 'reset=1&delete=1&cid=' . $contact->id);
1018 }
1019
1020 CRM_Utils_Recent::add($contact->display_name,
1021 CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $contact->id),
1022 $contact->id,
1023 $this->_contactType,
1024 $contact->id,
1025 $contact->display_name,
1026 $recentOther
1027 );
1028
1029 // here we replace the user context with the url to view this contact
1030 $buttonName = $this->controller->getButtonName();
1031 if ($buttonName == $this->getButtonName('upload', 'new')) {
1032 $resetStr = "reset=1&ct={$contact->contact_type}";
1033 $resetStr .= $this->_contactSubType ? "&cst={$this->_contactSubType}" : '';
1034 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/add', $resetStr));
1035 }
1036 else {
1037 $context = CRM_Utils_Request::retrieve('context', 'String', $this);
1038 $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this);
1039 //validate the qfKey
1040 $urlParams = 'reset=1&cid=' . $contact->id;
1041 if ($context) {
1042 $urlParams .= "&context=$context";
1043 }
1044 if (CRM_Utils_Rule::qfKey($qfKey)) {
1045 $urlParams .= "&key=$qfKey";
1046 }
1047
1048 $session->replaceUserContext(CRM_Utils_System::url('civicrm/contact/view', $urlParams));
1049 }
1050
1051 // now invoke the post hook
1052 if ($this->_action & CRM_Core_Action::UPDATE) {
1053 CRM_Utils_Hook::post('edit', $params['contact_type'], $contact->id, $contact);
1054 }
1055 else {
1056 CRM_Utils_Hook::post('create', $params['contact_type'], $contact->id, $contact);
1057 }
1058 }
1059
1060 /**
1061 * is there any real significant data in the hierarchical location array
1062 *
1063 * @param array $fields the hierarchical value representation of this location
1064 *
1065 * @return boolean true if data exists, false otherwise
1066 * @static
1067 * @access public
1068 */
1069 static function blockDataExists(&$fields) {
1070 if (!is_array($fields)) {
1071 return FALSE;
1072 }
1073
1074 static $skipFields = array('location_type_id', 'is_primary', 'phone_type_id', 'provider_id', 'country_id', 'website_type_id', 'master_id');
1075 foreach ($fields as $name => $value) {
1076 $skipField = FALSE;
1077 foreach ($skipFields as $skip) {
1078 if (strpos("[$skip]", $name) !== FALSE) {
1079 if ($name == 'phone') {
1080 continue;
1081 }
1082 $skipField = TRUE;
1083 break;
1084 }
1085 }
1086 if ($skipField) {
1087 continue;
1088 }
1089 if (is_array($value)) {
1090 if (self::blockDataExists($value)) {
1091 return TRUE;
1092 }
1093 }
1094 else {
1095 if (!empty($value)) {
1096 return TRUE;
1097 }
1098 }
1099 }
1100
1101 return FALSE;
1102 }
1103
1104 /**
1105 * Function to that checks for duplicate contacts
1106 *
1107 * @param array $fields fields array which are submitted
1108 * @param $errors
1109 * @param int $contactID contact id
1110 * @param string $contactType contact type
1111 *
1112 * @internal param array $error error message array
1113 */
1114 static function checkDuplicateContacts(&$fields, &$errors, $contactID, $contactType) {
1115 // if this is a forced save, ignore find duplicate rule
1116 if (empty($fields['_qf_Contact_upload_duplicate'])) {
1117
1118 $dedupeParams = CRM_Dedupe_Finder::formatParams($fields, $contactType);
1119 $ids = CRM_Dedupe_Finder::dupesByParams($dedupeParams, $contactType, 'Supervised', array($contactID));
1120 if ($ids) {
1121
1122 $contactLinks = CRM_Contact_BAO_Contact_Utils::formatContactIDSToLinks($ids, TRUE, TRUE, $contactID);
1123
1124 $duplicateContactsLinks = '<div class="matching-contacts-found">';
1125 $duplicateContactsLinks .= ts('One matching contact was found. ', array('count' => count($contactLinks['rows']), 'plural' => '%count matching contacts were found.<br />'));
1126 if ($contactLinks['msg'] == 'view') {
1127 $duplicateContactsLinks .= ts('You can View the existing contact', array('count' => count($contactLinks['rows']), 'plural' => 'You can View the existing contacts'));
1128 }
1129 else {
1130 $duplicateContactsLinks .= ts('You can View or Edit the existing contact', array('count' => count($contactLinks['rows']), 'plural' => 'You can View or Edit the existing contacts'));
1131 }
1132 if ($contactLinks['msg'] == 'merge') {
1133 // We should also get a merge link if this is for an existing contact
1134 $duplicateContactsLinks .= ts(', or Merge this contact with an existing contact');
1135 }
1136 $duplicateContactsLinks .= '.';
1137 $duplicateContactsLinks .= '</div>';
1138 $duplicateContactsLinks .= '<table class="matching-contacts-actions">';
1139 $row = '';
1140 for ($i = 0; $i < count($contactLinks['rows']); $i++) {
1141 $row .= ' <tr> ';
1142 $row .= ' <td class="matching-contacts-name"> ';
1143 $row .= $contactLinks['rows'][$i]['display_name'];
1144 $row .= ' </td>';
1145 $row .= ' <td class="matching-contacts-email"> ';
1146 $row .= $contactLinks['rows'][$i]['primary_email'];
1147 $row .= ' </td>';
1148 $row .= ' <td class="action-items"> ';
1149 $row .= $contactLinks['rows'][$i]['view'];
1150 $row .= $contactLinks['rows'][$i]['edit'];
1151 $row .= CRM_Utils_Array::value('merge', $contactLinks['rows'][$i]);
1152 $row .= ' </td>';
1153 $row .= ' </tr> ';
1154 }
1155
1156 $duplicateContactsLinks .= $row . '</table>';
1157 $duplicateContactsLinks .= ts("If you're sure this record is not a duplicate, click the 'Save Matching Contact' button below.");
1158
1159 $errors['_qf_default'] = $duplicateContactsLinks;
1160
1161
1162
1163 // let smarty know that there are duplicates
1164 $template = CRM_Core_Smarty::singleton();
1165 $template->assign('isDuplicate', 1);
1166 }
1167 elseif (!empty($fields['_qf_Contact_refresh_dedupe'])) {
1168 // add a session message for no matching contacts
1169 CRM_Core_Session::setStatus(ts('No matching contact found.'), ts('None Found'), 'info');
1170 }
1171 }
1172 }
1173
1174 /**
1175 * Use the form name to create the tpl file name
1176 *
1177 * @return string
1178 * @access public
1179 */
1180 /**
1181 * @return string
1182 */
1183 function getTemplateFileName() {
1184 if ($this->_contactSubType) {
1185 $templateFile = "CRM/Contact/Form/Edit/SubType/{$this->_contactSubType}.tpl";
1186 $template = CRM_Core_Form::getTemplate();
1187 if ($template->template_exists($templateFile)) {
1188 return $templateFile;
1189 }
1190 }
1191 return parent::getTemplateFileName();
1192 }
1193
1194 /**
1195 * Parse all address blocks present in given params
1196 * and return parse result for all address blocks,
1197 * This function either parse street address in to child
1198 * elements or build street address from child elements.
1199 *
1200 * @params $params an array of key value consist of address blocks.
1201 *
1202 * @param $params
1203 *
1204 * @return array $parseSuccess as array of sucess/fails for each address block@static
1205 */
1206 function parseAddress(&$params) {
1207 $parseSuccess = $parsedFields = array();
1208 if (!is_array($params['address']) ||
1209 CRM_Utils_System::isNull($params['address'])
1210 ) {
1211 return $parseSuccess;
1212 }
1213
1214 foreach ($params['address'] as $instance => & $address) {
1215 $buildStreetAddress = FALSE;
1216 $parseFieldName = 'street_address';
1217 foreach (array(
1218 'street_number', 'street_name', 'street_unit') as $fld) {
1219 if (!empty($address[$fld])) {
1220 $parseFieldName = 'street_number';
1221 $buildStreetAddress = TRUE;
1222 break;
1223 }
1224 }
1225
1226 // main parse string.
1227 $parseString = CRM_Utils_Array::value($parseFieldName, $address);
1228
1229 // parse address field.
1230 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($parseString);
1231
1232 if ($buildStreetAddress) {
1233 //hack to ignore spaces between number and suffix.
1234 //here user gives input as street_number so it has to
1235 //be street_number and street_number_suffix, but
1236 //due to spaces though preg detect string as street_name
1237 //consider it as 'street_number_suffix'.
1238 $suffix = $parsedFields['street_number_suffix'];
1239 if (!$suffix) {
1240 $suffix = $parsedFields['street_name'];
1241 }
1242 $address['street_number_suffix'] = $suffix;
1243 $address['street_number'] = $parsedFields['street_number'];
1244
1245 $streetAddress = NULL;
1246 foreach (array(
1247 'street_number', 'street_number_suffix', 'street_name', 'street_unit') as $fld) {
1248 if (in_array($fld, array(
1249 'street_name', 'street_unit'))) {
1250 $streetAddress .= ' ';
1251 }
1252 $streetAddress .= CRM_Utils_Array::value($fld, $address);
1253 }
1254 $address['street_address'] = trim($streetAddress);
1255 $parseSuccess[$instance] = TRUE;
1256 }
1257 else {
1258 $success = TRUE;
1259 // consider address is automatically parseable,
1260 // when we should found street_number and street_name
1261 if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) {
1262 $success = FALSE;
1263 }
1264
1265 // check for original street address string.
1266 if (empty($parseString)) {
1267 $success = TRUE;
1268 }
1269
1270 $parseSuccess[$instance] = $success;
1271
1272 // we do not reset element values, but keep what we've parsed
1273 // in case of partial matches: CRM-8378
1274
1275 // merge parse address in to main address block.
1276 $address = array_merge($address, $parsedFields);
1277 }
1278 }
1279
1280 return $parseSuccess;
1281 }
1282
1283 /**
1284 * check parse result and if some address block fails then this
1285 * function return the status message for all address blocks.
1286 *
1287 * @param $parseResult an array of address blk instance and its status.
1288 *
1289 * @return null|string $statusMsg string status message for all address blocks.@static
1290 */
1291 static function parseAddressStatusMsg($parseResult) {
1292 $statusMsg = NULL;
1293 if (!is_array($parseResult) || empty($parseResult)) {
1294 return $statusMsg;
1295 }
1296
1297 $parseFails = array();
1298 foreach ($parseResult as $instance => $success) {
1299 if (!$success) {
1300 $parseFails[] = self::ordinalNumber($instance);
1301 }
1302 }
1303
1304 if (!empty($parseFails)) {
1305 $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.",
1306 array(1 => implode(', ', $parseFails))
1307 );
1308 }
1309
1310 return $statusMsg;
1311 }
1312
1313 /**
1314 * Convert normal number to ordinal number format.
1315 * like 1 => 1st, 2 => 2nd and so on...
1316 *
1317 * @param $number int number to convert in to ordinal number.
1318 *
1319 * @return ordinal number for given number.
1320 * @static
1321 */
1322 static function ordinalNumber($number) {
1323 if (empty($number)) {
1324 return NULL;
1325 }
1326
1327 $str = 'th';
1328 switch (floor($number / 10) % 10) {
1329 case 1:
1330 default:
1331 switch ($number % 10) {
1332 case 1:
1333 $str = 'st';
1334 break;
1335
1336 case 2:
1337 $str = 'nd';
1338 break;
1339
1340 case 3:
1341 $str = 'rd';
1342 break;
1343 }
1344 }
1345
1346 return "$number$str";
1347 }
1348
1349 /**
1350 * Update membership status to deceased
1351 * function return the status message for updated membership.
1352 *
1353 * @param $deceasedParams array having contact id and deceased value.
1354 *
1355 * @return null|string $updateMembershipMsg string status message for updated membership.
1356 */
1357 function updateMembershipStatus($deceasedParams) {
1358 $updateMembershipMsg = NULL;
1359 $contactId = CRM_Utils_Array::value('contact_id', $deceasedParams);
1360 $deceasedDate = CRM_Utils_Array::value('deceased_date', $deceasedParams);
1361
1362 // process to set membership status to deceased for both active/inactive membership
1363 if ($contactId &&
1364 $this->_contactType == 'Individual' && !empty($deceasedParams['is_deceased'])) {
1365
1366 $session = CRM_Core_Session::singleton();
1367 $userId = $session->get('userID');
1368 if (!$userId) {
1369 $userId = $contactId;
1370 }
1371
1372
1373 // get deceased status id
1374 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1375 $deceasedStatusId = array_search('Deceased', $allStatus);
1376 if (!$deceasedStatusId) {
1377 return $updateMembershipMsg;
1378 }
1379
1380 $today = time();
1381 if ($deceasedDate && strtotime($deceasedDate) > $today) {
1382 return $updateMembershipMsg;
1383 }
1384
1385 // get non deceased membership
1386 $dao = new CRM_Member_DAO_Membership();
1387 $dao->contact_id = $contactId;
1388 $dao->whereAdd("status_id != $deceasedStatusId");
1389 $dao->find();
1390 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name');
1391 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1392 $memCount = 0;
1393 while ($dao->fetch()) {
1394 // update status to deceased (for both active/inactive membership )
1395 CRM_Core_DAO::setFieldValue('CRM_Member_DAO_Membership', $dao->id,
1396 'status_id', $deceasedStatusId
1397 );
1398
1399 // add membership log
1400 $membershipLog = array(
1401 'membership_id' => $dao->id,
1402 'status_id' => $deceasedStatusId,
1403 'start_date' => CRM_Utils_Date::isoToMysql($dao->start_date),
1404 'end_date' => CRM_Utils_Date::isoToMysql($dao->end_date),
1405 'modified_id' => $userId,
1406 'modified_date' => date('Ymd'),
1407 'membership_type_id' => $dao->membership_type_id,
1408 'max_related' => $dao->max_related,
1409 );
1410
1411
1412 CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
1413
1414 //create activity when membership status is changed
1415 $activityParam = array(
1416 'subject' => "Status changed from {$allStatus[$dao->status_id]} to {$allStatus[$deceasedStatusId]}",
1417 'source_contact_id' => $userId,
1418 'target_contact_id' => $dao->contact_id,
1419 'source_record_id' => $dao->id,
1420 'activity_type_id' => array_search('Change Membership Status', $activityTypes),
1421 'status_id' => 2,
1422 'version' => 3,
1423 'priority_id' => 2,
1424 'activity_date_time' => date('Y-m-d H:i:s'),
1425 'is_auto' => 0,
1426 'is_current_revision' => 1,
1427 'is_deleted' => 0,
1428 );
1429 $activityResult = civicrm_api('activity', 'create', $activityParam);
1430
1431 $memCount++;
1432 }
1433
1434 // set status msg
1435 if ($memCount) {
1436 $updateMembershipMsg = ts("%1 Current membership(s) for this contact have been set to 'Deceased' status.",
1437 array(1 => $memCount)
1438 );
1439 }
1440 }
1441
1442 return $updateMembershipMsg;
1443 }
1444 }
1445