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