Merge pull request #21243 from mattwire/contributionpageerrors
[civicrm-core.git] / CRM / Contact / Form / Task / EmailTrait.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 use Civi\Api4\Email;
19
20 /**
21 * This class provides the common functionality for tasks that send emails.
22 */
23 trait CRM_Contact_Form_Task_EmailTrait {
24
25 /**
26 * Are we operating in "single mode", i.e. sending email to one
27 * specific contact?
28 *
29 * @var bool
30 */
31 public $_single = FALSE;
32
33 /**
34 * All the existing templates in the system.
35 *
36 * @var array
37 */
38 public $_templates;
39
40 /**
41 * Store "to" contact details.
42 * @var array
43 */
44 public $_toContactDetails = [];
45
46 /**
47 * Store all selected contact id's, that includes to, cc and bcc contacts
48 * @var array
49 */
50 public $_allContactIds = [];
51
52 /**
53 * Store only "to" contact ids.
54 * @var array
55 */
56 public $_toContactIds = [];
57
58 /**
59 * Is the form being loaded from a search action.
60 *
61 * @var bool
62 */
63 public $isSearchContext = TRUE;
64
65 public $contactEmails = [];
66
67 /**
68 * Contacts form whom emails could not be sent.
69 *
70 * An array of contact ids and the relevant message details.
71 *
72 * @var array
73 */
74 protected $suppressedEmails = [];
75
76 /**
77 * Getter for isSearchContext.
78 *
79 * @return bool
80 */
81 public function isSearchContext(): bool {
82 return $this->isSearchContext;
83 }
84
85 /**
86 * Setter for isSearchContext.
87 *
88 * @param bool $isSearchContext
89 */
90 public function setIsSearchContext(bool $isSearchContext) {
91 $this->isSearchContext = $isSearchContext;
92 }
93
94 /**
95 * Build all the data structures needed to build the form.
96 *
97 * @throws \CiviCRM_API3_Exception
98 * @throws \CRM_Core_Exception
99 */
100 public function preProcess() {
101 $this->traitPreProcess();
102 }
103
104 /**
105 * Call trait preProcess function.
106 *
107 * This function exists as a transitional arrangement so classes overriding
108 * preProcess can still call it. Ideally it will be melded into preProcess
109 * later.
110 *
111 * @throws \CRM_Core_Exception
112 * @throws \API_Exception
113 */
114 protected function traitPreProcess() {
115 $this->preProcessFromAddress();
116 if ($this->isSearchContext()) {
117 // Currently only the contact email form is callable outside search context.
118 parent::preProcess();
119 }
120 $this->setContactIDs();
121 $this->assign('single', $this->_single);
122 if (CRM_Core_Permission::check('administer CiviCRM')) {
123 $this->assign('isAdmin', 1);
124 }
125 }
126
127 /**
128 * Pre Process Form Addresses to be used in Quickform
129 *
130 * @throws \API_Exception
131 * @throws \CRM_Core_Exception
132 */
133 protected function preProcessFromAddress(): void {
134 $form = $this;
135 $form->_emails = [];
136
137 // @TODO remove these line and to it somewhere more appropriate. Currently some classes (e.g Case
138 // are having to re-write contactIds afterwards due to this inappropriate variable setting
139 // If we don't have any contact IDs, use the logged in contact ID
140 $form->_contactIds = $form->_contactIds ?: [CRM_Core_Session::getLoggedInContactID()];
141
142 $fromEmailValues = CRM_Core_BAO_Email::getFromEmail();
143
144 if (empty($fromEmailValues)) {
145 CRM_Core_Error::statusBounce(ts('Your user record does not have a valid email address and no from addresses have been configured.'));
146 }
147
148 $form->_emails = $fromEmailValues;
149 $defaults = [];
150 $form->_fromEmails = $fromEmailValues;
151 if (is_numeric(key($form->_fromEmails))) {
152 $emailID = (int) key($form->_fromEmails);
153 $defaults = CRM_Core_BAO_Email::getEmailSignatureDefaults($emailID);
154 }
155 if (!Civi::settings()->get('allow_mail_from_logged_in_contact')) {
156 $defaults['from_email_address'] = current(CRM_Core_BAO_Domain::getNameAndEmail(FALSE, TRUE));
157 }
158 $form->setDefaults($defaults);
159 }
160
161 /**
162 * Build the form object.
163 *
164 * @throws \CRM_Core_Exception
165 */
166 public function buildQuickForm() {
167 // Suppress form might not be required but perhaps there was a risk some other process had set it to TRUE.
168 $this->assign('suppressForm', FALSE);
169 $this->assign('emailTask', TRUE);
170
171 $toArray = [];
172 $suppressedEmails = 0;
173 //here we are getting logged in user id as array but we need target contact id. CRM-5988
174 $cid = $this->get('cid');
175 if ($cid) {
176 $this->_contactIds = explode(',', $cid);
177 }
178 if (count($this->_contactIds) > 1) {
179 $this->_single = FALSE;
180 }
181 $this->bounceIfSimpleMailLimitExceeded(count($this->_contactIds));
182
183 $emailAttributes = [
184 'class' => 'huge',
185 ];
186 $to = $this->add('text', 'to', ts('To'), $emailAttributes, TRUE);
187
188 $this->addEntityRef('cc_id', ts('CC'), [
189 'entity' => 'Email',
190 'multiple' => TRUE,
191 ]);
192
193 $this->addEntityRef('bcc_id', ts('BCC'), [
194 'entity' => 'Email',
195 'multiple' => TRUE,
196 ]);
197
198 if ($to->getValue()) {
199 $this->_toContactIds = $this->_contactIds = [];
200 }
201 $setDefaults = TRUE;
202 if (property_exists($this, '_context') && $this->_context === 'standalone') {
203 $setDefaults = FALSE;
204 }
205
206 $this->_allContactIds = $this->_toContactIds = $this->_contactIds;
207
208 if ($to->getValue()) {
209 foreach ($this->getEmails($to) as $value) {
210 $contactId = $value['contact_id'];
211 $email = $value['email'];
212 if ($contactId) {
213 $this->_contactIds[] = $this->_toContactIds[] = $contactId;
214 $this->_toContactEmails[] = $email;
215 $this->_allContactIds[] = $contactId;
216 }
217 }
218 $setDefaults = TRUE;
219 }
220
221 //get the group of contacts as per selected by user in case of Find Activities
222 if (!empty($this->_activityHolderIds)) {
223 $contact = $this->get('contacts');
224 $this->_allContactIds = $this->_contactIds = $contact;
225 }
226
227 // check if we need to setdefaults and check for valid contact emails / communication preferences
228 if (is_array($this->_allContactIds) && $setDefaults) {
229 // get the details for all selected contacts ( to, cc and bcc contacts )
230 $allContactDetails = civicrm_api3('Contact', 'get', [
231 'id' => ['IN' => $this->_allContactIds],
232 'return' => ['sort_name', 'email', 'do_not_email', 'is_deceased', 'on_hold', 'display_name', 'preferred_mail_format'],
233 'options' => ['limit' => 0],
234 ])['values'];
235
236 // The contact task supports passing in email_id in a url. It supports a single email
237 // and is marked as having been related to CiviHR.
238 // The array will look like $this->_toEmail = ['email' => 'x', 'contact_id' => 2])
239 // If it exists we want to use the specified email which might be different to the primary email
240 // that we have.
241 if (!empty($this->_toEmail['contact_id']) && !empty($allContactDetails[$this->_toEmail['contact_id']])) {
242 $allContactDetails[$this->_toEmail['contact_id']]['email'] = $this->_toEmail['email'];
243 }
244
245 // perform all validations on unique contact Ids
246 foreach ($allContactDetails as $contactId => $value) {
247 if ($value['do_not_email'] || empty($value['email']) || !empty($value['is_deceased']) || $value['on_hold']) {
248 $this->setSuppressedEmail($contactId, $value);
249 }
250 elseif (in_array($contactId, $this->_toContactIds)) {
251 $this->_toContactDetails[$contactId] = $this->_contactDetails[$contactId] = $value;
252 $toArray[] = [
253 'text' => '"' . $value['sort_name'] . '" <' . $value['email'] . '>',
254 'id' => "$contactId::{$value['email']}",
255 ];
256 }
257 }
258
259 if (empty($toArray)) {
260 CRM_Core_Error::statusBounce(ts('Selected contact(s) do not have a valid email address, or communication preferences specify DO NOT EMAIL, or they are deceased or Primary email address is On Hold.'));
261 }
262 }
263
264 $this->assign('toContact', json_encode($toArray));
265
266 $this->assign('suppressedEmails', count($this->suppressedEmails));
267
268 $this->assign('totalSelectedContacts', count($this->_contactIds));
269
270 $this->add('text', 'subject', ts('Subject'), ['size' => 50, 'maxlength' => 254], TRUE);
271
272 $this->add('select', 'from_email_address', ts('From'), $this->_fromEmails, TRUE);
273
274 CRM_Mailing_BAO_Mailing::commonCompose($this);
275
276 // add attachments
277 CRM_Core_BAO_File::buildAttachment($this, NULL);
278
279 if ($this->_single) {
280 // also fix the user context stack
281 if ($this->getCaseID()) {
282 $ccid = CRM_Core_DAO::getFieldValue('CRM_Case_DAO_CaseContact', $this->_caseId,
283 'contact_id', 'case_id'
284 );
285 $url = CRM_Utils_System::url('civicrm/contact/view/case',
286 "&reset=1&action=view&cid={$ccid}&id=" . $this->getCaseID()
287 );
288 }
289 elseif ($this->_context) {
290 $url = CRM_Utils_System::url('civicrm/dashboard', 'reset=1');
291 }
292 else {
293 $url = CRM_Utils_System::url('civicrm/contact/view',
294 "&show=1&action=browse&cid={$this->_contactIds[0]}&selectedChild=activity"
295 );
296 }
297
298 $session = CRM_Core_Session::singleton();
299 $session->replaceUserContext($url);
300 $this->addDefaultButtons(ts('Send Email'), 'upload', 'cancel');
301 }
302 else {
303 $this->addDefaultButtons(ts('Send Email'), 'upload');
304 }
305
306 $fields = [
307 'followup_assignee_contact_id' => [
308 'type' => 'entityRef',
309 'label' => ts('Assigned to'),
310 'attributes' => [
311 'multiple' => TRUE,
312 'create' => TRUE,
313 'api' => ['params' => ['is_deceased' => 0]],
314 ],
315 ],
316 'followup_activity_type_id' => [
317 'type' => 'select',
318 'label' => ts('Followup Activity'),
319 'attributes' => ['' => '- ' . ts('select activity') . ' -'] + CRM_Core_PseudoConstant::ActivityType(FALSE),
320 'extra' => ['class' => 'crm-select2'],
321 ],
322 'followup_activity_subject' => [
323 'type' => 'text',
324 'label' => ts('Subject'),
325 'attributes' => CRM_Core_DAO::getAttribute('CRM_Activity_DAO_Activity',
326 'subject'
327 ),
328 ],
329 ];
330
331 //add followup date
332 $this->add('datepicker', 'followup_date', ts('in'));
333
334 foreach ($fields as $field => $values) {
335 if (!empty($fields[$field])) {
336 $attribute = $values['attributes'] ?? NULL;
337 $required = !empty($values['required']);
338
339 if ($values['type'] === 'select' && empty($attribute)) {
340 $this->addSelect($field, ['entity' => 'activity'], $required);
341 }
342 elseif ($values['type'] === 'entityRef') {
343 $this->addEntityRef($field, $values['label'], $attribute, $required);
344 }
345 else {
346 $this->add($values['type'], $field, $values['label'], $attribute, $required, CRM_Utils_Array::value('extra', $values));
347 }
348 }
349 }
350
351 //Added for CRM-15984: Add campaign field
352 CRM_Campaign_BAO_Campaign::addCampaign($this);
353
354 $this->addFormRule([__CLASS__, 'saveTemplateFormRule'], $this);
355 CRM_Core_Resources::singleton()->addScriptFile('civicrm', 'templates/CRM/Contact/Form/Task/EmailCommon.js', 0, 'html-header');
356 }
357
358 /**
359 * Process the form after the input has been submitted and validated.
360 *
361 * @throws \CRM_Core_Exception
362 * @throws \CiviCRM_API3_Exception
363 * @throws \Civi\API\Exception\UnauthorizedException
364 * @throws \API_Exception
365 */
366 public function postProcess() {
367 $this->bounceIfSimpleMailLimitExceeded(count($this->_contactIds));
368
369 // check and ensure that
370 $formValues = $this->controller->exportValues($this->getName());
371 $this->submit($formValues);
372 }
373
374 /**
375 * Bounce if there are more emails than permitted.
376 *
377 * @param int $count
378 * The number of emails the user is attempting to send
379 */
380 protected function bounceIfSimpleMailLimitExceeded($count) {
381 $limit = Civi::settings()->get('simple_mail_limit');
382 if ($count > $limit) {
383 CRM_Core_Error::statusBounce(ts('Please do not use this task to send a lot of emails (greater than %1). Many countries have legal requirements when sending bulk emails and the CiviMail framework has opt out functionality and domain tokens to help meet these.',
384 [1 => $limit]
385 ));
386 }
387 }
388
389 /**
390 * Submit the form values.
391 *
392 * This is also accessible for testing.
393 *
394 * @param array $formValues
395 *
396 * @throws \CRM_Core_Exception
397 * @throws \CiviCRM_API3_Exception
398 * @throws \Civi\API\Exception\UnauthorizedException
399 * @throws \API_Exception
400 */
401 public function submit($formValues) {
402 $this->saveMessageTemplate($formValues);
403
404 $from = $formValues['from_email_address'] ?? NULL;
405 // dev/core#357 User Emails are keyed by their id so that the Signature is able to be added
406 // If we have had a contact email used here the value returned from the line above will be the
407 // numerical key where as $from for use in the sendEmail in Activity needs to be of format of "To Name" <toemailaddress>
408 $from = CRM_Utils_Mail::formatFromAddress($from);
409
410 $ccArray = $formValues['cc_id'] ? explode(',', $formValues['cc_id']) : [];
411 $cc = $this->getEmailString($ccArray);
412 $additionalDetails = empty($ccArray) ? '' : "\ncc : " . $this->getEmailUrlString($ccArray);
413
414 $bccArray = $formValues['bcc_id'] ? explode(',', $formValues['bcc_id']) : [];
415 $bcc = $this->getEmailString($bccArray);
416 $additionalDetails .= empty($bccArray) ? '' : "\nbcc : " . $this->getEmailUrlString($bccArray);
417
418 // format contact details array to handle multiple emails from same contact
419 $formattedContactDetails = [];
420 foreach ($this->_contactIds as $key => $contactId) {
421 // if we dont have details on this contactID, we should ignore
422 // potentially this is due to the contact not wanting to receive email
423 if (!isset($this->_contactDetails[$contactId])) {
424 continue;
425 }
426 $email = $this->_toContactEmails[$key];
427 // prevent duplicate emails if same email address is selected CRM-4067
428 // we should allow same emails for different contacts
429 $details = $this->_contactDetails[$contactId];
430 $details['email'] = $email;
431 unset($details['email_id']);
432 $formattedContactDetails["{$contactId}::{$email}"] = $details;
433 }
434
435 // send the mail
436 [$sent, $activityIds] = CRM_Activity_BAO_Activity::sendEmail(
437 $formattedContactDetails,
438 $this->getSubject($formValues['subject']),
439 $formValues['text_message'],
440 $formValues['html_message'],
441 NULL,
442 NULL,
443 $from,
444 $this->getAttachments($formValues),
445 $cc,
446 $bcc,
447 array_keys($this->_toContactDetails),
448 $additionalDetails,
449 $this->getContributionIDs(),
450 CRM_Utils_Array::value('campaign_id', $formValues),
451 $this->getCaseID()
452 );
453
454 if ($sent) {
455 // Only use the first activity id if there's multiple.
456 // If there's multiple recipients the idea behind multiple activities
457 // is to record the token value replacements separately, but that
458 // has no meaning for followup activities, and this doesn't prevent
459 // creating more manually if desired.
460 $followupStatus = $this->createFollowUpActivities($formValues, $activityIds[0]);
461 $count_success = count($this->_toContactDetails);
462 CRM_Core_Session::setStatus(ts('One message was sent successfully. ', [
463 'plural' => '%count messages were sent successfully. ',
464 'count' => $count_success,
465 ]) . $followupStatus, ts('Message Sent', ['plural' => 'Messages Sent', 'count' => $count_success]), 'success');
466 }
467
468 if (!empty($this->suppressedEmails)) {
469 $status = '(' . ts('because no email address on file or communication preferences specify DO NOT EMAIL or Contact is deceased or Primary email address is On Hold') . ')<ul><li>' . implode('</li><li>', $this->suppressedEmails) . '</li></ul>';
470 CRM_Core_Session::setStatus($status, ts('One Message Not Sent', [
471 'count' => count($this->suppressedEmails),
472 'plural' => '%count Messages Not Sent',
473 ]), 'info');
474 }
475 }
476
477 /**
478 * Save the template if update selected.
479 *
480 * @param array $formValues
481 *
482 * @throws \CiviCRM_API3_Exception
483 * @throws \Civi\API\Exception\UnauthorizedException
484 */
485 protected function saveMessageTemplate($formValues) {
486 if (!empty($formValues['saveTemplate']) || !empty($formValues['updateTemplate'])) {
487 $messageTemplate = [
488 'msg_text' => $formValues['text_message'],
489 'msg_html' => $formValues['html_message'],
490 'msg_subject' => $formValues['subject'],
491 'is_active' => TRUE,
492 ];
493
494 if (!empty($formValues['saveTemplate'])) {
495 $messageTemplate['msg_title'] = $formValues['saveTemplateName'];
496 CRM_Core_BAO_MessageTemplate::add($messageTemplate);
497 }
498
499 if (!empty($formValues['template']) && !empty($formValues['updateTemplate'])) {
500 $messageTemplate['id'] = $formValues['template'];
501 unset($messageTemplate['msg_title']);
502 CRM_Core_BAO_MessageTemplate::add($messageTemplate);
503 }
504 }
505 }
506
507 /**
508 * List available tokens for this form.
509 *
510 * @return array
511 */
512 public function listTokens() {
513 return CRM_Core_SelectValues::contactTokens();
514 }
515
516 /**
517 * Get the emails from the added element.
518 *
519 * @param HTML_QuickForm_Element $element
520 *
521 * @return array
522 */
523 protected function getEmails($element): array {
524 $allEmails = explode(',', $element->getValue());
525 $return = [];
526 foreach ($allEmails as $value) {
527 $values = explode('::', $value);
528 $return[] = ['contact_id' => $values[0], 'email' => $values[1]];
529 }
530 return $return;
531 }
532
533 /**
534 * Get the string for the email IDs.
535 *
536 * @param array $emailIDs
537 * Array of email IDs.
538 *
539 * @return string
540 * e.g. "Smith, Bob<bob.smith@example.com>".
541 *
542 * @throws \API_Exception
543 * @throws \Civi\API\Exception\UnauthorizedException
544 */
545 protected function getEmailString(array $emailIDs): string {
546 if (empty($emailIDs)) {
547 return '';
548 }
549 $emails = Email::get()
550 ->addWhere('id', 'IN', $emailIDs)
551 ->setCheckPermissions(FALSE)
552 ->setSelect(['contact_id', 'email', 'contact_id.sort_name', 'contact_id.display_name'])->execute();
553 $emailStrings = [];
554 foreach ($emails as $email) {
555 $this->contactEmails[$email['id']] = $email;
556 $emailStrings[] = '"' . $email['contact_id.sort_name'] . '" <' . $email['email'] . '>';
557 }
558 return implode(',', $emailStrings);
559 }
560
561 /**
562 * Get the url string.
563 *
564 * This is called after the contacts have been retrieved so we don't need to re-retrieve.
565 *
566 * @param array $emailIDs
567 *
568 * @return string
569 * e.g. <a href='{$contactURL}'>Bob Smith</a>'
570 */
571 protected function getEmailUrlString(array $emailIDs): string {
572 $urls = [];
573 foreach ($emailIDs as $email) {
574 $contactURL = CRM_Utils_System::url('civicrm/contact/view', ['reset' => 1, 'cid' => $this->contactEmails[$email]['contact_id']], TRUE);
575 $urls[] = "<a href='{$contactURL}'>" . $this->contactEmails[$email]['contact_id.display_name'] . '</a>';
576 }
577 return implode(', ', $urls);
578 }
579
580 /**
581 * Set the emails that are not to be sent out.
582 *
583 * @param int $contactID
584 * @param array $values
585 */
586 protected function setSuppressedEmail($contactID, $values) {
587 $contactViewUrl = CRM_Utils_System::url('civicrm/contact/view', 'reset=1&cid=' . $contactID);
588 $this->suppressedEmails[$contactID] = "<a href='$contactViewUrl' title='{$values['email']}'>{$values['display_name']}</a>" . ($values['on_hold'] ? '(' . ts('on hold') . ')' : '');
589 }
590
591 /**
592 * Get any attachments.
593 *
594 * @param array $formValues
595 *
596 * @return array
597 */
598 protected function getAttachments(array $formValues): array {
599 $attachments = [];
600 CRM_Core_BAO_File::formatAttachment($formValues,
601 $attachments,
602 NULL, NULL
603 );
604 return $attachments;
605 }
606
607 /**
608 * Get the subject for the message.
609 *
610 * The case handling should possibly be on the case form.....
611 *
612 * @param string $subject
613 *
614 * @return string
615 * @throws \CRM_Core_Exception
616 */
617 protected function getSubject(string $subject):string {
618 // CRM-5916: prepend case id hash to CiviCase-originating emails’ subjects
619 if ($this->getCaseID()) {
620 $hash = substr(sha1(CIVICRM_SITE_KEY . $this->getCaseID()), 0, 7);
621 $subject = "[case #$hash] $subject";
622 }
623 return $subject;
624 }
625
626 /**
627 * Create any follow up activities.
628 *
629 * @param array $formValues
630 * @param int $activityId
631 *
632 * @return string
633 *
634 * @throws \CRM_Core_Exception
635 */
636 protected function createFollowUpActivities($formValues, $activityId): string {
637 $params = [];
638 $followupStatus = '';
639 $followupActivity = NULL;
640 if (!empty($formValues['followup_activity_type_id'])) {
641 $params['followup_activity_type_id'] = $formValues['followup_activity_type_id'];
642 $params['followup_activity_subject'] = $formValues['followup_activity_subject'];
643 $params['followup_date'] = $formValues['followup_date'];
644 $params['target_contact_id'] = $this->_contactIds;
645 $params['followup_assignee_contact_id'] = array_filter(explode(',', $formValues['followup_assignee_contact_id']));
646 $followupActivity = CRM_Activity_BAO_Activity::createFollowupActivity($activityId, $params);
647 $followupStatus = ts('A followup activity has been scheduled.');
648
649 if (Civi::settings()->get('activity_assignee_notification')) {
650 if ($followupActivity) {
651 $mailToFollowupContacts = [];
652 $assignee = [$followupActivity->id];
653 $assigneeContacts = CRM_Activity_BAO_ActivityAssignment::getAssigneeNames($assignee, TRUE, FALSE);
654 foreach ($assigneeContacts as $values) {
655 $mailToFollowupContacts[$values['email']] = $values;
656 }
657
658 $sentFollowup = CRM_Activity_BAO_Activity::sendToAssignee($followupActivity, $mailToFollowupContacts);
659 if ($sentFollowup) {
660 $followupStatus .= '<br />' . ts('A copy of the follow-up activity has also been sent to follow-up assignee contacts(s).');
661 }
662 }
663 }
664 }
665 return $followupStatus;
666 }
667
668 /**
669 * Form rule.
670 *
671 * @param array $fields
672 * The input form values.
673 *
674 * @return bool|array
675 * true if no errors, else array of errors
676 */
677 public static function saveTemplateFormRule(array $fields) {
678 $errors = [];
679 //Added for CRM-1393
680 if (!empty($fields['saveTemplate']) && empty($fields['saveTemplateName'])) {
681 $errors['saveTemplateName'] = ts('Enter name to save message template');
682 }
683 return empty($errors) ? TRUE : $errors;
684 }
685
686 /**
687 * Get selected contribution IDs.
688 *
689 * @return array
690 */
691 protected function getContributionIDs(): array {
692 return [];
693 }
694
695 /**
696 * Get case ID - if any.
697 *
698 * @return int|null
699 *
700 * @throws \CRM_Core_Exception
701 */
702 protected function getCaseID(): ?int {
703 $caseID = CRM_Utils_Request::retrieve('caseid', 'String', $this);
704 if ($caseID) {
705 return (int) $caseID;
706 }
707 return NULL;
708 }
709
710 }