3 +--------------------------------------------------------------------+
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
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. |
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. |
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 +--------------------------------------------------------------------+
31 * @copyright CiviCRM LLC (c) 2004-2019
35 * This class generates form components for Activity.
37 class CRM_Activity_Form_Activity
extends CRM_Contact_Form_Task
{
40 * The id of the object being edited / created
47 * Store activity ids when multiple activities are created.
51 public $_activityIds = [];
54 * The id of activity type.
58 public $_activityTypeId;
61 * The name of activity type.
65 public $_activityTypeName;
68 * The id of currently viewed contact.
72 public $_currentlyViewedContactId;
75 * The id of source contact and target contact.
79 protected $_sourceContactId;
80 protected $_targetContactId;
81 protected $_asigneeContactId;
88 public $_activityTypeFile;
91 * The id of the logged in user, used when add / edit
95 public $_currentUserId;
98 * The array of form field attributes.
105 * The the directory inside CRM, to include activity type file from
109 protected $_crmDir = 'Activity';
116 protected $_isSurveyActivity;
118 protected $_values = [];
120 protected $unsavedWarn = TRUE;
124 * Is it possible to create separate activities with this form?
126 * When TRUE, the form will ask whether the user wants to create separate
127 * activities (if the user has specified multiple contacts in the "with"
130 * When FALSE, the form will create one activity with all contacts together
131 * and won't ask the user anything.
133 * Note: This is a class property so that child classes can turn off this
134 * behavior (e.g. in CRM_Case_Form_Activity)
139 protected $supportsActivitySeparation = TRUE;
141 public $submitOnce = TRUE;
144 * Explicitly declare the entity api name.
148 public function getDefaultEntity() {
153 * The _fields var can be used by sub class to set/unset/edit the
154 * form fields based on their requirement
156 public function setFields() {
157 // Remove print document activity type
158 $unwanted = CRM_Core_OptionGroup
::values('activity_type', FALSE, FALSE, FALSE, "AND v.name = 'Print PDF Letter'");
159 $activityTypes = array_diff_key(CRM_Core_PseudoConstant
::ActivityType(FALSE), $unwanted);
164 'label' => ts('Subject'),
165 'attributes' => CRM_Core_DAO
::getAttribute('CRM_Activity_DAO_Activity', 'activity_subject'),
169 'label' => ts('Duration'),
170 'attributes' => ['class' => 'four', 'min' => 1],
175 'label' => ts('Location'),
176 'attributes' => CRM_Core_DAO
::getAttribute('CRM_Activity_DAO_Activity', 'location'),
181 'label' => ts('Details'),
182 'attributes' => ['class' => 'huge'],
193 'source_contact_id' => [
194 'type' => 'entityRef',
195 'label' => ts('Added By'),
198 'target_contact_id' => [
199 'type' => 'entityRef',
200 'label' => ts('With Contact'),
201 'attributes' => ['multiple' => TRUE, 'create' => TRUE],
203 'assignee_contact_id' => [
204 'type' => 'entityRef',
205 'label' => ts('Assigned to'),
209 'api' => ['params' => ['is_deceased' => 0]],
212 'activity_date_time' => [
213 'type' => 'datepicker',
214 'label' => ts('Date'),
217 'followup_assignee_contact_id' => [
218 'type' => 'entityRef',
219 'label' => ts('Assigned to'),
223 'api' => ['params' => ['is_deceased' => 0]],
226 'followup_activity_type_id' => [
228 'label' => ts('Followup Activity'),
229 'attributes' => ['' => '- ' . ts('select activity') . ' -'] +
$activityTypes,
230 'extra' => ['class' => 'crm-select2'],
232 // Add optional 'Subject' field for the Follow-up Activiity, CRM-4491
233 'followup_activity_subject' => [
235 'label' => ts('Subject'),
236 'attributes' => CRM_Core_DAO
::getAttribute('CRM_Activity_DAO_Activity', 'subject'),
242 * Build the form object.
244 public function preProcess() {
245 CRM_Core_Form_RecurringEntity
::preProcess('civicrm_activity');
246 $this->_atypefile
= CRM_Utils_Array
::value('atypefile', $_GET);
247 $this->assign('atypefile', FALSE);
248 if ($this->_atypefile
) {
249 $this->assign('atypefile', TRUE);
252 $session = CRM_Core_Session
::singleton();
253 $this->_currentUserId
= CRM_Core_Session
::getLoggedInContactID();
255 $this->_currentlyViewedContactId
= $this->get('contactId');
256 if (!$this->_currentlyViewedContactId
) {
257 $this->_currentlyViewedContactId
= CRM_Utils_Request
::retrieve('cid', 'Positive', $this);
259 $this->assign('contactId', $this->_currentlyViewedContactId
);
262 if (!isset($this->_context
)) {
263 $this->_context
= CRM_Utils_Request
::retrieve('context', 'Alphanumeric', $this);
264 if (CRM_Contact_Form_Search
::isSearchContext($this->_context
)) {
265 $this->_context
= 'search';
267 elseif (!in_array($this->_context
, ['dashlet', 'case', 'dashletFullscreen'])
268 && $this->_currentlyViewedContactId
270 $this->_context
= 'activity';
272 $this->_compContext
= CRM_Utils_Request
::retrieve('compContext', 'String', $this);
275 $this->assign('context', $this->_context
);
277 $this->_action
= CRM_Utils_Request
::retrieve('action', 'String', $this);
279 if ($this->_action
& CRM_Core_Action
::DELETE
) {
280 if (!CRM_Core_Permission
::check('delete activities')) {
281 CRM_Core_Error
::statusBounce(ts('You do not have permission to access this page.'));
286 // When we come from contact search, activity id never comes.
287 // So don't try to get from object, it might gives you wrong one.
289 // if we're not adding new one, there must be an id to
290 // an activity we're trying to work on.
291 if ($this->_action
!= CRM_Core_Action
::ADD
&&
292 get_class($this->controller
) != 'CRM_Contact_Controller_Search'
294 $this->_activityId
= CRM_Utils_Request
::retrieve('id', 'Positive', $this);
297 $this->_activityTypeId
= CRM_Utils_Request
::retrieve('atype', 'Positive', $this);
298 $this->assign('atype', $this->_activityTypeId
);
300 $this->assign('activityId', $this->_activityId
);
302 // Check for required permissions, CRM-6264.
303 if ($this->_activityId
&&
304 in_array($this->_action
, [
305 CRM_Core_Action
::UPDATE
,
306 CRM_Core_Action
::VIEW
,
308 !CRM_Activity_BAO_Activity
::checkPermission($this->_activityId
, $this->_action
)
310 CRM_Core_Error
::statusBounce(ts('You do not have permission to access this page.'));
312 if (($this->_action
& CRM_Core_Action
::VIEW
) &&
313 CRM_Activity_BAO_Activity
::checkPermission($this->_activityId
, CRM_Core_Action
::UPDATE
)
315 $this->assign('permission', 'edit');
316 $this->assign('allow_edit_inbound_emails', CRM_Activity_BAO_Activity
::checkEditInboundEmailsPermissions());
319 if (!$this->_activityTypeId
&& $this->_activityId
) {
320 $this->_activityTypeId
= CRM_Core_DAO
::getFieldValue('CRM_Activity_DAO_Activity',
326 $this->assignActivityType();
328 // Check the mode when this form is called either single or as
329 // search task action.
330 if ($this->_activityTypeId ||
331 $this->_context
== 'standalone' ||
332 $this->_currentlyViewedContactId
334 $this->_single
= TRUE;
335 $this->assign('urlPath', 'civicrm/activity');
338 // Set the appropriate action.
339 $url = CRM_Utils_System
::currentPath();
340 $urlArray = explode('/', $url);
341 $searchPath = array_pop($urlArray);
342 $searchType = 'basic';
343 $this->_action
= CRM_Core_Action
::BASIC
;
344 switch ($searchPath) {
346 $searchType = $searchPath;
347 $this->_action
= CRM_Core_Action
::BASIC
;
351 $searchType = $searchPath;
352 $this->_action
= CRM_Core_Action
::ADVANCED
;
356 $searchType = $searchPath;
357 $this->_action
= CRM_Core_Action
::PROFILE
;
361 $this->_action
= CRM_Core_Action
::COPY
;
362 $searchType = $searchPath;
366 parent
::preProcess();
367 $this->_single
= FALSE;
369 $this->assign('urlPath', "civicrm/contact/search/$searchType");
370 $this->assign('urlPathVar', "_qf_Activity_display=true&qfKey={$this->controller->_key}");
373 $this->assign('single', $this->_single
);
374 $this->assign('action', $this->_action
);
376 if ($this->_action
& CRM_Core_Action
::VIEW
) {
377 // Get the tree of custom fields.
378 $this->_groupTree
= CRM_Core_BAO_CustomGroup
::getTree('Activity', NULL,
379 $this->_activityId
, 0, $this->_activityTypeId
383 if ($this->_activityTypeId
) {
384 // Set activity type name and description to template.
385 list($this->_activityTypeName
, $activityTypeDescription) = CRM_Core_BAO_OptionValue
::getActivityTypeDetails($this->_activityTypeId
);
386 $this->assign('activityTypeName', $this->_activityTypeName
);
387 $this->assign('activityTypeDescription', $activityTypeDescription);
391 $urlParams = $urlString = NULL;
392 $qfKey = CRM_Utils_Request
::retrieve('key', 'String', $this);
394 $qfKey = CRM_Utils_Request
::retrieve('qfKey', 'String', $this);
397 // Validate the qfKey.
398 if (!CRM_Utils_Rule
::qfKey($qfKey)) {
402 if ($this->_context
== 'fulltext') {
404 $urlParams = 'force=1';
405 $urlString = 'civicrm/contact/search/custom';
406 if ($this->_action
== CRM_Core_Action
::UPDATE
) {
408 $urlParams .= '&context=fulltext&action=view';
409 $urlString = 'civicrm/contact/view/activity';
412 $urlParams .= "$keyName=$qfKey";
414 $this->assign('searchKey', $qfKey);
416 elseif (in_array($this->_context
, [
423 $urlParams = 'reset=1';
424 $urlString = 'civicrm/dashboard';
426 elseif ($this->_context
== 'search') {
427 $urlParams = 'force=1';
429 $urlParams .= "&qfKey=$qfKey";
431 $path = CRM_Utils_System
::currentPath();
432 if ($this->_compContext
== 'advanced') {
433 $urlString = 'civicrm/contact/search/advanced';
435 elseif ($path == 'civicrm/group/search'
436 ||
$path == 'civicrm/contact/search'
437 ||
$path == 'civicrm/contact/search/advanced'
438 ||
$path == 'civicrm/contact/search/custom'
439 ||
$path == 'civicrm/group/search'
444 $urlString = 'civicrm/activity/search';
446 $this->assign('searchKey', $qfKey);
448 elseif ($this->_context
!= 'caseActivity') {
449 $urlParams = "action=browse&reset=1&cid={$this->_currentlyViewedContactId}&selectedChild=activity";
450 $urlString = 'civicrm/contact/view';
454 $session->pushUserContext(CRM_Utils_System
::url($urlString, $urlParams));
457 // hack to retrieve activity type id from post variables
458 if (!$this->_activityTypeId
) {
459 $this->_activityTypeId
= CRM_Utils_Array
::value('activity_type_id', $_POST);
462 // when custom data is included in this page
463 $this->assign('cid', $this->_currentlyViewedContactId
);
464 if (!empty($_POST['hidden_custom'])) {
465 // We need to set it in the session for the code below to work.
467 // Need to assign custom data subtype to the template.
468 $this->set('type', 'Activity');
469 $this->set('subType', $this->_activityTypeId
);
470 $this->set('entityId', $this->_activityId
);
471 CRM_Custom_Form_CustomData
::preProcess($this, NULL, $this->_activityTypeId
, 1, 'Activity', $this->_activityId
);
472 CRM_Custom_Form_CustomData
::buildQuickForm($this);
473 CRM_Custom_Form_CustomData
::setDefaultValues($this);
476 // add attachments part
477 CRM_Core_BAO_File
::buildAttachment($this, 'civicrm_activity', $this->_activityId
, NULL, TRUE);
479 // figure out the file name for activity type, if any
480 if ($this->_activityTypeId
&&
481 $this->_activityTypeFile
= CRM_Activity_BAO_Activity
::getFileForActivityTypeId($this->_activityTypeId
, $this->_crmDir
)
483 $this->assign('activityTypeFile', $this->_activityTypeFile
);
484 $this->assign('crmDir', $this->_crmDir
);
489 if ($this->_activityTypeFile
) {
490 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
491 $className::preProcess($this);
494 $this->_values
= $this->get('values');
495 if (!is_array($this->_values
)) {
497 if (isset($this->_activityId
) && $this->_activityId
) {
498 $params = ['id' => $this->_activityId
];
499 CRM_Activity_BAO_Activity
::retrieve($params, $this->_values
);
502 $this->set('values', $this->_values
);
505 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
506 // We filter out alternatives, in case this is a stored e-mail, before sending to front-end
507 if (isset($this->_values
['details'])) {
508 $this->_values
['details'] = CRM_Utils_String
::stripAlternatives($this->_values
['details']) ?
: '';
511 if ($this->_activityTypeName
=== 'Inbound Email' &&
512 !CRM_Core_Permission
::check('edit inbound email basic information and content')
514 $this->_fields
['details']['type'] = 'static';
517 CRM_Core_Form_RecurringEntity
::preProcess('civicrm_activity');
520 if ($this->_action
& CRM_Core_Action
::VIEW
) {
521 $url = CRM_Utils_System
::url(implode("/", $this->urlPath
), "reset=1&id={$this->_activityId}&action=view&cid={$this->_values['source_contact_id']}");
522 CRM_Utils_Recent
::add(CRM_Utils_Array
::value('subject', $this->_values
, ts('(no subject)')),
524 $this->_values
['id'],
526 $this->_values
['source_contact_id'],
527 $this->_values
['source_contact']
533 * Set default values for the form.
535 * For edit/view mode the default values are retrieved from the database.
539 public function setDefaultValues() {
541 $defaults = $this->_values + CRM_Core_Form_RecurringEntity
::setDefaultValues();
542 // if we're editing...
543 if (isset($this->_activityId
)) {
545 if ($this->_context
!= 'standalone') {
546 $this->assign('target_contact_value',
547 CRM_Utils_Array
::value('target_contact_value', $defaults)
549 $this->assign('assignee_contact_value',
550 CRM_Utils_Array
::value('assignee_contact_value', $defaults)
554 // Fixme: why are we getting the wrong keys from upstream?
555 $defaults['target_contact_id'] = CRM_Utils_Array
::value('target_contact', $defaults);
556 $defaults['assignee_contact_id'] = CRM_Utils_Array
::value('assignee_contact', $defaults);
558 // set default tags if exists
559 $defaults['tag'] = implode(',', CRM_Core_BAO_EntityTag
::getTag($this->_activityId
, 'civicrm_activity'));
562 // if it's a new activity, we need to set default values for associated contact fields
563 $this->_sourceContactId
= $this->_currentUserId
;
564 $this->_targetContactId
= $this->_currentlyViewedContactId
;
566 $defaults['source_contact_id'] = $this->_sourceContactId
;
567 $defaults['target_contact_id'] = $this->_targetContactId
;
570 if (empty($defaults['activity_date_time'])) {
571 $defaults['activity_date_time'] = date('Y-m-d H:i:s');
574 if ($this->_activityTypeId
) {
575 $defaults['activity_type_id'] = $this->_activityTypeId
;
578 if (!$this->_single
&& !empty($this->_contactIds
)) {
579 $defaults['target_contact_id'] = $this->_contactIds
;
582 // CRM-15472 - 50 is around the practical limit of how many items a select2 entityRef can handle
583 if ($this->_action
== CRM_Core_Action
::UPDATE
&& !empty($defaults['target_contact_id'])) {
584 $count = count(is_array($defaults['target_contact_id']) ?
$defaults['target_contact_id'] : explode(',', $defaults['target_contact_id']));
586 $this->freeze(['target_contact_id']);
590 if ($this->_action
& (CRM_Core_Action
::DELETE | CRM_Core_Action
::RENEW
)) {
591 $this->assign('delName', CRM_Utils_Array
::value('subject', $defaults));
594 if ($this->_activityTypeFile
) {
595 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
596 $defaults +
= $className::setDefaultValues($this);
598 if (empty($defaults['priority_id'])) {
599 $priority = CRM_Core_PseudoConstant
::get('CRM_Activity_DAO_Activity', 'priority_id');
600 $defaults['priority_id'] = array_search('Normal', $priority);
602 if (empty($defaults['status_id'])) {
603 $defaults['status_id'] = CRM_Core_OptionGroup
::getDefaultValue('activity_status');
611 * @throws \CRM_Core_Exception
612 * @throws \CiviCRM_API3_Exception
614 public function buildQuickForm() {
615 if ($this->_action
& (CRM_Core_Action
::DELETE | CRM_Core_Action
::RENEW
)) {
616 //enable form element (ActivityLinks sets this true)
617 $this->assign('suppressForm', FALSE);
619 $button = ts('Delete');
620 if ($this->_action
& CRM_Core_Action
::RENEW
) {
621 $button = ts('Restore');
627 'spacing' => ' ',
632 'name' => ts('Cancel'),
638 // Build other activity links.
639 CRM_Activity_Form_ActivityLinks
::commonBuildQuickForm($this);
641 // Enable form element (ActivityLinks sets this true).
642 $this->assign('suppressForm', FALSE);
644 $element = &$this->add('select', 'activity_type_id', ts('Activity Type'),
645 ['' => '- ' . ts('select') . ' -'] +
$this->_fields
['followup_activity_type_id']['attributes'],
647 'onchange' => "CRM.buildCustomData( 'Activity', this.value, false, false, false, false, false, false, {$this->_currentlyViewedContactId});",
648 'class' => 'crm-select2 required',
652 // Freeze for update mode.
653 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
657 // Call to RecurringEntity buildQuickForm for add/update mode.
658 if ($this->_action
& (CRM_Core_Action
::UPDATE | CRM_Core_Action
::ADD
)) {
659 CRM_Core_Form_RecurringEntity
::buildQuickForm($this);
662 foreach ($this->_fields
as $field => $values) {
663 if (!empty($this->_fields
[$field])) {
664 $attribute = CRM_Utils_Array
::value('attributes', $values);
665 $required = !empty($values['required']);
667 if ($values['type'] == 'select' && empty($attribute)) {
668 $this->addSelect($field, ['entity' => 'activity'], $required);
670 elseif ($values['type'] == 'entityRef') {
671 $this->addEntityRef($field, $values['label'], $attribute, $required);
674 $this->add($values['type'], $field, $values['label'], $attribute, $required, CRM_Utils_Array
::value('extra', $values));
679 // CRM-7362 --add campaigns.
680 CRM_Campaign_BAO_Campaign
::addCampaign($this, CRM_Utils_Array
::value('campaign_id', $this->_values
));
682 // Add engagement level CRM-7775
683 $buildEngagementLevel = FALSE;
684 if (CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
685 CRM_Campaign_BAO_Campaign
::accessCampaign()
687 $buildEngagementLevel = TRUE;
688 $this->addSelect('engagement_level', ['entity' => 'activity']);
689 $this->addRule('engagement_level',
690 ts('Please enter the engagement index as a number (integers only).'),
694 $this->assign('buildEngagementLevel', $buildEngagementLevel);
696 // check for survey activity
697 $this->_isSurveyActivity
= FALSE;
699 if ($this->_activityId
&& CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
700 CRM_Campaign_BAO_Campaign
::accessCampaign()
703 $this->_isSurveyActivity
= CRM_Campaign_BAO_Survey
::isSurveyActivity($this->_activityId
);
704 if ($this->_isSurveyActivity
) {
705 $surveyId = CRM_Core_DAO
::getFieldValue('CRM_Activity_DAO_Activity',
709 $responseOptions = CRM_Campaign_BAO_Survey
::getResponsesOptions($surveyId);
710 if ($responseOptions) {
711 $this->add('select', 'result', ts('Result'),
712 ['' => ts('- select -')] +
array_combine($responseOptions, $responseOptions)
717 $surveyTitle = CRM_Core_DAO
::getFieldValue('CRM_Campaign_DAO_Survey', $surveyId, 'title');
719 $this->assign('surveyTitle', $surveyTitle);
722 $this->assign('surveyActivity', $this->_isSurveyActivity
);
724 // Add the "Activity Separation" field
725 $actionIsAdd = ($this->_action
!= CRM_Core_Action
::UPDATE
&& $this->_action
!= CRM_Core_Action
::VIEW
);
726 $separationIsPossible = $this->supportsActivitySeparation
;
727 if ($actionIsAdd && $separationIsPossible) {
730 ts('Activity Separation'),
732 'separate' => ts('Create separate activities for each contact'),
733 'combined' => ts('Create one activity with all contacts together'),
738 $this->addRule('duration',
739 ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger'
742 // Add followup date.
743 $this->add('datepicker', 'followup_date', ts('in'));
745 // Only admins and case-workers can change the activity source
746 if (!CRM_Core_Permission
::check('administer CiviCRM') && $this->_context
!= 'caseActivity') {
747 $this->getElement('source_contact_id')->freeze();
750 //need to assign custom data type and subtype to the template
751 $this->assign('customDataType', 'Activity');
752 $this->assign('customDataSubType', $this->_activityTypeId
);
753 $this->assign('entityID', $this->_activityId
);
755 $tags = CRM_Core_BAO_Tag
::getColorTags('civicrm_activity');
758 $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, [
760 'placeholder' => ts('- select -'),
765 // we need to hide activity tagset for special activities
766 $specialActivities = ['Open Case'];
768 if (!in_array($this->_activityTypeName
, $specialActivities)) {
770 $parentNames = CRM_Core_BAO_Tag
::getTagSet('civicrm_activity');
771 CRM_Core_Form_Tag
::buildQuickForm($this, $parentNames, 'civicrm_activity', $this->_activityId
);
774 // if we're viewing, we're assigning different buttons than for adding/editing
775 if ($this->_action
& CRM_Core_Action
::VIEW
) {
776 if (isset($this->_groupTree
)) {
777 CRM_Core_BAO_CustomGroup
::buildCustomDataView($this, $this->_groupTree
, FALSE, NULL, NULL, NULL, $this->_activityId
);
779 // form should be frozen for view mode
785 'name' => ts('Done'),
793 'name' => ts('Save'),
798 'name' => ts('Cancel'),
803 if ($this->_activityTypeFile
) {
804 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
806 $className::buildQuickForm($this);
807 $this->addFormRule([$className, 'formRule'], $this);
810 $this->addFormRule(['CRM_Activity_Form_Activity', 'formRule'], $this);
812 $doNotNotifyAssigneeFor = (array) Civi
::settings()
813 ->get('do_not_notify_assignees_for');
814 if (($this->_activityTypeId
&& in_array($this->_activityTypeId
, $doNotNotifyAssigneeFor)) ||
!Civi
::settings()
815 ->get('activity_assignee_notification')) {
816 $this->assign('activityAssigneeNotification', FALSE);
819 $this->assign('activityAssigneeNotification', TRUE);
821 $this->assign('doNotNotifyAssigneeFor', $doNotNotifyAssigneeFor);
827 * @param array $fields
828 * The input form values.
829 * @param array $files
830 * The uploaded files if any.
834 * true if no errors, else array of errors
836 public static function formRule($fields, $files, $self) {
837 // skip form rule if deleting
838 if (CRM_Utils_Array
::value('_qf_Activity_next_', $fields) == 'Delete') {
842 if ((array_key_exists('activity_type_id', $fields) ||
!$self->_single
) && empty($fields['activity_type_id'])) {
843 $errors['activity_type_id'] = ts('Activity Type is a required field');
846 $activity_type_id = CRM_Utils_Array
::value('activity_type_id', $fields);
847 $activity_status_id = CRM_Utils_Array
::value('status_id', $fields);
848 $scheduled_status_id = CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled');
850 if ($activity_type_id && $activity_status_id == $scheduled_status_id) {
851 if ($activity_type_id == CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email')) {
852 $errors['status_id'] = ts('You cannot record scheduled email activity.');
854 elseif ($activity_type_id == CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS')) {
855 $errors['status_id'] = ts('You cannot record scheduled SMS activity');
859 if (!empty($fields['followup_activity_type_id']) && empty($fields['followup_date'])) {
860 $errors['followup_date'] = ts('Followup date is a required field.');
862 // Activity type is mandatory if subject or follow-up date is specified for an Follow-up activity, CRM-4515.
863 if ((!empty($fields['followup_activity_subject']) ||
!empty($fields['followup_date'])) && empty($fields['followup_activity_type_id'])) {
864 $errors['followup_activity_subject'] = ts('Follow-up Activity type is a required field.');
867 // Check that a value has been set for the "activity separation" field if needed
868 $separationIsPossible = $self->supportsActivitySeparation
;
869 $actionIsAdd = $self->_action
== CRM_Core_Action
::ADD
;
870 $hasMultipleTargetContacts = !empty($fields['target_contact_id']) && strpos($fields['target_contact_id'], ',') !== FALSE;
871 $separationFieldIsEmpty = empty($fields['separation']);
872 if ($separationIsPossible && $actionIsAdd && $hasMultipleTargetContacts && $separationFieldIsEmpty) {
873 $errors['separation'] = ts('Activity Separation is a required field.');
880 * Process the form submission.
883 * @param array $params
886 * @throws \CiviCRM_API3_Exception
888 public function postProcess($params = NULL) {
889 if ($this->_action
& CRM_Core_Action
::DELETE
) {
890 // Look up any repeat activities to be deleted.
891 $activityIds = array_column(CRM_Core_BAO_RecurringEntity
::getEntitiesFor($this->_activityId
, 'civicrm_activity', TRUE, NULL), 'id');
893 // There are no repeat activities to delete - just this one.
894 $activityIds = [$this->_activityId
];
897 // Delete each activity.
898 foreach ($activityIds as $activityId) {
899 $deleteParams = ['id' => $activityId];
900 $moveToTrash = CRM_Case_BAO_Case
::isCaseActivity($activityId);
901 CRM_Activity_BAO_Activity
::deleteActivity($deleteParams, $moveToTrash);
903 // delete tags for the entity
905 'entity_table' => 'civicrm_activity',
906 'entity_id' => $activityId,
909 CRM_Core_BAO_EntityTag
::del($tagParams);
912 CRM_Core_Session
::setStatus(
913 ts("Selected Activity has been deleted successfully.", ['plural' => '%count Activities have been deleted successfully.', 'count' => count($activityIds)]),
914 ts('Record Deleted', ['plural' => 'Records Deleted', 'count' => count($activityIds)]), 'success'
920 // store the submitted values in an array
922 $params = $this->controller
->exportValues($this->_name
);
925 // Set activity type id.
926 if (empty($params['activity_type_id'])) {
927 $params['activity_type_id'] = $this->_activityTypeId
;
930 if (!empty($params['hidden_custom']) &&
931 !isset($params['custom'])
933 $customFields = CRM_Core_BAO_CustomField
::getFields('Activity', FALSE, FALSE,
934 $this->_activityTypeId
936 $customFields = CRM_Utils_Array
::crmArrayMerge($customFields,
937 CRM_Core_BAO_CustomField
::getFields('Activity', FALSE, FALSE,
941 $params['custom'] = CRM_Core_BAO_CustomField
::postProcess($params,
947 // format params as arrays
948 foreach (['target', 'assignee', 'followup_assignee'] as $name) {
949 if (!empty($params["{$name}_contact_id"])) {
950 $params["{$name}_contact_id"] = explode(',', $params["{$name}_contact_id"]);
953 $params["{$name}_contact_id"] = [];
957 // get ids for associated contacts
958 if (!$params['source_contact_id']) {
959 $params['source_contact_id'] = $this->_currentUserId
;
962 if (isset($this->_activityId
)) {
963 $params['id'] = $this->_activityId
;
966 // add attachments as needed
967 CRM_Core_BAO_File
::formatAttachment($params,
973 $params['is_multi_activity'] = CRM_Utils_Array
::value('separation', $params) == 'separate';
976 if (!empty($params['is_multi_activity']) &&
977 !CRM_Utils_Array
::crmIsEmptyArray($params['target_contact_id'])
979 $targetContacts = $params['target_contact_id'];
980 foreach ($targetContacts as $targetContactId) {
981 $params['target_contact_id'] = [$targetContactId];
983 $activity[] = $this->processActivity($params);
988 $activity = $this->processActivity($params);
991 // Redirect to contact page or activity view in standalone mode
992 if ($this->_context
== 'standalone') {
993 if (count($params['target_contact_id']) == 1) {
994 $url = CRM_Utils_System
::url('civicrm/contact/view', ['cid' => CRM_Utils_Array
::first($params['target_contact_id']), 'selectedChild' => 'activity']);
997 $url = CRM_Utils_System
::url('civicrm/activity', ['action' => 'view', 'reset' => 1, 'id' => $this->_activityId
]);
999 CRM_Core_Session
::singleton()->pushUserContext($url);
1002 $activityIds = empty($this->_activityIds
) ?
[$this->_activityId
] : $this->_activityIds
;
1003 foreach ($activityIds as $activityId) {
1004 // set params for repeat configuration in create mode
1005 $params['entity_id'] = $activityId;
1006 $params['entity_table'] = 'civicrm_activity';
1007 if (!empty($params['entity_id']) && !empty($params['entity_table'])) {
1008 $checkParentExistsForThisId = CRM_Core_BAO_RecurringEntity
::getParentFor($params['entity_id'], $params['entity_table']);
1009 if ($checkParentExistsForThisId) {
1010 $params['parent_entity_id'] = $checkParentExistsForThisId;
1011 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity
::getReminderDetailsByEntityId($checkParentExistsForThisId, $params['entity_table']);
1014 $params['parent_entity_id'] = $params['entity_id'];
1015 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity
::getReminderDetailsByEntityId($params['entity_id'], $params['entity_table']);
1017 if (property_exists($scheduleReminderDetails, 'id')) {
1018 $params['schedule_reminder_id'] = $scheduleReminderDetails->id
;
1021 $params['dateColumns'] = ['activity_date_time'];
1023 // Set default repetition start if it was not provided.
1024 if (empty($params['repetition_start_date'])) {
1025 $params['repetition_start_date'] = $params['activity_date_time'];
1028 // unset activity id
1029 unset($params['id']);
1032 'table' => 'civicrm_activity_contact',
1034 'activity_id' => $activityId,
1036 'linkedColumns' => ['activity_id'],
1037 'isRecurringEntityRecord' => FALSE,
1040 CRM_Core_Form_RecurringEntity
::postProcess($params, 'civicrm_activity', $linkedEntities);
1043 return ['activity' => $activity];
1047 * Process activity creation.
1049 * @param array $params
1050 * Associated array of submitted values.
1052 * @return self|null|object
1054 protected function processActivity(&$params) {
1055 $activityAssigned = [];
1056 $activityContacts = CRM_Activity_BAO_ActivityContact
::buildOptions('record_type_id', 'validate');
1057 $assigneeID = CRM_Utils_Array
::key('Activity Assignees', $activityContacts);
1058 // format assignee params
1059 if (!CRM_Utils_Array
::crmIsEmptyArray($params['assignee_contact_id'])) {
1060 //skip those assignee contacts which are already assigned
1061 //while sending a copy.CRM-4509.
1062 $activityAssigned = array_flip($params['assignee_contact_id']);
1063 if ($this->_activityId
) {
1064 $assigneeContacts = CRM_Activity_BAO_ActivityContact
::getNames($this->_activityId
, $assigneeID);
1065 $activityAssigned = array_diff_key($activityAssigned, $assigneeContacts);
1069 // call begin post process. Idea is to let injecting file do
1070 // any processing before the activity is added/updated.
1071 $this->beginPostProcess($params);
1073 $activity = CRM_Activity_BAO_Activity
::create($params);
1075 // add tags if exists
1077 if (!empty($params['tag'])) {
1078 if (!is_array($params['tag'])) {
1079 $params['tag'] = explode(',', $params['tag']);
1082 $tagParams = array_fill_keys($params['tag'], 1);
1085 // Save static tags.
1086 CRM_Core_BAO_EntityTag
::create($tagParams, 'civicrm_activity', $activity->id
);
1089 if (isset($params['activity_taglist']) && !empty($params['activity_taglist'])) {
1090 CRM_Core_Form_Tag
::postProcess($params['activity_taglist'], $activity->id
, 'civicrm_activity', $this);
1093 // call end post process. Idea is to let injecting file do any
1094 // processing needed, after the activity has been added/updated.
1095 $this->endPostProcess($params, $activity);
1098 if (!empty($params['is_multi_activity'])) {
1099 $this->_activityIds
[] = $activity->id
;
1102 $this->_activityId
= $activity->id
;
1105 // create follow up activity if needed
1106 $followupStatus = '';
1107 $followupActivity = NULL;
1108 if (!empty($params['followup_activity_type_id'])) {
1109 $followupActivity = CRM_Activity_BAO_Activity
::createFollowupActivity($activity->id
, $params);
1110 $followupStatus = ts('A followup activity has been scheduled.');
1113 // send copy to assignee contacts.CRM-4509
1116 if (Civi
::settings()->get('activity_assignee_notification')
1117 && !in_array($activity->activity_type_id
, Civi
::settings()
1118 ->get('do_not_notify_assignees_for'))) {
1119 $activityIDs = [$activity->id
];
1120 if ($followupActivity) {
1121 $activityIDs = array_merge($activityIDs, [$followupActivity->id
]);
1123 $assigneeContacts = CRM_Activity_BAO_ActivityAssignment
::getAssigneeNames($activityIDs, TRUE, FALSE);
1125 if (!CRM_Utils_Array
::crmIsEmptyArray($params['assignee_contact_id'])) {
1126 $mailToContacts = [];
1128 // Build an associative array with unique email addresses.
1129 foreach ($activityAssigned as $id => $dnc) {
1130 if (isset($id) && array_key_exists($id, $assigneeContacts)) {
1131 $mailToContacts[$assigneeContacts[$id]['email']] = $assigneeContacts[$id];
1135 $sent = CRM_Activity_BAO_Activity
::sendToAssignee($activity, $mailToContacts);
1137 $mailStatus .= ts("A copy of the activity has also been sent to assignee contacts(s).");
1141 // Also send email to follow-up activity assignees if set
1142 if ($followupActivity) {
1143 $mailToFollowupContacts = [];
1144 foreach ($assigneeContacts as $values) {
1145 if ($values['activity_id'] == $followupActivity->id
) {
1146 $mailToFollowupContacts[$values['email']] = $values;
1150 $sentFollowup = CRM_Activity_BAO_Activity
::sendToAssignee($followupActivity, $mailToFollowupContacts);
1151 if ($sentFollowup) {
1152 $mailStatus .= '<br />' . ts("A copy of the follow-up activity has also been sent to follow-up assignee contacts(s).");
1157 // set status message
1159 if (!empty($params['subject'])) {
1160 $subject = "'" . $params['subject'] . "'";
1163 CRM_Core_Session
::setStatus(ts('Activity %1 has been saved. %2 %3',
1166 2 => $followupStatus,
1169 ), ts('Saved'), 'success');
1175 * Shorthand for getting id by display name (makes code more readable)
1176 * @param $displayName
1177 * @return null|string
1179 protected function _getIdByDisplayName($displayName) {
1180 return CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
1188 * Shorthand for getting display name by id (makes code more readable)
1190 * @return null|string
1192 protected function _getDisplayNameById($id) {
1193 return CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
1201 * Let injecting activity type file do any processing.
1202 * needed, before the activity is added/updated
1204 * @param array $params
1206 public function beginPostProcess(&$params) {
1207 if ($this->_activityTypeFile
) {
1208 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1209 $className::beginPostProcess($this, $params);
1214 * Let injecting activity type file do any processing
1215 * needed, after the activity has been added/updated
1217 * @param array $params
1220 public function endPostProcess(&$params, &$activity) {
1221 if ($this->_activityTypeFile
) {
1222 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1223 $className::endPostProcess($this, $params, $activity);
1228 * 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.
1232 public function getActivityTypeDisplayLabels() {
1233 return CRM_Core_OptionGroup
::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId
, 'label');
1237 * For the moment this is just pulled from preProcess
1239 public function assignActivityType() {
1240 if ($this->_activityTypeId
) {
1241 $activityTypeDisplayLabels = $this->getActivityTypeDisplayLabels();
1242 if ($activityTypeDisplayLabels[$this->_activityTypeId
]) {
1243 $this->_activityTypeName
= $activityTypeDisplayLabels[$this->_activityTypeId
];
1244 // can't change this instance of activityTName yet - will come back to it dev/core#1116
1245 $this->assign('activityTName', $activityTypeDisplayLabels[$this->_activityTypeId
]);
1248 if (isset($activityTypeDisplayLabels)) {
1249 // 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'?
1250 $activityTypeDisplayLabel = CRM_Utils_Array
::value($this->_activityTypeId
, $activityTypeDisplayLabels);
1252 if ($this->_currentlyViewedContactId
) {
1253 $displayName = CRM_Contact_BAO_Contact
::displayName($this->_currentlyViewedContactId
);
1254 // Check if this is default domain contact CRM-10482.
1255 if (CRM_Contact_BAO_Contact
::checkDomainContact($this->_currentlyViewedContactId
)) {
1256 $displayName .= ' (' . ts('default organization') . ')';
1258 CRM_Utils_System
::setTitle($displayName . ' - ' . $activityTypeDisplayLabel);
1261 CRM_Utils_System
::setTitle(ts('%1 Activity', [1 => $activityTypeDisplayLabel]));