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