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
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 $url = CRM_Utils_System
::url(implode("/", $this->urlPath
), "reset=1&id={$this->_activityId}&action=view&cid={$this->_values['source_contact_id']}");
507 CRM_Utils_Recent
::add(CRM_Utils_Array
::value('subject', $this->_values
, ts('(no subject)')),
509 $this->_values
['id'],
511 $this->_values
['source_contact_id'],
512 $this->_values
['source_contact']
518 * Set default values for the form.
520 * For edit/view mode the default values are retrieved from the database.
524 public function setDefaultValues() {
526 $defaults = $this->_values + CRM_Core_Form_RecurringEntity
::setDefaultValues();
527 // if we're editing...
528 if (isset($this->_activityId
)) {
530 if ($this->_context
!== 'standalone') {
531 $this->assign('target_contact_value',
532 CRM_Utils_Array
::value('target_contact_value', $defaults)
534 $this->assign('assignee_contact_value',
535 CRM_Utils_Array
::value('assignee_contact_value', $defaults)
539 // Fixme: why are we getting the wrong keys from upstream?
540 $defaults['target_contact_id'] = $defaults['target_contact'] ??
NULL;
541 $defaults['assignee_contact_id'] = $defaults['assignee_contact'] ??
NULL;
543 // set default tags if exists
544 $defaults['tag'] = implode(',', CRM_Core_BAO_EntityTag
::getTag($this->_activityId
, 'civicrm_activity'));
547 // if it's a new activity, we need to set default values for associated contact fields
548 $this->_sourceContactId
= $this->_currentUserId
;
549 $this->_targetContactId
= $this->_currentlyViewedContactId
;
551 $defaults['source_contact_id'] = $this->_sourceContactId
;
552 $defaults['target_contact_id'] = $this->_targetContactId
;
555 if (empty($defaults['activity_date_time'])) {
556 $defaults['activity_date_time'] = date('Y-m-d H:i:s');
559 if ($this->_activityTypeId
) {
560 $defaults['activity_type_id'] = $this->_activityTypeId
;
563 if (!$this->_single
&& !empty($this->_contactIds
)) {
564 $defaults['target_contact_id'] = $this->_contactIds
;
567 // CRM-15472 - 50 is around the practical limit of how many items a select2 entityRef can handle
568 if ($this->_action
== CRM_Core_Action
::UPDATE
&& !empty($defaults['target_contact_id'])) {
569 $count = count(is_array($defaults['target_contact_id']) ?
$defaults['target_contact_id'] : explode(',', $defaults['target_contact_id']));
571 $this->freeze(['target_contact_id']);
575 if ($this->_action
& (CRM_Core_Action
::DELETE | CRM_Core_Action
::RENEW
)) {
576 $this->assign('delName', CRM_Utils_Array
::value('subject', $defaults));
579 if ($this->_activityTypeFile
) {
580 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
581 $defaults +
= $className::setDefaultValues($this);
583 if (empty($defaults['priority_id'])) {
584 $priority = CRM_Core_PseudoConstant
::get('CRM_Activity_DAO_Activity', 'priority_id');
585 $defaults['priority_id'] = CRM_Core_PseudoConstant
::getKey('CRM_Activity_DAO_Activity', 'priority_id', 'Normal');
587 if (empty($defaults['status_id'])) {
588 $defaults['status_id'] = CRM_Core_OptionGroup
::getDefaultValue('activity_status');
596 * @throws \CRM_Core_Exception
597 * @throws \CiviCRM_API3_Exception
599 public function buildQuickForm() {
600 if ($this->_action
& (CRM_Core_Action
::DELETE | CRM_Core_Action
::RENEW
)) {
601 //enable form element (ActivityLinks sets this true)
602 $this->assign('suppressForm', FALSE);
604 $button = ts('Delete');
605 if ($this->_action
& CRM_Core_Action
::RENEW
) {
606 $button = ts('Restore');
612 'spacing' => ' ',
617 'name' => ts('Cancel'),
623 // Build other activity links.
624 CRM_Activity_Form_ActivityLinks
::commonBuildQuickForm($this);
626 // Enable form element (ActivityLinks sets this true).
627 $this->assign('suppressForm', FALSE);
629 $element = $this->add('select', 'activity_type_id', ts('Activity Type'),
630 ['' => '- ' . ts('select') . ' -'] +
$this->_fields
['followup_activity_type_id']['attributes'],
632 'onchange' => "CRM.buildCustomData( 'Activity', this.value, false, false, false, false, false, false, {$this->_currentlyViewedContactId});",
633 'class' => 'crm-select2 required',
637 // Freeze for update mode.
638 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
642 // Call to RecurringEntity buildQuickForm for add/update mode.
643 if ($this->_action
& (CRM_Core_Action
::UPDATE | CRM_Core_Action
::ADD
)) {
644 CRM_Core_Form_RecurringEntity
::buildQuickForm($this);
647 foreach ($this->_fields
as $field => $values) {
648 if (!empty($this->_fields
[$field])) {
649 $attribute = $values['attributes'] ??
NULL;
650 $required = !empty($values['required']);
652 if ($values['type'] === 'select' && empty($attribute)) {
653 $this->addSelect($field, ['entity' => 'activity'], $required);
655 elseif ($values['type'] === 'entityRef') {
656 $this->addEntityRef($field, $values['label'], $attribute, $required);
659 $this->add($values['type'], $field, $values['label'], $attribute, $required, CRM_Utils_Array
::value('extra', $values));
664 // CRM-7362 --add campaigns.
665 CRM_Campaign_BAO_Campaign
::addCampaign($this, CRM_Utils_Array
::value('campaign_id', $this->_values
));
667 // Add engagement level CRM-7775
668 $buildEngagementLevel = FALSE;
669 if (CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
670 CRM_Campaign_BAO_Campaign
::accessCampaign()
672 $buildEngagementLevel = TRUE;
673 $this->addSelect('engagement_level', ['entity' => 'activity']);
674 $this->addRule('engagement_level',
675 ts('Please enter the engagement index as a number (integers only).'),
679 $this->assign('buildEngagementLevel', $buildEngagementLevel);
681 // check for survey activity
682 $this->_isSurveyActivity
= FALSE;
684 if ($this->_activityId
&& CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
685 CRM_Campaign_BAO_Campaign
::accessCampaign()
688 $this->_isSurveyActivity
= CRM_Campaign_BAO_Survey
::isSurveyActivity($this->_activityId
);
689 if ($this->_isSurveyActivity
) {
690 $surveyId = CRM_Core_DAO
::getFieldValue('CRM_Activity_DAO_Activity',
694 $responseOptions = CRM_Campaign_BAO_Survey
::getResponsesOptions($surveyId);
695 if ($responseOptions) {
696 $this->add('select', 'result', ts('Result'),
697 ['' => ts('- select -')] +
array_combine($responseOptions, $responseOptions)
702 $surveyTitle = CRM_Core_DAO
::getFieldValue('CRM_Campaign_DAO_Survey', $surveyId, 'title');
704 $this->assign('surveyTitle', $surveyTitle);
707 $this->assign('surveyActivity', $this->_isSurveyActivity
);
709 // Add the "Activity Separation" field
710 $actionIsAdd = ($this->_action
!= CRM_Core_Action
::UPDATE
&& $this->_action
!= CRM_Core_Action
::VIEW
);
711 $separationIsPossible = $this->supportsActivitySeparation
;
712 if ($actionIsAdd && $separationIsPossible) {
715 ts('Activity Separation'),
717 'separate' => ts('Create separate activities for each contact'),
718 'combined' => ts('Create one activity with all contacts together'),
723 $this->addRule('duration',
724 ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger'
727 // Add followup date.
728 $this->add('datepicker', 'followup_date', ts('in'));
730 // Only admins and case-workers can change the activity source
731 if (!CRM_Core_Permission
::check('administer CiviCRM') && $this->_context
!== 'caseActivity') {
732 $this->getElement('source_contact_id')->freeze();
735 //need to assign custom data type and subtype to the template
736 $this->assign('customDataType', 'Activity');
737 $this->assign('customDataSubType', $this->_activityTypeId
);
738 $this->assign('entityID', $this->_activityId
);
740 $tags = CRM_Core_BAO_Tag
::getColorTags('civicrm_activity');
743 $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, [
745 'placeholder' => ts('- select -'),
750 // we need to hide activity tagset for special activities
751 $specialActivities = ['Open Case'];
753 if (!in_array($this->_activityTypeName
, $specialActivities)) {
755 $parentNames = CRM_Core_BAO_Tag
::getTagSet('civicrm_activity');
756 CRM_Core_Form_Tag
::buildQuickForm($this, $parentNames, 'civicrm_activity', $this->_activityId
);
759 // if we're viewing, we're assigning different buttons than for adding/editing
760 if ($this->_action
& CRM_Core_Action
::VIEW
) {
761 if (isset($this->_groupTree
)) {
762 CRM_Core_BAO_CustomGroup
::buildCustomDataView($this, $this->_groupTree
, FALSE, NULL, NULL, NULL, $this->_activityId
);
764 // form should be frozen for view mode
770 'name' => ts('Done'),
778 'name' => ts('Save'),
783 'name' => ts('Cancel'),
788 if ($this->_activityTypeFile
) {
789 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
791 $className::buildQuickForm($this);
792 $this->addFormRule([$className, 'formRule'], $this);
795 $this->addFormRule(['CRM_Activity_Form_Activity', 'formRule'], $this);
797 $doNotNotifyAssigneeFor = (array) Civi
::settings()
798 ->get('do_not_notify_assignees_for');
799 if (($this->_activityTypeId
&& in_array($this->_activityTypeId
, $doNotNotifyAssigneeFor)) ||
!Civi
::settings()
800 ->get('activity_assignee_notification')) {
801 $this->assign('activityAssigneeNotification', FALSE);
804 $this->assign('activityAssigneeNotification', TRUE);
806 $this->assign('doNotNotifyAssigneeFor', $doNotNotifyAssigneeFor);
812 * @param array $fields
813 * The input form values.
814 * @param array $files
815 * The uploaded files if any.
819 * true if no errors, else array of errors
821 public static function formRule($fields, $files, $self) {
822 // skip form rule if deleting
823 if (($fields['_qf_Activity_next_'] ??
NULL) === 'Delete') {
827 if ((array_key_exists('activity_type_id', $fields) ||
!$self->_single
) && empty($fields['activity_type_id'])) {
828 $errors['activity_type_id'] = ts('Activity Type is a required field');
831 $activity_type_id = $fields['activity_type_id'] ??
NULL;
832 $activity_status_id = $fields['status_id'] ??
NULL;
833 $scheduled_status_id = CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled');
835 if ($activity_type_id && $activity_status_id == $scheduled_status_id) {
836 if ($activity_type_id == CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email')) {
837 $errors['status_id'] = ts('You cannot record scheduled email activity.');
839 elseif ($activity_type_id == CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS')) {
840 $errors['status_id'] = ts('You cannot record scheduled SMS activity');
844 if (!empty($fields['followup_activity_type_id']) && empty($fields['followup_date'])) {
845 $errors['followup_date'] = ts('Followup date is a required field.');
847 // Activity type is mandatory if subject or follow-up date is specified for an Follow-up activity, CRM-4515.
848 if ((!empty($fields['followup_activity_subject']) ||
!empty($fields['followup_date'])) && empty($fields['followup_activity_type_id'])) {
849 $errors['followup_activity_subject'] = ts('Follow-up Activity type is a required field.');
852 // Check that a value has been set for the "activity separation" field if needed
853 $separationIsPossible = $self->supportsActivitySeparation
;
854 $actionIsAdd = $self->_action
== CRM_Core_Action
::ADD
;
855 $hasMultipleTargetContacts = !empty($fields['target_contact_id']) && strpos($fields['target_contact_id'], ',') !== FALSE;
856 $separationFieldIsEmpty = empty($fields['separation']);
857 if ($separationIsPossible && $actionIsAdd && $hasMultipleTargetContacts && $separationFieldIsEmpty) {
858 $errors['separation'] = ts('Activity Separation is a required field.');
865 * Process the form submission.
868 * @param array $params
871 * @throws \CiviCRM_API3_Exception
873 public function postProcess($params = NULL) {
874 if ($this->_action
& CRM_Core_Action
::DELETE
) {
875 // Look up any repeat activities to be deleted.
876 $activityIds = array_column(CRM_Core_BAO_RecurringEntity
::getEntitiesFor($this->_activityId
, 'civicrm_activity', TRUE, NULL), 'id');
878 // There are no repeat activities to delete - just this one.
879 $activityIds = [$this->_activityId
];
882 // Delete each activity.
883 foreach ($activityIds as $activityId) {
884 $deleteParams = ['id' => $activityId];
885 $moveToTrash = CRM_Case_BAO_Case
::isCaseActivity($activityId);
886 CRM_Activity_BAO_Activity
::deleteActivity($deleteParams, $moveToTrash);
888 // delete tags for the entity
890 'entity_table' => 'civicrm_activity',
891 'entity_id' => $activityId,
894 CRM_Core_BAO_EntityTag
::del($tagParams);
897 CRM_Core_Session
::setStatus(
898 ts("Selected Activity has been deleted successfully.", ['plural' => '%count Activities have been deleted successfully.', 'count' => count($activityIds)]),
899 ts('Record Deleted', ['plural' => 'Records Deleted', 'count' => count($activityIds)]), 'success'
905 // store the submitted values in an array
907 $params = $this->controller
->exportValues($this->_name
);
910 // Set activity type id.
911 if (empty($params['activity_type_id'])) {
912 $params['activity_type_id'] = $this->_activityTypeId
;
915 if (!empty($params['hidden_custom']) &&
916 !isset($params['custom'])
918 $customFields = CRM_Core_BAO_CustomField
::getFields('Activity', FALSE, FALSE,
919 $this->_activityTypeId
921 $customFields = CRM_Utils_Array
::crmArrayMerge($customFields,
922 CRM_Core_BAO_CustomField
::getFields('Activity', FALSE, FALSE,
926 $params['custom'] = CRM_Core_BAO_CustomField
::postProcess($params,
932 // format params as arrays
933 foreach (['target', 'assignee', 'followup_assignee'] as $name) {
934 if (!empty($params["{$name}_contact_id"])) {
935 $params["{$name}_contact_id"] = explode(',', $params["{$name}_contact_id"]);
938 $params["{$name}_contact_id"] = [];
942 // get ids for associated contacts
943 if (!$params['source_contact_id']) {
944 $params['source_contact_id'] = $this->_currentUserId
;
947 if (isset($this->_activityId
)) {
948 $params['id'] = $this->_activityId
;
951 // add attachments as needed
952 CRM_Core_BAO_File
::formatAttachment($params,
958 $params['is_multi_activity'] = ($params['separation'] ??
NULL) === 'separate';
961 if (!empty($params['is_multi_activity']) &&
962 !CRM_Utils_Array
::crmIsEmptyArray($params['target_contact_id'])
964 $targetContacts = $params['target_contact_id'];
965 foreach ($targetContacts as $targetContactId) {
966 $params['target_contact_id'] = [$targetContactId];
968 $activity[] = $this->processActivity($params);
973 $activity = $this->processActivity($params);
976 // Redirect to contact page or activity view in standalone mode
977 if ($this->_context
=== 'standalone') {
978 if (count($params['target_contact_id']) == 1) {
979 $url = CRM_Utils_System
::url('civicrm/contact/view', ['cid' => CRM_Utils_Array
::first($params['target_contact_id']), 'selectedChild' => 'activity']);
982 $url = CRM_Utils_System
::url('civicrm/activity', ['action' => 'view', 'reset' => 1, 'id' => $this->_activityId
]);
984 CRM_Core_Session
::singleton()->pushUserContext($url);
987 $activityIds = empty($this->_activityIds
) ?
[$this->_activityId
] : $this->_activityIds
;
988 foreach ($activityIds as $activityId) {
989 // set params for repeat configuration in create mode
990 $params['entity_id'] = $activityId;
991 $params['entity_table'] = 'civicrm_activity';
992 if (!empty($params['entity_id']) && !empty($params['entity_table'])) {
993 $checkParentExistsForThisId = CRM_Core_BAO_RecurringEntity
::getParentFor($params['entity_id'], $params['entity_table']);
994 if ($checkParentExistsForThisId) {
995 $params['parent_entity_id'] = $checkParentExistsForThisId;
996 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity
::getReminderDetailsByEntityId($checkParentExistsForThisId, $params['entity_table']);
999 $params['parent_entity_id'] = $params['entity_id'];
1000 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity
::getReminderDetailsByEntityId($params['entity_id'], $params['entity_table']);
1002 if (property_exists($scheduleReminderDetails, 'id')) {
1003 $params['schedule_reminder_id'] = $scheduleReminderDetails->id
;
1006 $params['dateColumns'] = ['activity_date_time'];
1008 // Set default repetition start if it was not provided.
1009 if (empty($params['repetition_start_date'])) {
1010 $params['repetition_start_date'] = $params['activity_date_time'];
1013 // unset activity id
1014 unset($params['id']);
1017 'table' => 'civicrm_activity_contact',
1019 'activity_id' => $activityId,
1021 'linkedColumns' => ['activity_id'],
1022 'isRecurringEntityRecord' => FALSE,
1025 CRM_Core_Form_RecurringEntity
::postProcess($params, 'civicrm_activity', $linkedEntities);
1028 return ['activity' => $activity];
1032 * Process activity creation.
1034 * @param array $params
1035 * Associated array of submitted values.
1037 * @return self|null|object
1038 * @throws \CRM_Core_Exception
1040 protected function processActivity(&$params) {
1041 $activityAssigned = [];
1042 $activityContacts = CRM_Activity_BAO_ActivityContact
::buildOptions('record_type_id', 'validate');
1043 $assigneeID = CRM_Utils_Array
::key('Activity Assignees', $activityContacts);
1044 // format assignee params
1045 if (!CRM_Utils_Array
::crmIsEmptyArray($params['assignee_contact_id'])) {
1046 //skip those assignee contacts which are already assigned
1047 //while sending a copy.CRM-4509.
1048 $activityAssigned = array_flip($params['assignee_contact_id']);
1049 if ($this->_activityId
) {
1050 $assigneeContacts = CRM_Activity_BAO_ActivityContact
::getNames($this->_activityId
, $assigneeID);
1051 $activityAssigned = array_diff_key($activityAssigned, $assigneeContacts);
1055 // call begin post process. Idea is to let injecting file do
1056 // any processing before the activity is added/updated.
1057 $this->beginPostProcess($params);
1059 $activity = CRM_Activity_BAO_Activity
::create($params);
1061 // add tags if exists
1063 if (!empty($params['tag'])) {
1064 if (!is_array($params['tag'])) {
1065 $params['tag'] = explode(',', $params['tag']);
1068 $tagParams = array_fill_keys($params['tag'], 1);
1071 // Save static tags.
1072 CRM_Core_BAO_EntityTag
::create($tagParams, 'civicrm_activity', $activity->id
);
1075 if (isset($params['activity_taglist']) && !empty($params['activity_taglist'])) {
1076 CRM_Core_Form_Tag
::postProcess($params['activity_taglist'], $activity->id
, 'civicrm_activity', $this);
1079 // call end post process. Idea is to let injecting file do any
1080 // processing needed, after the activity has been added/updated.
1081 $this->endPostProcess($params, $activity);
1084 if (!empty($params['is_multi_activity'])) {
1085 $this->_activityIds
[] = $activity->id
;
1088 $this->_activityId
= $activity->id
;
1091 // create follow up activity if needed
1092 $followupStatus = '';
1093 $followupActivity = NULL;
1094 if (!empty($params['followup_activity_type_id'])) {
1095 $followupActivity = CRM_Activity_BAO_Activity
::createFollowupActivity($activity->id
, $params);
1096 $followupStatus = ts('A followup activity has been scheduled.');
1099 // send copy to assignee contacts.CRM-4509
1102 if (Civi
::settings()->get('activity_assignee_notification')
1103 && !in_array($activity->activity_type_id
, Civi
::settings()
1104 ->get('do_not_notify_assignees_for'))) {
1105 $activityIDs = [$activity->id
];
1106 if ($followupActivity) {
1107 $activityIDs = array_merge($activityIDs, [$followupActivity->id
]);
1109 $assigneeContacts = CRM_Activity_BAO_ActivityAssignment
::getAssigneeNames($activityIDs, TRUE, FALSE);
1111 if (!CRM_Utils_Array
::crmIsEmptyArray($params['assignee_contact_id'])) {
1112 $mailToContacts = [];
1114 // Build an associative array with unique email addresses.
1115 foreach ($activityAssigned as $id => $dnc) {
1116 if (isset($id) && array_key_exists($id, $assigneeContacts)) {
1117 $mailToContacts[$assigneeContacts[$id]['email']] = $assigneeContacts[$id];
1121 $sent = CRM_Activity_BAO_Activity
::sendToAssignee($activity, $mailToContacts);
1123 $mailStatus .= ts("A copy of the activity has also been sent to assignee contacts(s).");
1127 // Also send email to follow-up activity assignees if set
1128 if ($followupActivity) {
1129 $mailToFollowupContacts = [];
1130 foreach ($assigneeContacts as $values) {
1131 if ($values['activity_id'] == $followupActivity->id
) {
1132 $mailToFollowupContacts[$values['email']] = $values;
1136 $sentFollowup = CRM_Activity_BAO_Activity
::sendToAssignee($followupActivity, $mailToFollowupContacts);
1137 if ($sentFollowup) {
1138 $mailStatus .= '<br />' . ts("A copy of the follow-up activity has also been sent to follow-up assignee contacts(s).");
1143 // set status message
1145 if (!empty($params['subject'])) {
1146 $subject = "'" . $params['subject'] . "'";
1149 CRM_Core_Session
::setStatus(ts('Activity %1 has been saved. %2 %3',
1152 2 => $followupStatus,
1155 ), ts('Saved'), 'success');
1161 * Shorthand for getting id by display name (makes code more readable)
1163 * @param string $displayName
1165 * @return null|string
1166 * @throws \CRM_Core_Exception
1168 protected function _getIdByDisplayName($displayName) {
1169 return CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
1177 * Shorthand for getting display name by id (makes code more readable)
1181 * @return null|string
1182 * @throws \CRM_Core_Exception
1184 protected function _getDisplayNameById($id) {
1185 return CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
1193 * Let injecting activity type file do any processing.
1194 * needed, before the activity is added/updated
1196 * @param array $params
1198 public function beginPostProcess(&$params) {
1199 if ($this->_activityTypeFile
) {
1200 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1201 $className::beginPostProcess($this, $params);
1206 * Let injecting activity type file do any processing
1207 * needed, after the activity has been added/updated
1209 * @param array $params
1212 public function endPostProcess(&$params, &$activity) {
1213 if ($this->_activityTypeFile
) {
1214 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1215 $className::endPostProcess($this, $params, $activity);
1220 * 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.
1224 public function getActivityTypeDisplayLabels() {
1225 return CRM_Core_OptionGroup
::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId
, 'label');
1229 * For the moment this is just pulled from preProcess
1231 public function assignActivityType() {
1232 if ($this->_activityTypeId
) {
1233 $activityTypeDisplayLabels = $this->getActivityTypeDisplayLabels();
1234 if ($activityTypeDisplayLabels[$this->_activityTypeId
]) {
1235 $this->_activityTypeName
= $activityTypeDisplayLabels[$this->_activityTypeId
];
1237 // At the moment this is duplicating other code in this section, but refactoring in small steps.
1238 $activityTypeObj = new CRM_Activity_BAO_ActivityType($this->_activityTypeId
);
1239 $this->assign('activityTypeNameAndLabel', $activityTypeObj->getActivityType());
1242 if (isset($activityTypeDisplayLabels)) {
1243 // 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'?
1244 $activityTypeDisplayLabel = $activityTypeDisplayLabels[$this->_activityTypeId
] ??
NULL;
1246 if ($this->_currentlyViewedContactId
) {
1247 $displayName = CRM_Contact_BAO_Contact
::displayName($this->_currentlyViewedContactId
);
1248 // Check if this is default domain contact CRM-10482.
1249 if (CRM_Contact_BAO_Contact
::checkDomainContact($this->_currentlyViewedContactId
)) {
1250 $displayName .= ' (' . ts('default organization') . ')';
1252 CRM_Utils_System
::setTitle($displayName . ' - ' . $activityTypeDisplayLabel);
1255 CRM_Utils_System
::setTitle(ts('%1 Activity', [1 => $activityTypeDisplayLabel]));