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