Merge pull request #15098 from civicrm/5.17
[civicrm-core.git] / CRM / Activity / Form / Activity.php
1 <?php
2 /*
3 +--------------------------------------------------------------------+
4 | CiviCRM version 5 |
5 +--------------------------------------------------------------------+
6 | Copyright CiviCRM LLC (c) 2004-2019 |
7 +--------------------------------------------------------------------+
8 | This file is a part of CiviCRM. |
9 | |
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. |
13 | |
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. |
18 | |
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 +--------------------------------------------------------------------+
26 */
27
28 /**
29 *
30 * @package CRM
31 * @copyright CiviCRM LLC (c) 2004-2019
32 */
33
34 /**
35 * This class generates form components for Activity.
36 */
37 class CRM_Activity_Form_Activity extends CRM_Contact_Form_Task {
38
39 /**
40 * The id of the object being edited / created
41 *
42 * @var int
43 */
44 public $_activityId;
45
46 /**
47 * Store activity ids when multiple activities are created.
48 *
49 * @var int
50 */
51 public $_activityIds = [];
52
53 /**
54 * The id of activity type.
55 *
56 * @var int
57 */
58 public $_activityTypeId;
59
60 /**
61 * The name of activity type.
62 *
63 * @var string
64 */
65 public $_activityTypeName;
66
67 /**
68 * The id of currently viewed contact.
69 *
70 * @var int
71 */
72 public $_currentlyViewedContactId;
73
74 /**
75 * The id of source contact and target contact.
76 *
77 * @var int
78 */
79 protected $_sourceContactId;
80 protected $_targetContactId;
81 protected $_asigneeContactId;
82
83 protected $_single;
84
85 public $_context;
86 public $_compContext;
87 public $_action;
88 public $_activityTypeFile;
89
90 /**
91 * The id of the logged in user, used when add / edit
92 *
93 * @var int
94 */
95 public $_currentUserId;
96
97 /**
98 * The array of form field attributes.
99 *
100 * @var array
101 */
102 public $_fields;
103
104 /**
105 * The the directory inside CRM, to include activity type file from
106 *
107 * @var string
108 */
109 protected $_crmDir = 'Activity';
110
111 /**
112 * Survey activity.
113 *
114 * @var bool
115 */
116 protected $_isSurveyActivity;
117
118 protected $_values = [];
119
120 protected $unsavedWarn = TRUE;
121
122 /**
123 *
124 * Is it possible to create separate activities with this form?
125 *
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"
128 * field).
129 *
130 * When FALSE, the form will create one activity with all contacts together
131 * and won't ask the user anything.
132 *
133 * Note: This is a class property so that child classes can turn off this
134 * behavior (e.g. in CRM_Case_Form_Activity)
135 *
136 * @var bool
137 *
138 */
139 protected $supportsActivitySeparation = TRUE;
140
141 public $submitOnce = TRUE;
142
143 /**
144 * Explicitly declare the entity api name.
145 *
146 * @return string
147 */
148 public function getDefaultEntity() {
149 return 'Activity';
150 }
151
152 /**
153 * The _fields var can be used by sub class to set/unset/edit the
154 * form fields based on their requirement
155 */
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);
160
161 $this->_fields = [
162 'subject' => [
163 'type' => 'text',
164 'label' => ts('Subject'),
165 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'activity_subject'),
166 ],
167 'duration' => [
168 'type' => 'number',
169 'label' => ts('Duration'),
170 'attributes' => ['class' => 'four', 'min' => 1],
171 'required' => FALSE,
172 ],
173 'location' => [
174 'type' => 'text',
175 'label' => ts('Location'),
176 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'location'),
177 'required' => FALSE,
178 ],
179 'details' => [
180 'type' => 'wysiwyg',
181 'label' => ts('Details'),
182 'attributes' => ['class' => 'huge'],
183 'required' => FALSE,
184 ],
185 'status_id' => [
186 'type' => 'select',
187 'required' => TRUE,
188 ],
189 'priority_id' => [
190 'type' => 'select',
191 'required' => TRUE,
192 ],
193 'source_contact_id' => [
194 'type' => 'entityRef',
195 'label' => ts('Added By'),
196 'required' => FALSE,
197 ],
198 'target_contact_id' => [
199 'type' => 'entityRef',
200 'label' => ts('With Contact'),
201 'attributes' => ['multiple' => TRUE, 'create' => TRUE],
202 ],
203 'assignee_contact_id' => [
204 'type' => 'entityRef',
205 'label' => ts('Assigned to'),
206 'attributes' => [
207 'multiple' => TRUE,
208 'create' => TRUE,
209 'api' => ['params' => ['is_deceased' => 0]],
210 ],
211 ],
212 'activity_date_time' => [
213 'type' => 'datepicker',
214 'label' => ts('Date'),
215 'required' => TRUE,
216 ],
217 'followup_assignee_contact_id' => [
218 'type' => 'entityRef',
219 'label' => ts('Assigned to'),
220 'attributes' => [
221 'multiple' => TRUE,
222 'create' => TRUE,
223 'api' => ['params' => ['is_deceased' => 0]],
224 ],
225 ],
226 'followup_activity_type_id' => [
227 'type' => 'select',
228 'label' => ts('Followup Activity'),
229 'attributes' => ['' => '- ' . ts('select activity') . ' -'] + $activityTypes,
230 'extra' => ['class' => 'crm-select2'],
231 ],
232 // Add optional 'Subject' field for the Follow-up Activiity, CRM-4491
233 'followup_activity_subject' => [
234 'type' => 'text',
235 'label' => ts('Subject'),
236 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity', 'subject'),
237 ],
238 ];
239 }
240
241 /**
242 * Build the form object.
243 */
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);
250 }
251
252 $session = CRM_Core_Session::singleton();
253 $this->_currentUserId = CRM_Core_Session::getLoggedInContactID();
254
255 $this->_currentlyViewedContactId = $this->get('contactId');
256 if (!$this->_currentlyViewedContactId) {
257 $this->_currentlyViewedContactId = CRM_Utils_Request::retrieve('cid', 'Positive', $this);
258 }
259 $this->assign('contactId', $this->_currentlyViewedContactId);
260
261 // Give the context.
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';
266 }
267 elseif (!in_array($this->_context, ['dashlet', 'case', 'dashletFullscreen'])
268 && $this->_currentlyViewedContactId
269 ) {
270 $this->_context = 'activity';
271 }
272 $this->_compContext = CRM_Utils_Request::retrieve('compContext', 'String', $this);
273 }
274
275 $this->assign('context', $this->_context);
276
277 $this->_action = CRM_Utils_Request::retrieve('action', 'String', $this);
278
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.'));
282 }
283 }
284
285 // CRM-6957
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.
288
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'
293 ) {
294 $this->_activityId = CRM_Utils_Request::retrieve('id', 'Positive', $this);
295 }
296
297 $this->_activityTypeId = CRM_Utils_Request::retrieve('atype', 'Positive', $this);
298 $this->assign('atype', $this->_activityTypeId);
299
300 $this->assign('activityId', $this->_activityId);
301
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,
307 ]) &&
308 !CRM_Activity_BAO_Activity::checkPermission($this->_activityId, $this->_action)
309 ) {
310 CRM_Core_Error::statusBounce(ts('You do not have permission to access this page.'));
311 }
312 if (($this->_action & CRM_Core_Action::VIEW) &&
313 CRM_Activity_BAO_Activity::checkPermission($this->_activityId, CRM_Core_Action::UPDATE)
314 ) {
315 $this->assign('permission', 'edit');
316 $this->assign('allow_edit_inbound_emails', CRM_Activity_BAO_Activity::checkEditInboundEmailsPermissions());
317 }
318
319 if (!$this->_activityTypeId && $this->_activityId) {
320 $this->_activityTypeId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity',
321 $this->_activityId,
322 'activity_type_id'
323 );
324 }
325
326 $this->assignActivityType();
327
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
333 ) {
334 $this->_single = TRUE;
335 $this->assign('urlPath', 'civicrm/activity');
336 }
337 else {
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) {
345 case 'basic':
346 $searchType = $searchPath;
347 $this->_action = CRM_Core_Action::BASIC;
348 break;
349
350 case 'advanced':
351 $searchType = $searchPath;
352 $this->_action = CRM_Core_Action::ADVANCED;
353 break;
354
355 case 'builder':
356 $searchType = $searchPath;
357 $this->_action = CRM_Core_Action::PROFILE;
358 break;
359
360 case 'custom':
361 $this->_action = CRM_Core_Action::COPY;
362 $searchType = $searchPath;
363 break;
364 }
365
366 parent::preProcess();
367 $this->_single = FALSE;
368
369 $this->assign('urlPath', "civicrm/contact/search/$searchType");
370 $this->assign('urlPathVar', "_qf_Activity_display=true&qfKey={$this->controller->_key}");
371 }
372
373 $this->assign('single', $this->_single);
374 $this->assign('action', $this->_action);
375
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
380 );
381 }
382
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);
388 }
389
390 // set user context
391 $urlParams = $urlString = NULL;
392 $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this);
393 if (!$qfKey) {
394 $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this);
395 }
396
397 // Validate the qfKey.
398 if (!CRM_Utils_Rule::qfKey($qfKey)) {
399 $qfKey = NULL;
400 }
401
402 if ($this->_context == 'fulltext') {
403 $keyName = '&qfKey';
404 $urlParams = 'force=1';
405 $urlString = 'civicrm/contact/search/custom';
406 if ($this->_action == CRM_Core_Action::UPDATE) {
407 $keyName = '&key';
408 $urlParams .= '&context=fulltext&action=view';
409 $urlString = 'civicrm/contact/view/activity';
410 }
411 if ($qfKey) {
412 $urlParams .= "$keyName=$qfKey";
413 }
414 $this->assign('searchKey', $qfKey);
415 }
416 elseif (in_array($this->_context, [
417 'standalone',
418 'home',
419 'dashlet',
420 'dashletFullscreen',
421 ])
422 ) {
423 $urlParams = 'reset=1';
424 $urlString = 'civicrm/dashboard';
425 }
426 elseif ($this->_context == 'search') {
427 $urlParams = 'force=1';
428 if ($qfKey) {
429 $urlParams .= "&qfKey=$qfKey";
430 }
431 $path = CRM_Utils_System::currentPath();
432 if ($this->_compContext == 'advanced') {
433 $urlString = 'civicrm/contact/search/advanced';
434 }
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'
440 ) {
441 $urlString = $path;
442 }
443 else {
444 $urlString = 'civicrm/activity/search';
445 }
446 $this->assign('searchKey', $qfKey);
447 }
448 elseif ($this->_context != 'caseActivity') {
449 $urlParams = "action=browse&reset=1&cid={$this->_currentlyViewedContactId}&selectedChild=activity";
450 $urlString = 'civicrm/contact/view';
451 }
452
453 if ($urlString) {
454 $session->pushUserContext(CRM_Utils_System::url($urlString, $urlParams));
455 }
456
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);
460 }
461
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.
466 // CRM-3014
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);
474 }
475
476 // add attachments part
477 CRM_Core_BAO_File::buildAttachment($this, 'civicrm_activity', $this->_activityId, NULL, TRUE);
478
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)
482 ) {
483 $this->assign('activityTypeFile', $this->_activityTypeFile);
484 $this->assign('crmDir', $this->_crmDir);
485 }
486
487 $this->setFields();
488
489 if ($this->_activityTypeFile) {
490 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
491 $className::preProcess($this);
492 }
493
494 $this->_values = $this->get('values');
495 if (!is_array($this->_values)) {
496 $this->_values = [];
497 if (isset($this->_activityId) && $this->_activityId) {
498 $params = ['id' => $this->_activityId];
499 CRM_Activity_BAO_Activity::retrieve($params, $this->_values);
500 }
501
502 $this->set('values', $this->_values);
503 }
504
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']) ?: '';
509 }
510
511 if ($this->_activityTypeName === 'Inbound Email' &&
512 !CRM_Core_Permission::check('edit inbound email basic information and content')
513 ) {
514 $this->_fields['details']['type'] = 'static';
515 }
516
517 CRM_Core_Form_RecurringEntity::preProcess('civicrm_activity');
518 }
519
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)')),
523 $url,
524 $this->_values['id'],
525 'Activity',
526 $this->_values['source_contact_id'],
527 $this->_values['source_contact']
528 );
529 }
530 }
531
532 /**
533 * Set default values for the form.
534 *
535 * For edit/view mode the default values are retrieved from the database.
536 *
537 * @return array
538 */
539 public function setDefaultValues() {
540
541 $defaults = $this->_values + CRM_Core_Form_RecurringEntity::setDefaultValues();
542 // if we're editing...
543 if (isset($this->_activityId)) {
544
545 if ($this->_context != 'standalone') {
546 $this->assign('target_contact_value',
547 CRM_Utils_Array::value('target_contact_value', $defaults)
548 );
549 $this->assign('assignee_contact_value',
550 CRM_Utils_Array::value('assignee_contact_value', $defaults)
551 );
552 }
553
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);
557
558 // set default tags if exists
559 $defaults['tag'] = implode(',', CRM_Core_BAO_EntityTag::getTag($this->_activityId, 'civicrm_activity'));
560 }
561 else {
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;
565
566 $defaults['source_contact_id'] = $this->_sourceContactId;
567 $defaults['target_contact_id'] = $this->_targetContactId;
568 }
569
570 if (empty($defaults['activity_date_time'])) {
571 $defaults['activity_date_time'] = date('Y-m-d H:i:s');
572 }
573
574 if ($this->_activityTypeId) {
575 $defaults['activity_type_id'] = $this->_activityTypeId;
576 }
577
578 if (!$this->_single && !empty($this->_contactIds)) {
579 $defaults['target_contact_id'] = $this->_contactIds;
580 }
581
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']));
585 if ($count > 50) {
586 $this->freeze(['target_contact_id']);
587 }
588 }
589
590 if ($this->_action & (CRM_Core_Action::DELETE | CRM_Core_Action::RENEW)) {
591 $this->assign('delName', CRM_Utils_Array::value('subject', $defaults));
592 }
593
594 if ($this->_activityTypeFile) {
595 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
596 $defaults += $className::setDefaultValues($this);
597 }
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);
601 }
602 if (empty($defaults['status_id'])) {
603 $defaults['status_id'] = CRM_Core_OptionGroup::getDefaultValue('activity_status');
604 }
605 return $defaults;
606 }
607
608 /**
609 * Build Quick form.
610 *
611 * @throws \CRM_Core_Exception
612 * @throws \CiviCRM_API3_Exception
613 */
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);
618
619 $button = ts('Delete');
620 if ($this->_action & CRM_Core_Action::RENEW) {
621 $button = ts('Restore');
622 }
623 $this->addButtons([
624 [
625 'type' => 'next',
626 'name' => $button,
627 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
628 'isDefault' => TRUE,
629 ],
630 [
631 'type' => 'cancel',
632 'name' => ts('Cancel'),
633 ],
634 ]);
635 return;
636 }
637
638 // Build other activity links.
639 CRM_Activity_Form_ActivityLinks::commonBuildQuickForm($this);
640
641 // Enable form element (ActivityLinks sets this true).
642 $this->assign('suppressForm', FALSE);
643
644 $element = &$this->add('select', 'activity_type_id', ts('Activity Type'),
645 ['' => '- ' . ts('select') . ' -'] + $this->_fields['followup_activity_type_id']['attributes'],
646 FALSE, [
647 'onchange' => "CRM.buildCustomData( 'Activity', this.value, false, false, false, false, false, false, {$this->_currentlyViewedContactId});",
648 'class' => 'crm-select2 required',
649 ]
650 );
651
652 // Freeze for update mode.
653 if ($this->_action & CRM_Core_Action::UPDATE) {
654 $element->freeze();
655 }
656
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);
660 }
661
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']);
666
667 if ($values['type'] == 'select' && empty($attribute)) {
668 $this->addSelect($field, ['entity' => 'activity'], $required);
669 }
670 elseif ($values['type'] == 'entityRef') {
671 $this->addEntityRef($field, $values['label'], $attribute, $required);
672 }
673 else {
674 $this->add($values['type'], $field, $values['label'], $attribute, $required, CRM_Utils_Array::value('extra', $values));
675 }
676 }
677 }
678
679 // CRM-7362 --add campaigns.
680 CRM_Campaign_BAO_Campaign::addCampaign($this, CRM_Utils_Array::value('campaign_id', $this->_values));
681
682 // Add engagement level CRM-7775
683 $buildEngagementLevel = FALSE;
684 if (CRM_Campaign_BAO_Campaign::isCampaignEnable() &&
685 CRM_Campaign_BAO_Campaign::accessCampaign()
686 ) {
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).'),
691 'positiveInteger'
692 );
693 }
694 $this->assign('buildEngagementLevel', $buildEngagementLevel);
695
696 // check for survey activity
697 $this->_isSurveyActivity = FALSE;
698
699 if ($this->_activityId && CRM_Campaign_BAO_Campaign::isCampaignEnable() &&
700 CRM_Campaign_BAO_Campaign::accessCampaign()
701 ) {
702
703 $this->_isSurveyActivity = CRM_Campaign_BAO_Survey::isSurveyActivity($this->_activityId);
704 if ($this->_isSurveyActivity) {
705 $surveyId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity',
706 $this->_activityId,
707 'source_record_id'
708 );
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)
713 );
714 }
715 $surveyTitle = NULL;
716 if ($surveyId) {
717 $surveyTitle = CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $surveyId, 'title');
718 }
719 $this->assign('surveyTitle', $surveyTitle);
720 }
721 }
722 $this->assign('surveyActivity', $this->_isSurveyActivity);
723
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) {
728 $this->addRadio(
729 'separation',
730 ts('Activity Separation'),
731 [
732 'separate' => ts('Create separate activities for each contact'),
733 'combined' => ts('Create one activity with all contacts together'),
734 ]
735 );
736 }
737
738 $this->addRule('duration',
739 ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger'
740 );
741
742 // Add followup date.
743 $this->add('datepicker', 'followup_date', ts('in'));
744
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();
748 }
749
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);
754
755 $tags = CRM_Core_BAO_Tag::getColorTags('civicrm_activity');
756
757 if (!empty($tags)) {
758 $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, [
759 'class' => 'huge',
760 'placeholder' => ts('- select -'),
761 'multiple' => TRUE,
762 ]);
763 }
764
765 // we need to hide activity tagset for special activities
766 $specialActivities = ['Open Case'];
767
768 if (!in_array($this->_activityTypeName, $specialActivities)) {
769 // build tag widget
770 $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_activity');
771 CRM_Core_Form_Tag::buildQuickForm($this, $parentNames, 'civicrm_activity', $this->_activityId);
772 }
773
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);
778 }
779 // form should be frozen for view mode
780 $this->freeze();
781
782 $this->addButtons([
783 [
784 'type' => 'cancel',
785 'name' => ts('Done'),
786 ],
787 ]);
788 }
789 else {
790 $this->addButtons([
791 [
792 'type' => 'upload',
793 'name' => ts('Save'),
794 'isDefault' => TRUE,
795 ],
796 [
797 'type' => 'cancel',
798 'name' => ts('Cancel'),
799 ],
800 ]);
801 }
802
803 if ($this->_activityTypeFile) {
804 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
805
806 $className::buildQuickForm($this);
807 $this->addFormRule([$className, 'formRule'], $this);
808 }
809
810 $this->addFormRule(['CRM_Activity_Form_Activity', 'formRule'], $this);
811
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);
817 }
818 else {
819 $this->assign('activityAssigneeNotification', TRUE);
820 }
821 $this->assign('doNotNotifyAssigneeFor', $doNotNotifyAssigneeFor);
822 }
823
824 /**
825 * Global form rule.
826 *
827 * @param array $fields
828 * The input form values.
829 * @param array $files
830 * The uploaded files if any.
831 * @param $self
832 *
833 * @return bool|array
834 * true if no errors, else array of errors
835 */
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') {
839 return TRUE;
840 }
841 $errors = [];
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');
844 }
845
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');
849
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.');
853 }
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');
856 }
857 }
858
859 if (!empty($fields['followup_activity_type_id']) && empty($fields['followup_date'])) {
860 $errors['followup_date'] = ts('Followup date is a required field.');
861 }
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.');
865 }
866
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.');
874 }
875
876 return $errors;
877 }
878
879 /**
880 * Process the form submission.
881 *
882 *
883 * @param array $params
884 *
885 * @return array|null
886 * @throws \CiviCRM_API3_Exception
887 */
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');
892 if (!$activityIds) {
893 // There are no repeat activities to delete - just this one.
894 $activityIds = [$this->_activityId];
895 }
896
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);
902
903 // delete tags for the entity
904 $tagParams = [
905 'entity_table' => 'civicrm_activity',
906 'entity_id' => $activityId,
907 ];
908
909 CRM_Core_BAO_EntityTag::del($tagParams);
910 }
911
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'
915 );
916
917 return NULL;
918 }
919
920 // store the submitted values in an array
921 if (!$params) {
922 $params = $this->controller->exportValues($this->_name);
923 }
924
925 // Set activity type id.
926 if (empty($params['activity_type_id'])) {
927 $params['activity_type_id'] = $this->_activityTypeId;
928 }
929
930 if (!empty($params['hidden_custom']) &&
931 !isset($params['custom'])
932 ) {
933 $customFields = CRM_Core_BAO_CustomField::getFields('Activity', FALSE, FALSE,
934 $this->_activityTypeId
935 );
936 $customFields = CRM_Utils_Array::crmArrayMerge($customFields,
937 CRM_Core_BAO_CustomField::getFields('Activity', FALSE, FALSE,
938 NULL, NULL, TRUE
939 )
940 );
941 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
942 $this->_activityId,
943 'Activity'
944 );
945 }
946
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"]);
951 }
952 else {
953 $params["{$name}_contact_id"] = [];
954 }
955 }
956
957 // get ids for associated contacts
958 if (!$params['source_contact_id']) {
959 $params['source_contact_id'] = $this->_currentUserId;
960 }
961
962 if (isset($this->_activityId)) {
963 $params['id'] = $this->_activityId;
964 }
965
966 // add attachments as needed
967 CRM_Core_BAO_File::formatAttachment($params,
968 $params,
969 'civicrm_activity',
970 $this->_activityId
971 );
972
973 $params['is_multi_activity'] = CRM_Utils_Array::value('separation', $params) == 'separate';
974
975 $activity = [];
976 if (!empty($params['is_multi_activity']) &&
977 !CRM_Utils_Array::crmIsEmptyArray($params['target_contact_id'])
978 ) {
979 $targetContacts = $params['target_contact_id'];
980 foreach ($targetContacts as $targetContactId) {
981 $params['target_contact_id'] = [$targetContactId];
982 // save activity
983 $activity[] = $this->processActivity($params);
984 }
985 }
986 else {
987 // save activity
988 $activity = $this->processActivity($params);
989 }
990
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']);
995 }
996 else {
997 $url = CRM_Utils_System::url('civicrm/activity', ['action' => 'view', 'reset' => 1, 'id' => $this->_activityId]);
998 }
999 CRM_Core_Session::singleton()->pushUserContext($url);
1000 }
1001
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']);
1012 }
1013 else {
1014 $params['parent_entity_id'] = $params['entity_id'];
1015 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity::getReminderDetailsByEntityId($params['entity_id'], $params['entity_table']);
1016 }
1017 if (property_exists($scheduleReminderDetails, 'id')) {
1018 $params['schedule_reminder_id'] = $scheduleReminderDetails->id;
1019 }
1020 }
1021 $params['dateColumns'] = ['activity_date_time'];
1022
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'];
1026 }
1027
1028 // unset activity id
1029 unset($params['id']);
1030 $linkedEntities = [
1031 [
1032 'table' => 'civicrm_activity_contact',
1033 'findCriteria' => [
1034 'activity_id' => $activityId,
1035 ],
1036 'linkedColumns' => ['activity_id'],
1037 'isRecurringEntityRecord' => FALSE,
1038 ],
1039 ];
1040 CRM_Core_Form_RecurringEntity::postProcess($params, 'civicrm_activity', $linkedEntities);
1041 }
1042
1043 return ['activity' => $activity];
1044 }
1045
1046 /**
1047 * Process activity creation.
1048 *
1049 * @param array $params
1050 * Associated array of submitted values.
1051 *
1052 * @return self|null|object
1053 */
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);
1066 }
1067 }
1068
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);
1072
1073 $activity = CRM_Activity_BAO_Activity::create($params);
1074
1075 // add tags if exists
1076 $tagParams = [];
1077 if (!empty($params['tag'])) {
1078 if (!is_array($params['tag'])) {
1079 $params['tag'] = explode(',', $params['tag']);
1080 }
1081
1082 $tagParams = array_fill_keys($params['tag'], 1);
1083 }
1084
1085 // Save static tags.
1086 CRM_Core_BAO_EntityTag::create($tagParams, 'civicrm_activity', $activity->id);
1087
1088 // Save free tags.
1089 if (isset($params['activity_taglist']) && !empty($params['activity_taglist'])) {
1090 CRM_Core_Form_Tag::postProcess($params['activity_taglist'], $activity->id, 'civicrm_activity', $this);
1091 }
1092
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);
1096
1097 // CRM-9590
1098 if (!empty($params['is_multi_activity'])) {
1099 $this->_activityIds[] = $activity->id;
1100 }
1101 else {
1102 $this->_activityId = $activity->id;
1103 }
1104
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.');
1111 }
1112
1113 // send copy to assignee contacts.CRM-4509
1114 $mailStatus = '';
1115
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]);
1122 }
1123 $assigneeContacts = CRM_Activity_BAO_ActivityAssignment::getAssigneeNames($activityIDs, TRUE, FALSE);
1124
1125 if (!CRM_Utils_Array::crmIsEmptyArray($params['assignee_contact_id'])) {
1126 $mailToContacts = [];
1127
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];
1132 }
1133 }
1134
1135 $sent = CRM_Activity_BAO_Activity::sendToAssignee($activity, $mailToContacts);
1136 if ($sent) {
1137 $mailStatus .= ts("A copy of the activity has also been sent to assignee contacts(s).");
1138 }
1139 }
1140
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;
1147 }
1148 }
1149
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).");
1153 }
1154 }
1155 }
1156
1157 // set status message
1158 $subject = '';
1159 if (!empty($params['subject'])) {
1160 $subject = "'" . $params['subject'] . "'";
1161 }
1162
1163 CRM_Core_Session::setStatus(ts('Activity %1 has been saved. %2 %3',
1164 [
1165 1 => $subject,
1166 2 => $followupStatus,
1167 3 => $mailStatus,
1168 ]
1169 ), ts('Saved'), 'success');
1170
1171 return $activity;
1172 }
1173
1174 /**
1175 * Shorthand for getting id by display name (makes code more readable)
1176 * @param $displayName
1177 * @return null|string
1178 */
1179 protected function _getIdByDisplayName($displayName) {
1180 return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
1181 $displayName,
1182 'id',
1183 'sort_name'
1184 );
1185 }
1186
1187 /**
1188 * Shorthand for getting display name by id (makes code more readable)
1189 * @param $id
1190 * @return null|string
1191 */
1192 protected function _getDisplayNameById($id) {
1193 return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
1194 $id,
1195 'sort_name',
1196 'id'
1197 );
1198 }
1199
1200 /**
1201 * Let injecting activity type file do any processing.
1202 * needed, before the activity is added/updated
1203 *
1204 * @param array $params
1205 */
1206 public function beginPostProcess(&$params) {
1207 if ($this->_activityTypeFile) {
1208 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1209 $className::beginPostProcess($this, $params);
1210 }
1211 }
1212
1213 /**
1214 * Let injecting activity type file do any processing
1215 * needed, after the activity has been added/updated
1216 *
1217 * @param array $params
1218 * @param $activity
1219 */
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);
1224 }
1225 }
1226
1227 /**
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.
1229 *
1230 * @return string[]
1231 */
1232 public function getActivityTypeDisplayLabels() {
1233 return CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId, 'label');
1234 }
1235
1236 /**
1237 * For the moment this is just pulled from preProcess
1238 */
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]);
1246 }
1247 // Set title.
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);
1251
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') . ')';
1257 }
1258 CRM_Utils_System::setTitle($displayName . ' - ' . $activityTypeDisplayLabel);
1259 }
1260 else {
1261 CRM_Utils_System::setTitle(ts('%1 Activity', [1 => $activityTypeDisplayLabel]));
1262 }
1263 }
1264 }
1265 }
1266
1267 }