|
85 | 85 | $content = ''; |
86 | 86 | $searchFormToString = ''; |
87 | 87 |
|
| 88 | +/** |
| 89 | + * Build a local URL for this announcements page. |
| 90 | + * Keeps cidreq and preserves legacy navigation params (origin/page) when present. |
| 91 | + */ |
| 92 | +function announcements_build_url(string $action, ?int $id = null, array $extra = []): string |
| 93 | +{ |
| 94 | + $url = api_get_self().'?action='.$action; |
| 95 | + if (null !== $id && $id > 0) { |
| 96 | + $url .= '&id='.(int) $id; |
| 97 | + } |
| 98 | + $url .= '&'.api_get_cidreq(); |
| 99 | + |
| 100 | + // Preserve optional navigation context (legacy learnpath and similar flows). |
| 101 | + if (!empty($_REQUEST['origin'])) { |
| 102 | + $url .= '&origin='.rawurlencode((string) $_REQUEST['origin']); |
| 103 | + } |
| 104 | + if (!empty($_REQUEST['page'])) { |
| 105 | + $url .= '&page='.rawurlencode((string) $_REQUEST['page']); |
| 106 | + } |
| 107 | + |
| 108 | + foreach ($extra as $key => $value) { |
| 109 | + if (null === $value || '' === $value) { |
| 110 | + continue; |
| 111 | + } |
| 112 | + $url .= '&'.rawurlencode((string) $key).'='.rawurlencode((string) $value); |
| 113 | + } |
| 114 | + |
| 115 | + return $url; |
| 116 | +} |
| 117 | + |
| 118 | +/** |
| 119 | + * Resolve a safe return URL based only on explicit return_action/return_id params. |
| 120 | + * If params are missing/invalid, fallback to the provided URL. |
| 121 | + */ |
| 122 | +function announcements_get_return_url(string $fallbackUrl): string |
| 123 | +{ |
| 124 | + $returnAction = (string) ($_REQUEST['return_action'] ?? ''); |
| 125 | + $returnId = (int) ($_REQUEST['return_id'] ?? 0); |
| 126 | + |
| 127 | + if ('list' === $returnAction) { |
| 128 | + return announcements_build_url('list'); |
| 129 | + } |
| 130 | + |
| 131 | + if (in_array($returnAction, ['view', 'modify'], true) && $returnId > 0) { |
| 132 | + return announcements_build_url($returnAction, $returnId); |
| 133 | + } |
| 134 | + |
| 135 | + return $fallbackUrl; |
| 136 | +} |
| 137 | + |
88 | 138 | $logInfo = [ |
89 | 139 | 'tool' => TOOL_ANNOUNCEMENT, |
90 | 140 | 'action' => $action, |
|
94 | 144 | $announcementAttachmentIsDisabled = ('true' === api_get_setting('announcement.disable_announcement_attachment')); |
95 | 145 | $thisAnnouncementId = null; |
96 | 146 | $htmlHeadXtra[] = api_get_css_asset('select2/css/select2.min.css'); |
97 | | -$htmlHeadXtra[] = api_get_asset('select2/js/select2.min.j'); |
| 147 | +$htmlHeadXtra[] = api_get_asset('select2/js/select2.min.js'); |
98 | 148 |
|
99 | 149 | switch ($action) { |
100 | 150 | case 'move': |
|
251 | 301 | '; |
252 | 302 | } |
253 | 303 |
|
| 304 | + // Safe responsive resize (ES5 only). This avoids blank pages caused by modern JS syntax. |
| 305 | + $resizeJs = ' |
| 306 | + (function () { |
| 307 | + function resizeAnnouncementsGrid() { |
| 308 | + var $grid = $("#announcements"); |
| 309 | + if (!$grid.length) { |
| 310 | + return; |
| 311 | + } |
| 312 | + // jqGrid marks the grid on the DOM node when initialized |
| 313 | + if (!$grid[0].grid) { |
| 314 | + return; |
| 315 | + } |
| 316 | + var $wrap = $grid.closest(".ui-jqgrid").parent(); |
| 317 | + if ($wrap.length && $wrap.width()) { |
| 318 | + $grid.jqGrid("setGridWidth", $wrap.width(), true); |
| 319 | + } |
| 320 | + } |
| 321 | +
|
| 322 | + // Run after init + also after a short delay (layout may still be settling) |
| 323 | + resizeAnnouncementsGrid(); |
| 324 | + setTimeout(resizeAnnouncementsGrid, 250); |
| 325 | +
|
| 326 | + // Keep it responsive on window resize |
| 327 | + $(window).off("resize.announcementsGrid").on("resize.announcementsGrid", function () { |
| 328 | + resizeAnnouncementsGrid(); |
| 329 | + }); |
| 330 | + })(); |
| 331 | + '; |
| 332 | + |
254 | 333 | $content = '<script> |
255 | 334 | $(function() {'. |
256 | 335 | Display::grid_js( |
|
262 | 341 | [], |
263 | 342 | '', |
264 | 343 | true |
265 | | - ).$editOptions.' |
| 344 | + ).$editOptions.$resizeJs.' |
266 | 345 | }); |
267 | 346 | </script>'; |
268 | 347 |
|
|
284 | 363 | } |
285 | 364 | $content = $html; |
286 | 365 | } else { |
287 | | - $content .= Display::grid_html('announcements'); |
| 366 | + // Use inline style (no Tailwind dependency) to allow horizontal scroll on small screens. |
| 367 | + $content .= '<div style="width:100%; overflow-x:auto;">'.Display::grid_html('announcements').'</div>'; |
288 | 368 | } |
289 | 369 |
|
290 | 370 | break; |
|
309 | 389 | } |
310 | 390 | header('Location: '.$homeUrl); |
311 | 391 | exit; |
312 | | - |
313 | | - break; |
314 | 392 | case 'delete_all': |
315 | 393 | if (api_is_allowed_to_edit()) { |
316 | 394 | $allow = ('true' === api_get_setting('announcement.disable_delete_all_announcements')); |
|
324 | 402 |
|
325 | 403 | break; |
326 | 404 | case 'delete_attachment': |
327 | | - $id = (int) $_GET['id_attach']; |
| 405 | + $id = (int) ($_GET['id_attach'] ?? 0); |
328 | 406 |
|
329 | | - if (api_is_allowed_to_edit()) { |
| 407 | + if (api_is_allowed_to_edit() && $id > 0) { |
330 | 408 | AnnouncementManager::delete_announcement_attachment_file($id); |
331 | 409 | } |
332 | 410 |
|
333 | | - header('Location: '.$homeUrl); |
| 411 | + $redirectUrl = announcements_get_return_url($homeUrl); |
| 412 | + header('Location: '.$redirectUrl); |
334 | 413 | exit; |
335 | | - |
336 | | - break; |
337 | 414 | case 'set_visibility': |
338 | 415 | if (!empty($announcement_id)) { |
339 | 416 | if (0 != $sessionId && |
|
357 | 434 | $session |
358 | 435 | ); |
359 | 436 | Display::addFlash(Display::return_message(get_lang('The visibility has been changed.'))); |
360 | | - header('Location: '.$homeUrl); |
| 437 | + |
| 438 | + $redirectUrl = announcements_get_return_url($homeUrl); |
| 439 | + header('Location: '.$redirectUrl); |
361 | 440 | exit; |
362 | 441 | } |
363 | 442 | } |
|
378 | 457 |
|
379 | 458 | // DISPLAY ADD ANNOUNCEMENT COMMAND |
380 | 459 | $id = isset($_GET['id']) ? (int) $_GET['id'] : 0; |
381 | | - $url = api_get_self().'?action='.$action.'&id='.$id.'&'.api_get_cidreq(); |
| 460 | + |
| 461 | + $url = announcements_build_url( |
| 462 | + $action, |
| 463 | + $id > 0 ? $id : null, |
| 464 | + [ |
| 465 | + 'return_action' => (string) ($_REQUEST['return_action'] ?? ''), |
| 466 | + 'return_id' => (int) ($_REQUEST['return_id'] ?? 0), |
| 467 | + ] |
| 468 | + ); |
382 | 469 |
|
383 | 470 | $form = new FormValidator( |
384 | 471 | 'announcement', |
|
434 | 521 | api_get_setting('siteName') |
435 | 522 | ); |
436 | 523 | } elseif (isset($_GET['remindallinactives']) && 'true' === $_GET['remindallinactives']) { |
437 | | - // we want to remind inactive users. The $_GET['since'] parameter |
438 | | - // determines which users have to be warned (i.e the users who have been inactive for x days or more |
439 | 524 | $since = 6; |
440 | 525 | if (isset($_GET['since'])) { |
441 | 526 | if ('never' === $_GET['since']) { |
|
445 | 530 | } |
446 | 531 | } |
447 | 532 |
|
448 | | - // Getting the users who have to be reminded |
449 | 533 | $to = Tracking::getInactiveStudentsInCourse( |
450 | 534 | api_get_course_int_id(), |
451 | 535 | $since, |
452 | 536 | $sessionId |
453 | 537 | ); |
454 | 538 |
|
455 | | - // setting the variables for the form elements: the users who need to receive the message |
456 | 539 | foreach ($to as &$user) { |
457 | 540 | $user = 'USER:'.$user; |
458 | 541 | } |
459 | | - // setting the variables for the form elements: the message has to be sent by email |
460 | 542 | $email_ann = '1'; |
461 | | - // setting the variables for the form elements: the title of the email |
462 | 543 | $title_to_modify = sprintf( |
463 | 544 | get_lang('Inactivity on %s'), |
464 | 545 | api_get_setting('siteName') |
465 | 546 | ); |
466 | | - // setting the variables for the form elements: the message of the email |
467 | 547 | $content_to_modify = sprintf( |
468 | 548 | get_lang('Dear user,<br /><br /> you are not active on %s since more than %s days.'), |
469 | 549 | api_get_setting('siteName'), |
470 | 550 | $since |
471 | 551 | ); |
472 | | - // when we want to remind the users who have never been active |
473 | | - // then we have a different subject and content for the announcement |
474 | 552 | if ('never' === $_GET['since']) { |
475 | 553 | $title_to_modify = sprintf( |
476 | 554 | get_lang('Inactivity on %s'), |
|
525 | 603 | } |
526 | 604 | } |
527 | 605 |
|
528 | | - $ajaxUrl = api_get_path(WEB_AJAX_PATH).'announcement.ajax.php?'.api_get_cidreq().'&a=preview'; |
| 606 | + $ajaxUrl = api_get_path(WEB_AJAX_PATH).'announcement.ajax.php?'.api_get_cidreq().'&a=preview'; |
529 | 607 | $ajaxUserGroupUrl = api_get_path(WEB_AJAX_PATH).'usergroup.ajax.php?'.api_get_cidreq(); |
530 | 608 | $form->addHtml(" |
531 | 609 | <script> |
|
654 | 732 | ['ToolbarSet' => 'Announcements'] |
655 | 733 | ); |
656 | 734 |
|
657 | | - if (!$announcementAttachmentIsDisabled) { |
658 | | - $form->addElement('file', 'user_upload', get_lang('Add attachment')); |
659 | | - $form->addElement('textarea', 'file_comment', get_lang('File comment')); |
| 735 | + if (!$announcementAttachmentIsDisabled) { |
| 736 | + // Allow multiple files in one selection |
| 737 | + $form->addElement('file', 'user_upload[]', get_lang('Add attachment'), ['multiple' => 'multiple']); |
| 738 | + $form->addElement('textarea', 'file_comment', get_lang('File comment')); |
| 739 | + |
| 740 | + // Existing attachments (edit mode) |
| 741 | + if (!empty($announcementInfo)) { |
| 742 | + $attachRepo = Container::getAnnouncementAttachmentRepository(); |
| 743 | + $stok = Security::get_existing_token(); |
| 744 | + |
| 745 | + $baseUrl = api_get_self().'?'.api_get_cidreq(); |
| 746 | + $returnQs = '&return_action=modify&return_id='.(int) $id; |
| 747 | + |
| 748 | + $attachments = $announcementInfo->getAttachments(); |
| 749 | + if (count($attachments) > 0) { |
| 750 | + $html = '<div class="announcement-attachments" style="margin:20px 0;">'; |
| 751 | + $html .= '<strong>'.get_lang('Attachments').'</strong>'; |
| 752 | + $html .= '<ul style="margin:6px 0 0 18px;">'; |
| 753 | + |
| 754 | + foreach ($attachments as $attachment) { |
| 755 | + $downloadUrl = $attachRepo->getResourceFileDownloadUrl($attachment).'?'.api_get_cidreq(); |
| 756 | + $deleteUrl = $baseUrl |
| 757 | + .'&action=delete_attachment' |
| 758 | + .'&id_attach='.(int) $attachment->getIid() |
| 759 | + .$returnQs |
| 760 | + .'&sec_token='.$stok; |
| 761 | + |
| 762 | + $html .= '<li style="margin:4px 0;">'; |
| 763 | + $html .= Display::getMdiIcon(ObjectIcon::ATTACHMENT, 'ch-tool-icon', null, ICON_SIZE_TINY); |
| 764 | + $html .= ' <a href="'.$downloadUrl.'">'.api_htmlentities($attachment->getFilename()).'</a>'; |
| 765 | + |
| 766 | + $comment = trim((string) $attachment->getComment()); |
| 767 | + if ('' !== $comment) { |
| 768 | + $html .= ' - <span class="forum_attach_comment">'.api_htmlentities($comment).'</span>'; |
| 769 | + } |
| 770 | + |
| 771 | + $html .= ' '.Display::url( |
| 772 | + Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_TINY, get_lang('Delete')), |
| 773 | + $deleteUrl |
| 774 | + ); |
| 775 | + $html .= '</li>'; |
| 776 | + } |
| 777 | + |
| 778 | + $html .= '</ul></div><br />'; |
| 779 | + $form->addElement('html', $html); |
| 780 | + } |
660 | 781 | } |
| 782 | + } |
| 783 | + |
661 | 784 |
|
662 | | - $form->addHidden('sec_token', $token); |
| 785 | + $form->addHidden('sec_token', $token); |
663 | 786 |
|
664 | 787 | if (empty($sessionId)) { |
665 | 788 | $form->addCheckBox( |
|
760 | 883 |
|
761 | 884 | $reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : []; |
762 | 885 | if (!empty($id)) { |
763 | | - // there is an Id => the announcement already exists => update mode |
764 | 886 | $file_comment = $announcementAttachmentIsDisabled ? null : $_POST['file_comment']; |
765 | 887 | $file = $announcementAttachmentIsDisabled ? [] : $_FILES['user_upload']; |
766 | 888 | $announcement = AnnouncementManager::edit_announcement( |
|
773 | 895 | $sendToUsersInSession |
774 | 896 | ); |
775 | 897 |
|
776 | | - // Send mail |
777 | 898 | $messageSentTo = []; |
778 | 899 | if (isset($_POST['email_ann']) && empty($_POST['onlyThoseMails'])) { |
779 | 900 | $messageSentTo = AnnouncementManager::sendEmail( |
|
797 | 918 | ) |
798 | 919 | ); |
799 | 920 | Security::clear_token(); |
800 | | - header('Location: '.$homeUrl); |
| 921 | + $redirectUrl = announcements_get_return_url($homeUrl); |
| 922 | + header('Location: '.$redirectUrl); |
801 | 923 | exit; |
802 | 924 | } else { |
803 | | - // Insert mode |
804 | 925 | $file = $_FILES['user_upload']; |
805 | 926 | $file_comment = $data['file_comment']; |
806 | 927 |
|
|
850 | 971 | ) |
851 | 972 | ); |
852 | 973 |
|
853 | | - // Send mail |
854 | 974 | $messageSentTo = []; |
855 | 975 | if (isset($data['email_ann']) && $data['email_ann']) { |
856 | 976 | $messageSentTo = AnnouncementManager::sendEmail( |
|
883 | 1003 | } |
884 | 1004 |
|
885 | 1005 | if (empty($_GET['origin']) || 'learnpath' !== $_GET['origin']) { |
886 | | - // We are not in the learning path |
887 | 1006 | Display::display_header($nameTools, get_lang('Announcements')); |
888 | 1007 | } |
889 | 1008 |
|
890 | | -// Tool introduction |
891 | 1009 | if (empty($_GET['origin']) || 'learnpath' !== $_GET['origin']) { |
892 | 1010 | Display::display_introduction_section(TOOL_ANNOUNCEMENT); |
893 | 1011 | } |
894 | 1012 |
|
895 | | -// Actions |
896 | 1013 | $show_actions = false; |
897 | 1014 | $actionsLeft = ''; |
898 | 1015 | if (($allowToEdit || $allowStudentInGroupToSend) && (empty($_GET['origin']) || 'learnpath' !== $_GET['origin'])) { |
|
913 | 1030 | } |
914 | 1031 | } |
915 | 1032 |
|
916 | | -/* |
917 | | -if ($allowToEdit && 0 == api_get_group_id()) { |
918 | | - $allow = ('true' === api_get_setting('announcement.disable_delete_all_announcements')); |
919 | | - if (false === $allow && api_is_allowed_to_edit()) { |
920 | | - if (!isset($_GET['action']) || |
921 | | - isset($_GET['action']) && 'list' == $_GET['action'] |
922 | | - ) { |
923 | | - $actionsLeft .= '<a href="'.api_get_self().'?'.api_get_cidreq()."&action=delete_all\" onclick=\"javascript:if(!confirm('".get_lang('Please confirm your choice')."')) return false;\">". |
924 | | - Display::getMdiIcon('delete', 'ch-tool-icon', null, 32, get_lang('Clear list of announcements')).'</a>'; |
925 | | - } |
926 | | - } |
927 | | -}*/ |
928 | | - |
929 | 1033 | if ($show_actions) { |
930 | 1034 | echo Display::toolbarAction('toolbar', [$actionsLeft, $searchFormToString]); |
931 | 1035 | } |
932 | 1036 |
|
933 | 1037 | echo $content; |
934 | 1038 |
|
935 | 1039 | if (empty($_GET['origin']) || 'learnpath' !== $_GET['origin']) { |
936 | | - //we are not in learnpath tool |
937 | 1040 | Display::display_footer(); |
938 | 1041 | } |
0 commit comments