activity export fail
[civicrm-core.git] / CRM / Activity / Form / Activity.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | Copyright CiviCRM LLC. All rights reserved. |
5 | |
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 +--------------------------------------------------------------------+
10 */
11
12 /**
13 *
14 * @package CRM
15 * @copyright CiviCRM LLC https://civicrm.org/licensing
16 */
17
18 /**
19 * This class generates form components for Activity.
20 */
21 class CRM_Activity_Form_Activity extends CRM_Contact_Form_Task {
22
23 /**
24 * The id of the object being edited / created
25 *
26 * @var int
27 */
28 public $_activityId;
29
30 /**
31 * Store activity ids when multiple activities are created.
32 *
33 * @var int
34 */
35 public $_activityIds = [];
36
37 /**
38 * The id of activity type.
39 *
40 * @var int
41 */
42 public $_activityTypeId;
43
44 /**
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.
49 *
50 * @var string
51 */
52 public $_activityTypeName;
53
54 /**
55 * The id of currently viewed contact.
56 *
57 * @var int
58 */
59 public $_currentlyViewedContactId;
60
61 /**
62 * The id of source contact and target contact.
63 *
64 * @var int
65 */
66 protected $_sourceContactId;
67 protected $_targetContactId;
68 protected $_asigneeContactId;
69
70 protected $_single;
71
72 public $_context;
73 public $_compContext;
74 public $_action;
75 public $_activityTypeFile;
76
77 /**
78 * The id of the logged in user, used when add / edit
79 *
80 * @var int
81 */
82 public $_currentUserId;
83
84 /**
85 * The array of form field attributes.
86 *
87 * @var array
88 */
89 public $_fields;
90
91 /**
92 * The the directory inside CRM, to include activity type file from
93 *
94 * @var string
95 */
96 protected $_crmDir = 'Activity';
97
98 /**
99 * Survey activity.
100 *
101 * @var bool
102 */
103 protected $_isSurveyActivity;
104
105 protected $_values = [];
106
107 protected $unsavedWarn = TRUE;
108
109 /**
110 *
111 * Is it possible to create separate activities with this form?
112 *
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"
115 * field).
116 *
117 * When FALSE, the form will create one activity with all contacts together
118 * and won't ask the user anything.
119 *
120 * Note: This is a class property so that child classes can turn off this
121 * behavior (e.g. in CRM_Case_Form_Activity)
122 *
123 * @var bool
124 *
125 */
126 protected $supportsActivitySeparation = TRUE;
127
128 public $submitOnce = TRUE;
129
130 /**
131 * Explicitly declare the entity api name.
132 *
133 * @return string
134 */
135 public function getDefaultEntity() {
136 return 'Activity';
137 }
138
139 /**
140 * The _fields var can be used by sub class to set/unset/edit the
141 * form fields based on their requirement
142 */
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);
147
148 $this->_fields = [
149 'subject' => [
150 'type' => 'text',
151 'label' => ts('Subject'),
152 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'activity_subject'),
153 ],
154 'duration' => [
155 'type' => 'number',
156 'label' => ts('Duration'),
157 'attributes' => ['class' => 'four', 'min' => 1],
158 'required' => FALSE,
159 ],
160 'location' => [
161 'type' => 'text',
162 'label' => ts('Location'),
163 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'location'),
164 'required' => FALSE,
165 ],
166 'details' => [
167 'type' => 'wysiwyg',
168 'label' => ts('Details'),
169 'attributes' => ['class' => 'huge'],
170 'required' => FALSE,
171 ],
172 'status_id' => [
173 'type' => 'select',
174 'required' => TRUE,
175 ],
176 'priority_id' => [
177 'type' => 'select',
178 'required' => TRUE,
179 ],
180 'source_contact_id' => [
181 'type' => 'entityRef',
182 'label' => ts('Added by'),
183 'required' => FALSE,
184 ],
185 'target_contact_id' => [
186 'type' => 'entityRef',
187 'label' => ts('With Contact'),
188 'attributes' => ['multiple' => TRUE, 'create' => TRUE],
189 ],
190 'assignee_contact_id' => [
191 'type' => 'entityRef',
192 'label' => ts('Assigned to'),
193 'attributes' => [
194 'multiple' => TRUE,
195 'create' => TRUE,
196 'api' => ['params' => ['is_deceased' => 0]],
197 ],
198 ],
199 'activity_date_time' => [
200 'type' => 'datepicker',
201 'label' => ts('Date'),
202 'required' => TRUE,
203 ],
204 'followup_assignee_contact_id' => [
205 'type' => 'entityRef',
206 'label' => ts('Assigned to'),
207 'attributes' => [
208 'multiple' => TRUE,
209 'create' => TRUE,
210 'api' => ['params' => ['is_deceased' => 0]],
211 ],
212 ],
213 'followup_activity_type_id' => [
214 'type' => 'select',
215 'label' => ts('Followup Activity'),
216 'attributes' => ['' => '- ' . ts('select activity') . ' -'] + $activityTypes,
217 'extra' => ['class' => 'crm-select2'],
218 ],
219 // Add optional 'Subject' field for the Follow-up Activiity, CRM-4491
220 'followup_activity_subject' => [
221 'type' => 'text',
222 'label' => ts('Subject'),
223 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'subject'),
224 ],
225 ];
226 }
227
228 /**
229 * Build the form object.
230 *
231 * @throws \CRM_Core_Exception
232 */
233 public function preProcess() {
234 CRM_Core_Form_RecurringEntity::preProcess('civicrm_activity');
235
236 $session = CRM_Core_Session::singleton();
237 $this->_currentUserId = CRM_Core_Session::getLoggedInContactID();
238
239 $this->_currentlyViewedContactId = $this->get('contactId');
240 if (!$this->_currentlyViewedContactId) {
241 $this->_currentlyViewedContactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
242 }
243 $this->assign('contactId', $this->_currentlyViewedContactId);
244
245 // Give the context.
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';
250 }
251 elseif (!in_array($this->_context, ['dashlet', 'case', 'dashletFullscreen'])
252 && $this->_currentlyViewedContactId
253 ) {
254 $this->_context = 'activity';
255 }
256 $this->_compContext = CRM_Utils_Request::retrieve('compContext', 'String', $this);
257 }
258
259 $this->assign('context', $this->_context);
260
261 $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this);
262
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.'));
266 }
267 }
268
269 // CRM-6957
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.
272
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'
277 ) {
278 $this->_activityId = CRM_Utils_Request::retrieve('id', 'Positive', $this);
279 }
280
281 $this->_activityTypeId = CRM_Utils_Request::retrieve('atype', 'Positive', $this);
282 $this->assign('atype', $this->_activityTypeId);
283
284 $this->assign('activityId', $this->_activityId);
285
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,
291 ]) &&
292 !CRM_Activity_BAO_Activity::checkPermission($this->_activityId, $this->_action)
293 ) {
294 CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.'));
295 }
296 if (($this->_action & CRM_Core_Action::VIEW) &&
297 CRM_Activity_BAO_Activity::checkPermission($this->_activityId, CRM_Core_Action::UPDATE)
298 ) {
299 $this->assign('permission', 'edit');
300 $this->assign('allow_edit_inbound_emails', CRM_Activity_BAO_Activity::checkEditInboundEmailsPermissions());
301 }
302
303 if (!$this->_activityTypeId && $this->_activityId) {
304 $this->_activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity',
305 $this->_activityId,
306 'activity_type_id'
307 );
308 }
309
310 $this->assignActivityType();
311
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
317 ) {
318 $this->_single = TRUE;
319 $this->assign('urlPath', 'civicrm/activity');
320 }
321 else {
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) {
329 case 'basic':
330 $searchType = $searchPath;
331 $this->_action = CRM_Core_Action::BASIC;
332 break;
333
334 case 'advanced':
335 $searchType = $searchPath;
336 $this->_action = CRM_Core_Action::ADVANCED;
337 break;
338
339 case 'builder':
340 $searchType = $searchPath;
341 $this->_action = CRM_Core_Action::PROFILE;
342 break;
343
344 case 'custom':
345 $this->_action = CRM_Core_Action::COPY;
346 $searchType = $searchPath;
347 break;
348 }
349
350 parent::preProcess();
351 $this->_single = FALSE;
352
353 $this->assign('urlPath', "civicrm/contact/search/$searchType");
354 $this->assign('urlPathVar', "_qf_Activity_display=true&qfKey={$this->controller->_key}");
355 }
356
357 $this->assign('single', $this->_single);
358 $this->assign('action', $this->_action);
359
360 if ($this->_action & CRM_Core_Action::VIEW) {
361 // Get the tree of custom fields.
362 $this->_groupTree = CRM_Core_BAO_CustomGroup::getTree('Activity', NULL,
363 $this->_activityId, 0, $this->_activityTypeId, NULL, TRUE, NULL, FALSE, CRM_Core_Permission::VIEW
364 );
365 }
366
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);
372 }
373
374 // set user context
375 $urlParams = $urlString = NULL;
376 $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this);
377 if (!$qfKey) {
378 $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this);
379 }
380
381 // Validate the qfKey.
382 if (!CRM_Utils_Rule::qfKey($qfKey)) {
383 $qfKey = NULL;
384 }
385
386 if ($this->_context === 'fulltext') {
387 $keyName = '&qfKey';
388 $urlParams = 'force=1';
389 $urlString = 'civicrm/contact/search/custom';
390 if ($this->_action == CRM_Core_Action::UPDATE) {
391 $keyName = '&key';
392 $urlParams .= '&context=fulltext&action=view';
393 $urlString = 'civicrm/contact/view/activity';
394 }
395 if ($qfKey) {
396 $urlParams .= "$keyName=$qfKey";
397 }
398 $this->assign('searchKey', $qfKey);
399 }
400 elseif (in_array($this->_context, [
401 'standalone',
402 'home',
403 'dashlet',
404 'dashletFullscreen',
405 ])
406 ) {
407 $urlParams = 'reset=1';
408 $urlString = 'civicrm/dashboard';
409 }
410 elseif ($this->_context === 'search') {
411 $urlParams = 'force=1';
412 if ($qfKey) {
413 $urlParams .= "&qfKey=$qfKey";
414 }
415 $path = CRM_Utils_System::currentPath();
416 if ($this->_compContext === 'advanced') {
417 $urlString = 'civicrm/contact/search/advanced';
418 }
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'
425 ) {
426 $urlString = $path;
427 }
428 else {
429 $urlString = 'civicrm/activity/search';
430 }
431 $this->assign('searchKey', $qfKey);
432 }
433 elseif ($this->_context !== 'caseActivity') {
434 $urlParams = "action=browse&reset=1&cid={$this->_currentlyViewedContactId}&selectedChild=activity";
435 $urlString = 'civicrm/contact/view';
436 }
437
438 if ($urlString) {
439 $session->pushUserContext(CRM_Utils_System::url($urlString, $urlParams));
440 }
441
442 // hack to retrieve activity type id from post variables
443 if (!$this->_activityTypeId) {
444 $this->_activityTypeId = $_POST['activity_type_id'] ?? NULL;
445 }
446
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.
451 // CRM-3014
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);
459 }
460
461 // add attachments part
462 CRM_Core_BAO_File::buildAttachment($this, 'civicrm_activity', $this->_activityId, NULL, TRUE);
463
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)
467 ) {
468 $this->assign('activityTypeFile', $this->_activityTypeFile);
469 $this->assign('crmDir', $this->_crmDir);
470 }
471
472 $this->setFields();
473
474 if ($this->_activityTypeFile) {
475 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
476 $className::preProcess($this);
477 }
478
479 $this->_values = $this->get('values');
480 if (!is_array($this->_values)) {
481 $this->_values = [];
482 if (isset($this->_activityId) && $this->_activityId) {
483 $params = ['id' => $this->_activityId];
484 CRM_Activity_BAO_Activity::retrieve($params, $this->_values);
485 }
486
487 $this->set('values', $this->_values);
488 }
489
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']) ?: '';
494 }
495
496 if ($this->_activityTypeName === 'Inbound Email' &&
497 !CRM_Core_Permission::check('edit inbound email basic information and content')
498 ) {
499 $this->_fields['details']['type'] = 'static';
500 }
501
502 CRM_Core_Form_RecurringEntity::preProcess('civicrm_activity');
503 }
504
505 if ($this->_action & CRM_Core_Action::VIEW) {
506 $this->_values['details'] = CRM_Utils_String::purifyHtml($this->_values['details'] ?? '');
507 $url = CRM_Utils_System::url(implode("/", $this->urlPath), "reset=1&id={$this->_activityId}&action=view&cid={$this->_values['source_contact_id']}");
508 CRM_Utils_Recent::add(CRM_Utils_Array::value('subject', $this->_values, ts('(no subject)')),
509 $url,
510 $this->_values['id'],
511 'Activity',
512 $this->_values['source_contact_id'],
513 $this->_values['source_contact']
514 );
515 }
516 }
517
518 /**
519 * Set default values for the form.
520 *
521 * For edit/view mode the default values are retrieved from the database.
522 *
523 * @return array
524 */
525 public function setDefaultValues() {
526
527 $defaults = $this->_values + CRM_Core_Form_RecurringEntity::setDefaultValues();
528 // if we're editing...
529 if (isset($this->_activityId)) {
530
531 if ($this->_context !== 'standalone') {
532 $this->assign('target_contact_value',
533 CRM_Utils_Array::value('target_contact_value', $defaults)
534 );
535 $this->assign('assignee_contact_value',
536 CRM_Utils_Array::value('assignee_contact_value', $defaults)
537 );
538 }
539
540 // Fixme: why are we getting the wrong keys from upstream?
541 $defaults['target_contact_id'] = $defaults['target_contact'] ?? NULL;
542 $defaults['assignee_contact_id'] = $defaults['assignee_contact'] ?? NULL;
543
544 // set default tags if exists
545 $defaults['tag'] = implode(',', CRM_Core_BAO_EntityTag::getTag($this->_activityId, 'civicrm_activity'));
546 }
547 else {
548 // if it's a new activity, we need to set default values for associated contact fields
549 $this->_sourceContactId = $this->_currentUserId;
550 $this->_targetContactId = $this->_currentlyViewedContactId;
551
552 $defaults['source_contact_id'] = $this->_sourceContactId;
553 $defaults['target_contact_id'] = $this->_targetContactId;
554 }
555
556 if (empty($defaults['activity_date_time'])) {
557 $defaults['activity_date_time'] = date('Y-m-d H:i:s');
558 }
559
560 if ($this->_activityTypeId) {
561 $defaults['activity_type_id'] = $this->_activityTypeId;
562 }
563
564 if (!$this->_single && !empty($this->_contactIds)) {
565 $defaults['target_contact_id'] = $this->_contactIds;
566 }
567
568 // CRM-15472 - 50 is around the practical limit of how many items a select2 entityRef can handle
569 if ($this->_action == CRM_Core_Action::UPDATE && !empty($defaults['target_contact_id'])) {
570 $count = count(is_array($defaults['target_contact_id']) ? $defaults['target_contact_id'] : explode(',', $defaults['target_contact_id']));
571 if ($count > 50) {
572 $this->freeze(['target_contact_id']);
573 $this->assign('disable_swap_button', TRUE);
574 }
575 }
576
577 if ($this->_action & (CRM_Core_Action::DELETE | CRM_Core_Action::RENEW)) {
578 $this->assign('delName', CRM_Utils_Array::value('subject', $defaults));
579 }
580
581 if ($this->_activityTypeFile) {
582 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
583 $defaults += $className::setDefaultValues($this);
584 }
585 if (empty($defaults['priority_id'])) {
586 $priority = CRM_Core_PseudoConstant::get('CRM_Activity_DAO_Activity', 'priority_id');
587 $defaults['priority_id'] = CRM_Core_PseudoConstant::getKey('CRM_Activity_DAO_Activity', 'priority_id', 'Normal');
588 }
589 if (empty($defaults['status_id'])) {
590 $defaults['status_id'] = CRM_Core_OptionGroup::getDefaultValue('activity_status');
591 }
592 return $defaults;
593 }
594
595 /**
596 * Build Quick form.
597 *
598 * @throws \CRM_Core_Exception
599 * @throws \CiviCRM_API3_Exception
600 */
601 public function buildQuickForm() {
602 if ($this->_action & (CRM_Core_Action::DELETE | CRM_Core_Action::RENEW)) {
603 //enable form element (ActivityLinks sets this true)
604 $this->assign('suppressForm', FALSE);
605
606 $button = ts('Delete');
607 if ($this->_action & CRM_Core_Action::RENEW) {
608 $button = ts('Restore');
609 }
610 $this->addButtons([
611 [
612 'type' => 'next',
613 'name' => $button,
614 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
615 'isDefault' => TRUE,
616 ],
617 [
618 'type' => 'cancel',
619 'name' => ts('Cancel'),
620 ],
621 ]);
622 return;
623 }
624
625 // Build other activity links.
626 CRM_Activity_Form_ActivityLinks::commonBuildQuickForm($this);
627
628 // Enable form element (ActivityLinks sets this true).
629 $this->assign('suppressForm', FALSE);
630
631 $element = $this->add('select', 'activity_type_id', ts('Activity Type'),
632 $this->_fields['followup_activity_type_id']['attributes'],
633 FALSE, [
634 'onchange' => "CRM.buildCustomData( 'Activity', this.value, false, false, false, false, false, false, {$this->_currentlyViewedContactId});",
635 'class' => 'crm-select2 required',
636 'placeholder' => TRUE,
637 ]
638 );
639
640 // Freeze for update mode.
641 if ($this->_action & CRM_Core_Action::UPDATE) {
642 $element->freeze();
643 }
644
645 // Call to RecurringEntity buildQuickForm for add/update mode.
646 if ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD)) {
647 CRM_Core_Form_RecurringEntity::buildQuickForm($this);
648 }
649
650 foreach ($this->_fields as $field => $values) {
651 if (!empty($this->_fields[$field])) {
652 $attribute = $values['attributes'] ?? NULL;
653 $required = !empty($values['required']);
654
655 if ($values['type'] === 'select' && empty($attribute)) {
656 $this->addSelect($field, ['entity' => 'activity'], $required);
657 }
658 elseif ($values['type'] === 'entityRef') {
659 $this->addEntityRef($field, $values['label'], $attribute, $required);
660 }
661 else {
662 $this->add($values['type'], $field, $values['label'], $attribute, $required, CRM_Utils_Array::value('extra', $values));
663 }
664 }
665 }
666
667 // CRM-7362 --add campaigns.
668 CRM_Campaign_BAO_Campaign::addCampaign($this, CRM_Utils_Array::value('campaign_id', $this->_values));
669
670 // Add engagement level CRM-7775
671 $buildEngagementLevel = FALSE;
672 if (CRM_Campaign_BAO_Campaign::isCampaignEnable() &&
673 CRM_Campaign_BAO_Campaign::accessCampaign()
674 ) {
675 $buildEngagementLevel = TRUE;
676 $this->addSelect('engagement_level', ['entity' => 'activity']);
677 $this->addRule('engagement_level',
678 ts('Please enter the engagement index as a number (integers only).'),
679 'positiveInteger'
680 );
681 }
682 $this->assign('buildEngagementLevel', $buildEngagementLevel);
683
684 // check for survey activity
685 $this->_isSurveyActivity = FALSE;
686
687 if ($this->_activityId && CRM_Campaign_BAO_Campaign::isCampaignEnable() &&
688 CRM_Campaign_BAO_Campaign::accessCampaign()
689 ) {
690
691 $this->_isSurveyActivity = CRM_Campaign_BAO_Survey::isSurveyActivity($this->_activityId);
692 if ($this->_isSurveyActivity) {
693 $surveyId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity',
694 $this->_activityId,
695 'source_record_id'
696 );
697 $responseOptions = CRM_Campaign_BAO_Survey::getResponsesOptions($surveyId);
698 if ($responseOptions) {
699 $this->add('select', 'result', ts('Result'),
700 array_combine($responseOptions, $responseOptions),
701 FALSE, ['placeholder' => TRUE]
702 );
703 }
704 $surveyTitle = NULL;
705 if ($surveyId) {
706 $surveyTitle = CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $surveyId, 'title');
707 }
708 $this->assign('surveyTitle', $surveyTitle);
709 }
710 }
711 $this->assign('surveyActivity', $this->_isSurveyActivity);
712
713 // Add the "Activity Separation" field
714 $actionIsAdd = ($this->_action != CRM_Core_Action::UPDATE && $this->_action != CRM_Core_Action::VIEW);
715 $separationIsPossible = $this->supportsActivitySeparation;
716 if ($actionIsAdd && $separationIsPossible) {
717 $this->addRadio(
718 'separation',
719 ts('Activity Separation'),
720 [
721 'separate' => ts('Create separate activities for each contact'),
722 'combined' => ts('Create one activity with all contacts together'),
723 ]
724 );
725 }
726
727 $this->addRule('duration',
728 ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger'
729 );
730
731 // Add followup date.
732 $this->add('datepicker', 'followup_date', ts('in'));
733
734 // Only admins and case-workers can change the activity source
735 if (!CRM_Core_Permission::check('administer CiviCRM') && $this->_context !== 'caseActivity') {
736 $this->getElement('source_contact_id')->freeze();
737 }
738
739 //need to assign custom data type and subtype to the template
740 $this->assign('customDataType', 'Activity');
741 $this->assign('customDataSubType', $this->_activityTypeId);
742 $this->assign('entityID', $this->_activityId);
743
744 $tags = CRM_Core_BAO_Tag::getColorTags('civicrm_activity');
745
746 if (!empty($tags)) {
747 $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, [
748 'class' => 'huge',
749 'placeholder' => ts('- select -'),
750 'multiple' => TRUE,
751 ]);
752 }
753
754 // we need to hide activity tagset for special activities
755 $specialActivities = ['Open Case'];
756
757 if (!in_array($this->_activityTypeName, $specialActivities)) {
758 // build tag widget
759 $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_activity');
760 CRM_Core_Form_Tag::buildQuickForm($this, $parentNames, 'civicrm_activity', $this->_activityId);
761 }
762
763 // if we're viewing, we're assigning different buttons than for adding/editing
764 if ($this->_action & CRM_Core_Action::VIEW) {
765 if (isset($this->_groupTree)) {
766 CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $this->_groupTree, FALSE, NULL, NULL, NULL, $this->_activityId);
767 }
768 // form should be frozen for view mode
769 $this->freeze();
770
771 $this->addButtons([
772 [
773 'type' => 'cancel',
774 'name' => ts('Done'),
775 ],
776 ]);
777 }
778 else {
779 $this->addButtons([
780 [
781 'type' => 'upload',
782 'name' => ts('Save'),
783 'isDefault' => TRUE,
784 ],
785 [
786 'type' => 'cancel',
787 'name' => ts('Cancel'),
788 ],
789 ]);
790 }
791
792 if ($this->_activityTypeFile) {
793 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
794
795 $className::buildQuickForm($this);
796 $this->addFormRule([$className, 'formRule'], $this);
797 }
798
799 $this->addFormRule(['CRM_Activity_Form_Activity', 'formRule'], $this);
800
801 $doNotNotifyAssigneeFor = (array) Civi::settings()
802 ->get('do_not_notify_assignees_for');
803 if (($this->_activityTypeId && in_array($this->_activityTypeId, $doNotNotifyAssigneeFor)) || !Civi::settings()
804 ->get('activity_assignee_notification')) {
805 $this->assign('activityAssigneeNotification', FALSE);
806 }
807 else {
808 $this->assign('activityAssigneeNotification', TRUE);
809 }
810 $this->assign('doNotNotifyAssigneeFor', $doNotNotifyAssigneeFor);
811 }
812
813 /**
814 * Global form rule.
815 *
816 * @param array $fields
817 * The input form values.
818 * @param array $files
819 * The uploaded files if any.
820 * @param $self
821 *
822 * @return bool|array
823 * true if no errors, else array of errors
824 */
825 public static function formRule($fields, $files, $self) {
826 // skip form rule if deleting
827 if (($fields['_qf_Activity_next_'] ?? NULL) === 'Delete') {
828 return TRUE;
829 }
830 $errors = [];
831 if ((array_key_exists('activity_type_id', $fields) || !$self->_single) && empty($fields['activity_type_id'])) {
832 $errors['activity_type_id'] = ts('Activity Type is a required field');
833 }
834
835 $activity_type_id = $fields['activity_type_id'] ?? NULL;
836 $activity_status_id = $fields['status_id'] ?? NULL;
837 $scheduled_status_id = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled');
838
839 if ($activity_type_id && $activity_status_id == $scheduled_status_id) {
840 if ($activity_type_id == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email')) {
841 $errors['status_id'] = ts('You cannot record scheduled email activity.');
842 }
843 elseif ($activity_type_id == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS')) {
844 $errors['status_id'] = ts('You cannot record scheduled SMS activity');
845 }
846 }
847
848 if (!empty($fields['followup_activity_type_id']) && empty($fields['followup_date'])) {
849 $errors['followup_date'] = ts('Followup date is a required field.');
850 }
851 // Activity type is mandatory if subject or follow-up date is specified for an Follow-up activity, CRM-4515.
852 if ((!empty($fields['followup_activity_subject']) || !empty($fields['followup_date'])) && empty($fields['followup_activity_type_id'])) {
853 $errors['followup_activity_subject'] = ts('Follow-up Activity type is a required field.');
854 }
855
856 // Check that a value has been set for the "activity separation" field if needed
857 $separationIsPossible = $self->supportsActivitySeparation;
858 $actionIsAdd = $self->_action == CRM_Core_Action::ADD;
859 $hasMultipleTargetContacts = !empty($fields['target_contact_id']) && strpos($fields['target_contact_id'], ',') !== FALSE;
860 $separationFieldIsEmpty = empty($fields['separation']);
861 if ($separationIsPossible && $actionIsAdd && $hasMultipleTargetContacts && $separationFieldIsEmpty) {
862 $errors['separation'] = ts('Activity Separation is a required field.');
863 }
864
865 return $errors;
866 }
867
868 /**
869 * Process the form submission.
870 *
871 *
872 * @param array $params
873 *
874 * @return array|null
875 * @throws \CiviCRM_API3_Exception
876 */
877 public function postProcess($params = NULL) {
878 if ($this->_action & CRM_Core_Action::DELETE) {
879 // Look up any repeat activities to be deleted.
880 $activityIds = array_column(CRM_Core_BAO_RecurringEntity::getEntitiesFor($this->_activityId, 'civicrm_activity', TRUE, NULL), 'id');
881 if (!$activityIds) {
882 // There are no repeat activities to delete - just this one.
883 $activityIds = [$this->_activityId];
884 }
885
886 // Delete each activity.
887 foreach ($activityIds as $activityId) {
888 $deleteParams = ['id' => $activityId];
889 $moveToTrash = CRM_Case_BAO_Case::isCaseActivity($activityId);
890 CRM_Activity_BAO_Activity::deleteActivity($deleteParams, $moveToTrash);
891
892 // delete tags for the entity
893 $tagParams = [
894 'entity_table' => 'civicrm_activity',
895 'entity_id' => $activityId,
896 ];
897
898 CRM_Core_BAO_EntityTag::del($tagParams);
899 }
900
901 CRM_Core_Session::setStatus(
902 ts("Selected Activity has been deleted successfully.", ['plural' => '%count Activities have been deleted successfully.', 'count' => count($activityIds)]),
903 ts('Record Deleted', ['plural' => 'Records Deleted', 'count' => count($activityIds)]), 'success'
904 );
905
906 return NULL;
907 }
908
909 // store the submitted values in an array
910 if (!$params) {
911 $params = $this->controller->exportValues($this->_name);
912 }
913
914 // Set activity type id.
915 if (empty($params['activity_type_id'])) {
916 $params['activity_type_id'] = $this->_activityTypeId;
917 }
918
919 if (!empty($params['hidden_custom']) && !isset($params['custom'])) {
920 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
921 $this->_activityId,
922 'Activity'
923 );
924 }
925
926 // format params as arrays
927 foreach (['target', 'assignee', 'followup_assignee'] as $name) {
928 if (!empty($params["{$name}_contact_id"])) {
929 $params["{$name}_contact_id"] = explode(',', $params["{$name}_contact_id"]);
930 }
931 else {
932 $params["{$name}_contact_id"] = [];
933 }
934 }
935
936 // get ids for associated contacts
937 if (!$params['source_contact_id']) {
938 $params['source_contact_id'] = $this->_currentUserId;
939 }
940
941 if (isset($this->_activityId)) {
942 $params['id'] = $this->_activityId;
943 }
944
945 // add attachments as needed
946 CRM_Core_BAO_File::formatAttachment($params,
947 $params,
948 'civicrm_activity',
949 $this->_activityId
950 );
951
952 $params['is_multi_activity'] = ($params['separation'] ?? NULL) === 'separate';
953
954 $activity = [];
955 if (!empty($params['is_multi_activity']) &&
956 !CRM_Utils_Array::crmIsEmptyArray($params['target_contact_id'])
957 ) {
958 $targetContacts = $params['target_contact_id'];
959 foreach ($targetContacts as $targetContactId) {
960 $params['target_contact_id'] = [$targetContactId];
961 // save activity
962 $activity[] = $this->processActivity($params);
963 }
964 }
965 else {
966 // save activity
967 $activity = $this->processActivity($params);
968 }
969
970 // Redirect to contact page or activity view in standalone mode
971 if ($this->_context === 'standalone') {
972 if (count($params['target_contact_id']) == 1) {
973 $url = CRM_Utils_System::url('civicrm/contact/view', ['cid' => CRM_Utils_Array::first($params['target_contact_id']), 'selectedChild' => 'activity']);
974 }
975 else {
976 $url = CRM_Utils_System::url('civicrm/activity', ['action' => 'view', 'reset' => 1, 'id' => $this->_activityId]);
977 }
978 CRM_Core_Session::singleton()->pushUserContext($url);
979 }
980
981 $activityIds = empty($this->_activityIds) ? [$this->_activityId] : $this->_activityIds;
982 foreach ($activityIds as $activityId) {
983 // set params for repeat configuration in create mode
984 $params['entity_id'] = $activityId;
985 $params['entity_table'] = 'civicrm_activity';
986 if (!empty($params['entity_id']) && !empty($params['entity_table'])) {
987 $checkParentExistsForThisId = CRM_Core_BAO_RecurringEntity::getParentFor($params['entity_id'], $params['entity_table']);
988 if ($checkParentExistsForThisId) {
989 $params['parent_entity_id'] = $checkParentExistsForThisId;
990 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity::getReminderDetailsByEntityId($checkParentExistsForThisId, $params['entity_table']);
991 }
992 else {
993 $params['parent_entity_id'] = $params['entity_id'];
994 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity::getReminderDetailsByEntityId($params['entity_id'], $params['entity_table']);
995 }
996 if (property_exists($scheduleReminderDetails, 'id')) {
997 $params['schedule_reminder_id'] = $scheduleReminderDetails->id;
998 }
999 }
1000 $params['dateColumns'] = ['activity_date_time'];
1001
1002 // Set default repetition start if it was not provided.
1003 if (empty($params['repetition_start_date'])) {
1004 $params['repetition_start_date'] = $params['activity_date_time'];
1005 }
1006
1007 // unset activity id
1008 unset($params['id']);
1009 $linkedEntities = [
1010 [
1011 'table' => 'civicrm_activity_contact',
1012 'findCriteria' => [
1013 'activity_id' => $activityId,
1014 ],
1015 'linkedColumns' => ['activity_id'],
1016 'isRecurringEntityRecord' => FALSE,
1017 ],
1018 ];
1019 CRM_Core_Form_RecurringEntity::postProcess($params, 'civicrm_activity', $linkedEntities);
1020 }
1021
1022 return ['activity' => $activity];
1023 }
1024
1025 /**
1026 * Process activity creation.
1027 *
1028 * @param array $params
1029 * Associated array of submitted values.
1030 *
1031 * @return self|null|object
1032 * @throws \CRM_Core_Exception
1033 */
1034 protected function processActivity(&$params) {
1035 $activityAssigned = [];
1036 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
1037 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
1038 // format assignee params
1039 if (!CRM_Utils_Array::crmIsEmptyArray($params['assignee_contact_id'])) {
1040 //skip those assignee contacts which are already assigned
1041 //while sending a copy.CRM-4509.
1042 $activityAssigned = array_flip($params['assignee_contact_id']);
1043 if ($this->_activityId) {
1044 $assigneeContacts = CRM_Activity_BAO_ActivityContact::getNames($this->_activityId, $assigneeID);
1045 $activityAssigned = array_diff_key($activityAssigned, $assigneeContacts);
1046 }
1047 }
1048
1049 // call begin post process. Idea is to let injecting file do
1050 // any processing before the activity is added/updated.
1051 $this->beginPostProcess($params);
1052
1053 $activity = CRM_Activity_BAO_Activity::create($params);
1054
1055 // add tags if exists
1056 $tagParams = [];
1057 if (!empty($params['tag'])) {
1058 if (!is_array($params['tag'])) {
1059 $params['tag'] = explode(',', $params['tag']);
1060 }
1061
1062 $tagParams = array_fill_keys($params['tag'], 1);
1063 }
1064
1065 // Save static tags.
1066 CRM_Core_BAO_EntityTag::create($tagParams, 'civicrm_activity', $activity->id);
1067
1068 // Save free tags.
1069 if (isset($params['activity_taglist']) && !empty($params['activity_taglist'])) {
1070 CRM_Core_Form_Tag::postProcess($params['activity_taglist'], $activity->id, 'civicrm_activity', $this);
1071 }
1072
1073 // call end post process. Idea is to let injecting file do any
1074 // processing needed, after the activity has been added/updated.
1075 $this->endPostProcess($params, $activity);
1076
1077 // CRM-9590
1078 if (!empty($params['is_multi_activity'])) {
1079 $this->_activityIds[] = $activity->id;
1080 }
1081 else {
1082 $this->_activityId = $activity->id;
1083 }
1084
1085 // create follow up activity if needed
1086 $followupStatus = '';
1087 $followupActivity = NULL;
1088 if (!empty($params['followup_activity_type_id'])) {
1089 $followupActivity = CRM_Activity_BAO_Activity::createFollowupActivity($activity->id, $params);
1090 $followupStatus = ts('A followup activity has been scheduled.');
1091 }
1092
1093 // send copy to assignee contacts.CRM-4509
1094 $mailStatus = '';
1095
1096 if (Civi::settings()->get('activity_assignee_notification')
1097 && !in_array($activity->activity_type_id, Civi::settings()
1098 ->get('do_not_notify_assignees_for'))) {
1099 $activityIDs = [$activity->id];
1100 if ($followupActivity) {
1101 $activityIDs = array_merge($activityIDs, [$followupActivity->id]);
1102 }
1103 $assigneeContacts = CRM_Activity_BAO_ActivityAssignment::getAssigneeNames($activityIDs, TRUE, FALSE);
1104
1105 if (!CRM_Utils_Array::crmIsEmptyArray($params['assignee_contact_id'])) {
1106 $mailToContacts = [];
1107
1108 // Build an associative array with unique email addresses.
1109 foreach ($activityAssigned as $id => $dnc) {
1110 if (isset($id) && array_key_exists($id, $assigneeContacts)) {
1111 $mailToContacts[$assigneeContacts[$id]['email']] = $assigneeContacts[$id];
1112 }
1113 }
1114
1115 $sent = CRM_Activity_BAO_Activity::sendToAssignee($activity, $mailToContacts);
1116 if ($sent) {
1117 $mailStatus .= ts("A copy of the activity has also been sent to assignee contacts(s).");
1118 }
1119 }
1120
1121 // Also send email to follow-up activity assignees if set
1122 if ($followupActivity) {
1123 $mailToFollowupContacts = [];
1124 foreach ($assigneeContacts as $values) {
1125 if ($values['activity_id'] == $followupActivity->id) {
1126 $mailToFollowupContacts[$values['email']] = $values;
1127 }
1128 }
1129
1130 $sentFollowup = CRM_Activity_BAO_Activity::sendToAssignee($followupActivity, $mailToFollowupContacts);
1131 if ($sentFollowup) {
1132 $mailStatus .= '<br />' . ts("A copy of the follow-up activity has also been sent to follow-up assignee contacts(s).");
1133 }
1134 }
1135 }
1136
1137 // set status message
1138 $subject = '';
1139 if (!empty($params['subject'])) {
1140 $subject = "'" . $params['subject'] . "'";
1141 }
1142
1143 CRM_Core_Session::setStatus(ts('Activity %1 has been saved. %2 %3',
1144 [
1145 1 => $subject,
1146 2 => $followupStatus,
1147 3 => $mailStatus,
1148 ]
1149 ), ts('Saved'), 'success');
1150
1151 return $activity;
1152 }
1153
1154 /**
1155 * Shorthand for getting id by display name (makes code more readable)
1156 *
1157 * @param string $displayName
1158 *
1159 * @return null|string
1160 * @throws \CRM_Core_Exception
1161 */
1162 protected function _getIdByDisplayName($displayName) {
1163 return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
1164 $displayName,
1165 'id',
1166 'sort_name'
1167 );
1168 }
1169
1170 /**
1171 * Shorthand for getting display name by id (makes code more readable)
1172 *
1173 * @param int $id
1174 *
1175 * @return null|string
1176 * @throws \CRM_Core_Exception
1177 */
1178 protected function _getDisplayNameById($id) {
1179 return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
1180 $id,
1181 'sort_name',
1182 'id'
1183 );
1184 }
1185
1186 /**
1187 * Let injecting activity type file do any processing.
1188 * needed, before the activity is added/updated
1189 *
1190 * @param array $params
1191 */
1192 public function beginPostProcess(&$params) {
1193 if ($this->_activityTypeFile) {
1194 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1195 $className::beginPostProcess($this, $params);
1196 }
1197 }
1198
1199 /**
1200 * Let injecting activity type file do any processing
1201 * needed, after the activity has been added/updated
1202 *
1203 * @param array $params
1204 * @param $activity
1205 */
1206 public function endPostProcess(&$params, &$activity) {
1207 if ($this->_activityTypeFile) {
1208 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1209 $className::endPostProcess($this, $params, $activity);
1210 }
1211 }
1212
1213 /**
1214 * For the moment keeping this the same as the original pulled from preProcess(). Also note the "s" at the end of the function name - planning to change that but in baby steps.
1215 *
1216 * @return string[]
1217 */
1218 public function getActivityTypeDisplayLabels() {
1219 return CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId, 'label');
1220 }
1221
1222 /**
1223 * For the moment this is just pulled from preProcess
1224 */
1225 public function assignActivityType() {
1226 if ($this->_activityTypeId) {
1227 $activityTypeDisplayLabels = $this->getActivityTypeDisplayLabels();
1228 if ($activityTypeDisplayLabels[$this->_activityTypeId]) {
1229 $this->_activityTypeName = $activityTypeDisplayLabels[$this->_activityTypeId];
1230
1231 // At the moment this is duplicating other code in this section, but refactoring in small steps.
1232 $activityTypeObj = new CRM_Activity_BAO_ActivityType($this->_activityTypeId);
1233 $this->assign('activityTypeNameAndLabel', $activityTypeObj->getActivityType());
1234 }
1235 // Set title.
1236 if (isset($activityTypeDisplayLabels)) {
1237 // FIXME - it's not clear why the if line just above is needed here and why we can't just set this once above and re-use. What is interesting, but can't possibly be the reason, is that the first if block will fail if the label is the string '0', whereas this one won't. But who would have an activity type called '0'?
1238 $activityTypeDisplayLabel = $activityTypeDisplayLabels[$this->_activityTypeId] ?? NULL;
1239
1240 if ($this->_currentlyViewedContactId) {
1241 $displayName = CRM_Contact_BAO_Contact::displayName($this->_currentlyViewedContactId);
1242 // Check if this is default domain contact CRM-10482.
1243 if (CRM_Contact_BAO_Contact::checkDomainContact($this->_currentlyViewedContactId)) {
1244 $displayName .= ' (' . ts('default organization') . ')';
1245 }
1246 $this->setTitle($displayName . ' - ' . $activityTypeDisplayLabel);
1247 }
1248 else {
1249 $this->setTitle(ts('%1 Activity', [1 => $activityTypeDisplayLabel]));
1250 }
1251 }
1252 }
1253 }
1254
1255 }