3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
6 | This work is published under the GNU AGPLv3 license with some |
7 | permitted exceptions and without any warranty. For full license |
8 | and copyright information, see https://civicrm.org/licensing |
9 +--------------------------------------------------------------------+
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
19 * This class generates form components for Activity.
21 class CRM_Activity_Form_Activity
extends CRM_Contact_Form_Task
{
24 * The id of the object being edited / created
31 * Store activity ids when multiple activities are created.
35 public $_activityIds = [];
38 * The id of activity type.
42 public $_activityTypeId;
45 * The label of the activity type.
46 * Unfortunately this variable is called Name but don't want to change it
47 * since it's public and might be commonly used in customized code. See also
48 * activityTypeNameAndLabel used in the smarty template.
52 public $_activityTypeName;
55 * The id of currently viewed contact.
59 public $_currentlyViewedContactId;
62 * The id of source contact and target contact.
66 protected $_sourceContactId;
67 protected $_targetContactId;
68 protected $_asigneeContactId;
75 public $_activityTypeFile;
78 * The id of the logged in user, used when add / edit
82 public $_currentUserId;
85 * The array of form field attributes.
92 * The the directory inside CRM, to include activity type file from
96 protected $_crmDir = 'Activity';
103 protected $_isSurveyActivity;
105 protected $_values = [];
107 protected $unsavedWarn = TRUE;
111 * Is it possible to create separate activities with this form?
113 * When TRUE, the form will ask whether the user wants to create separate
114 * activities (if the user has specified multiple contacts in the "with"
117 * When FALSE, the form will create one activity with all contacts together
118 * and won't ask the user anything.
120 * Note: This is a class property so that child classes can turn off this
121 * behavior (e.g. in CRM_Case_Form_Activity)
126 protected $supportsActivitySeparation = TRUE;
128 public $submitOnce = TRUE;
131 * Explicitly declare the entity api name.
135 public function getDefaultEntity() {
140 * The _fields var can be used by sub class to set/unset/edit the
141 * form fields based on their requirement
143 public function setFields() {
144 // Remove print document activity type
145 $unwanted = CRM_Core_OptionGroup
::values('activity_type', FALSE, FALSE, FALSE, "AND v.name = 'Print PDF Letter'");
146 $activityTypes = array_diff_key(CRM_Core_PseudoConstant
::ActivityType(FALSE), $unwanted);
151 'label' => ts('Subject'),
152 'attributes' => CRM_Core_DAO
::getAttribute('CRM_Activity_DAO_Activity', 'activity_subject'),
156 'label' => ts('Duration'),
157 'attributes' => ['class' => 'four', 'min' => 1],
162 'label' => ts('Location'),
163 'attributes' => CRM_Core_DAO
::getAttribute('CRM_Activity_DAO_Activity', 'location'),
168 'label' => ts('Details'),
169 'attributes' => ['class' => 'huge'],
180 'source_contact_id' => [
181 'type' => 'entityRef',
182 'label' => ts('Added by'),
185 'target_contact_id' => [
186 'type' => 'entityRef',
187 'label' => ts('With Contact'),
188 'attributes' => ['multiple' => TRUE, 'create' => TRUE],
190 'assignee_contact_id' => [
191 'type' => 'entityRef',
192 'label' => ts('Assigned to'),
196 'api' => ['params' => ['is_deceased' => 0]],
199 'activity_date_time' => [
200 'type' => 'datepicker',
201 'label' => ts('Date'),
204 'followup_assignee_contact_id' => [
205 'type' => 'entityRef',
206 'label' => ts('Assigned to'),
210 'api' => ['params' => ['is_deceased' => 0]],
213 'followup_activity_type_id' => [
215 'label' => ts('Followup Activity'),
216 'attributes' => ['' => '- ' . ts('select activity') . ' -'] +
$activityTypes,
217 'extra' => ['class' => 'crm-select2'],
219 // Add optional 'Subject' field for the Follow-up Activiity, CRM-4491
220 'followup_activity_subject' => [
222 'label' => ts('Subject'),
223 'attributes' => CRM_Core_DAO
::getAttribute('CRM_Activity_DAO_Activity', 'subject'),
229 * Build the form object.
231 * @throws \CRM_Core_Exception
233 public function preProcess() {
234 CRM_Core_Form_RecurringEntity
::preProcess('civicrm_activity');
236 $session = CRM_Core_Session
::singleton();
237 $this->_currentUserId
= CRM_Core_Session
::getLoggedInContactID();
239 $this->_currentlyViewedContactId
= $this->get('contactId');
240 if (!$this->_currentlyViewedContactId
) {
241 $this->_currentlyViewedContactId
= CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
243 $this->assign('contactId', $this->_currentlyViewedContactId
);
246 if (!isset($this->_context
)) {
247 $this->_context
= CRM_Utils_Request
::retrieve('context', 'Alphanumeric', $this);
248 if (CRM_Contact_Form_Search
::isSearchContext($this->_context
)) {
249 $this->_context
= 'search';
251 elseif (!in_array($this->_context
, ['dashlet', 'case', 'dashletFullscreen'])
252 && $this->_currentlyViewedContactId
254 $this->_context
= 'activity';
256 $this->_compContext
= CRM_Utils_Request
::retrieve('compContext', 'String', $this);
259 $this->assign('context', $this->_context
);
261 $this->_action
= CRM_Utils_Request
::retrieve('action', 'String', $this);
263 if ($this->_action
& CRM_Core_Action
::DELETE
) {
264 if (!CRM_Core_Permission
::check('delete activities')) {
265 CRM_Core_Error
::statusBounce(ts('You do not have permission to access this page.'));
270 // When we come from contact search, activity id never comes.
271 // So don't try to get from object, it might gives you wrong one.
273 // if we're not adding new one, there must be an id to
274 // an activity we're trying to work on.
275 if ($this->_action
!= CRM_Core_Action
::ADD
&&
276 get_class($this->controller
) !== 'CRM_Contact_Controller_Search'
278 $this->_activityId
= CRM_Utils_Request
::retrieve('id', 'Positive', $this);
281 $this->_activityTypeId
= CRM_Utils_Request
::retrieve('atype', 'Positive', $this);
282 $this->assign('atype', $this->_activityTypeId
);
284 $this->assign('activityId', $this->_activityId
);
286 // Check for required permissions, CRM-6264.
287 if ($this->_activityId
&&
288 in_array($this->_action
, [
289 CRM_Core_Action
::UPDATE
,
290 CRM_Core_Action
::VIEW
,
292 !CRM_Activity_BAO_Activity
::checkPermission($this->_activityId
, $this->_action
)
294 CRM_Core_Error
::statusBounce(ts('You do not have permission to access this page.'));
296 if (($this->_action
& CRM_Core_Action
::VIEW
) &&
297 CRM_Activity_BAO_Activity
::checkPermission($this->_activityId
, CRM_Core_Action
::UPDATE
)
299 $this->assign('permission', 'edit');
300 $this->assign('allow_edit_inbound_emails', CRM_Activity_BAO_Activity
::checkEditInboundEmailsPermissions());
303 if (!$this->_activityTypeId
&& $this->_activityId
) {
304 $this->_activityTypeId
= CRM_Core_DAO
::getFieldValue('CRM_Activity_DAO_Activity',
310 $this->assignActivityType();
312 // Check the mode when this form is called either single or as
313 // search task action.
314 if ($this->_activityTypeId ||
315 $this->_context
=== 'standalone' ||
316 $this->_currentlyViewedContactId
318 $this->_single
= TRUE;
319 $this->assign('urlPath', 'civicrm/activity');
322 // Set the appropriate action.
323 $url = CRM_Utils_System
::currentPath();
324 $urlArray = explode('/', $url);
325 $searchPath = array_pop($urlArray);
326 $searchType = 'basic';
327 $this->_action
= CRM_Core_Action
::BASIC
;
328 switch ($searchPath) {
330 $searchType = $searchPath;
331 $this->_action
= CRM_Core_Action
::BASIC
;
335 $searchType = $searchPath;
336 $this->_action
= CRM_Core_Action
::ADVANCED
;
340 $searchType = $searchPath;
341 $this->_action
= CRM_Core_Action
::PROFILE
;
345 $this->_action
= CRM_Core_Action
::COPY
;
346 $searchType = $searchPath;
350 parent
::preProcess();
351 $this->_single
= FALSE;
353 $this->assign('urlPath', "civicrm/contact/search/$searchType");
354 $this->assign('urlPathVar', "_qf_Activity_display=true&qfKey={$this->controller->_key}");
357 $this->assign('single', $this->_single
);
358 $this->assign('action', $this->_action
);
360 if ($this->_action
& CRM_Core_Action
::VIEW
) {
361 // Get the tree of custom fields.
362 $this->_groupTree
= CRM_Core_BAO_CustomGroup
::getTree('Activity', NULL,
363 $this->_activityId
, 0, $this->_activityTypeId
, NULL, TRUE, NULL, FALSE, CRM_Core_Permission
::VIEW
367 if ($this->_activityTypeId
) {
368 // Set activity type name and description to template.
369 list($this->_activityTypeName
, $activityTypeDescription) = CRM_Core_BAO_OptionValue
::getActivityTypeDetails($this->_activityTypeId
);
370 $this->assign('activityTypeName', $this->_activityTypeName
);
371 $this->assign('activityTypeDescription', $activityTypeDescription);
375 $urlParams = $urlString = NULL;
376 $qfKey = CRM_Utils_Request
::retrieve('key', 'String', $this);
378 $qfKey = CRM_Utils_Request
::retrieve('qfKey', 'String', $this);
381 // Validate the qfKey.
382 if (!CRM_Utils_Rule
::qfKey($qfKey)) {
386 if ($this->_context
=== 'fulltext') {
388 $urlParams = 'force=1';
389 $urlString = 'civicrm/contact/search/custom';
390 if ($this->_action
== CRM_Core_Action
::UPDATE
) {
392 $urlParams .= '&context=fulltext&action=view';
393 $urlString = 'civicrm/contact/view/activity';
396 $urlParams .= "$keyName=$qfKey";
398 $this->assign('searchKey', $qfKey);
400 elseif (in_array($this->_context
, [
407 $urlParams = 'reset=1';
408 $urlString = 'civicrm/dashboard';
410 elseif ($this->_context
=== 'search') {
411 $urlParams = 'force=1';
413 $urlParams .= "&qfKey=$qfKey";
415 $path = CRM_Utils_System
::currentPath();
416 if ($this->_compContext
=== 'advanced') {
417 $urlString = 'civicrm/contact/search/advanced';
419 elseif ($path === 'civicrm/group/search'
420 ||
$path === 'civicrm/contact/search'
421 ||
$path === 'civicrm/contact/search/advanced'
422 ||
$path === 'civicrm/contact/search/custom'
423 ||
$path === 'civicrm/group/search'
424 ||
$path === 'civicrm/contact/search/builder'
429 $urlString = 'civicrm/activity/search';
431 $this->assign('searchKey', $qfKey);
433 elseif ($this->_context
!== 'caseActivity') {
434 $urlParams = "action=browse&reset=1&cid={$this->_currentlyViewedContactId}&selectedChild=activity";
435 $urlString = 'civicrm/contact/view';
439 $session->pushUserContext(CRM_Utils_System
::url($urlString, $urlParams));
442 // hack to retrieve activity type id from post variables
443 if (!$this->_activityTypeId
) {
444 $this->_activityTypeId
= $_POST['activity_type_id'] ??
NULL;
447 // when custom data is included in this page
448 $this->assign('cid', $this->_currentlyViewedContactId
);
449 if (!empty($_POST['hidden_custom'])) {
450 // We need to set it in the session for the code below to work.
452 // Need to assign custom data subtype to the template.
453 $this->set('type', 'Activity');
454 $this->set('subType', $this->_activityTypeId
);
455 $this->set('entityId', $this->_activityId
);
456 CRM_Custom_Form_CustomData
::preProcess($this, NULL, $this->_activityTypeId
, 1, 'Activity', $this->_activityId
);
457 CRM_Custom_Form_CustomData
::buildQuickForm($this);
458 CRM_Custom_Form_CustomData
::setDefaultValues($this);
461 // add attachments part
462 CRM_Core_BAO_File
::buildAttachment($this, 'civicrm_activity', $this->_activityId
, NULL, TRUE);
464 // figure out the file name for activity type, if any
465 if ($this->_activityTypeId
&&
466 $this->_activityTypeFile
= CRM_Activity_BAO_Activity
::getFileForActivityTypeId($this->_activityTypeId
, $this->_crmDir
)
468 $this->assign('activityTypeFile', $this->_activityTypeFile
);
469 $this->assign('crmDir', $this->_crmDir
);
474 if ($this->_activityTypeFile
) {
475 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
476 $className::preProcess($this);
479 $this->_values
= $this->get('values');
480 if (!is_array($this->_values
)) {
482 if (isset($this->_activityId
) && $this->_activityId
) {
483 $params = ['id' => $this->_activityId
];
484 CRM_Activity_BAO_Activity
::retrieve($params, $this->_values
);
487 $this->set('values', $this->_values
);
490 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
491 // We filter out alternatives, in case this is a stored e-mail, before sending to front-end
492 if (isset($this->_values
['details'])) {
493 $this->_values
['details'] = CRM_Utils_String
::stripAlternatives($this->_values
['details']) ?
: '';
496 if ($this->_activityTypeName
=== 'Inbound Email' &&
497 !CRM_Core_Permission
::check('edit inbound email basic information and content')
499 $this->_fields
['details']['type'] = 'static';
502 CRM_Core_Form_RecurringEntity
::preProcess('civicrm_activity');
505 if ($this->_action
& CRM_Core_Action
::VIEW
) {
506 $this->_values
['details'] = CRM_Utils_String
::purifyHtml($this->_values
['details'] ??
'');
507 $url = CRM_Utils_System
::url(implode("/", $this->urlPath
), "reset=1&id={$this->_activityId}&action=view&cid={$this->_values['source_contact_id']}");
508 CRM_Utils_Recent
::add(CRM_Utils_Array
::value('subject', $this->_values
, ts('(no subject)')),
510 $this->_values
['id'],
512 $this->_values
['source_contact_id'],
513 $this->_values
['source_contact']
519 * Set default values for the form.
521 * For edit/view mode the default values are retrieved from the database.
525 public function setDefaultValues() {
527 $defaults = $this->_values + CRM_Core_Form_RecurringEntity
::setDefaultValues();
528 // if we're editing...
529 if (isset($this->_activityId
)) {
531 if ($this->_context
!== 'standalone') {
532 $this->assign('target_contact_value',
533 CRM_Utils_Array
::value('target_contact_value', $defaults)
535 $this->assign('assignee_contact_value',
536 CRM_Utils_Array
::value('assignee_contact_value', $defaults)
540 // Fixme: why are we getting the wrong keys from upstream?
541 $defaults['target_contact_id'] = $defaults['target_contact'] ??
NULL;
542 $defaults['assignee_contact_id'] = $defaults['assignee_contact'] ??
NULL;
544 // set default tags if exists
545 $defaults['tag'] = implode(',', CRM_Core_BAO_EntityTag
::getTag($this->_activityId
, 'civicrm_activity'));
548 // if it's a new activity, we need to set default values for associated contact fields
549 $this->_sourceContactId
= $this->_currentUserId
;
550 $this->_targetContactId
= $this->_currentlyViewedContactId
;
552 $defaults['source_contact_id'] = $this->_sourceContactId
;
553 $defaults['target_contact_id'] = $this->_targetContactId
;
556 if (empty($defaults['activity_date_time'])) {
557 $defaults['activity_date_time'] = date('Y-m-d H:i:s');
560 if ($this->_activityTypeId
) {
561 $defaults['activity_type_id'] = $this->_activityTypeId
;
564 if (!$this->_single
&& !empty($this->_contactIds
)) {
565 $defaults['target_contact_id'] = $this->_contactIds
;
568 // CRM-15472 - 50 is around the practical limit of how many items a select2 entityRef can handle
569 if ($this->_action
== CRM_Core_Action
::UPDATE
&& !empty($defaults['target_contact_id'])) {
570 $count = count(is_array($defaults['target_contact_id']) ?
$defaults['target_contact_id'] : explode(',', $defaults['target_contact_id']));
572 $this->freeze(['target_contact_id']);
573 $this->assign('disable_swap_button', TRUE);
577 if ($this->_action
& (CRM_Core_Action
::DELETE | CRM_Core_Action
::RENEW
)) {
578 $this->assign('delName', CRM_Utils_Array
::value('subject', $defaults));
581 if ($this->_activityTypeFile
) {
582 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
583 $defaults +
= $className::setDefaultValues($this);
585 if (empty($defaults['priority_id'])) {
586 $priority = CRM_Core_PseudoConstant
::get('CRM_Activity_DAO_Activity', 'priority_id');
587 $defaults['priority_id'] = CRM_Core_PseudoConstant
::getKey('CRM_Activity_DAO_Activity', 'priority_id', 'Normal');
589 if (empty($defaults['status_id'])) {
590 $defaults['status_id'] = CRM_Core_OptionGroup
::getDefaultValue('activity_status');
598 * @throws \CRM_Core_Exception
599 * @throws \CiviCRM_API3_Exception
601 public function buildQuickForm() {
602 if ($this->_action
& (CRM_Core_Action
::DELETE | CRM_Core_Action
::RENEW
)) {
603 //enable form element (ActivityLinks sets this true)
604 $this->assign('suppressForm', FALSE);
606 $button = ts('Delete');
607 if ($this->_action
& CRM_Core_Action
::RENEW
) {
608 $button = ts('Restore');
614 'spacing' => ' ',
619 'name' => ts('Cancel'),
625 // Build other activity links.
626 CRM_Activity_Form_ActivityLinks
::commonBuildQuickForm($this);
628 // Enable form element (ActivityLinks sets this true).
629 $this->assign('suppressForm', FALSE);
631 $element = $this->add('select', 'activity_type_id', ts('Activity Type'),
632 $this->_fields
['followup_activity_type_id']['attributes'],
634 'onchange' => "CRM.buildCustomData( 'Activity', this.value, false, false, false, false, false, false, {$this->_currentlyViewedContactId});",
635 'class' => 'crm-select2 required',
636 'placeholder' => TRUE,
640 // Freeze for update mode.
641 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
645 // Call to RecurringEntity buildQuickForm for add/update mode.
646 if ($this->_action
& (CRM_Core_Action
::UPDATE | CRM_Core_Action
::ADD
)) {
647 CRM_Core_Form_RecurringEntity
::buildQuickForm($this);
650 foreach ($this->_fields
as $field => $values) {
651 if (!empty($this->_fields
[$field])) {
652 $attribute = $values['attributes'] ??
NULL;
653 $required = !empty($values['required']);
655 if ($values['type'] === 'select' && empty($attribute)) {
656 $this->addSelect($field, ['entity' => 'activity'], $required);
658 elseif ($values['type'] === 'entityRef') {
659 $this->addEntityRef($field, $values['label'], $attribute, $required);
662 $this->add($values['type'], $field, $values['label'], $attribute, $required, CRM_Utils_Array
::value('extra', $values));
667 // CRM-7362 --add campaigns.
668 CRM_Campaign_BAO_Campaign
::addCampaign($this, CRM_Utils_Array
::value('campaign_id', $this->_values
));
670 // Add engagement level CRM-7775
671 $buildEngagementLevel = FALSE;
672 if (CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
673 CRM_Campaign_BAO_Campaign
::accessCampaign()
675 $buildEngagementLevel = TRUE;
676 $this->addSelect('engagement_level', ['entity' => 'activity']);
677 $this->addRule('engagement_level',
678 ts('Please enter the engagement index as a number (integers only).'),
682 $this->assign('buildEngagementLevel', $buildEngagementLevel);
684 // check for survey activity
685 $this->_isSurveyActivity
= FALSE;
687 if ($this->_activityId
&& CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
688 CRM_Campaign_BAO_Campaign
::accessCampaign()
691 $this->_isSurveyActivity
= CRM_Campaign_BAO_Survey
::isSurveyActivity($this->_activityId
);
692 if ($this->_isSurveyActivity
) {
693 $surveyId = CRM_Core_DAO
::getFieldValue('CRM_Activity_DAO_Activity',
697 $responseOptions = CRM_Campaign_BAO_Survey
::getResponsesOptions($surveyId);
698 if ($responseOptions) {
699 $this->add('select', 'result', ts('Result'),
700 array_combine($responseOptions, $responseOptions),
701 FALSE, ['placeholder' => TRUE]
706 $surveyTitle = CRM_Core_DAO
::getFieldValue('CRM_Campaign_DAO_Survey', $surveyId, 'title');
708 $this->assign('surveyTitle', $surveyTitle);
711 $this->assign('surveyActivity', $this->_isSurveyActivity
);
713 // Add the "Activity Separation" field
714 $actionIsAdd = ($this->_action
!= CRM_Core_Action
::UPDATE
&& $this->_action
!= CRM_Core_Action
::VIEW
);
715 $separationIsPossible = $this->supportsActivitySeparation
;
716 if ($actionIsAdd && $separationIsPossible) {
719 ts('Activity Separation'),
721 'separate' => ts('Create separate activities for each contact'),
722 'combined' => ts('Create one activity with all contacts together'),
727 $this->addRule('duration',
728 ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger'
731 // Add followup date.
732 $this->add('datepicker', 'followup_date', ts('in'));
734 // Only admins and case-workers can change the activity source
735 if (!CRM_Core_Permission
::check('administer CiviCRM') && $this->_context
!== 'caseActivity') {
736 $this->getElement('source_contact_id')->freeze();
739 //need to assign custom data type and subtype to the template
740 $this->assign('customDataType', 'Activity');
741 $this->assign('customDataSubType', $this->_activityTypeId
);
742 $this->assign('entityID', $this->_activityId
);
744 $tags = CRM_Core_BAO_Tag
::getColorTags('civicrm_activity');
747 $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, [
749 'placeholder' => ts('- select -'),
754 // we need to hide activity tagset for special activities
755 $specialActivities = ['Open Case'];
757 if (!in_array($this->_activityTypeName
, $specialActivities)) {
759 $parentNames = CRM_Core_BAO_Tag
::getTagSet('civicrm_activity');
760 CRM_Core_Form_Tag
::buildQuickForm($this, $parentNames, 'civicrm_activity', $this->_activityId
);
763 // if we're viewing, we're assigning different buttons than for adding/editing
764 if ($this->_action
& CRM_Core_Action
::VIEW
) {
765 if (isset($this->_groupTree
)) {
766 CRM_Core_BAO_CustomGroup
::buildCustomDataView($this, $this->_groupTree
, FALSE, NULL, NULL, NULL, $this->_activityId
);
768 // form should be frozen for view mode
774 'name' => ts('Done'),
782 'name' => ts('Save'),
787 'name' => ts('Cancel'),
792 if ($this->_activityTypeFile
) {
793 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
795 $className::buildQuickForm($this);
796 $this->addFormRule([$className, 'formRule'], $this);
799 $this->addFormRule(['CRM_Activity_Form_Activity', 'formRule'], $this);
801 $doNotNotifyAssigneeFor = (array) Civi
::settings()
802 ->get('do_not_notify_assignees_for');
803 if (($this->_activityTypeId
&& in_array($this->_activityTypeId
, $doNotNotifyAssigneeFor)) ||
!Civi
::settings()
804 ->get('activity_assignee_notification')) {
805 $this->assign('activityAssigneeNotification', FALSE);
808 $this->assign('activityAssigneeNotification', TRUE);
810 $this->assign('doNotNotifyAssigneeFor', $doNotNotifyAssigneeFor);
816 * @param array $fields
817 * The input form values.
818 * @param array $files
819 * The uploaded files if any.
823 * true if no errors, else array of errors
825 public static function formRule($fields, $files, $self) {
826 // skip form rule if deleting
827 if (($fields['_qf_Activity_next_'] ??
NULL) === 'Delete') {
831 if ((array_key_exists('activity_type_id', $fields) ||
!$self->_single
) && empty($fields['activity_type_id'])) {
832 $errors['activity_type_id'] = ts('Activity Type is a required field');
835 $activity_type_id = $fields['activity_type_id'] ??
NULL;
836 $activity_status_id = $fields['status_id'] ??
NULL;
837 $scheduled_status_id = CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled');
839 if ($activity_type_id && $activity_status_id == $scheduled_status_id) {
840 if ($activity_type_id == CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email')) {
841 $errors['status_id'] = ts('You cannot record scheduled email activity.');
843 elseif ($activity_type_id == CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS')) {
844 $errors['status_id'] = ts('You cannot record scheduled SMS activity');
848 if (!empty($fields['followup_activity_type_id']) && empty($fields['followup_date'])) {
849 $errors['followup_date'] = ts('Followup date is a required field.');
851 // Activity type is mandatory if subject or follow-up date is specified for an Follow-up activity, CRM-4515.
852 if ((!empty($fields['followup_activity_subject']) ||
!empty($fields['followup_date'])) && empty($fields['followup_activity_type_id'])) {
853 $errors['followup_activity_subject'] = ts('Follow-up Activity type is a required field.');
856 // Check that a value has been set for the "activity separation" field if needed
857 $separationIsPossible = $self->supportsActivitySeparation
;
858 $actionIsAdd = $self->_action
== CRM_Core_Action
::ADD
;
859 $hasMultipleTargetContacts = !empty($fields['target_contact_id']) && strpos($fields['target_contact_id'], ',') !== FALSE;
860 $separationFieldIsEmpty = empty($fields['separation']);
861 if ($separationIsPossible && $actionIsAdd && $hasMultipleTargetContacts && $separationFieldIsEmpty) {
862 $errors['separation'] = ts('Activity Separation is a required field.');
869 * Process the form submission.
872 * @param array $params
875 * @throws \CiviCRM_API3_Exception
877 public function postProcess($params = NULL) {
878 if ($this->_action
& CRM_Core_Action
::DELETE
) {
879 // Look up any repeat activities to be deleted.
880 $activityIds = array_column(CRM_Core_BAO_RecurringEntity
::getEntitiesFor($this->_activityId
, 'civicrm_activity', TRUE, NULL), 'id');
882 // There are no repeat activities to delete - just this one.
883 $activityIds = [$this->_activityId
];
886 // Delete each activity.
887 foreach ($activityIds as $activityId) {
888 $deleteParams = ['id' => $activityId];
889 $moveToTrash = CRM_Case_BAO_Case
::isCaseActivity($activityId);
890 CRM_Activity_BAO_Activity
::deleteActivity($deleteParams, $moveToTrash);
892 // delete tags for the entity
894 'entity_table' => 'civicrm_activity',
895 'entity_id' => $activityId,
898 CRM_Core_BAO_EntityTag
::del($tagParams);
901 CRM_Core_Session
::setStatus(
902 ts("Selected Activity has been deleted successfully.", ['plural' => '%count Activities have been deleted successfully.', 'count' => count($activityIds)]),
903 ts('Record Deleted', ['plural' => 'Records Deleted', 'count' => count($activityIds)]), 'success'
909 // store the submitted values in an array
911 $params = $this->controller
->exportValues($this->_name
);
914 // Set activity type id.
915 if (empty($params['activity_type_id'])) {
916 $params['activity_type_id'] = $this->_activityTypeId
;
919 if (!empty($params['hidden_custom']) && !isset($params['custom'])) {
920 $params['custom'] = CRM_Core_BAO_CustomField
::postProcess($params,
926 // format params as arrays
927 foreach (['target', 'assignee', 'followup_assignee'] as $name) {
928 if (!empty($params["{$name}_contact_id"])) {
929 $params["{$name}_contact_id"] = explode(',', $params["{$name}_contact_id"]);
932 $params["{$name}_contact_id"] = [];
936 // get ids for associated contacts
937 if (!$params['source_contact_id']) {
938 $params['source_contact_id'] = $this->_currentUserId
;
941 if (isset($this->_activityId
)) {
942 $params['id'] = $this->_activityId
;
945 // add attachments as needed
946 CRM_Core_BAO_File
::formatAttachment($params,
952 $params['is_multi_activity'] = ($params['separation'] ??
NULL) === 'separate';
955 if (!empty($params['is_multi_activity']) &&
956 !CRM_Utils_Array
::crmIsEmptyArray($params['target_contact_id'])
958 $targetContacts = $params['target_contact_id'];
959 foreach ($targetContacts as $targetContactId) {
960 $params['target_contact_id'] = [$targetContactId];
962 $activity[] = $this->processActivity($params);
967 $activity = $this->processActivity($params);
970 // Redirect to contact page or activity view in standalone mode
971 if ($this->_context
=== 'standalone') {
972 if (count($params['target_contact_id']) == 1) {
973 $url = CRM_Utils_System
::url('civicrm/contact/view', ['cid' => CRM_Utils_Array
::first($params['target_contact_id']), 'selectedChild' => 'activity']);
976 $url = CRM_Utils_System
::url('civicrm/activity', ['action' => 'view', 'reset' => 1, 'id' => $this->_activityId
]);
978 CRM_Core_Session
::singleton()->pushUserContext($url);
981 $activityIds = empty($this->_activityIds
) ?
[$this->_activityId
] : $this->_activityIds
;
982 foreach ($activityIds as $activityId) {
983 // set params for repeat configuration in create mode
984 $params['entity_id'] = $activityId;
985 $params['entity_table'] = 'civicrm_activity';
986 if (!empty($params['entity_id']) && !empty($params['entity_table'])) {
987 $checkParentExistsForThisId = CRM_Core_BAO_RecurringEntity
::getParentFor($params['entity_id'], $params['entity_table']);
988 if ($checkParentExistsForThisId) {
989 $params['parent_entity_id'] = $checkParentExistsForThisId;
990 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity
::getReminderDetailsByEntityId($checkParentExistsForThisId, $params['entity_table']);
993 $params['parent_entity_id'] = $params['entity_id'];
994 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity
::getReminderDetailsByEntityId($params['entity_id'], $params['entity_table']);
996 if (property_exists($scheduleReminderDetails, 'id')) {
997 $params['schedule_reminder_id'] = $scheduleReminderDetails->id
;
1000 $params['dateColumns'] = ['activity_date_time'];
1002 // Set default repetition start if it was not provided.
1003 if (empty($params['repetition_start_date'])) {
1004 $params['repetition_start_date'] = $params['activity_date_time'];
1007 // unset activity id
1008 unset($params['id']);
1011 'table' => 'civicrm_activity_contact',
1013 'activity_id' => $activityId,
1015 'linkedColumns' => ['activity_id'],
1016 'isRecurringEntityRecord' => FALSE,
1019 CRM_Core_Form_RecurringEntity
::postProcess($params, 'civicrm_activity', $linkedEntities);
1022 return ['activity' => $activity];
1026 * Process activity creation.
1028 * @param array $params
1029 * Associated array of submitted values.
1031 * @return self|null|object
1032 * @throws \CRM_Core_Exception
1034 protected function processActivity(&$params) {
1035 $activityAssigned = [];
1036 $activityContacts = CRM_Activity_BAO_ActivityContact
::buildOptions('record_type_id', 'validate');
1037 $assigneeID = CRM_Utils_Array
::key('Activity Assignees', $activityContacts);
1038 // format assignee params
1039 if (!CRM_Utils_Array
::crmIsEmptyArray($params['assignee_contact_id'])) {
1040 //skip those assignee contacts which are already assigned
1041 //while sending a copy.CRM-4509.
1042 $activityAssigned = array_flip($params['assignee_contact_id']);
1043 if ($this->_activityId
) {
1044 $assigneeContacts = CRM_Activity_BAO_ActivityContact
::getNames($this->_activityId
, $assigneeID);
1045 $activityAssigned = array_diff_key($activityAssigned, $assigneeContacts);
1049 // call begin post process. Idea is to let injecting file do
1050 // any processing before the activity is added/updated.
1051 $this->beginPostProcess($params);
1053 $activity = CRM_Activity_BAO_Activity
::create($params);
1055 // add tags if exists
1057 if (!empty($params['tag'])) {
1058 if (!is_array($params['tag'])) {
1059 $params['tag'] = explode(',', $params['tag']);
1062 $tagParams = array_fill_keys($params['tag'], 1);
1065 // Save static tags.
1066 CRM_Core_BAO_EntityTag
::create($tagParams, 'civicrm_activity', $activity->id
);
1069 if (isset($params['activity_taglist']) && !empty($params['activity_taglist'])) {
1070 CRM_Core_Form_Tag
::postProcess($params['activity_taglist'], $activity->id
, 'civicrm_activity', $this);
1073 // call end post process. Idea is to let injecting file do any
1074 // processing needed, after the activity has been added/updated.
1075 $this->endPostProcess($params, $activity);
1078 if (!empty($params['is_multi_activity'])) {
1079 $this->_activityIds
[] = $activity->id
;
1082 $this->_activityId
= $activity->id
;
1085 // create follow up activity if needed
1086 $followupStatus = '';
1087 $followupActivity = NULL;
1088 if (!empty($params['followup_activity_type_id'])) {
1089 $followupActivity = CRM_Activity_BAO_Activity
::createFollowupActivity($activity->id
, $params);
1090 $followupStatus = ts('A followup activity has been scheduled.');
1093 // send copy to assignee contacts.CRM-4509
1096 if (Civi
::settings()->get('activity_assignee_notification')
1097 && !in_array($activity->activity_type_id
, Civi
::settings()
1098 ->get('do_not_notify_assignees_for'))) {
1099 $activityIDs = [$activity->id
];
1100 if ($followupActivity) {
1101 $activityIDs = array_merge($activityIDs, [$followupActivity->id
]);
1103 $assigneeContacts = CRM_Activity_BAO_ActivityAssignment
::getAssigneeNames($activityIDs, TRUE, FALSE);
1105 if (!CRM_Utils_Array
::crmIsEmptyArray($params['assignee_contact_id'])) {
1106 $mailToContacts = [];
1108 // Build an associative array with unique email addresses.
1109 foreach ($activityAssigned as $id => $dnc) {
1110 if (isset($id) && array_key_exists($id, $assigneeContacts)) {
1111 $mailToContacts[$assigneeContacts[$id]['email']] = $assigneeContacts[$id];
1115 $sent = CRM_Activity_BAO_Activity
::sendToAssignee($activity, $mailToContacts);
1117 $mailStatus .= ts("A copy of the activity has also been sent to assignee contacts(s).");
1121 // Also send email to follow-up activity assignees if set
1122 if ($followupActivity) {
1123 $mailToFollowupContacts = [];
1124 foreach ($assigneeContacts as $values) {
1125 if ($values['activity_id'] == $followupActivity->id
) {
1126 $mailToFollowupContacts[$values['email']] = $values;
1130 $sentFollowup = CRM_Activity_BAO_Activity
::sendToAssignee($followupActivity, $mailToFollowupContacts);
1131 if ($sentFollowup) {
1132 $mailStatus .= '<br />' . ts("A copy of the follow-up activity has also been sent to follow-up assignee contacts(s).");
1137 // set status message
1139 if (!empty($params['subject'])) {
1140 $subject = "'" . $params['subject'] . "'";
1143 CRM_Core_Session
::setStatus(ts('Activity %1 has been saved. %2 %3',
1146 2 => $followupStatus,
1149 ), ts('Saved'), 'success');
1155 * Shorthand for getting id by display name (makes code more readable)
1157 * @param string $displayName
1159 * @return null|string
1160 * @throws \CRM_Core_Exception
1162 protected function _getIdByDisplayName($displayName) {
1163 return CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
1171 * Shorthand for getting display name by id (makes code more readable)
1175 * @return null|string
1176 * @throws \CRM_Core_Exception
1178 protected function _getDisplayNameById($id) {
1179 return CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
1187 * Let injecting activity type file do any processing.
1188 * needed, before the activity is added/updated
1190 * @param array $params
1192 public function beginPostProcess(&$params) {
1193 if ($this->_activityTypeFile
) {
1194 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1195 $className::beginPostProcess($this, $params);
1200 * Let injecting activity type file do any processing
1201 * needed, after the activity has been added/updated
1203 * @param array $params
1206 public function endPostProcess(&$params, &$activity) {
1207 if ($this->_activityTypeFile
) {
1208 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1209 $className::endPostProcess($this, $params, $activity);
1214 * For the moment keeping this the same as the original pulled from preProcess(). Also note the "s" at the end of the function name - planning to change that but in baby steps.
1218 public function getActivityTypeDisplayLabels() {
1219 return CRM_Core_OptionGroup
::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId
, 'label');
1223 * For the moment this is just pulled from preProcess
1225 public function assignActivityType() {
1226 if ($this->_activityTypeId
) {
1227 $activityTypeDisplayLabels = $this->getActivityTypeDisplayLabels();
1228 if ($activityTypeDisplayLabels[$this->_activityTypeId
]) {
1229 $this->_activityTypeName
= $activityTypeDisplayLabels[$this->_activityTypeId
];
1231 // At the moment this is duplicating other code in this section, but refactoring in small steps.
1232 $activityTypeObj = new CRM_Activity_BAO_ActivityType($this->_activityTypeId
);
1233 $this->assign('activityTypeNameAndLabel', $activityTypeObj->getActivityType());
1236 if (isset($activityTypeDisplayLabels)) {
1237 // FIXME - it's not clear why the if line just above is needed here and why we can't just set this once above and re-use. What is interesting, but can't possibly be the reason, is that the first if block will fail if the label is the string '0', whereas this one won't. But who would have an activity type called '0'?
1238 $activityTypeDisplayLabel = $activityTypeDisplayLabels[$this->_activityTypeId
] ??
NULL;
1240 if ($this->_currentlyViewedContactId
) {
1241 $displayName = CRM_Contact_BAO_Contact
::displayName($this->_currentlyViewedContactId
);
1242 // Check if this is default domain contact CRM-10482.
1243 if (CRM_Contact_BAO_Contact
::checkDomainContact($this->_currentlyViewedContactId
)) {
1244 $displayName .= ' (' . ts('default organization') . ')';
1246 $this->setTitle($displayName . ' - ' . $activityTypeDisplayLabel);
1249 $this->setTitle(ts('%1 Activity', [1 => $activityTypeDisplayLabel]));