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 // Assigning Activity type name.
327 if ($this->_activityTypeId
) {
328 $activityTypeDisplayLabels = CRM_Core_OptionGroup
::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId
, 'label');
329 if ($activityTypeDisplayLabels[$this->_activityTypeId
]) {
330 $this->_activityTypeName
= $activityTypeDisplayLabels[$this->_activityTypeId
];
331 // can't change this instance of activityTName yet - will come back to it dev/core#1116
332 $this->assign('activityTName', $activityTypeDisplayLabels[$this->_activityTypeId
]);
335 if (isset($activityTypeDisplayLabels)) {
336 // 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'?
337 $activityTypeDisplayLabel = CRM_Utils_Array
::value($this->_activityTypeId
, $activityTypeDisplayLabels);
339 if ($this->_currentlyViewedContactId
) {
340 $displayName = CRM_Contact_BAO_Contact
::displayName($this->_currentlyViewedContactId
);
341 // Check if this is default domain contact CRM-10482.
342 if (CRM_Contact_BAO_Contact
::checkDomainContact($this->_currentlyViewedContactId
)) {
343 $displayName .= ' (' . ts('default organization') . ')';
345 CRM_Utils_System
::setTitle($displayName . ' - ' . $activityTypeDisplayLabel);
348 CRM_Utils_System
::setTitle(ts('%1 Activity', [1 => $activityTypeDisplayLabel]));
353 // Check the mode when this form is called either single or as
354 // search task action.
355 if ($this->_activityTypeId ||
356 $this->_context
== 'standalone' ||
357 $this->_currentlyViewedContactId
359 $this->_single
= TRUE;
360 $this->assign('urlPath', 'civicrm/activity');
363 // Set the appropriate action.
364 $url = CRM_Utils_System
::currentPath();
365 $urlArray = explode('/', $url);
366 $searchPath = array_pop($urlArray);
367 $searchType = 'basic';
368 $this->_action
= CRM_Core_Action
::BASIC
;
369 switch ($searchPath) {
371 $searchType = $searchPath;
372 $this->_action
= CRM_Core_Action
::BASIC
;
376 $searchType = $searchPath;
377 $this->_action
= CRM_Core_Action
::ADVANCED
;
381 $searchType = $searchPath;
382 $this->_action
= CRM_Core_Action
::PROFILE
;
386 $this->_action
= CRM_Core_Action
::COPY
;
387 $searchType = $searchPath;
391 parent
::preProcess();
392 $this->_single
= FALSE;
394 $this->assign('urlPath', "civicrm/contact/search/$searchType");
395 $this->assign('urlPathVar', "_qf_Activity_display=true&qfKey={$this->controller->_key}");
398 $this->assign('single', $this->_single
);
399 $this->assign('action', $this->_action
);
401 if ($this->_action
& CRM_Core_Action
::VIEW
) {
402 // Get the tree of custom fields.
403 $this->_groupTree
= CRM_Core_BAO_CustomGroup
::getTree('Activity', NULL,
404 $this->_activityId
, 0, $this->_activityTypeId
408 if ($this->_activityTypeId
) {
409 // Set activity type name and description to template.
410 list($this->_activityTypeName
, $activityTypeDescription) = CRM_Core_BAO_OptionValue
::getActivityTypeDetails($this->_activityTypeId
);
411 $this->assign('activityTypeName', $this->_activityTypeName
);
412 $this->assign('activityTypeDescription', $activityTypeDescription);
416 $urlParams = $urlString = NULL;
417 $qfKey = CRM_Utils_Request
::retrieve('key', 'String', $this);
419 $qfKey = CRM_Utils_Request
::retrieve('qfKey', 'String', $this);
422 // Validate the qfKey.
423 if (!CRM_Utils_Rule
::qfKey($qfKey)) {
427 if ($this->_context
== 'fulltext') {
429 $urlParams = 'force=1';
430 $urlString = 'civicrm/contact/search/custom';
431 if ($this->_action
== CRM_Core_Action
::UPDATE
) {
433 $urlParams .= '&context=fulltext&action=view';
434 $urlString = 'civicrm/contact/view/activity';
437 $urlParams .= "$keyName=$qfKey";
439 $this->assign('searchKey', $qfKey);
441 elseif (in_array($this->_context
, [
448 $urlParams = 'reset=1';
449 $urlString = 'civicrm/dashboard';
451 elseif ($this->_context
== 'search') {
452 $urlParams = 'force=1';
454 $urlParams .= "&qfKey=$qfKey";
456 $path = CRM_Utils_System
::currentPath();
457 if ($this->_compContext
== 'advanced') {
458 $urlString = 'civicrm/contact/search/advanced';
460 elseif ($path == 'civicrm/group/search'
461 ||
$path == 'civicrm/contact/search'
462 ||
$path == 'civicrm/contact/search/advanced'
463 ||
$path == 'civicrm/contact/search/custom'
464 ||
$path == 'civicrm/group/search'
469 $urlString = 'civicrm/activity/search';
471 $this->assign('searchKey', $qfKey);
473 elseif ($this->_context
!= 'caseActivity') {
474 $urlParams = "action=browse&reset=1&cid={$this->_currentlyViewedContactId}&selectedChild=activity";
475 $urlString = 'civicrm/contact/view';
479 $session->pushUserContext(CRM_Utils_System
::url($urlString, $urlParams));
482 // hack to retrieve activity type id from post variables
483 if (!$this->_activityTypeId
) {
484 $this->_activityTypeId
= CRM_Utils_Array
::value('activity_type_id', $_POST);
487 // when custom data is included in this page
488 if (!empty($_POST['hidden_custom'])) {
489 // We need to set it in the session for the code below to work.
491 // Need to assign custom data subtype to the template.
492 $this->set('type', 'Activity');
493 $this->set('subType', $this->_activityTypeId
);
494 $this->set('entityId', $this->_activityId
);
495 CRM_Custom_Form_CustomData
::preProcess($this, NULL, $this->_activityTypeId
, 1, 'Activity', $this->_activityId
);
496 CRM_Custom_Form_CustomData
::buildQuickForm($this);
497 CRM_Custom_Form_CustomData
::setDefaultValues($this);
500 // add attachments part
501 CRM_Core_BAO_File
::buildAttachment($this, 'civicrm_activity', $this->_activityId
, NULL, TRUE);
503 // figure out the file name for activity type, if any
504 if ($this->_activityTypeId
&&
505 $this->_activityTypeFile
= CRM_Activity_BAO_Activity
::getFileForActivityTypeId($this->_activityTypeId
, $this->_crmDir
)
507 $this->assign('activityTypeFile', $this->_activityTypeFile
);
508 $this->assign('crmDir', $this->_crmDir
);
513 if ($this->_activityTypeFile
) {
514 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
515 $className::preProcess($this);
518 $this->_values
= $this->get('values');
519 if (!is_array($this->_values
)) {
521 if (isset($this->_activityId
) && $this->_activityId
) {
522 $params = ['id' => $this->_activityId
];
523 CRM_Activity_BAO_Activity
::retrieve($params, $this->_values
);
526 $this->set('values', $this->_values
);
529 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
530 // We filter out alternatives, in case this is a stored e-mail, before sending to front-end
531 if (isset($this->_values
['details'])) {
532 $this->_values
['details'] = CRM_Utils_String
::stripAlternatives($this->_values
['details']) ?
: '';
535 if ($this->_activityTypeName
=== 'Inbound Email' &&
536 !CRM_Core_Permission
::check('edit inbound email basic information and content')
538 $this->_fields
['details']['type'] = 'static';
541 CRM_Core_Form_RecurringEntity
::preProcess('civicrm_activity');
544 if ($this->_action
& CRM_Core_Action
::VIEW
) {
545 $url = CRM_Utils_System
::url(implode("/", $this->urlPath
), "reset=1&id={$this->_activityId}&action=view&cid={$this->_values['source_contact_id']}");
546 CRM_Utils_Recent
::add(CRM_Utils_Array
::value('subject', $this->_values
, ts('(no subject)')),
548 $this->_values
['id'],
550 $this->_values
['source_contact_id'],
551 $this->_values
['source_contact']
557 * Set default values for the form.
559 * For edit/view mode the default values are retrieved from the database.
563 public function setDefaultValues() {
565 $defaults = $this->_values + CRM_Core_Form_RecurringEntity
::setDefaultValues();
566 // if we're editing...
567 if (isset($this->_activityId
)) {
569 if ($this->_context
!= 'standalone') {
570 $this->assign('target_contact_value',
571 CRM_Utils_Array
::value('target_contact_value', $defaults)
573 $this->assign('assignee_contact_value',
574 CRM_Utils_Array
::value('assignee_contact_value', $defaults)
578 // Fixme: why are we getting the wrong keys from upstream?
579 $defaults['target_contact_id'] = CRM_Utils_Array
::value('target_contact', $defaults);
580 $defaults['assignee_contact_id'] = CRM_Utils_Array
::value('assignee_contact', $defaults);
582 // set default tags if exists
583 $defaults['tag'] = implode(',', CRM_Core_BAO_EntityTag
::getTag($this->_activityId
, 'civicrm_activity'));
586 // if it's a new activity, we need to set default values for associated contact fields
587 $this->_sourceContactId
= $this->_currentUserId
;
588 $this->_targetContactId
= $this->_currentlyViewedContactId
;
590 $defaults['source_contact_id'] = $this->_sourceContactId
;
591 $defaults['target_contact_id'] = $this->_targetContactId
;
594 if (empty($defaults['activity_date_time'])) {
595 $defaults['activity_date_time'] = date('Y-m-d H:i:s');
598 if ($this->_activityTypeId
) {
599 $defaults['activity_type_id'] = $this->_activityTypeId
;
602 if (!$this->_single
&& !empty($this->_contactIds
)) {
603 $defaults['target_contact_id'] = $this->_contactIds
;
606 // CRM-15472 - 50 is around the practical limit of how many items a select2 entityRef can handle
607 if ($this->_action
== CRM_Core_Action
::UPDATE
&& !empty($defaults['target_contact_id'])) {
608 $count = count(is_array($defaults['target_contact_id']) ?
$defaults['target_contact_id'] : explode(',', $defaults['target_contact_id']));
610 $this->freeze(['target_contact_id']);
614 if ($this->_action
& (CRM_Core_Action
::DELETE | CRM_Core_Action
::RENEW
)) {
615 $this->assign('delName', CRM_Utils_Array
::value('subject', $defaults));
618 if ($this->_activityTypeFile
) {
619 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
620 $defaults +
= $className::setDefaultValues($this);
622 if (empty($defaults['priority_id'])) {
623 $priority = CRM_Core_PseudoConstant
::get('CRM_Activity_DAO_Activity', 'priority_id');
624 $defaults['priority_id'] = array_search('Normal', $priority);
626 if (empty($defaults['status_id'])) {
627 $defaults['status_id'] = CRM_Core_OptionGroup
::getDefaultValue('activity_status');
635 * @throws \CRM_Core_Exception
636 * @throws \CiviCRM_API3_Exception
638 public function buildQuickForm() {
639 if ($this->_action
& (CRM_Core_Action
::DELETE | CRM_Core_Action
::RENEW
)) {
640 //enable form element (ActivityLinks sets this true)
641 $this->assign('suppressForm', FALSE);
643 $button = ts('Delete');
644 if ($this->_action
& CRM_Core_Action
::RENEW
) {
645 $button = ts('Restore');
651 'spacing' => ' ',
656 'name' => ts('Cancel'),
662 // Build other activity links.
663 CRM_Activity_Form_ActivityLinks
::commonBuildQuickForm($this);
665 // Enable form element (ActivityLinks sets this true).
666 $this->assign('suppressForm', FALSE);
668 $element = &$this->add('select', 'activity_type_id', ts('Activity Type'),
669 ['' => '- ' . ts('select') . ' -'] +
$this->_fields
['followup_activity_type_id']['attributes'],
671 'onchange' => "CRM.buildCustomData( 'Activity', this.value );",
672 'class' => 'crm-select2 required',
676 // Freeze for update mode.
677 if ($this->_action
& CRM_Core_Action
::UPDATE
) {
681 // Call to RecurringEntity buildQuickForm for add/update mode.
682 if ($this->_action
& (CRM_Core_Action
::UPDATE | CRM_Core_Action
::ADD
)) {
683 CRM_Core_Form_RecurringEntity
::buildQuickForm($this);
686 foreach ($this->_fields
as $field => $values) {
687 if (!empty($this->_fields
[$field])) {
688 $attribute = CRM_Utils_Array
::value('attributes', $values);
689 $required = !empty($values['required']);
691 if ($values['type'] == 'select' && empty($attribute)) {
692 $this->addSelect($field, ['entity' => 'activity'], $required);
694 elseif ($values['type'] == 'entityRef') {
695 $this->addEntityRef($field, $values['label'], $attribute, $required);
698 $this->add($values['type'], $field, $values['label'], $attribute, $required, CRM_Utils_Array
::value('extra', $values));
703 // CRM-7362 --add campaigns.
704 CRM_Campaign_BAO_Campaign
::addCampaign($this, CRM_Utils_Array
::value('campaign_id', $this->_values
));
706 // Add engagement level CRM-7775
707 $buildEngagementLevel = FALSE;
708 if (CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
709 CRM_Campaign_BAO_Campaign
::accessCampaign()
711 $buildEngagementLevel = TRUE;
712 $this->addSelect('engagement_level', ['entity' => 'activity']);
713 $this->addRule('engagement_level',
714 ts('Please enter the engagement index as a number (integers only).'),
718 $this->assign('buildEngagementLevel', $buildEngagementLevel);
720 // check for survey activity
721 $this->_isSurveyActivity
= FALSE;
723 if ($this->_activityId
&& CRM_Campaign_BAO_Campaign
::isCampaignEnable() &&
724 CRM_Campaign_BAO_Campaign
::accessCampaign()
727 $this->_isSurveyActivity
= CRM_Campaign_BAO_Survey
::isSurveyActivity($this->_activityId
);
728 if ($this->_isSurveyActivity
) {
729 $surveyId = CRM_Core_DAO
::getFieldValue('CRM_Activity_DAO_Activity',
733 $responseOptions = CRM_Campaign_BAO_Survey
::getResponsesOptions($surveyId);
734 if ($responseOptions) {
735 $this->add('select', 'result', ts('Result'),
736 ['' => ts('- select -')] +
array_combine($responseOptions, $responseOptions)
741 $surveyTitle = CRM_Core_DAO
::getFieldValue('CRM_Campaign_DAO_Survey', $surveyId, 'title');
743 $this->assign('surveyTitle', $surveyTitle);
746 $this->assign('surveyActivity', $this->_isSurveyActivity
);
748 // Add the "Activity Separation" field
749 $actionIsAdd = $this->_action
!= CRM_Core_Action
::UPDATE
;
750 $separationIsPossible = $this->supportsActivitySeparation
;
751 if ($actionIsAdd && $separationIsPossible) {
754 ts('Activity Separation'),
756 'separate' => ts('Create separate activities for each contact'),
757 'combined' => ts('Create one activity with all contacts together'),
762 $this->addRule('duration',
763 ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger'
766 // Add followup date.
767 $this->add('datepicker', 'followup_date', ts('in'));
769 // Only admins and case-workers can change the activity source
770 if (!CRM_Core_Permission
::check('administer CiviCRM') && $this->_context
!= 'caseActivity') {
771 $this->getElement('source_contact_id')->freeze();
774 //need to assign custom data type and subtype to the template
775 $this->assign('customDataType', 'Activity');
776 $this->assign('customDataSubType', $this->_activityTypeId
);
777 $this->assign('entityID', $this->_activityId
);
779 $tags = CRM_Core_BAO_Tag
::getColorTags('civicrm_activity');
782 $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, [
784 'placeholder' => ts('- select -'),
789 // we need to hide activity tagset for special activities
790 $specialActivities = ['Open Case'];
792 if (!in_array($this->_activityTypeName
, $specialActivities)) {
794 $parentNames = CRM_Core_BAO_Tag
::getTagSet('civicrm_activity');
795 CRM_Core_Form_Tag
::buildQuickForm($this, $parentNames, 'civicrm_activity', $this->_activityId
);
798 // if we're viewing, we're assigning different buttons than for adding/editing
799 if ($this->_action
& CRM_Core_Action
::VIEW
) {
800 if (isset($this->_groupTree
)) {
801 CRM_Core_BAO_CustomGroup
::buildCustomDataView($this, $this->_groupTree
, FALSE, NULL, NULL, NULL, $this->_activityId
);
803 // form should be frozen for view mode
809 'name' => ts('Done'),
817 'name' => ts('Save'),
822 'name' => ts('Cancel'),
827 if ($this->_activityTypeFile
) {
828 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
830 $className::buildQuickForm($this);
831 $this->addFormRule([$className, 'formRule'], $this);
834 $this->addFormRule(['CRM_Activity_Form_Activity', 'formRule'], $this);
836 $doNotNotifyAssigneeFor = (array) Civi
::settings()
837 ->get('do_not_notify_assignees_for');
838 if (($this->_activityTypeId
&& in_array($this->_activityTypeId
, $doNotNotifyAssigneeFor)) ||
!Civi
::settings()
839 ->get('activity_assignee_notification')) {
840 $this->assign('activityAssigneeNotification', FALSE);
843 $this->assign('activityAssigneeNotification', TRUE);
845 $this->assign('doNotNotifyAssigneeFor', $doNotNotifyAssigneeFor);
851 * @param array $fields
852 * The input form values.
853 * @param array $files
854 * The uploaded files if any.
858 * true if no errors, else array of errors
860 public static function formRule($fields, $files, $self) {
861 // skip form rule if deleting
862 if (CRM_Utils_Array
::value('_qf_Activity_next_', $fields) == 'Delete') {
866 if ((array_key_exists('activity_type_id', $fields) ||
!$self->_single
) && empty($fields['activity_type_id'])) {
867 $errors['activity_type_id'] = ts('Activity Type is a required field');
870 $activity_type_id = CRM_Utils_Array
::value('activity_type_id', $fields);
871 $activity_status_id = CRM_Utils_Array
::value('status_id', $fields);
872 $scheduled_status_id = CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled');
874 if ($activity_type_id && $activity_status_id == $scheduled_status_id) {
875 if ($activity_type_id == CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email')) {
876 $errors['status_id'] = ts('You cannot record scheduled email activity.');
878 elseif ($activity_type_id == CRM_Core_PseudoConstant
::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS')) {
879 $errors['status_id'] = ts('You cannot record scheduled SMS activity');
883 if (!empty($fields['followup_activity_type_id']) && empty($fields['followup_date'])) {
884 $errors['followup_date'] = ts('Followup date is a required field.');
886 // Activity type is mandatory if subject or follow-up date is specified for an Follow-up activity, CRM-4515.
887 if ((!empty($fields['followup_activity_subject']) ||
!empty($fields['followup_date'])) && empty($fields['followup_activity_type_id'])) {
888 $errors['followup_activity_subject'] = ts('Follow-up Activity type is a required field.');
891 // Check that a value has been set for the "activity separation" field if needed
892 $separationIsPossible = $self->supportsActivitySeparation
;
893 $actionIsAdd = $self->_action
== CRM_Core_Action
::ADD
;
894 $hasMultipleTargetContacts = !empty($fields['target_contact_id']) && strpos($fields['target_contact_id'], ',') !== FALSE;
895 $separationFieldIsEmpty = empty($fields['separation']);
896 if ($separationIsPossible && $actionIsAdd && $hasMultipleTargetContacts && $separationFieldIsEmpty) {
897 $errors['separation'] = ts('Activity Separation is a required field.');
904 * Process the form submission.
907 * @param array $params
910 * @throws \CiviCRM_API3_Exception
912 public function postProcess($params = NULL) {
913 if ($this->_action
& CRM_Core_Action
::DELETE
) {
914 // Look up any repeat activities to be deleted.
915 $activityIds = array_column(CRM_Core_BAO_RecurringEntity
::getEntitiesFor($this->_activityId
, 'civicrm_activity', TRUE, NULL), 'id');
917 // There are no repeat activities to delete - just this one.
918 $activityIds = [$this->_activityId
];
921 // Delete each activity.
922 foreach ($activityIds as $activityId) {
923 $deleteParams = ['id' => $activityId];
924 $moveToTrash = CRM_Case_BAO_Case
::isCaseActivity($activityId);
925 CRM_Activity_BAO_Activity
::deleteActivity($deleteParams, $moveToTrash);
927 // delete tags for the entity
929 'entity_table' => 'civicrm_activity',
930 'entity_id' => $activityId,
933 CRM_Core_BAO_EntityTag
::del($tagParams);
936 CRM_Core_Session
::setStatus(
937 ts("Selected Activity has been deleted successfully.", ['plural' => '%count Activities have been deleted successfully.', 'count' => count($activityIds)]),
938 ts('Record Deleted', ['plural' => 'Records Deleted', 'count' => count($activityIds)]), 'success'
944 // store the submitted values in an array
946 $params = $this->controller
->exportValues($this->_name
);
949 // Set activity type id.
950 if (empty($params['activity_type_id'])) {
951 $params['activity_type_id'] = $this->_activityTypeId
;
954 if (!empty($params['hidden_custom']) &&
955 !isset($params['custom'])
957 $customFields = CRM_Core_BAO_CustomField
::getFields('Activity', FALSE, FALSE,
958 $this->_activityTypeId
960 $customFields = CRM_Utils_Array
::crmArrayMerge($customFields,
961 CRM_Core_BAO_CustomField
::getFields('Activity', FALSE, FALSE,
965 $params['custom'] = CRM_Core_BAO_CustomField
::postProcess($params,
971 // format params as arrays
972 foreach (['target', 'assignee', 'followup_assignee'] as $name) {
973 if (!empty($params["{$name}_contact_id"])) {
974 $params["{$name}_contact_id"] = explode(',', $params["{$name}_contact_id"]);
977 $params["{$name}_contact_id"] = [];
981 // get ids for associated contacts
982 if (!$params['source_contact_id']) {
983 $params['source_contact_id'] = $this->_currentUserId
;
986 if (isset($this->_activityId
)) {
987 $params['id'] = $this->_activityId
;
990 // add attachments as needed
991 CRM_Core_BAO_File
::formatAttachment($params,
997 $params['is_multi_activity'] = CRM_Utils_Array
::value('separation', $params) == 'separate';
1000 if (!empty($params['is_multi_activity']) &&
1001 !CRM_Utils_Array
::crmIsEmptyArray($params['target_contact_id'])
1003 $targetContacts = $params['target_contact_id'];
1004 foreach ($targetContacts as $targetContactId) {
1005 $params['target_contact_id'] = [$targetContactId];
1007 $activity[] = $this->processActivity($params);
1012 $activity = $this->processActivity($params);
1015 // Redirect to contact page or activity view in standalone mode
1016 if ($this->_context
== 'standalone') {
1017 if (count($params['target_contact_id']) == 1) {
1018 $url = CRM_Utils_System
::url('civicrm/contact/view', ['cid' => CRM_Utils_Array
::first($params['target_contact_id']), 'selectedChild' => 'activity']);
1021 $url = CRM_Utils_System
::url('civicrm/activity', ['action' => 'view', 'reset' => 1, 'id' => $this->_activityId
]);
1023 CRM_Core_Session
::singleton()->pushUserContext($url);
1026 $activityIds = empty($this->_activityIds
) ?
[$this->_activityId
] : $this->_activityIds
;
1027 foreach ($activityIds as $activityId) {
1028 // set params for repeat configuration in create mode
1029 $params['entity_id'] = $activityId;
1030 $params['entity_table'] = 'civicrm_activity';
1031 if (!empty($params['entity_id']) && !empty($params['entity_table'])) {
1032 $checkParentExistsForThisId = CRM_Core_BAO_RecurringEntity
::getParentFor($params['entity_id'], $params['entity_table']);
1033 if ($checkParentExistsForThisId) {
1034 $params['parent_entity_id'] = $checkParentExistsForThisId;
1035 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity
::getReminderDetailsByEntityId($checkParentExistsForThisId, $params['entity_table']);
1038 $params['parent_entity_id'] = $params['entity_id'];
1039 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity
::getReminderDetailsByEntityId($params['entity_id'], $params['entity_table']);
1041 if (property_exists($scheduleReminderDetails, 'id')) {
1042 $params['schedule_reminder_id'] = $scheduleReminderDetails->id
;
1045 $params['dateColumns'] = ['activity_date_time'];
1047 // Set default repetition start if it was not provided.
1048 if (empty($params['repetition_start_date'])) {
1049 $params['repetition_start_date'] = $params['activity_date_time'];
1052 // unset activity id
1053 unset($params['id']);
1056 'table' => 'civicrm_activity_contact',
1058 'activity_id' => $activityId,
1060 'linkedColumns' => ['activity_id'],
1061 'isRecurringEntityRecord' => FALSE,
1064 CRM_Core_Form_RecurringEntity
::postProcess($params, 'civicrm_activity', $linkedEntities);
1067 return ['activity' => $activity];
1071 * Process activity creation.
1073 * @param array $params
1074 * Associated array of submitted values.
1076 * @return self|null|object
1078 protected function processActivity(&$params) {
1079 $activityAssigned = [];
1080 $activityContacts = CRM_Activity_BAO_ActivityContact
::buildOptions('record_type_id', 'validate');
1081 $assigneeID = CRM_Utils_Array
::key('Activity Assignees', $activityContacts);
1082 // format assignee params
1083 if (!CRM_Utils_Array
::crmIsEmptyArray($params['assignee_contact_id'])) {
1084 //skip those assignee contacts which are already assigned
1085 //while sending a copy.CRM-4509.
1086 $activityAssigned = array_flip($params['assignee_contact_id']);
1087 if ($this->_activityId
) {
1088 $assigneeContacts = CRM_Activity_BAO_ActivityContact
::getNames($this->_activityId
, $assigneeID);
1089 $activityAssigned = array_diff_key($activityAssigned, $assigneeContacts);
1093 // call begin post process. Idea is to let injecting file do
1094 // any processing before the activity is added/updated.
1095 $this->beginPostProcess($params);
1097 $activity = CRM_Activity_BAO_Activity
::create($params);
1099 // add tags if exists
1101 if (!empty($params['tag'])) {
1102 if (!is_array($params['tag'])) {
1103 $params['tag'] = explode(',', $params['tag']);
1106 $tagParams = array_fill_keys($params['tag'], 1);
1109 // Save static tags.
1110 CRM_Core_BAO_EntityTag
::create($tagParams, 'civicrm_activity', $activity->id
);
1113 if (isset($params['activity_taglist']) && !empty($params['activity_taglist'])) {
1114 CRM_Core_Form_Tag
::postProcess($params['activity_taglist'], $activity->id
, 'civicrm_activity', $this);
1117 // call end post process. Idea is to let injecting file do any
1118 // processing needed, after the activity has been added/updated.
1119 $this->endPostProcess($params, $activity);
1122 if (!empty($params['is_multi_activity'])) {
1123 $this->_activityIds
[] = $activity->id
;
1126 $this->_activityId
= $activity->id
;
1129 // create follow up activity if needed
1130 $followupStatus = '';
1131 $followupActivity = NULL;
1132 if (!empty($params['followup_activity_type_id'])) {
1133 $followupActivity = CRM_Activity_BAO_Activity
::createFollowupActivity($activity->id
, $params);
1134 $followupStatus = ts('A followup activity has been scheduled.');
1137 // send copy to assignee contacts.CRM-4509
1140 if (Civi
::settings()->get('activity_assignee_notification')
1141 && !in_array($activity->activity_type_id
, Civi
::settings()
1142 ->get('do_not_notify_assignees_for'))) {
1143 $activityIDs = [$activity->id
];
1144 if ($followupActivity) {
1145 $activityIDs = array_merge($activityIDs, [$followupActivity->id
]);
1147 $assigneeContacts = CRM_Activity_BAO_ActivityAssignment
::getAssigneeNames($activityIDs, TRUE, FALSE);
1149 if (!CRM_Utils_Array
::crmIsEmptyArray($params['assignee_contact_id'])) {
1150 $mailToContacts = [];
1152 // Build an associative array with unique email addresses.
1153 foreach ($activityAssigned as $id => $dnc) {
1154 if (isset($id) && array_key_exists($id, $assigneeContacts)) {
1155 $mailToContacts[$assigneeContacts[$id]['email']] = $assigneeContacts[$id];
1159 $sent = CRM_Activity_BAO_Activity
::sendToAssignee($activity, $mailToContacts);
1161 $mailStatus .= ts("A copy of the activity has also been sent to assignee contacts(s).");
1165 // Also send email to follow-up activity assignees if set
1166 if ($followupActivity) {
1167 $mailToFollowupContacts = [];
1168 foreach ($assigneeContacts as $values) {
1169 if ($values['activity_id'] == $followupActivity->id
) {
1170 $mailToFollowupContacts[$values['email']] = $values;
1174 $sentFollowup = CRM_Activity_BAO_Activity
::sendToAssignee($followupActivity, $mailToFollowupContacts);
1175 if ($sentFollowup) {
1176 $mailStatus .= '<br />' . ts("A copy of the follow-up activity has also been sent to follow-up assignee contacts(s).");
1181 // set status message
1183 if (!empty($params['subject'])) {
1184 $subject = "'" . $params['subject'] . "'";
1187 CRM_Core_Session
::setStatus(ts('Activity %1 has been saved. %2 %3',
1190 2 => $followupStatus,
1193 ), ts('Saved'), 'success');
1199 * Shorthand for getting id by display name (makes code more readable)
1200 * @param $displayName
1201 * @return null|string
1203 protected function _getIdByDisplayName($displayName) {
1204 return CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
1212 * Shorthand for getting display name by id (makes code more readable)
1214 * @return null|string
1216 protected function _getDisplayNameById($id) {
1217 return CRM_Core_DAO
::getFieldValue('CRM_Contact_DAO_Contact',
1225 * Let injecting activity type file do any processing.
1226 * needed, before the activity is added/updated
1228 * @param array $params
1230 public function beginPostProcess(&$params) {
1231 if ($this->_activityTypeFile
) {
1232 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1233 $className::beginPostProcess($this, $params);
1238 * Let injecting activity type file do any processing
1239 * needed, after the activity has been added/updated
1241 * @param array $params
1244 public function endPostProcess(&$params, &$activity) {
1245 if ($this->_activityTypeFile
) {
1246 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1247 $className::endPostProcess($this, $params, $activity);