Skip to content

Commit be0f0d3

Browse files
Merge pull request #7420 from christianbeeznest/fixes-updates235
Announcement: Fix announcements & email attachments handling
2 parents 98c1c77 + 7b0eda5 commit be0f0d3

4 files changed

Lines changed: 368 additions & 126 deletions

File tree

public/main/announcements/announcements.php

Lines changed: 151 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,56 @@
8585
$content = '';
8686
$searchFormToString = '';
8787

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+
88138
$logInfo = [
89139
'tool' => TOOL_ANNOUNCEMENT,
90140
'action' => $action,
@@ -94,7 +144,7 @@
94144
$announcementAttachmentIsDisabled = ('true' === api_get_setting('announcement.disable_announcement_attachment'));
95145
$thisAnnouncementId = null;
96146
$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');
98148

99149
switch ($action) {
100150
case 'move':
@@ -251,6 +301,35 @@
251301
';
252302
}
253303

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+
254333
$content = '<script>
255334
$(function() {'.
256335
Display::grid_js(
@@ -262,7 +341,7 @@
262341
[],
263342
'',
264343
true
265-
).$editOptions.'
344+
).$editOptions.$resizeJs.'
266345
});
267346
</script>';
268347

@@ -284,7 +363,8 @@
284363
}
285364
$content = $html;
286365
} 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>';
288368
}
289369

290370
break;
@@ -309,8 +389,6 @@
309389
}
310390
header('Location: '.$homeUrl);
311391
exit;
312-
313-
break;
314392
case 'delete_all':
315393
if (api_is_allowed_to_edit()) {
316394
$allow = ('true' === api_get_setting('announcement.disable_delete_all_announcements'));
@@ -324,16 +402,15 @@
324402

325403
break;
326404
case 'delete_attachment':
327-
$id = (int) $_GET['id_attach'];
405+
$id = (int) ($_GET['id_attach'] ?? 0);
328406

329-
if (api_is_allowed_to_edit()) {
407+
if (api_is_allowed_to_edit() && $id > 0) {
330408
AnnouncementManager::delete_announcement_attachment_file($id);
331409
}
332410

333-
header('Location: '.$homeUrl);
411+
$redirectUrl = announcements_get_return_url($homeUrl);
412+
header('Location: '.$redirectUrl);
334413
exit;
335-
336-
break;
337414
case 'set_visibility':
338415
if (!empty($announcement_id)) {
339416
if (0 != $sessionId &&
@@ -357,7 +434,9 @@
357434
$session
358435
);
359436
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);
361440
exit;
362441
}
363442
}
@@ -378,7 +457,15 @@
378457

379458
// DISPLAY ADD ANNOUNCEMENT COMMAND
380459
$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+
);
382469

383470
$form = new FormValidator(
384471
'announcement',
@@ -434,8 +521,6 @@
434521
api_get_setting('siteName')
435522
);
436523
} 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
439524
$since = 6;
440525
if (isset($_GET['since'])) {
441526
if ('never' === $_GET['since']) {
@@ -445,32 +530,25 @@
445530
}
446531
}
447532

448-
// Getting the users who have to be reminded
449533
$to = Tracking::getInactiveStudentsInCourse(
450534
api_get_course_int_id(),
451535
$since,
452536
$sessionId
453537
);
454538

455-
// setting the variables for the form elements: the users who need to receive the message
456539
foreach ($to as &$user) {
457540
$user = 'USER:'.$user;
458541
}
459-
// setting the variables for the form elements: the message has to be sent by email
460542
$email_ann = '1';
461-
// setting the variables for the form elements: the title of the email
462543
$title_to_modify = sprintf(
463544
get_lang('Inactivity on %s'),
464545
api_get_setting('siteName')
465546
);
466-
// setting the variables for the form elements: the message of the email
467547
$content_to_modify = sprintf(
468548
get_lang('Dear user,<br /><br /> you are not active on %s since more than %s days.'),
469549
api_get_setting('siteName'),
470550
$since
471551
);
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
474552
if ('never' === $_GET['since']) {
475553
$title_to_modify = sprintf(
476554
get_lang('Inactivity on %s'),
@@ -525,7 +603,7 @@
525603
}
526604
}
527605

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';
529607
$ajaxUserGroupUrl = api_get_path(WEB_AJAX_PATH).'usergroup.ajax.php?'.api_get_cidreq();
530608
$form->addHtml("
531609
<script>
@@ -654,12 +732,57 @@
654732
['ToolbarSet' => 'Announcements']
655733
);
656734

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+
}
660781
}
782+
}
783+
661784

662-
$form->addHidden('sec_token', $token);
785+
$form->addHidden('sec_token', $token);
663786

664787
if (empty($sessionId)) {
665788
$form->addCheckBox(
@@ -760,7 +883,6 @@
760883

761884
$reminders = $notificationCount ? array_map(null, $notificationCount, $notificationPeriod) : [];
762885
if (!empty($id)) {
763-
// there is an Id => the announcement already exists => update mode
764886
$file_comment = $announcementAttachmentIsDisabled ? null : $_POST['file_comment'];
765887
$file = $announcementAttachmentIsDisabled ? [] : $_FILES['user_upload'];
766888
$announcement = AnnouncementManager::edit_announcement(
@@ -773,7 +895,6 @@
773895
$sendToUsersInSession
774896
);
775897

776-
// Send mail
777898
$messageSentTo = [];
778899
if (isset($_POST['email_ann']) && empty($_POST['onlyThoseMails'])) {
779900
$messageSentTo = AnnouncementManager::sendEmail(
@@ -797,10 +918,10 @@
797918
)
798919
);
799920
Security::clear_token();
800-
header('Location: '.$homeUrl);
921+
$redirectUrl = announcements_get_return_url($homeUrl);
922+
header('Location: '.$redirectUrl);
801923
exit;
802924
} else {
803-
// Insert mode
804925
$file = $_FILES['user_upload'];
805926
$file_comment = $data['file_comment'];
806927

@@ -850,7 +971,6 @@
850971
)
851972
);
852973

853-
// Send mail
854974
$messageSentTo = [];
855975
if (isset($data['email_ann']) && $data['email_ann']) {
856976
$messageSentTo = AnnouncementManager::sendEmail(
@@ -883,16 +1003,13 @@
8831003
}
8841004

8851005
if (empty($_GET['origin']) || 'learnpath' !== $_GET['origin']) {
886-
// We are not in the learning path
8871006
Display::display_header($nameTools, get_lang('Announcements'));
8881007
}
8891008

890-
// Tool introduction
8911009
if (empty($_GET['origin']) || 'learnpath' !== $_GET['origin']) {
8921010
Display::display_introduction_section(TOOL_ANNOUNCEMENT);
8931011
}
8941012

895-
// Actions
8961013
$show_actions = false;
8971014
$actionsLeft = '';
8981015
if (($allowToEdit || $allowStudentInGroupToSend) && (empty($_GET['origin']) || 'learnpath' !== $_GET['origin'])) {
@@ -913,26 +1030,12 @@
9131030
}
9141031
}
9151032

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-
9291033
if ($show_actions) {
9301034
echo Display::toolbarAction('toolbar', [$actionsLeft, $searchFormToString]);
9311035
}
9321036

9331037
echo $content;
9341038

9351039
if (empty($_GET['origin']) || 'learnpath' !== $_GET['origin']) {
936-
//we are not in learnpath tool
9371040
Display::display_footer();
9381041
}

0 commit comments

Comments
 (0)