Move contact-summary css into its own file
[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 CRM_Core_Resources::singleton()
142 ->addStyleFile('civicrm', 'css/contactSummary.css', 2, 'html-header');
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 ) {
158 CRM_Core_Error::statusBounce(ts('Could not get a contact id and/or contact type'));
159 }
160
161 $this->_isContactSubType = FALSE;
162 if ($this->_contactSubType = CRM_Utils_Request::retrieve('cst', 'String', $this)) {
163 $this->_isContactSubType = TRUE;
164 }
165
166 if (
167 $this->_contactSubType &&
168 !(CRM_Contact_BAO_ContactType::isExtendsContactType($this->_contactSubType, $this->_contactType, TRUE))
169 ) {
170 CRM_Core_Error::statusBounce(ts("Could not get a valid contact subtype for contact type '%1'", array(1 => $this->_contactType)));
171 }
172
173 $this->_gid = CRM_Utils_Request::retrieve('gid', 'Integer',
174 CRM_Core_DAO::$_nullObject,
175 FALSE, NULL, 'GET'
176 );
177 $this->_tid = CRM_Utils_Request::retrieve('tid', 'Integer',
178 CRM_Core_DAO::$_nullObject,
179 FALSE, NULL, 'GET'
180 );
181 $typeLabel = CRM_Contact_BAO_ContactType::contactTypePairs(TRUE, $this->_contactSubType ?
182 $this->_contactSubType : $this->_contactType
183 );
184 $typeLabel = implode(' / ', $typeLabel);
185
186 CRM_Utils_System::setTitle(ts('New %1', array(1 => $typeLabel)));
187 $session->pushUserContext(CRM_Utils_System::url('civicrm/dashboard', 'reset=1'));
188 $this->_contactId = NULL;
189 }
190 else {
191 //update mode
192 if (!$this->_contactId) {
193 $this->_contactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this, TRUE);
194 }
195
196 if ($this->_contactId) {
197 $defaults = array();
198 $params = array('id' => $this->_contactId);
199 $returnProperities = array('id', 'contact_type', 'contact_sub_type', 'modified_date');
200 CRM_Core_DAO::commonRetrieve('CRM_Contact_DAO_Contact', $params, $defaults, $returnProperities);
201
202 if (empty($defaults['id'])) {
203 CRM_Core_Error::statusBounce(ts('A Contact with that ID does not exist: %1', array(1 => $this->_contactId)));
204 }
205
206 $this->_contactType = CRM_Utils_Array::value('contact_type', $defaults);
207 $this->_contactSubType = CRM_Utils_Array::value('contact_sub_type', $defaults);
208
209 // check for permissions
210 $session = CRM_Core_Session::singleton();
211 if (!CRM_Contact_BAO_Contact_Permission::allow($this->_contactId, CRM_Core_Permission::EDIT)) {
212 CRM_Core_Error::statusBounce(ts('You do not have the necessary permission to edit this contact.'));
213 }
214
215 $displayName = CRM_Contact_BAO_Contact::displayName($this->_contactId);
216 $displayName = ts('Edit %1', array(1 => $displayName));
217
218 // Check if this is default domain contact CRM-10482
219 if (CRM_Contact_BAO_Contact::checkDomainContact($this->_contactId)) {
220 $displayName .= ' (' . ts('default organization') . ')';
221 }
222
223 // omitting contactImage from title for now since the summary overlay css doesn't work outside of our crm-container
224 CRM_Utils_System::setTitle($displayName);
225 $context = CRM_Utils_Request::retrieve('context', 'String', $this);
226 $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this);
227
228 $urlParams = 'reset=1&cid=' . $this->_contactId;
229 if ($context) {
230 $urlParams .= "&context=$context";
231 }
232
233 if (CRM_Utils_Rule::qfKey($qfKey)) {
234
235 $urlParams .= "&key=$qfKey";
236
237 }
238 $session->pushUserContext(CRM_Utils_System::url('civicrm/contact/view', $urlParams));
239
240 $values = $this->get('values');
241 // get contact values.
242 if (!empty($values)) {
243 $this->_values = $values;
244 }
245 else {
246 $params = array(
247 'id' => $this->_contactId,
248 'contact_id' => $this->_contactId,
249 'noRelationships' => TRUE,
250 'noNotes' => TRUE,
251 'noGroups' => TRUE,
252 );
253
254 $contact = CRM_Contact_BAO_Contact::retrieve($params, $this->_values, TRUE);
255 $this->set('values', $this->_values);
256 }
257 }
258 else {
259 CRM_Core_Error::statusBounce(ts('Could not get a contact_id and/or contact_type'));
260 }
261 }
262
263 // parse street address, CRM-5450
264 $this->_parseStreetAddress = $this->get('parseStreetAddress');
265 if (!isset($this->_parseStreetAddress)) {
266 $addressOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
267 'address_options'
268 );
269 $this->_parseStreetAddress = FALSE;
270 if (!empty($addressOptions['street_address']) && !empty($addressOptions['street_address_parsing'])) {
271 $this->_parseStreetAddress = TRUE;
272 }
273 $this->set('parseStreetAddress', $this->_parseStreetAddress);
274 }
275 $this->assign('parseStreetAddress', $this->_parseStreetAddress);
276
277 $this->_editOptions = $this->get('contactEditOptions');
278 if (CRM_Utils_System::isNull($this->_editOptions)) {
279 $this->_editOptions = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
280 'contact_edit_options', TRUE, NULL,
281 FALSE, 'name', TRUE, 'AND v.filter = 0'
282 );
283 $this->set('contactEditOptions', $this->_editOptions);
284 }
285
286 // build demographics only for Individual contact type
287 if ($this->_contactType != 'Individual' &&
288 array_key_exists('Demographics', $this->_editOptions)
289 ) {
290 unset($this->_editOptions['Demographics']);
291 }
292
293 // in update mode don't show notes
294 if ($this->_contactId && array_key_exists('Notes', $this->_editOptions)) {
295 unset($this->_editOptions['Notes']);
296 }
297
298 $this->assign('editOptions', $this->_editOptions);
299 $this->assign('contactType', $this->_contactType);
300 $this->assign('contactSubType', $this->_contactSubType);
301
302 //build contact subtype form element, CRM-6864
303 $buildContactSubType = TRUE;
304 if ($this->_contactSubType && ($this->_action & CRM_Core_Action::ADD)) {
305 $buildContactSubType = FALSE;
306 }
307 $this->assign('buildContactSubType', $buildContactSubType);
308
309 // get the location blocks.
310 $this->_blocks = $this->get('blocks');
311 if (CRM_Utils_System::isNull($this->_blocks)) {
312 $this->_blocks = CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
313 'contact_edit_options', TRUE, NULL,
314 FALSE, 'name', TRUE, 'AND v.filter = 1'
315 );
316 $this->set('blocks', $this->_blocks);
317 }
318 $this->assign('blocks', $this->_blocks);
319
320 // this is needed for custom data.
321 $this->assign('entityID', $this->_contactId);
322
323 // also keep the convention.
324 $this->assign('contactId', $this->_contactId);
325
326 // location blocks.
327 CRM_Contact_Form_Location::preProcess($this);
328
329 // retain the multiple count custom fields value
330 if (!empty($_POST['hidden_custom'])) {
331 $customGroupCount = CRM_Utils_Array::value('hidden_custom_group_count', $_POST);
332
333 if ($contactSubType = CRM_Utils_Array::value('contact_sub_type', $_POST)) {
334 $paramSubType = implode(',', $contactSubType);
335 }
336
337 $this->_getCachedTree = FALSE;
338 unset($customGroupCount[0]);
339 foreach ($customGroupCount as $groupID => $groupCount) {
340 if ($groupCount > 1) {
341 $this->set('groupID', $groupID);
342 //loop the group
343 for ($i = 0; $i <= $groupCount; $i++) {
344 CRM_Custom_Form_CustomData::preProcess($this, NULL, $contactSubType,
345 $i, $this->_contactType
346 );
347 CRM_Contact_Form_Edit_CustomData::buildQuickForm($this);
348 }
349 }
350 }
351
352 //reset all the ajax stuff, for normal processing
353 if (isset($this->_groupTree)) {
354 $this->_groupTree = NULL;
355 }
356 $this->set('groupID', NULL);
357 $this->_getCachedTree = TRUE;
358 }
359
360 // execute preProcess dynamically by js else execute normal preProcess
361 if (array_key_exists('CustomData', $this->_editOptions)) {
362 //assign a parameter to pass for sub type multivalue
363 //custom field to load
364 if ($this->_contactSubType || isset($paramSubType)) {
365 $paramSubType = (isset($paramSubType)) ? $paramSubType :
366 str_replace(CRM_Core_DAO::VALUE_SEPARATOR, ',', trim($this->_contactSubType, CRM_Core_DAO::VALUE_SEPARATOR));
367
368 $this->assign('paramSubType', $paramSubType);
369 }
370
371 if (CRM_Utils_Request::retrieve('type', 'String', CRM_Core_DAO::$_nullObject)) {
372 CRM_Contact_Form_Edit_CustomData::preProcess($this);
373 }
374 else {
375 $contactSubType = $this->_contactSubType;
376 // need contact sub type to build related grouptree array during post process
377 if (!empty($_POST['contact_sub_type'])) {
378 $contactSubType = $_POST['contact_sub_type'];
379 }
380 //only custom data has preprocess hence directly call it
381 CRM_Custom_Form_CustomData::preProcess($this, NULL, $contactSubType,
382 1, $this->_contactType, $this->_contactId
383 );
384 $this->assign('customValueCount', $this->_customValueCount);
385 }
386 }
387 }
388
389 /**
390 * Set default values for the form. Note that in edit/view mode
391 * the default values are retrieved from the database
392 *
393 *
394 * @return void
395 */
396 public function setDefaultValues() {
397 $defaults = $this->_values;
398 $params = array();
399
400 if ($this->_action & CRM_Core_Action::ADD) {
401 if (array_key_exists('TagsAndGroups', $this->_editOptions)) {
402 // set group and tag defaults if any
403 if ($this->_gid) {
404 $defaults['group'][] = $this->_gid;
405 }
406 if ($this->_tid) {
407 $defaults['tag'][$this->_tid] = 1;
408 }
409 }
410 if ($this->_contactSubType) {
411 $defaults['contact_sub_type'] = $this->_contactSubType;
412 }
413 }
414 else {
415 foreach ($defaults['email'] as $dontCare => & $val) {
416 if (isset($val['signature_text'])) {
417 $val['signature_text_hidden'] = $val['signature_text'];
418 }
419 if (isset($val['signature_html'])) {
420 $val['signature_html_hidden'] = $val['signature_html'];
421 }
422 }
423
424 if (!empty($defaults['contact_sub_type'])) {
425 $defaults['contact_sub_type'] = $this->_oldSubtypes;
426 }
427 }
428 // set defaults for blocks ( custom data, address, communication preference, notes, tags and groups )
429 foreach ($this->_editOptions as $name => $label) {
430 if (!in_array($name, array('Address', 'Notes'))) {
431 $className = 'CRM_Contact_Form_Edit_' . $name;
432 $className::setDefaultValues($this, $defaults);
433 }
434 }
435
436 //set address block defaults
437 CRM_Contact_Form_Edit_Address::setDefaultValues($defaults, $this);
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 public function blockSetDefaults(&$defaults) {
461 $locationTypeKeys = array_filter(array_keys(CRM_Core_PseudoConstant::get('CRM_Core_DAO_Address', 'location_type_id')), 'is_int');
462 sort($locationTypeKeys);
463
464 // get the default location type
465 $locationType = CRM_Core_BAO_LocationType::getDefault();
466
467 // unset primary location type
468 $primaryLocationTypeIdKey = CRM_Utils_Array::key($locationType->id, $locationTypeKeys);
469 unset($locationTypeKeys[$primaryLocationTypeIdKey]);
470
471 // reset the array sequence
472 $locationTypeKeys = array_values($locationTypeKeys);
473
474 // get default phone and im provider id.
475 $defPhoneTypeId = key(CRM_Core_OptionGroup::values('phone_type', FALSE, FALSE, FALSE, ' AND is_default = 1'));
476 $defIMProviderId = key(CRM_Core_OptionGroup::values('instant_messenger_service',
477 FALSE, FALSE, FALSE, ' AND is_default = 1'
478 ));
479 $defWebsiteTypeId = key(CRM_Core_OptionGroup::values('website_type',
480 FALSE, FALSE, FALSE, ' AND is_default = 1'
481 ));
482
483 $allBlocks = $this->_blocks;
484 if (array_key_exists('Address', $this->_editOptions)) {
485 $allBlocks['Address'] = $this->_editOptions['Address'];
486 }
487
488 $config = CRM_Core_Config::singleton();
489 foreach ($allBlocks as $blockName => $label) {
490 $name = strtolower($blockName);
491 $hasPrimary = $updateMode = FALSE;
492
493 // user is in update mode.
494 if (array_key_exists($name, $defaults) &&
495 !CRM_Utils_System::isNull($defaults[$name])
496 ) {
497 $updateMode = TRUE;
498 }
499
500 for ($instance = 1; $instance <= $this->get($blockName . '_Block_Count'); $instance++) {
501 // make we require one primary block, CRM-5505
502 if ($updateMode) {
503 if (!$hasPrimary) {
504 $hasPrimary = CRM_Utils_Array::value(
505 'is_primary',
506 CRM_Utils_Array::value($instance, $defaults[$name])
507 );
508 }
509 continue;
510 }
511
512 //set location to primary for first one.
513 if ($instance == 1) {
514 $hasPrimary = TRUE;
515 $defaults[$name][$instance]['is_primary'] = TRUE;
516 $defaults[$name][$instance]['location_type_id'] = $locationType->id;
517 }
518 else {
519 $locTypeId = isset($locationTypeKeys[$instance - 1]) ? $locationTypeKeys[$instance - 1] : $locationType->id;
520 $defaults[$name][$instance]['location_type_id'] = $locTypeId;
521 }
522
523 //set default country
524 if ($name == 'address' && $config->defaultContactCountry) {
525 $defaults[$name][$instance]['country_id'] = $config->defaultContactCountry;
526 }
527
528 //set default state/province
529 if ($name == 'address' && $config->defaultContactStateProvince) {
530 $defaults[$name][$instance]['state_province_id'] = $config->defaultContactStateProvince;
531 }
532
533 //set default phone type.
534 if ($name == 'phone' && $defPhoneTypeId) {
535 $defaults[$name][$instance]['phone_type_id'] = $defPhoneTypeId;
536 }
537 //set default website type.
538 if ($name == 'website' && $defWebsiteTypeId) {
539 $defaults[$name][$instance]['website_type_id'] = $defWebsiteTypeId;
540 }
541
542 //set default im provider.
543 if ($name == 'im' && $defIMProviderId) {
544 $defaults[$name][$instance]['provider_id'] = $defIMProviderId;
545 }
546 }
547
548 if (!$hasPrimary) {
549 $defaults[$name][1]['is_primary'] = TRUE;
550 }
551 }
552 }
553
554 /**
555 * add the rules (mainly global rules) for form.
556 * All local rules are added near the element
557 *
558 * @return void
559 * @see valid_date
560 */
561 public function addRules() {
562 // skip adding formRules when custom data is build
563 if ($this->_addBlockName || ($this->_action & CRM_Core_Action::DELETE)) {
564 return;
565 }
566
567 $this->addFormRule(array('CRM_Contact_Form_Edit_' . $this->_contactType, 'formRule'), $this->_contactId);
568
569 // Call Locking check if editing existing contact
570 if ($this->_contactId) {
571 $this->addFormRule(array('CRM_Contact_Form_Edit_Lock', 'formRule'), $this->_contactId);
572 }
573
574 if (array_key_exists('Address', $this->_editOptions)) {
575 $this->addFormRule(array('CRM_Contact_Form_Edit_Address', 'formRule'), $this);
576 }
577
578 if (array_key_exists('CommunicationPreferences', $this->_editOptions)) {
579 $this->addFormRule(array('CRM_Contact_Form_Edit_CommunicationPreferences', 'formRule'), $this);
580 }
581 }
582
583 /**
584 * Global validation rules for the form.
585 *
586 * @param array $fields
587 * Posted values of the form.
588 * @param array $errors
589 * List of errors to be posted back to the form.
590 * @param int $contactId
591 * Contact id if doing update.
592 *
593 * @return bool
594 * email/openId
595 */
596 public 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',
646 'openid',
647 )) && !empty($blockValues[$name])
648 ) {
649 $primaryID = $blockValues[$name];
650 }
651 }
652
653 if (empty($blockValues['location_type_id'])) {
654 $errors["{$name}[$instance][location_type_id]"] = ts('The Location Type should be set if there is %1 information.', array(1 => $label));
655 }
656 }
657
658 if ($name == 'openid' && !empty($blockValues[$name])) {
659 $oid = new CRM_Core_DAO_OpenID();
660 $oid->openid = $openIds[$instance] = CRM_Utils_Array::value($name, $blockValues);
661 $cid = isset($contactId) ? $contactId : 0;
662 if ($oid->find(TRUE) && ($oid->contact_id != $cid)) {
663 $errors["{$name}[$instance][openid]"] = ts('%1 already exist.', array(1 => $blocks['OpenID']));
664 }
665 }
666 }
667
668 if (empty($hasPrimary) && !empty($hasData)) {
669 $errors["{$name}[1][is_primary]"] = ts('One %1 should be marked as primary.', array(1 => $label));
670 }
671
672 if (count($hasPrimary) > 1) {
673 $errors["{$name}[" . array_pop($hasPrimary) . "][is_primary]"] = ts('Only one %1 can be marked as primary.',
674 array(1 => $label)
675 );
676 }
677 }
678 }
679
680 //do validations for all opend ids they should be distinct.
681 if (!empty($openIds) && (count(array_unique($openIds)) != count($openIds))) {
682 foreach ($openIds as $instance => $value) {
683 if (!array_key_exists($instance, array_unique($openIds))) {
684 $errors["openid[$instance][openid]"] = ts('%1 already used.', array(1 => $blocks['OpenID']));
685 }
686 }
687 }
688
689 // street number should be digit + suffix, CRM-5450
690 $parseStreetAddress = CRM_Utils_Array::value('street_address_parsing',
691 CRM_Core_BAO_Setting::valueOptions(CRM_Core_BAO_Setting::SYSTEM_PREFERENCES_NAME,
692 'address_options'
693 )
694 );
695 if ($parseStreetAddress) {
696 if (isset($fields['address']) &&
697 is_array($fields['address'])
698 ) {
699 $invalidStreetNumbers = array();
700 foreach ($fields['address'] as $cnt => $address) {
701 if ($streetNumber = CRM_Utils_Array::value('street_number', $address)) {
702 $parsedAddress = CRM_Core_BAO_Address::parseStreetAddress($address['street_number']);
703 if (empty($parsedAddress['street_number'])) {
704 $invalidStreetNumbers[] = $cnt;
705 }
706 }
707 }
708
709 if (!empty($invalidStreetNumbers)) {
710 $first = $invalidStreetNumbers[0];
711 foreach ($invalidStreetNumbers as & $num) {
712 $num = CRM_Contact_Form_Contact::ordinalNumber($num);
713 }
714 $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)));
715 }
716 }
717 }
718
719 return $primaryID;
720 }
721
722 /**
723 * Build the form object.
724 *
725 * @return void
726 */
727 public function buildQuickForm() {
728 //load form for child blocks
729 if ($this->_addBlockName) {
730 $className = 'CRM_Contact_Form_Edit_' . $this->_addBlockName;
731 return $className::buildQuickForm($this);
732 }
733
734 if ($this->_action == CRM_Core_Action::UPDATE) {
735 $deleteExtra = ts('Are you sure you want to delete contact image.');
736 $deleteURL = array(
737 CRM_Core_Action::DELETE => 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 bool
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 // let smarty know that there are duplicates
1185 $template = CRM_Core_Smarty::singleton();
1186 $template->assign('isDuplicate', 1);
1187 }
1188 elseif (!empty($fields['_qf_Contact_refresh_dedupe'])) {
1189 // add a session message for no matching contacts
1190 CRM_Core_Session::setStatus(ts('No matching contact found.'), ts('None Found'), 'info');
1191 }
1192 }
1193 }
1194
1195 /**
1196 * Use the form name to create the tpl file name.
1197 *
1198 * @return string
1199 */
1200 public function getTemplateFileName() {
1201 if ($this->_contactSubType) {
1202 $templateFile = "CRM/Contact/Form/Edit/SubType/{$this->_contactSubType}.tpl";
1203 $template = CRM_Core_Form::getTemplate();
1204 if ($template->template_exists($templateFile)) {
1205 return $templateFile;
1206 }
1207 }
1208 return parent::getTemplateFileName();
1209 }
1210
1211 /**
1212 * Parse all address blocks present in given params
1213 * and return parse result for all address blocks,
1214 * This function either parse street address in to child
1215 * elements or build street address from child elements.
1216 *
1217 * @param array $params
1218 * of key value consist of address blocks.
1219 *
1220 * @return array
1221 * as array of sucess/fails for each address block
1222 */
1223 public function parseAddress(&$params) {
1224 $parseSuccess = $parsedFields = array();
1225 if (!is_array($params['address']) ||
1226 CRM_Utils_System::isNull($params['address'])
1227 ) {
1228 return $parseSuccess;
1229 }
1230
1231 foreach ($params['address'] as $instance => & $address) {
1232 $buildStreetAddress = FALSE;
1233 $parseFieldName = 'street_address';
1234 foreach (array(
1235 'street_number',
1236 'street_name',
1237 'street_unit',
1238 ) as $fld) {
1239 if (!empty($address[$fld])) {
1240 $parseFieldName = 'street_number';
1241 $buildStreetAddress = TRUE;
1242 break;
1243 }
1244 }
1245
1246 // main parse string.
1247 $parseString = CRM_Utils_Array::value($parseFieldName, $address);
1248
1249 // parse address field.
1250 $parsedFields = CRM_Core_BAO_Address::parseStreetAddress($parseString);
1251
1252 if ($buildStreetAddress) {
1253 //hack to ignore spaces between number and suffix.
1254 //here user gives input as street_number so it has to
1255 //be street_number and street_number_suffix, but
1256 //due to spaces though preg detect string as street_name
1257 //consider it as 'street_number_suffix'.
1258 $suffix = $parsedFields['street_number_suffix'];
1259 if (!$suffix) {
1260 $suffix = $parsedFields['street_name'];
1261 }
1262 $address['street_number_suffix'] = $suffix;
1263 $address['street_number'] = $parsedFields['street_number'];
1264
1265 $streetAddress = NULL;
1266 foreach (array(
1267 'street_number',
1268 'street_number_suffix',
1269 'street_name',
1270 'street_unit',
1271 ) as $fld) {
1272 if (in_array($fld, array(
1273 'street_name',
1274 'street_unit',
1275 ))) {
1276 $streetAddress .= ' ';
1277 }
1278 $streetAddress .= CRM_Utils_Array::value($fld, $address);
1279 }
1280 $address['street_address'] = trim($streetAddress);
1281 $parseSuccess[$instance] = TRUE;
1282 }
1283 else {
1284 $success = TRUE;
1285 // consider address is automatically parseable,
1286 // when we should found street_number and street_name
1287 if (empty($parsedFields['street_name']) || empty($parsedFields['street_number'])) {
1288 $success = FALSE;
1289 }
1290
1291 // check for original street address string.
1292 if (empty($parseString)) {
1293 $success = TRUE;
1294 }
1295
1296 $parseSuccess[$instance] = $success;
1297
1298 // we do not reset element values, but keep what we've parsed
1299 // in case of partial matches: CRM-8378
1300
1301 // merge parse address in to main address block.
1302 $address = array_merge($address, $parsedFields);
1303 }
1304 }
1305
1306 return $parseSuccess;
1307 }
1308
1309 /**
1310 * Check parse result and if some address block fails then this
1311 * function return the status message for all address blocks.
1312 *
1313 * @param array $parseResult
1314 * An array of address blk instance and its status.
1315 *
1316 * @return null|string
1317 * $statusMsg string status message for all address blocks.
1318 */
1319 public static function parseAddressStatusMsg($parseResult) {
1320 $statusMsg = NULL;
1321 if (!is_array($parseResult) || empty($parseResult)) {
1322 return $statusMsg;
1323 }
1324
1325 $parseFails = array();
1326 foreach ($parseResult as $instance => $success) {
1327 if (!$success) {
1328 $parseFails[] = self::ordinalNumber($instance);
1329 }
1330 }
1331
1332 if (!empty($parseFails)) {
1333 $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.",
1334 array(1 => implode(', ', $parseFails))
1335 );
1336 }
1337
1338 return $statusMsg;
1339 }
1340
1341 /**
1342 * Convert normal number to ordinal number format.
1343 * like 1 => 1st, 2 => 2nd and so on...
1344 *
1345 * @param int $number
1346 * number to convert in to ordinal number.
1347 *
1348 * @return string
1349 * ordinal number for given number.
1350 */
1351 public static function ordinalNumber($number) {
1352 if (empty($number)) {
1353 return NULL;
1354 }
1355
1356 $str = 'th';
1357 switch (floor($number / 10) % 10) {
1358 case 1:
1359 default:
1360 switch ($number % 10) {
1361 case 1:
1362 $str = 'st';
1363 break;
1364
1365 case 2:
1366 $str = 'nd';
1367 break;
1368
1369 case 3:
1370 $str = 'rd';
1371 break;
1372 }
1373 }
1374
1375 return "$number$str";
1376 }
1377
1378 /**
1379 * Update membership status to deceased.
1380 * function return the status message for updated membership.
1381 *
1382 * @param array $deceasedParams
1383 * having contact id and deceased value.
1384 *
1385 * @return null|string
1386 * $updateMembershipMsg string status message for updated membership.
1387 */
1388 public function updateMembershipStatus($deceasedParams) {
1389 $updateMembershipMsg = NULL;
1390 $contactId = CRM_Utils_Array::value('contact_id', $deceasedParams);
1391 $deceasedDate = CRM_Utils_Array::value('deceased_date', $deceasedParams);
1392
1393 // process to set membership status to deceased for both active/inactive membership
1394 if ($contactId &&
1395 $this->_contactType == 'Individual' && !empty($deceasedParams['is_deceased'])
1396 ) {
1397
1398 $session = CRM_Core_Session::singleton();
1399 $userId = $session->get('userID');
1400 if (!$userId) {
1401 $userId = $contactId;
1402 }
1403
1404 // get deceased status id
1405 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1406 $deceasedStatusId = array_search('Deceased', $allStatus);
1407 if (!$deceasedStatusId) {
1408 return $updateMembershipMsg;
1409 }
1410
1411 $today = time();
1412 if ($deceasedDate && strtotime($deceasedDate) > $today) {
1413 return $updateMembershipMsg;
1414 }
1415
1416 // get non deceased membership
1417 $dao = new CRM_Member_DAO_Membership();
1418 $dao->contact_id = $contactId;
1419 $dao->whereAdd("status_id != $deceasedStatusId");
1420 $dao->find();
1421 $activityTypes = CRM_Core_PseudoConstant::activityType(TRUE, FALSE, FALSE, 'name');
1422 $allStatus = CRM_Member_PseudoConstant::membershipStatus();
1423 $memCount = 0;
1424 while ($dao->fetch()) {
1425 // update status to deceased (for both active/inactive membership )
1426 CRM_Core_DAO::setFieldValue('CRM_Member_DAO_Membership', $dao->id,
1427 'status_id', $deceasedStatusId
1428 );
1429
1430 // add membership log
1431 $membershipLog = array(
1432 'membership_id' => $dao->id,
1433 'status_id' => $deceasedStatusId,
1434 'start_date' => CRM_Utils_Date::isoToMysql($dao->start_date),
1435 'end_date' => CRM_Utils_Date::isoToMysql($dao->end_date),
1436 'modified_id' => $userId,
1437 'modified_date' => date('Ymd'),
1438 'membership_type_id' => $dao->membership_type_id,
1439 'max_related' => $dao->max_related,
1440 );
1441
1442 CRM_Member_BAO_MembershipLog::add($membershipLog, CRM_Core_DAO::$_nullArray);
1443
1444 //create activity when membership status is changed
1445 $activityParam = array(
1446 'subject' => "Status changed from {$allStatus[$dao->status_id]} to {$allStatus[$deceasedStatusId]}",
1447 'source_contact_id' => $userId,
1448 'target_contact_id' => $dao->contact_id,
1449 'source_record_id' => $dao->id,
1450 'activity_type_id' => array_search('Change Membership Status', $activityTypes),
1451 'status_id' => 2,
1452 'version' => 3,
1453 'priority_id' => 2,
1454 'activity_date_time' => date('Y-m-d H:i:s'),
1455 'is_auto' => 0,
1456 'is_current_revision' => 1,
1457 'is_deleted' => 0,
1458 );
1459 $activityResult = civicrm_api('activity', 'create', $activityParam);
1460
1461 $memCount++;
1462 }
1463
1464 // set status msg
1465 if ($memCount) {
1466 $updateMembershipMsg = ts("%1 Current membership(s) for this contact have been set to 'Deceased' status.",
1467 array(1 => $memCount)
1468 );
1469 }
1470 }
1471
1472 return $updateMembershipMsg;
1473 }
1474
1475 }