(no commit message)
[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 // Assigning Activity type name.
327 if ($this->_activityTypeId) {
328 $activityTypeDisplayLabels = CRM_Core_OptionGroup::values('activity_type', FALSE, FALSE, FALSE, 'AND v.value = ' . $this->_activityTypeId, 'label');
329 if ($activityTypeDisplayLabels[$this->_activityTypeId]) {
330 $this->_activityTypeName = $activityTypeDisplayLabels[$this->_activityTypeId];
331 // can't change this instance of activityTName yet - will come back to it dev/core#1116
332 $this->assign('activityTName', $activityTypeDisplayLabels[$this->_activityTypeId]);
333 }
334 // Set title.
335 if (isset($activityTypeDisplayLabels)) {
336 // FIXME - it's not clear why the if line just above is needed here and why we can't just set this once above and re-use. What is interesting, but can't possibly be the reason, is that the first if block will fail if the label is the string '0', whereas this one won't. But who would have an activity type called '0'?
337 $activityTypeDisplayLabel = CRM_Utils_Array::value($this->_activityTypeId, $activityTypeDisplayLabels);
338
339 if ($this->_currentlyViewedContactId) {
340 $displayName = CRM_Contact_BAO_Contact::displayName($this->_currentlyViewedContactId);
341 // Check if this is default domain contact CRM-10482.
342 if (CRM_Contact_BAO_Contact::checkDomainContact($this->_currentlyViewedContactId)) {
343 $displayName .= ' (' . ts('default organization') . ')';
344 }
345 CRM_Utils_System::setTitle($displayName . ' - ' . $activityTypeDisplayLabel);
346 }
347 else {
348 CRM_Utils_System::setTitle(ts('%1 Activity', [1 => $activityTypeDisplayLabel]));
349 }
350 }
351 }
352
353 // Check the mode when this form is called either single or as
354 // search task action.
355 if ($this->_activityTypeId ||
356 $this->_context == 'standalone' ||
357 $this->_currentlyViewedContactId
358 ) {
359 $this->_single = TRUE;
360 $this->assign('urlPath', 'civicrm/activity');
361 }
362 else {
363 // Set the appropriate action.
364 $url = CRM_Utils_System::currentPath();
365 $urlArray = explode('/', $url);
366 $searchPath = array_pop($urlArray);
367 $searchType = 'basic';
368 $this->_action = CRM_Core_Action::BASIC;
369 switch ($searchPath) {
370 case 'basic':
371 $searchType = $searchPath;
372 $this->_action = CRM_Core_Action::BASIC;
373 break;
374
375 case 'advanced':
376 $searchType = $searchPath;
377 $this->_action = CRM_Core_Action::ADVANCED;
378 break;
379
380 case 'builder':
381 $searchType = $searchPath;
382 $this->_action = CRM_Core_Action::PROFILE;
383 break;
384
385 case 'custom':
386 $this->_action = CRM_Core_Action::COPY;
387 $searchType = $searchPath;
388 break;
389 }
390
391 parent::preProcess();
392 $this->_single = FALSE;
393
394 $this->assign('urlPath', "civicrm/contact/search/$searchType");
395 $this->assign('urlPathVar', "_qf_Activity_display=true&qfKey={$this->controller->_key}");
396 }
397
398 $this->assign('single', $this->_single);
399 $this->assign('action', $this->_action);
400
401 if ($this->_action & CRM_Core_Action::VIEW) {
402 // Get the tree of custom fields.
403 $this->_groupTree = CRM_Core_BAO_CustomGroup::getTree('Activity', NULL,
404 $this->_activityId, 0, $this->_activityTypeId
405 );
406 }
407
408 if ($this->_activityTypeId) {
409 // Set activity type name and description to template.
410 list($this->_activityTypeName, $activityTypeDescription) = CRM_Core_BAO_OptionValue::getActivityTypeDetails($this->_activityTypeId);
411 $this->assign('activityTypeName', $this->_activityTypeName);
412 $this->assign('activityTypeDescription', $activityTypeDescription);
413 }
414
415 // set user context
416 $urlParams = $urlString = NULL;
417 $qfKey = CRM_Utils_Request::retrieve('key', 'String', $this);
418 if (!$qfKey) {
419 $qfKey = CRM_Utils_Request::retrieve('qfKey', 'String', $this);
420 }
421
422 // Validate the qfKey.
423 if (!CRM_Utils_Rule::qfKey($qfKey)) {
424 $qfKey = NULL;
425 }
426
427 if ($this->_context == 'fulltext') {
428 $keyName = '&qfKey';
429 $urlParams = 'force=1';
430 $urlString = 'civicrm/contact/search/custom';
431 if ($this->_action == CRM_Core_Action::UPDATE) {
432 $keyName = '&key';
433 $urlParams .= '&context=fulltext&action=view';
434 $urlString = 'civicrm/contact/view/activity';
435 }
436 if ($qfKey) {
437 $urlParams .= "$keyName=$qfKey";
438 }
439 $this->assign('searchKey', $qfKey);
440 }
441 elseif (in_array($this->_context, [
442 'standalone',
443 'home',
444 'dashlet',
445 'dashletFullscreen',
446 ])
447 ) {
448 $urlParams = 'reset=1';
449 $urlString = 'civicrm/dashboard';
450 }
451 elseif ($this->_context == 'search') {
452 $urlParams = 'force=1';
453 if ($qfKey) {
454 $urlParams .= "&qfKey=$qfKey";
455 }
456 $path = CRM_Utils_System::currentPath();
457 if ($this->_compContext == 'advanced') {
458 $urlString = 'civicrm/contact/search/advanced';
459 }
460 elseif ($path == 'civicrm/group/search'
461 || $path == 'civicrm/contact/search'
462 || $path == 'civicrm/contact/search/advanced'
463 || $path == 'civicrm/contact/search/custom'
464 || $path == 'civicrm/group/search'
465 ) {
466 $urlString = $path;
467 }
468 else {
469 $urlString = 'civicrm/activity/search';
470 }
471 $this->assign('searchKey', $qfKey);
472 }
473 elseif ($this->_context != 'caseActivity') {
474 $urlParams = "action=browse&reset=1&cid={$this->_currentlyViewedContactId}&selectedChild=activity";
475 $urlString = 'civicrm/contact/view';
476 }
477
478 if ($urlString) {
479 $session->pushUserContext(CRM_Utils_System::url($urlString, $urlParams));
480 }
481
482 // hack to retrieve activity type id from post variables
483 if (!$this->_activityTypeId) {
484 $this->_activityTypeId = CRM_Utils_Array::value('activity_type_id', $_POST);
485 }
486
487 // when custom data is included in this page
488 if (!empty($_POST['hidden_custom'])) {
489 // We need to set it in the session for the code below to work.
490 // CRM-3014
491 // Need to assign custom data subtype to the template.
492 $this->set('type', 'Activity');
493 $this->set('subType', $this->_activityTypeId);
494 $this->set('entityId', $this->_activityId);
495 CRM_Custom_Form_CustomData::preProcess($this, NULL, $this->_activityTypeId, 1, 'Activity', $this->_activityId);
496 CRM_Custom_Form_CustomData::buildQuickForm($this);
497 CRM_Custom_Form_CustomData::setDefaultValues($this);
498 }
499
500 // add attachments part
501 CRM_Core_BAO_File::buildAttachment($this, 'civicrm_activity', $this->_activityId, NULL, TRUE);
502
503 // figure out the file name for activity type, if any
504 if ($this->_activityTypeId &&
505 $this->_activityTypeFile = CRM_Activity_BAO_Activity::getFileForActivityTypeId($this->_activityTypeId, $this->_crmDir)
506 ) {
507 $this->assign('activityTypeFile', $this->_activityTypeFile);
508 $this->assign('crmDir', $this->_crmDir);
509 }
510
511 $this->setFields();
512
513 if ($this->_activityTypeFile) {
514 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
515 $className::preProcess($this);
516 }
517
518 $this->_values = $this->get('values');
519 if (!is_array($this->_values)) {
520 $this->_values = [];
521 if (isset($this->_activityId) && $this->_activityId) {
522 $params = ['id' => $this->_activityId];
523 CRM_Activity_BAO_Activity::retrieve($params, $this->_values);
524 }
525
526 $this->set('values', $this->_values);
527 }
528
529 if ($this->_action & CRM_Core_Action::UPDATE) {
530 // We filter out alternatives, in case this is a stored e-mail, before sending to front-end
531 if (isset($this->_values['details'])) {
532 $this->_values['details'] = CRM_Utils_String::stripAlternatives($this->_values['details']) ?: '';
533 }
534
535 if ($this->_activityTypeName === 'Inbound Email' &&
536 !CRM_Core_Permission::check('edit inbound email basic information and content')
537 ) {
538 $this->_fields['details']['type'] = 'static';
539 }
540
541 CRM_Core_Form_RecurringEntity::preProcess('civicrm_activity');
542 }
543
544 if ($this->_action & CRM_Core_Action::VIEW) {
545 $url = CRM_Utils_System::url(implode("/", $this->urlPath), "reset=1&id={$this->_activityId}&action=view&cid={$this->_values['source_contact_id']}");
546 CRM_Utils_Recent::add(CRM_Utils_Array::value('subject', $this->_values, ts('(no subject)')),
547 $url,
548 $this->_values['id'],
549 'Activity',
550 $this->_values['source_contact_id'],
551 $this->_values['source_contact']
552 );
553 }
554 }
555
556 /**
557 * Set default values for the form.
558 *
559 * For edit/view mode the default values are retrieved from the database.
560 *
561 * @return array
562 */
563 public function setDefaultValues() {
564
565 $defaults = $this->_values + CRM_Core_Form_RecurringEntity::setDefaultValues();
566 // if we're editing...
567 if (isset($this->_activityId)) {
568
569 if ($this->_context != 'standalone') {
570 $this->assign('target_contact_value',
571 CRM_Utils_Array::value('target_contact_value', $defaults)
572 );
573 $this->assign('assignee_contact_value',
574 CRM_Utils_Array::value('assignee_contact_value', $defaults)
575 );
576 }
577
578 // Fixme: why are we getting the wrong keys from upstream?
579 $defaults['target_contact_id'] = CRM_Utils_Array::value('target_contact', $defaults);
580 $defaults['assignee_contact_id'] = CRM_Utils_Array::value('assignee_contact', $defaults);
581
582 // set default tags if exists
583 $defaults['tag'] = implode(',', CRM_Core_BAO_EntityTag::getTag($this->_activityId, 'civicrm_activity'));
584 }
585 else {
586 // if it's a new activity, we need to set default values for associated contact fields
587 $this->_sourceContactId = $this->_currentUserId;
588 $this->_targetContactId = $this->_currentlyViewedContactId;
589
590 $defaults['source_contact_id'] = $this->_sourceContactId;
591 $defaults['target_contact_id'] = $this->_targetContactId;
592 }
593
594 if (empty($defaults['activity_date_time'])) {
595 $defaults['activity_date_time'] = date('Y-m-d H:i:s');
596 }
597
598 if ($this->_activityTypeId) {
599 $defaults['activity_type_id'] = $this->_activityTypeId;
600 }
601
602 if (!$this->_single && !empty($this->_contactIds)) {
603 $defaults['target_contact_id'] = $this->_contactIds;
604 }
605
606 // CRM-15472 - 50 is around the practical limit of how many items a select2 entityRef can handle
607 if ($this->_action == CRM_Core_Action::UPDATE && !empty($defaults['target_contact_id'])) {
608 $count = count(is_array($defaults['target_contact_id']) ? $defaults['target_contact_id'] : explode(',', $defaults['target_contact_id']));
609 if ($count > 50) {
610 $this->freeze(['target_contact_id']);
611 }
612 }
613
614 if ($this->_action & (CRM_Core_Action::DELETE | CRM_Core_Action::RENEW)) {
615 $this->assign('delName', CRM_Utils_Array::value('subject', $defaults));
616 }
617
618 if ($this->_activityTypeFile) {
619 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
620 $defaults += $className::setDefaultValues($this);
621 }
622 if (empty($defaults['priority_id'])) {
623 $priority = CRM_Core_PseudoConstant::get('CRM_Activity_DAO_Activity', 'priority_id');
624 $defaults['priority_id'] = array_search('Normal', $priority);
625 }
626 if (empty($defaults['status_id'])) {
627 $defaults['status_id'] = CRM_Core_OptionGroup::getDefaultValue('activity_status');
628 }
629 return $defaults;
630 }
631
632 /**
633 * Build Quick form.
634 *
635 * @throws \CRM_Core_Exception
636 * @throws \CiviCRM_API3_Exception
637 */
638 public function buildQuickForm() {
639 if ($this->_action & (CRM_Core_Action::DELETE | CRM_Core_Action::RENEW)) {
640 //enable form element (ActivityLinks sets this true)
641 $this->assign('suppressForm', FALSE);
642
643 $button = ts('Delete');
644 if ($this->_action & CRM_Core_Action::RENEW) {
645 $button = ts('Restore');
646 }
647 $this->addButtons([
648 [
649 'type' => 'next',
650 'name' => $button,
651 'spacing' => '&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;',
652 'isDefault' => TRUE,
653 ],
654 [
655 'type' => 'cancel',
656 'name' => ts('Cancel'),
657 ],
658 ]);
659 return;
660 }
661
662 // Build other activity links.
663 CRM_Activity_Form_ActivityLinks::commonBuildQuickForm($this);
664
665 // Enable form element (ActivityLinks sets this true).
666 $this->assign('suppressForm', FALSE);
667
668 $element = &$this->add('select', 'activity_type_id', ts('Activity Type'),
669 ['' => '- ' . ts('select') . ' -'] + $this->_fields['followup_activity_type_id']['attributes'],
670 FALSE, [
671 'onchange' => "CRM.buildCustomData( 'Activity', this.value );",
672 'class' => 'crm-select2 required',
673 ]
674 );
675
676 // Freeze for update mode.
677 if ($this->_action & CRM_Core_Action::UPDATE) {
678 $element->freeze();
679 }
680
681 // Call to RecurringEntity buildQuickForm for add/update mode.
682 if ($this->_action & (CRM_Core_Action::UPDATE | CRM_Core_Action::ADD)) {
683 CRM_Core_Form_RecurringEntity::buildQuickForm($this);
684 }
685
686 foreach ($this->_fields as $field => $values) {
687 if (!empty($this->_fields[$field])) {
688 $attribute = CRM_Utils_Array::value('attributes', $values);
689 $required = !empty($values['required']);
690
691 if ($values['type'] == 'select' && empty($attribute)) {
692 $this->addSelect($field, ['entity' => 'activity'], $required);
693 }
694 elseif ($values['type'] == 'entityRef') {
695 $this->addEntityRef($field, $values['label'], $attribute, $required);
696 }
697 else {
698 $this->add($values['type'], $field, $values['label'], $attribute, $required, CRM_Utils_Array::value('extra', $values));
699 }
700 }
701 }
702
703 // CRM-7362 --add campaigns.
704 CRM_Campaign_BAO_Campaign::addCampaign($this, CRM_Utils_Array::value('campaign_id', $this->_values));
705
706 // Add engagement level CRM-7775
707 $buildEngagementLevel = FALSE;
708 if (CRM_Campaign_BAO_Campaign::isCampaignEnable() &&
709 CRM_Campaign_BAO_Campaign::accessCampaign()
710 ) {
711 $buildEngagementLevel = TRUE;
712 $this->addSelect('engagement_level', ['entity' => 'activity']);
713 $this->addRule('engagement_level',
714 ts('Please enter the engagement index as a number (integers only).'),
715 'positiveInteger'
716 );
717 }
718 $this->assign('buildEngagementLevel', $buildEngagementLevel);
719
720 // check for survey activity
721 $this->_isSurveyActivity = FALSE;
722
723 if ($this->_activityId && CRM_Campaign_BAO_Campaign::isCampaignEnable() &&
724 CRM_Campaign_BAO_Campaign::accessCampaign()
725 ) {
726
727 $this->_isSurveyActivity = CRM_Campaign_BAO_Survey::isSurveyActivity($this->_activityId);
728 if ($this->_isSurveyActivity) {
729 $surveyId = CRM_Core_DAO::getFieldValue('CRM_Activity_DAO_Activity',
730 $this->_activityId,
731 'source_record_id'
732 );
733 $responseOptions = CRM_Campaign_BAO_Survey::getResponsesOptions($surveyId);
734 if ($responseOptions) {
735 $this->add('select', 'result', ts('Result'),
736 ['' => ts('- select -')] + array_combine($responseOptions, $responseOptions)
737 );
738 }
739 $surveyTitle = NULL;
740 if ($surveyId) {
741 $surveyTitle = CRM_Core_DAO::getFieldValue('CRM_Campaign_DAO_Survey', $surveyId, 'title');
742 }
743 $this->assign('surveyTitle', $surveyTitle);
744 }
745 }
746 $this->assign('surveyActivity', $this->_isSurveyActivity);
747
748 // Add the "Activity Separation" field
749 $actionIsAdd = $this->_action != CRM_Core_Action::UPDATE;
750 $separationIsPossible = $this->supportsActivitySeparation;
751 if ($actionIsAdd && $separationIsPossible) {
752 $this->addRadio(
753 'separation',
754 ts('Activity Separation'),
755 [
756 'separate' => ts('Create separate activities for each contact'),
757 'combined' => ts('Create one activity with all contacts together'),
758 ]
759 );
760 }
761
762 $this->addRule('duration',
763 ts('Please enter the duration as number of minutes (integers only).'), 'positiveInteger'
764 );
765
766 // Add followup date.
767 $this->add('datepicker', 'followup_date', ts('in'));
768
769 // Only admins and case-workers can change the activity source
770 if (!CRM_Core_Permission::check('administer CiviCRM') && $this->_context != 'caseActivity') {
771 $this->getElement('source_contact_id')->freeze();
772 }
773
774 //need to assign custom data type and subtype to the template
775 $this->assign('customDataType', 'Activity');
776 $this->assign('customDataSubType', $this->_activityTypeId);
777 $this->assign('entityID', $this->_activityId);
778
779 $tags = CRM_Core_BAO_Tag::getColorTags('civicrm_activity');
780
781 if (!empty($tags)) {
782 $this->add('select2', 'tag', ts('Tags'), $tags, FALSE, [
783 'class' => 'huge',
784 'placeholder' => ts('- select -'),
785 'multiple' => TRUE,
786 ]);
787 }
788
789 // we need to hide activity tagset for special activities
790 $specialActivities = ['Open Case'];
791
792 if (!in_array($this->_activityTypeName, $specialActivities)) {
793 // build tag widget
794 $parentNames = CRM_Core_BAO_Tag::getTagSet('civicrm_activity');
795 CRM_Core_Form_Tag::buildQuickForm($this, $parentNames, 'civicrm_activity', $this->_activityId);
796 }
797
798 // if we're viewing, we're assigning different buttons than for adding/editing
799 if ($this->_action & CRM_Core_Action::VIEW) {
800 if (isset($this->_groupTree)) {
801 CRM_Core_BAO_CustomGroup::buildCustomDataView($this, $this->_groupTree, FALSE, NULL, NULL, NULL, $this->_activityId);
802 }
803 // form should be frozen for view mode
804 $this->freeze();
805
806 $this->addButtons([
807 [
808 'type' => 'cancel',
809 'name' => ts('Done'),
810 ],
811 ]);
812 }
813 else {
814 $this->addButtons([
815 [
816 'type' => 'upload',
817 'name' => ts('Save'),
818 'isDefault' => TRUE,
819 ],
820 [
821 'type' => 'cancel',
822 'name' => ts('Cancel'),
823 ],
824 ]);
825 }
826
827 if ($this->_activityTypeFile) {
828 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
829
830 $className::buildQuickForm($this);
831 $this->addFormRule([$className, 'formRule'], $this);
832 }
833
834 $this->addFormRule(['CRM_Activity_Form_Activity', 'formRule'], $this);
835
836 $doNotNotifyAssigneeFor = (array) Civi::settings()
837 ->get('do_not_notify_assignees_for');
838 if (($this->_activityTypeId && in_array($this->_activityTypeId, $doNotNotifyAssigneeFor)) || !Civi::settings()
839 ->get('activity_assignee_notification')) {
840 $this->assign('activityAssigneeNotification', FALSE);
841 }
842 else {
843 $this->assign('activityAssigneeNotification', TRUE);
844 }
845 $this->assign('doNotNotifyAssigneeFor', $doNotNotifyAssigneeFor);
846 }
847
848 /**
849 * Global form rule.
850 *
851 * @param array $fields
852 * The input form values.
853 * @param array $files
854 * The uploaded files if any.
855 * @param $self
856 *
857 * @return bool|array
858 * true if no errors, else array of errors
859 */
860 public static function formRule($fields, $files, $self) {
861 // skip form rule if deleting
862 if (CRM_Utils_Array::value('_qf_Activity_next_', $fields) == 'Delete') {
863 return TRUE;
864 }
865 $errors = [];
866 if ((array_key_exists('activity_type_id', $fields) || !$self->_single) && empty($fields['activity_type_id'])) {
867 $errors['activity_type_id'] = ts('Activity Type is a required field');
868 }
869
870 $activity_type_id = CRM_Utils_Array::value('activity_type_id', $fields);
871 $activity_status_id = CRM_Utils_Array::value('status_id', $fields);
872 $scheduled_status_id = CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'status_id', 'Scheduled');
873
874 if ($activity_type_id && $activity_status_id == $scheduled_status_id) {
875 if ($activity_type_id == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'Email')) {
876 $errors['status_id'] = ts('You cannot record scheduled email activity.');
877 }
878 elseif ($activity_type_id == CRM_Core_PseudoConstant::getKey('CRM_Activity_BAO_Activity', 'activity_type_id', 'SMS')) {
879 $errors['status_id'] = ts('You cannot record scheduled SMS activity');
880 }
881 }
882
883 if (!empty($fields['followup_activity_type_id']) && empty($fields['followup_date'])) {
884 $errors['followup_date'] = ts('Followup date is a required field.');
885 }
886 // Activity type is mandatory if subject or follow-up date is specified for an Follow-up activity, CRM-4515.
887 if ((!empty($fields['followup_activity_subject']) || !empty($fields['followup_date'])) && empty($fields['followup_activity_type_id'])) {
888 $errors['followup_activity_subject'] = ts('Follow-up Activity type is a required field.');
889 }
890
891 // Check that a value has been set for the "activity separation" field if needed
892 $separationIsPossible = $self->supportsActivitySeparation;
893 $actionIsAdd = $self->_action == CRM_Core_Action::ADD;
894 $hasMultipleTargetContacts = !empty($fields['target_contact_id']) && strpos($fields['target_contact_id'], ',') !== FALSE;
895 $separationFieldIsEmpty = empty($fields['separation']);
896 if ($separationIsPossible && $actionIsAdd && $hasMultipleTargetContacts && $separationFieldIsEmpty) {
897 $errors['separation'] = ts('Activity Separation is a required field.');
898 }
899
900 return $errors;
901 }
902
903 /**
904 * Process the form submission.
905 *
906 *
907 * @param array $params
908 *
909 * @return array|null
910 * @throws \CiviCRM_API3_Exception
911 */
912 public function postProcess($params = NULL) {
913 if ($this->_action & CRM_Core_Action::DELETE) {
914 // Look up any repeat activities to be deleted.
915 $activityIds = array_column(CRM_Core_BAO_RecurringEntity::getEntitiesFor($this->_activityId, 'civicrm_activity', TRUE, NULL), 'id');
916 if (!$activityIds) {
917 // There are no repeat activities to delete - just this one.
918 $activityIds = [$this->_activityId];
919 }
920
921 // Delete each activity.
922 foreach ($activityIds as $activityId) {
923 $deleteParams = ['id' => $activityId];
924 $moveToTrash = CRM_Case_BAO_Case::isCaseActivity($activityId);
925 CRM_Activity_BAO_Activity::deleteActivity($deleteParams, $moveToTrash);
926
927 // delete tags for the entity
928 $tagParams = [
929 'entity_table' => 'civicrm_activity',
930 'entity_id' => $activityId,
931 ];
932
933 CRM_Core_BAO_EntityTag::del($tagParams);
934 }
935
936 CRM_Core_Session::setStatus(
937 ts("Selected Activity has been deleted successfully.", ['plural' => '%count Activities have been deleted successfully.', 'count' => count($activityIds)]),
938 ts('Record Deleted', ['plural' => 'Records Deleted', 'count' => count($activityIds)]), 'success'
939 );
940
941 return NULL;
942 }
943
944 // store the submitted values in an array
945 if (!$params) {
946 $params = $this->controller->exportValues($this->_name);
947 }
948
949 // Set activity type id.
950 if (empty($params['activity_type_id'])) {
951 $params['activity_type_id'] = $this->_activityTypeId;
952 }
953
954 if (!empty($params['hidden_custom']) &&
955 !isset($params['custom'])
956 ) {
957 $customFields = CRM_Core_BAO_CustomField::getFields('Activity', FALSE, FALSE,
958 $this->_activityTypeId
959 );
960 $customFields = CRM_Utils_Array::crmArrayMerge($customFields,
961 CRM_Core_BAO_CustomField::getFields('Activity', FALSE, FALSE,
962 NULL, NULL, TRUE
963 )
964 );
965 $params['custom'] = CRM_Core_BAO_CustomField::postProcess($params,
966 $this->_activityId,
967 'Activity'
968 );
969 }
970
971 // format params as arrays
972 foreach (['target', 'assignee', 'followup_assignee'] as $name) {
973 if (!empty($params["{$name}_contact_id"])) {
974 $params["{$name}_contact_id"] = explode(',', $params["{$name}_contact_id"]);
975 }
976 else {
977 $params["{$name}_contact_id"] = [];
978 }
979 }
980
981 // get ids for associated contacts
982 if (!$params['source_contact_id']) {
983 $params['source_contact_id'] = $this->_currentUserId;
984 }
985
986 if (isset($this->_activityId)) {
987 $params['id'] = $this->_activityId;
988 }
989
990 // add attachments as needed
991 CRM_Core_BAO_File::formatAttachment($params,
992 $params,
993 'civicrm_activity',
994 $this->_activityId
995 );
996
997 $params['is_multi_activity'] = CRM_Utils_Array::value('separation', $params) == 'separate';
998
999 $activity = [];
1000 if (!empty($params['is_multi_activity']) &&
1001 !CRM_Utils_Array::crmIsEmptyArray($params['target_contact_id'])
1002 ) {
1003 $targetContacts = $params['target_contact_id'];
1004 foreach ($targetContacts as $targetContactId) {
1005 $params['target_contact_id'] = [$targetContactId];
1006 // save activity
1007 $activity[] = $this->processActivity($params);
1008 }
1009 }
1010 else {
1011 // save activity
1012 $activity = $this->processActivity($params);
1013 }
1014
1015 // Redirect to contact page or activity view in standalone mode
1016 if ($this->_context == 'standalone') {
1017 if (count($params['target_contact_id']) == 1) {
1018 $url = CRM_Utils_System::url('civicrm/contact/view', ['cid' => CRM_Utils_Array::first($params['target_contact_id']), 'selectedChild' => 'activity']);
1019 }
1020 else {
1021 $url = CRM_Utils_System::url('civicrm/activity', ['action' => 'view', 'reset' => 1, 'id' => $this->_activityId]);
1022 }
1023 CRM_Core_Session::singleton()->pushUserContext($url);
1024 }
1025
1026 $activityIds = empty($this->_activityIds) ? [$this->_activityId] : $this->_activityIds;
1027 foreach ($activityIds as $activityId) {
1028 // set params for repeat configuration in create mode
1029 $params['entity_id'] = $activityId;
1030 $params['entity_table'] = 'civicrm_activity';
1031 if (!empty($params['entity_id']) && !empty($params['entity_table'])) {
1032 $checkParentExistsForThisId = CRM_Core_BAO_RecurringEntity::getParentFor($params['entity_id'], $params['entity_table']);
1033 if ($checkParentExistsForThisId) {
1034 $params['parent_entity_id'] = $checkParentExistsForThisId;
1035 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity::getReminderDetailsByEntityId($checkParentExistsForThisId, $params['entity_table']);
1036 }
1037 else {
1038 $params['parent_entity_id'] = $params['entity_id'];
1039 $scheduleReminderDetails = CRM_Core_BAO_RecurringEntity::getReminderDetailsByEntityId($params['entity_id'], $params['entity_table']);
1040 }
1041 if (property_exists($scheduleReminderDetails, 'id')) {
1042 $params['schedule_reminder_id'] = $scheduleReminderDetails->id;
1043 }
1044 }
1045 $params['dateColumns'] = ['activity_date_time'];
1046
1047 // Set default repetition start if it was not provided.
1048 if (empty($params['repetition_start_date'])) {
1049 $params['repetition_start_date'] = $params['activity_date_time'];
1050 }
1051
1052 // unset activity id
1053 unset($params['id']);
1054 $linkedEntities = [
1055 [
1056 'table' => 'civicrm_activity_contact',
1057 'findCriteria' => [
1058 'activity_id' => $activityId,
1059 ],
1060 'linkedColumns' => ['activity_id'],
1061 'isRecurringEntityRecord' => FALSE,
1062 ],
1063 ];
1064 CRM_Core_Form_RecurringEntity::postProcess($params, 'civicrm_activity', $linkedEntities);
1065 }
1066
1067 return ['activity' => $activity];
1068 }
1069
1070 /**
1071 * Process activity creation.
1072 *
1073 * @param array $params
1074 * Associated array of submitted values.
1075 *
1076 * @return self|null|object
1077 */
1078 protected function processActivity(&$params) {
1079 $activityAssigned = [];
1080 $activityContacts = CRM_Activity_BAO_ActivityContact::buildOptions('record_type_id', 'validate');
1081 $assigneeID = CRM_Utils_Array::key('Activity Assignees', $activityContacts);
1082 // format assignee params
1083 if (!CRM_Utils_Array::crmIsEmptyArray($params['assignee_contact_id'])) {
1084 //skip those assignee contacts which are already assigned
1085 //while sending a copy.CRM-4509.
1086 $activityAssigned = array_flip($params['assignee_contact_id']);
1087 if ($this->_activityId) {
1088 $assigneeContacts = CRM_Activity_BAO_ActivityContact::getNames($this->_activityId, $assigneeID);
1089 $activityAssigned = array_diff_key($activityAssigned, $assigneeContacts);
1090 }
1091 }
1092
1093 // call begin post process. Idea is to let injecting file do
1094 // any processing before the activity is added/updated.
1095 $this->beginPostProcess($params);
1096
1097 $activity = CRM_Activity_BAO_Activity::create($params);
1098
1099 // add tags if exists
1100 $tagParams = [];
1101 if (!empty($params['tag'])) {
1102 if (!is_array($params['tag'])) {
1103 $params['tag'] = explode(',', $params['tag']);
1104 }
1105
1106 $tagParams = array_fill_keys($params['tag'], 1);
1107 }
1108
1109 // Save static tags.
1110 CRM_Core_BAO_EntityTag::create($tagParams, 'civicrm_activity', $activity->id);
1111
1112 // Save free tags.
1113 if (isset($params['activity_taglist']) && !empty($params['activity_taglist'])) {
1114 CRM_Core_Form_Tag::postProcess($params['activity_taglist'], $activity->id, 'civicrm_activity', $this);
1115 }
1116
1117 // call end post process. Idea is to let injecting file do any
1118 // processing needed, after the activity has been added/updated.
1119 $this->endPostProcess($params, $activity);
1120
1121 // CRM-9590
1122 if (!empty($params['is_multi_activity'])) {
1123 $this->_activityIds[] = $activity->id;
1124 }
1125 else {
1126 $this->_activityId = $activity->id;
1127 }
1128
1129 // create follow up activity if needed
1130 $followupStatus = '';
1131 $followupActivity = NULL;
1132 if (!empty($params['followup_activity_type_id'])) {
1133 $followupActivity = CRM_Activity_BAO_Activity::createFollowupActivity($activity->id, $params);
1134 $followupStatus = ts('A followup activity has been scheduled.');
1135 }
1136
1137 // send copy to assignee contacts.CRM-4509
1138 $mailStatus = '';
1139
1140 if (Civi::settings()->get('activity_assignee_notification')
1141 && !in_array($activity->activity_type_id, Civi::settings()
1142 ->get('do_not_notify_assignees_for'))) {
1143 $activityIDs = [$activity->id];
1144 if ($followupActivity) {
1145 $activityIDs = array_merge($activityIDs, [$followupActivity->id]);
1146 }
1147 $assigneeContacts = CRM_Activity_BAO_ActivityAssignment::getAssigneeNames($activityIDs, TRUE, FALSE);
1148
1149 if (!CRM_Utils_Array::crmIsEmptyArray($params['assignee_contact_id'])) {
1150 $mailToContacts = [];
1151
1152 // Build an associative array with unique email addresses.
1153 foreach ($activityAssigned as $id => $dnc) {
1154 if (isset($id) && array_key_exists($id, $assigneeContacts)) {
1155 $mailToContacts[$assigneeContacts[$id]['email']] = $assigneeContacts[$id];
1156 }
1157 }
1158
1159 $sent = CRM_Activity_BAO_Activity::sendToAssignee($activity, $mailToContacts);
1160 if ($sent) {
1161 $mailStatus .= ts("A copy of the activity has also been sent to assignee contacts(s).");
1162 }
1163 }
1164
1165 // Also send email to follow-up activity assignees if set
1166 if ($followupActivity) {
1167 $mailToFollowupContacts = [];
1168 foreach ($assigneeContacts as $values) {
1169 if ($values['activity_id'] == $followupActivity->id) {
1170 $mailToFollowupContacts[$values['email']] = $values;
1171 }
1172 }
1173
1174 $sentFollowup = CRM_Activity_BAO_Activity::sendToAssignee($followupActivity, $mailToFollowupContacts);
1175 if ($sentFollowup) {
1176 $mailStatus .= '<br />' . ts("A copy of the follow-up activity has also been sent to follow-up assignee contacts(s).");
1177 }
1178 }
1179 }
1180
1181 // set status message
1182 $subject = '';
1183 if (!empty($params['subject'])) {
1184 $subject = "'" . $params['subject'] . "'";
1185 }
1186
1187 CRM_Core_Session::setStatus(ts('Activity %1 has been saved. %2 %3',
1188 [
1189 1 => $subject,
1190 2 => $followupStatus,
1191 3 => $mailStatus,
1192 ]
1193 ), ts('Saved'), 'success');
1194
1195 return $activity;
1196 }
1197
1198 /**
1199 * Shorthand for getting id by display name (makes code more readable)
1200 * @param $displayName
1201 * @return null|string
1202 */
1203 protected function _getIdByDisplayName($displayName) {
1204 return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
1205 $displayName,
1206 'id',
1207 'sort_name'
1208 );
1209 }
1210
1211 /**
1212 * Shorthand for getting display name by id (makes code more readable)
1213 * @param $id
1214 * @return null|string
1215 */
1216 protected function _getDisplayNameById($id) {
1217 return CRM_Core_DAO::getFieldValue('CRM_Contact_DAO_Contact',
1218 $id,
1219 'sort_name',
1220 'id'
1221 );
1222 }
1223
1224 /**
1225 * Let injecting activity type file do any processing.
1226 * needed, before the activity is added/updated
1227 *
1228 * @param array $params
1229 */
1230 public function beginPostProcess(&$params) {
1231 if ($this->_activityTypeFile) {
1232 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1233 $className::beginPostProcess($this, $params);
1234 }
1235 }
1236
1237 /**
1238 * Let injecting activity type file do any processing
1239 * needed, after the activity has been added/updated
1240 *
1241 * @param array $params
1242 * @param $activity
1243 */
1244 public function endPostProcess(&$params, &$activity) {
1245 if ($this->_activityTypeFile) {
1246 $className = "CRM_{$this->_crmDir}_Form_Activity_{$this->_activityTypeFile}";
1247 $className::endPostProcess($this, $params, $activity);
1248 }
1249 }
1250
1251 }