From 43d975057f20ebb0054e8c53bedd028dffb984e7 Mon Sep 17 00:00:00 2001 From: Christian Beeznest Date: Fri, 22 May 2026 12:24:48 -0500 Subject: [PATCH] Plugin: Improve Learning Calendar plugin UI and agenda integration --- .../ccalendarevent/CCalendarEventInfo.vue | 13 +- .../vue/composables/calendar/calendarEvent.js | 81 ++- .../ccalendarevent/CCalendarEventList.vue | 19 + public/main/admin/settings.lib.php | 1 + public/main/my_space/index.php | 35 +- .../LearningCalendarPlugin.php | 374 ++++++++-- public/plugin/LearningCalendar/ajax.php | 29 +- public/plugin/LearningCalendar/calendar.php | 45 +- .../LearningCalendar/calendar_users.php | 241 ++++--- public/plugin/LearningCalendar/lang/en_US.php | 21 + public/plugin/LearningCalendar/lang/es.php | 21 + public/plugin/LearningCalendar/my_events.php | 59 ++ public/plugin/LearningCalendar/start.php | 155 ++-- .../plugin/LearningCalendar/view/calendar.tpl | 664 +++++++++++++++--- public/plugin/LearningCalendar/view/start.tpl | 132 +++- 15 files changed, 1497 insertions(+), 393 deletions(-) create mode 100644 public/plugin/LearningCalendar/my_events.php diff --git a/assets/vue/components/ccalendarevent/CCalendarEventInfo.vue b/assets/vue/components/ccalendarevent/CCalendarEventInfo.vue index 52a7c912d69..e9e9fbcd418 100644 --- a/assets/vue/components/ccalendarevent/CCalendarEventInfo.vue +++ b/assets/vue/components/ccalendarevent/CCalendarEventInfo.vue @@ -21,6 +21,14 @@ v-else-if="type.invitation === event.invitationType" :event="event" /> +
+ {{ t("Type") }}: + {{ event.eventType }} +
+ - +
} @@ -87,18 +100,56 @@ function allowUnsubscribeToEvent(event, userId) { async function requestCalendarEvents(params) { const calendarEvents = await cCalendarEventService.findAll({ params }).then((response) => response.json()) - return calendarEvents["hydra:member"].map((event) => { - const timezone = getCurrentTimezone() - const start = DateTime.fromISO(event.startDate, { zone: "utc" }).setZone(timezone) - const end = DateTime.fromISO(event.endDate, { zone: "utc" }).setZone(timezone) + return calendarEvents["hydra:member"].map(mapCalendarEvent) +} - return { - ...event, - start: start.toString(), - end: end.toString(), - color: event.color || "#007BFF", - } +function shouldLoadLearningCalendarEvents(commonParams) { + if (!commonParams) { + return true + } + + if (commonParams.cid || commonParams.sid || commonParams.gid || commonParams.type === "global") { + return false + } + + return true +} + +/** + * @param {Object} startDate + * @param {Object} endDate + * @param {Object} commonParams + * @returns {Promise} + */ +async function requestLearningCalendarEvents(startDate, endDate, commonParams) { + if (!shouldLoadLearningCalendarEvents(commonParams)) { + return [] + } + + const params = new URLSearchParams({ + startDate: startDate.toISOString(), + endDate: endDate.toISOString(), }) + + try { + const response = await fetch(`/plugin/LearningCalendar/my_events.php?${params.toString()}`, { + credentials: "same-origin", + headers: { + Accept: "application/json", + }, + }) + + if (!response.ok) { + return [] + } + + const payload = await response.json() + const events = Array.isArray(payload.events) ? payload.events : [] + + return events.map(mapCalendarEvent) + } catch (error) { + return [] + } } /** @@ -126,10 +177,13 @@ async function getCalendarEvents(startDate, endDate, commonParams) { "startDate[after]": startDate.toISOString(), }) - const [endingEvents, currentEvents, startingEvents] = await Promise.all([ + const learningCalendarEventsPromise = requestLearningCalendarEvents(startDate, endDate, commonParams) + + const [endingEvents, currentEvents, startingEvents, learningCalendarEvents] = await Promise.all([ endingEventsPromise, currentEventsPromise, startingEventsPromise, + learningCalendarEventsPromise, ]) const uniqueEventsMap = new Map() @@ -137,6 +191,7 @@ async function getCalendarEvents(startDate, endDate, commonParams) { endingEvents .concat(startingEvents) .concat(currentEvents) + .concat(learningCalendarEvents) .forEach((event) => uniqueEventsMap.set(event.id, event)) return Array.from(uniqueEventsMap.values()) diff --git a/assets/vue/views/ccalendarevent/CCalendarEventList.vue b/assets/vue/views/ccalendarevent/CCalendarEventList.vue index fb45ddbd3b2..20a212c39cb 100644 --- a/assets/vue/views/ccalendarevent/CCalendarEventList.vue +++ b/assets/vue/views/ccalendarevent/CCalendarEventList.vue @@ -667,6 +667,25 @@ const calendarOptions = ref({ return } + if (event.extendedProps["objectType"] && event.extendedProps["objectType"] === "learning_calendar") { + item.value = { + ...event.extendedProps, + id: event.id, + title: event.title, + startDate: event.start ? new Date(event.start) : null, + endDate: event.end ? new Date(event.end) : null, + type: "personal", + resourceLinkListFromEntity: [], + } + + allowToEdit.value = false + allowToSubscribe.value = false + allowToUnsubscribe.value = false + dialogShow.value = true + + return + } + item.value = { ...event.extendedProps } item.value["@id"] = "/api/c_calendar_events/" + event.id.match(/\d+$/)[0] diff --git a/public/main/admin/settings.lib.php b/public/main/admin/settings.lib.php index 45828684b02..7b1356059ec 100644 --- a/public/main/admin/settings.lib.php +++ b/public/main/admin/settings.lib.php @@ -327,6 +327,7 @@ function getStablePluginAllowList(): array 'Static', 'ShowUserInfo', 'StudentFollowUp', + 'LearningCalendar', ]; } diff --git a/public/main/my_space/index.php b/public/main/my_space/index.php index 930b10c38c9..2f64d7988ae 100644 --- a/public/main/my_space/index.php +++ b/public/main/my_space/index.php @@ -151,20 +151,27 @@ // 2) Extra actions: "View my progress", calendar plugin, certificates. - // Optional Learning Calendar plugin entry (teachers only). - $pluginCalendar = 'true' === api_get_plugin_setting('learning_calendar', 'enabled'); - if ($pluginCalendar && api_is_teacher()) { - $lpCalendar = \LearningCalendarPlugin::create(); - $actionsLeft .= Display::url( - Display::getMdiIcon( - 'calendar-text', - 'ch-tool-icon', - null, - 32, - $lpCalendar->get_lang('Learning calendar') - ), - api_get_path(WEB_PLUGIN_PATH).'LearningCalendar/start.php' - ); + // Optional Learning Calendar plugin entry for users allowed to access reporting. + $learningCalendarPluginPath = api_get_path(SYS_PLUGIN_PATH).'LearningCalendar/LearningCalendarPlugin.php'; + if (file_exists($learningCalendarPluginPath)) { + require_once $learningCalendarPluginPath; + } + + $canAccessLearningCalendar = $allowToTrack || $is_drh || $is_coach; + if (class_exists('LearningCalendarPlugin') && $canAccessLearningCalendar) { + $learningCalendarPlugin = LearningCalendarPlugin::create(); + if ($learningCalendarPlugin->isEnabled()) { + $actionsLeft .= Display::url( + Display::getMdiIcon( + 'calendar-text', + 'ch-tool-icon', + null, + 32, + $learningCalendarPlugin->get_lang('LearningCalendar') + ), + api_get_path(WEB_PLUGIN_PATH).'LearningCalendar/start.php' + ); + } } // Optional StudentFollowUp plugin entry for users allowed to access reporting. diff --git a/public/plugin/LearningCalendar/LearningCalendarPlugin.php b/public/plugin/LearningCalendar/LearningCalendarPlugin.php index f18f6222b2c..194220c6e78 100644 --- a/public/plugin/LearningCalendar/LearningCalendarPlugin.php +++ b/public/plugin/LearningCalendar/LearningCalendarPlugin.php @@ -32,9 +32,21 @@ protected function __construct() public function getEventTypeList() { return [ - self::EVENT_TYPE_TAKEN => ['color' => 'red', 'name' => self::get_lang('EventTypeTaken')], - self::EVENT_TYPE_EXAM => ['color' => 'yellow', 'name' => self::get_lang('EventTypeExam')], - self::EVENT_TYPE_FREE => ['color' => 'green', 'name' => self::get_lang('EventTypeFree')], + self::EVENT_TYPE_TAKEN => [ + 'color' => 'red', + 'name' => self::get_lang('EventTypeTaken'), + 'title' => self::get_lang('EventTypeTaken'), + ], + self::EVENT_TYPE_EXAM => [ + 'color' => 'yellow', + 'name' => self::get_lang('EventTypeExam'), + 'title' => self::get_lang('EventTypeExam'), + ], + self::EVENT_TYPE_FREE => [ + 'color' => 'green', + 'name' => self::get_lang('EventTypeFree'), + 'title' => self::get_lang('EventTypeFree'), + ], ]; } @@ -116,28 +128,34 @@ public function install() Database::query($sql); $extraField = new ExtraField('lp_item'); - $params = [ - 'display_text' => $this->get_lang('Learning calendar one day marker'), - 'variable' => 'calendar', - 'visible_to_self' => 1, - 'changeable' => 1, - 'visible_to_others' => 1, - 'value_type' => ExtraField::FIELD_TYPE_CHECKBOX, - ]; + $fieldInfo = $extraField->get_handler_field_info_by_field_variable('calendar'); + if (empty($fieldInfo)) { + $params = [ + 'display_text' => $this->get_lang('Learning calendar one day marker'), + 'variable' => 'calendar', + 'visible_to_self' => 1, + 'changeable' => 1, + 'visible_to_others' => 1, + 'value_type' => ExtraField::FIELD_TYPE_CHECKBOX, + ]; - $extraField->save($params); + $extraField->save($params); + } $extraField = new ExtraField('course'); - $params = [ - 'display_text' => $this->get_lang('Course duration (h)'), - 'variable' => 'course_hours_duration', - 'visible_to_self' => 1, - 'changeable' => 1, - 'visible_to_others' => 1, - 'value_type' => ExtraField::FIELD_TYPE_TEXT, - ]; + $fieldInfo = $extraField->get_handler_field_info_by_field_variable('course_hours_duration'); + if (empty($fieldInfo)) { + $params = [ + 'display_text' => $this->get_lang('CourseHoursDuration'), + 'variable' => 'course_hours_duration', + 'visible_to_self' => 1, + 'changeable' => 1, + 'visible_to_others' => 1, + 'value_type' => ExtraField::FIELD_TYPE_TEXT, + ]; - $extraField->save($params); + $extraField->save($params); + } return true; } @@ -151,6 +169,7 @@ public function uninstall() 'learning_calendar', 'learning_calendar_events', 'learning_calendar_user', + 'learning_calendar_control_point', ]; foreach ($tables as $table) { @@ -343,6 +362,48 @@ public function getCalendarCount() return (int) $result['count']; } + /** + * Returns calendars visible to the current user without jqGrid formatting. + * + * @return array + */ + public function getCalendarList() + { + if (api_is_platform_admin()) { + $sql = 'SELECT * FROM learning_calendar ORDER BY title ASC'; + } else { + $userId = api_get_user_id(); + $sql = "SELECT * FROM learning_calendar WHERE author_id = $userId ORDER BY title ASC"; + } + + $result = Database::query($sql); + $list = []; + while ($row = Database::fetch_assoc($result)) { + $calendarId = (int) $row['id']; + $row['event_count'] = $this->getCalendarEventCount($calendarId); + $row['user_count'] = $this->getUsersPerCalendarCount($calendarId); + $list[] = $row; + } + + return $list; + } + + /** + * @param int $calendarId + * + * @return int + */ + public function getCalendarEventCount($calendarId) + { + $calendarId = (int) $calendarId; + $sql = "SELECT count(id) as count FROM learning_calendar_events WHERE calendar_id = $calendarId"; + $result = Database::query($sql); + $row = Database::fetch_assoc($result); + + return (int) $row['count']; + } + + /** * @param int $calendarId * @@ -352,11 +413,20 @@ public function getUsersPerCalendar($calendarId) { $calendarId = (int) $calendarId; $sql = "SELECT * FROM learning_calendar_user - WHERE calendar_id = $calendarId"; + WHERE calendar_id = $calendarId + ORDER BY id ASC"; $result = Database::query($sql); $list = []; while ($row = Database::fetch_assoc($result)) { - $userInfo = api_get_user_info($row['user_id']); + $userId = (int) $row['user_id']; + $userInfo = api_get_user_info($userId); + + if (empty($userInfo)) { + continue; + } + + $userInfo['calendar_user_id'] = (int) $row['id']; + $userInfo['user_id'] = $userId; $userInfo['exam'] = 'exam'; $list[] = $userInfo; } @@ -435,10 +505,15 @@ public function getItemVisibility($id) public function getCalendar($calendarId) { $calendarId = (int) $calendarId; + if (empty($calendarId)) { + return []; + } + $sql = "SELECT * FROM learning_calendar WHERE id = $calendarId"; $result = Database::query($sql); + $row = Database::fetch_assoc($result); - return Database::fetch_assoc($result); + return $row ?: []; } /** @@ -449,7 +524,11 @@ public function getCalendar($calendarId) public function getUserCalendar($userId) { $userId = (int) $userId; - $sql = "SELECT * FROM learning_calendar_user WHERE user_id = $userId"; + $sql = "SELECT * + FROM learning_calendar_user + WHERE user_id = $userId + ORDER BY id ASC + LIMIT 1"; $result = Database::query($sql); return Database::fetch_assoc($result); @@ -570,11 +649,94 @@ public function deleteAllCalendarFromUser($calendarId, $userId) return true; }*/ + /** + * @param string $identifier + * + * @return array + */ + public function findUserForCalendarAssignment($identifier) + { + $identifier = trim((string) $identifier); + + if ('' === $identifier) { + return []; + } + + $userTable = Database::get_main_table(TABLE_MAIN_USER); + + if (ctype_digit($identifier)) { + $userId = (int) $identifier; + $sql = "SELECT id, username, firstname, lastname + FROM $userTable + WHERE id = $userId + LIMIT 1"; + } else { + $identifier = Database::escape_string($identifier); + $sql = "SELECT id, username, firstname, lastname + FROM $userTable + WHERE username = '$identifier' + LIMIT 1"; + } + + $result = Database::query($sql); + $user = Database::fetch_assoc($result); + + return $user ?: []; + } + + /** + * Assigns a user to one learning calendar. + * + * @param int $calendarId + * @param int $userId + * + * @return string + */ + public function assignUserToCalendar($calendarId, $userId) + { + $calendarId = (int) $calendarId; + $userId = (int) $userId; + + if (empty($calendarId) || empty($userId)) { + return 'invalid'; + } + + $calendar = $this->getUserCalendar($userId); + + if (empty($calendar)) { + $this->addUserToCalendar($calendarId, $userId); + + return 'added'; + } + + if ((int) $calendar['calendar_id'] === $calendarId) { + return 'already'; + } + + $sql = "DELETE FROM learning_calendar_user WHERE user_id = $userId"; + Database::query($sql); + + Database::insert('learning_calendar_user', [ + 'calendar_id' => $calendarId, + 'user_id' => $userId, + ]); + + return 'moved'; + } + public function getForm(FormValidator &$form) { $form->addText('title', get_lang('Title')); + $form->addRule('title', get_lang('Required field'), 'required'); + $form->addText('total_hours', get_lang('Total hours')); + $form->addRule('total_hours', get_lang('Required field'), 'required'); + $form->addRule('total_hours', get_lang('Only numbers'), 'numeric'); + $form->addText('minutes_per_day', get_lang('Minutes per day')); + $form->addRule('minutes_per_day', get_lang('Required field'), 'required'); + $form->addRule('minutes_per_day', get_lang('Only numbers'), 'numeric'); + $form->addHtmlEditor('description', get_lang('Description'), false); } @@ -631,6 +793,90 @@ public function getPersonalEvents($agenda, $start, $end) return $list; } + public function getPersonalEventsForApi($userId, $startDate, $endDate) + { + $userId = (int) $userId; + + if (empty($userId)) { + return []; + } + + $calendarRelUser = $this->getUserCalendar($userId); + + if (empty($calendarRelUser)) { + return []; + } + + $calendarInfo = $this->getCalendar($calendarRelUser['calendar_id']); + + if (empty($calendarInfo)) { + return []; + } + + $startDate = $this->normalizeApiDate($startDate, date('Y-m-d')); + $endDate = $this->normalizeApiDate($endDate, date('Y-m-d', strtotime('+1 month'))); + + $calendarId = (int) $calendarInfo['id']; + $startDate = Database::escape_string($startDate); + $endDate = Database::escape_string($endDate); + + $sql = "SELECT * + FROM learning_calendar_events + WHERE calendar_id = $calendarId + AND start_date < '$endDate' + AND (end_date >= '$startDate' OR end_date IS NULL) + ORDER BY start_date ASC, id ASC"; + $result = Database::query($sql); + + $list = []; + $eventTypeList = $this->getEventTypeList(); + + while ($row = Database::fetch_assoc($result)) { + $type = (int) $row['type']; + $typeInfo = $eventTypeList[$type] ?? $eventTypeList[self::EVENT_TYPE_FREE]; + $start = $this->normalizeApiDate($row['start_date'], $startDate); + $end = $this->normalizeApiDate($row['end_date'] ?: $row['start_date'], $start); + + $list[] = [ + 'id' => 'learning_calendar_'.$row['id'], + 'title' => $calendarInfo['title'], + 'content' => $typeInfo['title'], + 'startDate' => $start.'T00:00:00+00:00', + 'endDate' => $end.'T23:59:00+00:00', + 'allDay' => true, + 'url' => null, + 'color' => $typeInfo['color'], + 'type' => 'personal', + 'objectType' => 'learning_calendar', + 'eventType' => $typeInfo['title'], + 'resourceLinkListFromEntity' => [], + 'learningCalendar' => [ + 'id' => (int) $calendarInfo['id'], + 'title' => $calendarInfo['title'], + ], + ]; + } + + return $list; + } + + private function normalizeApiDate($value, $fallback) + { + $value = trim((string) $value); + + if ('' === $value) { + return $fallback; + } + + try { + $date = new DateTime($value); + + return $date->format('Y-m-d'); + } catch (Exception $exception) { + return $fallback; + } + } + /** * @param int $userId * @param array $coursesAndSessions @@ -962,6 +1208,10 @@ public function deleteCalendar($calendarId) $sql = "DELETE FROM learning_calendar_events WHERE calendar_id = $calendarId"; Database::query($sql); + // Delete user assignments to avoid orphan rows in the plugin table. + $sql = "DELETE FROM learning_calendar_user WHERE calendar_id = $calendarId"; + Database::query($sql); + return true; } @@ -974,43 +1224,41 @@ public function toogleDayType($calendarId, $startDate) $startDate = Database::escape_string($startDate); $calendarId = (int) $calendarId; - $eventTypeList = $this->getEventTypeColorList(); - // Remove the free type to loop correctly when toogle days. - unset($eventTypeList[self::EVENT_TYPE_FREE]); - $sql = "SELECT * FROM learning_calendar_events WHERE start_date = '$startDate' AND calendar_id = $calendarId "; $result = Database::query($sql); if (Database::num_rows($result)) { $row = Database::fetch_assoc($result); - $currentType = $row['type']; - $currentType++; - if ($currentType > count($eventTypeList)) { + $currentType = (int) $row['type']; + + if ($currentType >= self::EVENT_TYPE_FREE) { Database::delete( 'learning_calendar_events', [' calendar_id = ? AND start_date = ?' => [$calendarId, $startDate]] ); - } else { - $params = [ - 'type' => $currentType, - ]; - Database::update( - 'learning_calendar_events', - $params, - [' calendar_id = ? AND start_date = ?' => [$calendarId, $startDate]] - ); + + return; } - } else { - $params = [ - 'title' => '', - 'calendar_id' => $calendarId, - 'start_date' => $startDate, - 'end_date' => $startDate, - 'type' => self::EVENT_TYPE_TAKEN, - ]; - Database::insert('learning_calendar_events', $params); + + Database::update( + 'learning_calendar_events', + [ + 'type' => $currentType + 1, + ], + [' calendar_id = ? AND start_date = ?' => [$calendarId, $startDate]] + ); + + return; } + + Database::insert('learning_calendar_events', [ + 'title' => '', + 'calendar_id' => $calendarId, + 'start_date' => $startDate, + 'end_date' => $startDate, + 'type' => self::EVENT_TYPE_TAKEN, + ]); } /** @@ -1021,7 +1269,7 @@ public function toogleDayType($calendarId, $startDate) public function getEvents($calendarId) { $calendarId = (int) $calendarId; - $eventTypeList = $this->getEventTypeColorList(); + $eventTypeList = $this->getEventTypeList(); $sql = "SELECT * FROM learning_calendar_events WHERE calendar_id = $calendarId "; @@ -1029,17 +1277,23 @@ public function getEvents($calendarId) $list = []; while ($row = Database::fetch_assoc($result)) { + $type = (int) $row['type']; + $eventType = $eventTypeList[$type] ?? $eventTypeList[self::EVENT_TYPE_FREE]; + $list[] = [ + 'id' => (int) $row['id'], + 'title' => $row['title'] ?: $eventType['title'], 'start_date' => $row['start_date'], - 'end_date' => $row['start_date'], - 'color' => $eventTypeList[$row['type']], + 'end_date' => $row['end_date'] ?: $row['start_date'], + 'type' => $type, + 'color' => $eventType['color'], ]; } return $list; } - public function protectCalendar(array $calendarInfo) + public function protectCalendar($calendarInfo) { $allow = api_is_platform_admin() || api_is_teacher(); @@ -1047,11 +1301,13 @@ public function protectCalendar(array $calendarInfo) api_not_allowed(true); } - if (!empty($calendarInfo)) { - if (!api_is_platform_admin() && api_is_teacher()) { - if ($calendarInfo['author_id'] != api_get_user_id()) { - api_not_allowed(true); - } + if (empty($calendarInfo) || !is_array($calendarInfo)) { + api_not_allowed(true); + } + + if (!api_is_platform_admin() && api_is_teacher()) { + if ((int) $calendarInfo['author_id'] !== api_get_user_id()) { + api_not_allowed(true); } } } diff --git a/public/plugin/LearningCalendar/ajax.php b/public/plugin/LearningCalendar/ajax.php index 2b9f8b3860e..d3146000595 100644 --- a/public/plugin/LearningCalendar/ajax.php +++ b/public/plugin/LearningCalendar/ajax.php @@ -4,30 +4,34 @@ require_once __DIR__.'/../../main/inc/global.inc.php'; -$action = isset($_REQUEST['a']) ? $_REQUEST['a'] : ''; -$calendarId = isset($_REQUEST['id']) ? $_REQUEST['id'] : 0; - $plugin = LearningCalendarPlugin::create(); + +if (!$plugin->isEnabled()) { + api_not_allowed(true); +} + +$action = isset($_REQUEST['a']) ? Security::remove_XSS($_REQUEST['a']) : ''; +$calendarId = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0; + $item = $plugin->getCalendar($calendarId); $plugin->protectCalendar($item); switch ($action) { case 'toggle_day': - $startDate = isset($_REQUEST['start_date']) ? $_REQUEST['start_date'] : ''; + $startDate = isset($_REQUEST['start_date']) ? Security::remove_XSS($_REQUEST['start_date']) : ''; if (empty($startDate)) { exit; } - $endDate = isset($_REQUEST['end_date']) ? $_REQUEST['end_date'] : ''; - if ($startDate == $endDate) { - // One day + + $endDate = isset($_REQUEST['end_date']) ? Security::remove_XSS($_REQUEST['end_date']) : ''; + if ($startDate === $endDate || empty($endDate)) { $plugin->toogleDayType($calendarId, $startDate); } else { - // A list of days $startDateTime = new DateTime($startDate); $endDateTime = new DateTime($endDate); $diff = $startDateTime->diff($endDateTime); - $countDays = $diff->format('%a'); - $dayList[] = $startDate; + $countDays = (int) $diff->format('%a'); + $dayList = [$startDate]; for ($i = 0; $i < $countDays; $i++) { $startDateTime->modify('+1 day'); $dayList[] = $startDateTime->format('Y-m-d'); @@ -39,8 +43,7 @@ break; case 'get_events': - $list = $plugin->getEvents($calendarId); - echo json_encode($list); - + header('Content-Type: application/json'); + echo json_encode($plugin->getEvents($calendarId)); break; } diff --git a/public/plugin/LearningCalendar/calendar.php b/public/plugin/LearningCalendar/calendar.php index ed9ce6488f4..a6905325754 100644 --- a/public/plugin/LearningCalendar/calendar.php +++ b/public/plugin/LearningCalendar/calendar.php @@ -6,43 +6,42 @@ require_once __DIR__.'/../../main/inc/global.inc.php'; -$calendarId = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0; $plugin = LearningCalendarPlugin::create(); -$item = $plugin->getCalendar($calendarId); -$plugin->protectCalendar($item); -$isoCode = api_get_language_isocode(); -$htmlHeadXtra[] = api_get_asset('bootstrap-year-calendar/js/bootstrap-year-calendar.js'); -$calendarLanguage = 'en'; -if ('en' !== $isoCode) { - $file = 'bootstrap-year-calendar/js/languages/bootstrap-year-calendar.'.$isoCode.'.js'; - $path = api_get_path(SYS_PUBLIC_PATH).'assets/'.$file; - if (file_exists($path)) { - $htmlHeadXtra[] = api_get_asset($file); - $calendarLanguage = $isoCode; - } +if (!$plugin->isEnabled()) { + api_not_allowed(true); } -$htmlHeadXtra[] = api_get_css_asset('bootstrap-year-calendar/css/bootstrap-year-calendar.css'); +$calendarId = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0; +$item = $plugin->getCalendar($calendarId); +$plugin->protectCalendar($item); -$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : ''; -$formToString = ''; +$template = new Template($item['title']); -$template = new Template(); -$actionLeft = Display::url( - Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Add')), +$toolbarActions = []; +$toolbarActions[] = Display::url( + Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, $plugin->get_lang('BackToMySpace')), + api_get_path(WEB_CODE_PATH).'my_space/index.php' +); +$toolbarActions[] = Display::url( + Display::getMdiIcon('format-list-bulleted', 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('List')), api_get_path(WEB_PLUGIN_PATH).'LearningCalendar/start.php' ); -$actions = Display::toolbarAction('toolbar-forum', [$actionLeft]); - $eventList = $plugin->getEventTypeList(); $template->assign('events', $eventList); -$template->assign('calendar_language', $calendarLanguage); $template->assign('ajax_url', api_get_path(WEB_PLUGIN_PATH).'LearningCalendar/ajax.php?id='.$calendarId); +$template->assign('plugin_title', $plugin->get_lang('LearningCalendar')); $template->assign('header', $item['title']); +$template->assign('description', $item['description'] ?? ''); +$template->assign('total_hours', (int) $item['total_hours']); +$template->assign('minutes_per_day', (int) $item['minutes_per_day']); +$template->assign('calendar_help', $plugin->get_lang('LearningCalendarSelectRangeHelp')); +$template->assign('calendar_cycle_help', $plugin->get_lang('LearningCalendarCycleHelp')); +$template->assign('calendar_range_help', $plugin->get_lang('LearningCalendarRangeHelp')); + $content = $template->fetch('LearningCalendar/view/calendar.tpl'); -$template->assign('actions', $actions); +$template->assign('actions', Display::toolbarAction('toolbar-calendar', $toolbarActions)); $template->assign('content', $content); $template->display_one_col_template(); diff --git a/public/plugin/LearningCalendar/calendar_users.php b/public/plugin/LearningCalendar/calendar_users.php index cf23a4e7277..1c8666547eb 100644 --- a/public/plugin/LearningCalendar/calendar_users.php +++ b/public/plugin/LearningCalendar/calendar_users.php @@ -1,102 +1,173 @@ protectCalendar($calendarId); -$item = $plugin->getCalendar($calendarId); -if (empty($item)) { +if (!$plugin->isEnabled()) { api_not_allowed(true); } -$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : ''; -$formToString = ''; -$template = new Template(); -$actionLeft = Display::url( - Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Back')), +$calendarId = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0; +$item = $plugin->getCalendar($calendarId); +$plugin->protectCalendar($item); + +$tokenSessionKey = 'learning_calendar_users_token_'.$calendarId; +if (empty($_SESSION[$tokenSessionKey])) { + $_SESSION[$tokenSessionKey] = bin2hex(random_bytes(16)); +} +$secToken = $_SESSION[$tokenSessionKey]; + +if ('POST' === $_SERVER['REQUEST_METHOD']) { + $postedToken = isset($_POST['sec_token']) ? (string) $_POST['sec_token'] : ''; + + if (!hash_equals($secToken, $postedToken)) { + api_not_allowed(true); + } + + $formAction = isset($_POST['form_action']) ? (string) $_POST['form_action'] : ''; + + switch ($formAction) { + case 'assign_user': + $identifier = isset($_POST['user_identifier']) ? trim((string) $_POST['user_identifier']) : ''; + $user = $plugin->findUserForCalendarAssignment($identifier); + + if (empty($user)) { + Display::addFlash(Display::return_message($plugin->get_lang('UserNotFound'), 'warning')); + header('Location: '.api_get_self().'?id='.$calendarId); + exit; + } + + $status = $plugin->assignUserToCalendar($calendarId, (int) $user['id']); + + if ('added' === $status) { + Display::addFlash(Display::return_message($plugin->get_lang('UserAssignedToCalendar'))); + } elseif ('moved' === $status) { + Display::addFlash(Display::return_message($plugin->get_lang('UserMovedToCalendar'))); + } elseif ('already' === $status) { + Display::addFlash(Display::return_message($plugin->get_lang('UserAlreadyAssignedToThisCalendar'), 'warning')); + } else { + Display::addFlash(Display::return_message($plugin->get_lang('CalendarUserAssignmentFailed'), 'error')); + } + + header('Location: '.api_get_self().'?id='.$calendarId); + exit; + + case 'remove_user': + $userId = isset($_POST['user_id']) ? (int) $_POST['user_id'] : 0; + + if (!empty($userId)) { + $plugin->deleteAllCalendarFromUser($calendarId, $userId); + Display::addFlash(Display::return_message($plugin->get_lang('UserRemovedFromCalendar'))); + } + + header('Location: '.api_get_self().'?id='.$calendarId); + exit; + } +} + +$template = new Template($plugin->get_lang('LearningCalendar')); +$users = $plugin->getUsersPerCalendar($calendarId); + +$toolbarActions = []; +$toolbarActions[] = Display::url( + Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, $plugin->get_lang('BackToMySpace')), + api_get_path(WEB_CODE_PATH).'my_space/index.php' +); +$toolbarActions[] = Display::url( + Display::getMdiIcon('format-list-bulleted', 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('List')), api_get_path(WEB_PLUGIN_PATH).'LearningCalendar/start.php' ); -$actions = Display::toolbarAction('toolbar-forum', [$actionLeft]); - -// jqgrid will use this URL to do the selects -$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_calendar_users&id='.$calendarId; - -// The order is important you need to check the $column variable in the model.ajax.php file -$columns = [ - get_lang('First name'), - get_lang('Last name'), - get_lang('Test'), -]; - -// Column config -$column_model = [ - ['name' => 'firstname', 'index' => 'firstname', 'width' => '35', 'align' => 'left', 'sortable' => 'false'], - ['name' => 'lastname', 'index' => 'lastname', 'width' => '35', 'align' => 'left', 'sortable' => 'false'], - [ - 'name' => 'exam', - 'index' => 'exam', - 'width' => '20', - 'align' => 'center', - 'sortable' => 'false', - ], -]; - -// Autowidth -$extraParams['autowidth'] = 'true'; -// height auto -$extraParams['height'] = 'auto'; -$extraParams['sortname'] = 'title'; -$extraParams['sortorder'] = 'desc'; -$extraParams['multiselect'] = true; - -$deleteIcon = Display::getMdiIcon(ActionIcon::DELETE, 'ch-tool-icon', null, ICON_SIZE_SMALL, get_lang('Delete')); -$urlStats = api_get_path(WEB_CODE_PATH); -$action_links = ''; -$deleteUrl = ''; - -Display::display_header(); - -?> - -get_lang('AssignUser')); +$userIdentifierLabel = Security::remove_XSS($plugin->get_lang('UserIdentifier')); +$userIdentifierHelp = Security::remove_XSS($plugin->get_lang('UserIdentifierHelp')); + +$content = '
'; +$content .= '
'; +$content .= '
'. + Security::remove_XSS($plugin->get_lang('LearningCalendar')).'
'; +$content .= '

'.Security::remove_XSS($item['title']).'

'; +$content .= '

'.Security::remove_XSS($plugin->get_lang('CalendarUsersDescription')).'

'; +$content .= '
'; + +$content .= '
'; +$content .= '
'; +$content .= ''; +$content .= '

'.Security::remove_XSS($plugin->get_lang('AssignUsers')).'

'; +$content .= '
'; +$content .= '

'.Security::remove_XSS($plugin->get_lang('AssignUserToCalendarHelp')).'

'; +$content .= '
'; +$content .= ''; +$content .= ''; +$content .= '
'; +$content .= ''; +$content .= ''; +$content .= '

'.$userIdentifierHelp.'

'; +$content .= '
'; +$content .= ''; +$content .= '
'; +$content .= '
'; -// action links -echo ''; -echo Display::grid_html('usergroups'); +$content .= '
'; +$content .= '
'; +$content .= '

'.get_lang('Users').'

'; +$content .= '
'; + +if (empty($users)) { + $content .= '
'.get_lang('No data available').'
'; +} else { + $content .= '
'; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + foreach ($users as $user) { + $userId = (int) ($user['user_id'] ?? 0); + $removeLabel = Security::remove_XSS($plugin->get_lang('RemoveFromCalendar')); + $removeConfirm = htmlspecialchars( + json_encode((string) $plugin->get_lang('RemoveUserConfirm')), + ENT_QUOTES, + 'UTF-8' + ); + + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + } + $content .= '
'.get_lang('First name').''.get_lang('Last name').''.get_lang('Username').''.get_lang('Actions').'
'.Security::remove_XSS($user['firstname'] ?? '').''.Security::remove_XSS($user['lastname'] ?? '').''.Security::remove_XSS($user['username'] ?? '').''; + $content .= '
'; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= ''; + $content .= '
'; + $content .= '
'; +} +$content .= '
'; +$content .= '
'; -Display::display_footer(); +$template->assign('actions', Display::toolbarAction('toolbar-calendar-users', $toolbarActions)); +$template->assign('content', $content); +$template->display_one_col_template(); diff --git a/public/plugin/LearningCalendar/lang/en_US.php b/public/plugin/LearningCalendar/lang/en_US.php index 5903ba3c9e4..a4ac4576eb1 100644 --- a/public/plugin/LearningCalendar/lang/en_US.php +++ b/public/plugin/LearningCalendar/lang/en_US.php @@ -21,3 +21,24 @@ $strings['Date'] = 'Date'; $strings['AddMultipleUsersToCalendar'] = 'Add multiple users to a calendar'; $strings['UpdateCalendar'] = 'Update calendar'; +$strings['LearningCalendarDescription'] = 'Create learning calendars and mark busy, exam and free days for learners.'; +$strings['NoLearningCalendarAvailable'] = 'No learning calendars are available yet.'; +$strings['LearningCalendarSelectRangeHelp'] = 'Select one or more days to toggle the planned day type.'; +$strings['CalendarUsersDescription'] = 'Review users assigned to this learning calendar.'; +$strings['BackToMySpace'] = 'Back to reporting'; +$strings['AssignUsers'] = 'Assign users'; +$strings['AssignUserToCalendarHelp'] = 'Assign a learner to this learning calendar. Each learner can be assigned to one learning calendar at a time.'; +$strings['UserIdentifier'] = 'Username or user ID'; +$strings['UserIdentifierHelp'] = 'Enter an existing username or numeric user ID.'; +$strings['AssignUser'] = 'Assign user'; +$strings['UserAssignedToCalendar'] = 'The user has been assigned to this learning calendar.'; +$strings['UserMovedToCalendar'] = 'The user was assigned to another learning calendar and has been moved to this one.'; +$strings['UserAlreadyAssignedToThisCalendar'] = 'The user is already assigned to this learning calendar.'; +$strings['UserRemovedFromCalendar'] = 'The user has been removed from this learning calendar.'; +$strings['UserNotFound'] = 'User not found.'; +$strings['RemoveFromCalendar'] = 'Remove from calendar'; +$strings['RemoveUserConfirm'] = 'Remove this user from the learning calendar?'; +$strings['CalendarUserAssignmentFailed'] = 'The user could not be assigned to this learning calendar.'; + +$strings['LearningCalendarCycleHelp'] = 'Click a day to cycle: Busy → Exam → Free → clear.'; +$strings['LearningCalendarRangeHelp'] = 'Use Shift + click after selecting one day to apply the same cycle to a date range.'; diff --git a/public/plugin/LearningCalendar/lang/es.php b/public/plugin/LearningCalendar/lang/es.php index 9f7da9a0d73..17ba6914c92 100644 --- a/public/plugin/LearningCalendar/lang/es.php +++ b/public/plugin/LearningCalendar/lang/es.php @@ -21,3 +21,24 @@ $strings['Date'] = 'Fecha'; $strings['AddMultipleUsersToCalendar'] = 'Añadir usuarios múltiples a un calendario'; $strings['UpdateCalendar'] = 'Actualizar calendario'; +$strings['LearningCalendarDescription'] = 'Crea calendarios de aprendizaje y marca días ocupados, de examen o libres para los estudiantes.'; +$strings['NoLearningCalendarAvailable'] = 'Todavía no hay calendarios de aprendizaje disponibles.'; +$strings['LearningCalendarSelectRangeHelp'] = 'Selecciona uno o varios días para cambiar el tipo de día planificado.'; +$strings['CalendarUsersDescription'] = 'Revisa los usuarios asignados a este calendario de aprendizaje.'; +$strings['BackToMySpace'] = 'Volver a seguimiento'; +$strings['AssignUsers'] = 'Asignar usuarios'; +$strings['AssignUserToCalendarHelp'] = 'Asigna un estudiante a este calendario de aprendizaje. Cada estudiante puede estar asignado a un solo calendario de aprendizaje a la vez.'; +$strings['UserIdentifier'] = 'Usuario o ID de usuario'; +$strings['UserIdentifierHelp'] = 'Introduce un nombre de usuario existente o un ID numérico de usuario.'; +$strings['AssignUser'] = 'Asignar usuario'; +$strings['UserAssignedToCalendar'] = 'El usuario ha sido asignado a este calendario de aprendizaje.'; +$strings['UserMovedToCalendar'] = 'El usuario estaba asignado a otro calendario de aprendizaje y fue movido a este.'; +$strings['UserAlreadyAssignedToThisCalendar'] = 'El usuario ya está asignado a este calendario de aprendizaje.'; +$strings['UserRemovedFromCalendar'] = 'El usuario ha sido quitado de este calendario de aprendizaje.'; +$strings['UserNotFound'] = 'Usuario no encontrado.'; +$strings['RemoveFromCalendar'] = 'Quitar del calendario'; +$strings['RemoveUserConfirm'] = '¿Quitar este usuario del calendario de aprendizaje?'; +$strings['CalendarUserAssignmentFailed'] = 'No se pudo asignar el usuario a este calendario de aprendizaje.'; + +$strings['LearningCalendarCycleHelp'] = 'Haz clic en un día para cambiar: Ocupado → Examen → Libre → limpiar.'; +$strings['LearningCalendarRangeHelp'] = 'Usa Shift + clic después de seleccionar un día para aplicar el mismo cambio a un rango de fechas.'; diff --git a/public/plugin/LearningCalendar/my_events.php b/public/plugin/LearningCalendar/my_events.php new file mode 100644 index 00000000000..e220880a41f --- /dev/null +++ b/public/plugin/LearningCalendar/my_events.php @@ -0,0 +1,59 @@ + [], + ]); + exit; +} + +function learning_calendar_table_exists(string $tableName): bool +{ + $tableName = Database::escape_string($tableName); + $result = Database::query("SHOW TABLES LIKE '$tableName'"); + + return false !== $result && Database::num_rows($result) > 0; +} + +try { + $plugin = LearningCalendarPlugin::create(); + + if (!$plugin->isEnabled()) { + learning_calendar_empty_response(); + } + + $userId = api_get_user_id(); + + if (empty($userId)) { + learning_calendar_empty_response(); + } + + $requiredTables = [ + 'learning_calendar', + 'learning_calendar_events', + 'learning_calendar_user', + ]; + + foreach ($requiredTables as $requiredTable) { + if (!learning_calendar_table_exists($requiredTable)) { + learning_calendar_empty_response(); + } + } + + $startDate = isset($_GET['startDate']) ? (string) $_GET['startDate'] : ''; + $endDate = isset($_GET['endDate']) ? (string) $_GET['endDate'] : ''; + + echo json_encode([ + 'events' => $plugin->getPersonalEventsForApi((int) $userId, $startDate, $endDate), + ]); +} catch (Throwable $exception) { + learning_calendar_empty_response(); +} diff --git a/public/plugin/LearningCalendar/start.php b/public/plugin/LearningCalendar/start.php index 43e2308e0c4..20bda76a94c 100644 --- a/public/plugin/LearningCalendar/start.php +++ b/public/plugin/LearningCalendar/start.php @@ -6,14 +6,19 @@ require_once __DIR__.'/../../main/inc/global.inc.php'; +$plugin = LearningCalendarPlugin::create(); + +if (!$plugin->isEnabled()) { + api_not_allowed(true); +} + $allow = api_is_platform_admin() || api_is_teacher(); if (!$allow) { api_not_allowed(true); } -$plugin = LearningCalendarPlugin::create(); -$action = isset($_REQUEST['action']) ? $_REQUEST['action'] : ''; +$action = isset($_REQUEST['action']) ? Security::remove_XSS($_REQUEST['action']) : ''; $calendarId = isset($_REQUEST['id']) ? (int) $_REQUEST['id'] : 0; $formToString = ''; @@ -27,10 +32,10 @@ if ($form->validate()) { $values = $form->getSubmitValues(); $params = [ - 'title' => $values['title'], - 'total_hours' => $values['total_hours'], - 'minutes_per_day' => $values['minutes_per_day'], - 'description' => $values['description'], + 'title' => trim((string) $values['title']), + 'total_hours' => (int) $values['total_hours'], + 'minutes_per_day' => (int) $values['minutes_per_day'], + 'description' => (string) $values['description'], 'author_id' => api_get_user_id(), ]; Database::insert('learning_calendar', $params); @@ -41,26 +46,22 @@ break; case 'edit': - $form = new FormValidator('calendar', 'post', api_get_self().'?action=edit&id='.$calendarId); - $plugin->getForm($form); - $form->addButtonSave(get_lang('Update')); $item = $plugin->getCalendar($calendarId); $plugin->protectCalendar($item); - if (empty($item)) { - api_not_allowed(true); - } - + $form = new FormValidator('calendar', 'post', api_get_self().'?action=edit&id='.$calendarId); + $plugin->getForm($form); + $form->addButtonSave(get_lang('Update')); $form->setDefaults($item); $formToString = $form->returnForm(); if ($form->validate()) { $values = $form->getSubmitValues(); $params = [ - 'title' => $values['title'], - 'total_hours' => $values['total_hours'], - 'minutes_per_day' => $values['minutes_per_day'], - 'description' => $values['description'], + 'title' => trim((string) $values['title']), + 'total_hours' => (int) $values['total_hours'], + 'minutes_per_day' => (int) $values['minutes_per_day'], + 'description' => (string) $values['description'], ]; Database::update('learning_calendar', $params, ['id = ?' => $calendarId]); Display::addFlash(Display::return_message(get_lang('Update successful'))); @@ -88,8 +89,8 @@ break; case 'toggle_visibility': - $itemId = isset($_REQUEST['lp_item_id']) ? $_REQUEST['lp_item_id'] : 0; - $lpId = isset($_REQUEST['lp_id']) ? $_REQUEST['lp_id'] : 0; + $itemId = isset($_REQUEST['lp_item_id']) ? (int) $_REQUEST['lp_item_id'] : 0; + $lpId = isset($_REQUEST['lp_id']) ? (int) $_REQUEST['lp_id'] : 0; $plugin->toggleVisibility($itemId); Display::addFlash(Display::return_message(get_lang('Update successful'))); $url = api_get_path(WEB_CODE_PATH). @@ -100,85 +101,59 @@ break; } -$url = api_get_path(WEB_AJAX_PATH).'model.ajax.php?a=get_learning_path_calendars'; -$columns = [ - get_lang('Title'), - get_lang('Total hours'), - get_lang('Minutes per day'), - get_lang('Detail'), -]; - -$columnModel = [ - [ - 'name' => 'title', - 'index' => 'title', - 'width' => '300', - 'align' => 'left', - 'sortable' => 'false', - ], - [ - 'name' => 'total_hours', - 'index' => 'total_hours', - 'width' => '100', - 'align' => 'left', - 'sortable' => 'false', - ], - [ - 'name' => 'minutes_per_day', - 'index' => 'minutes_per_day', - 'width' => '100', - 'align' => 'left', - 'sortable' => 'false', - ], - [ - 'name' => 'actions', - 'index' => 'actions', - 'width' => '150', - 'align' => 'left', - //'formatter' => 'action_formatter', - 'sortable' => 'false', - ], -]; - -$extraParams = []; -$extraParams['autowidth'] = 'true'; -// height auto -$extraParams['height'] = 'auto'; - -$template = new Template(); - -if (in_array($action, ['add', 'edit'])) { - $actionLeft = Display::url( - Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Back')), - api_get_self().'?'.api_get_cidreq() +$template = new Template($plugin->get_lang('LearningCalendar')); + +$toolbarActions = []; +$toolbarActions[] = Display::url( + Display::getMdiIcon(ActionIcon::BACK, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, $plugin->get_lang('BackToMySpace')), + api_get_path(WEB_CODE_PATH).'my_space/index.php' +); + +if (in_array($action, ['add', 'edit'], true)) { + $toolbarActions[] = Display::url( + Display::getMdiIcon('format-list-bulleted', 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('List')), + api_get_self() ); } else { - $actionLeft = Display::url( + $toolbarActions[] = Display::url( Display::getMdiIcon(ActionIcon::ADD, 'ch-tool-icon', null, ICON_SIZE_MEDIUM, get_lang('Add')), - api_get_self().'?'.api_get_cidreq().'&action=add' + api_get_self().'?action=add' ); +} - $content = ''; - - $content .= Display::grid_html('calendars'); - $template->assign('grid', $content); +$calendarList = []; +if (!in_array($action, ['add', 'edit'], true)) { + foreach ($plugin->getCalendarList() as $calendar) { + $calendarId = (int) $calendar['id']; + $calendarList[] = [ + 'id' => $calendarId, + 'title' => $calendar['title'], + 'description' => $calendar['description'], + 'total_hours' => (int) $calendar['total_hours'], + 'minutes_per_day' => (int) $calendar['minutes_per_day'], + 'event_count' => (int) $calendar['event_count'], + 'user_count' => (int) $calendar['user_count'], + 'view_url' => api_get_path(WEB_PLUGIN_PATH).'LearningCalendar/calendar.php?id='.$calendarId, + 'users_url' => api_get_path(WEB_PLUGIN_PATH).'LearningCalendar/calendar_users.php?id='.$calendarId, + 'edit_url' => api_get_self().'?action=edit&id='.$calendarId, + 'copy_url' => api_get_self().'?action=copy&id='.$calendarId, + 'delete_url' => api_get_self().'?action=delete&id='.$calendarId, + ]; + } } +$template->assign('is_form_view', in_array($action, ['add', 'edit'], true)); $template->assign('form', $formToString); -$actions = Display::toolbarAction('toolbar-calendar', [$actionLeft]); +$template->assign('calendars', $calendarList); +$template->assign('page_title', $plugin->get_lang('LearningCalendar')); +$template->assign('page_subtitle', $plugin->get_lang('LearningCalendarDescription')); +$template->assign('empty_message', $plugin->get_lang('NoLearningCalendarAvailable')); +$template->assign('delete_confirm', get_lang('Please confirm your choice')); +$template->assign('add_url', api_get_self().'?action=add'); +$template->assign('back_url', api_get_path(WEB_CODE_PATH).'my_space/index.php'); +$template->assign('back_label', $plugin->get_lang('BackToMySpace')); + +$actions = Display::toolbarAction('toolbar-calendar', $toolbarActions); $content = $template->fetch('LearningCalendar/view/start.tpl'); $template->assign('content', $content); $template->assign('actions', $actions); diff --git a/public/plugin/LearningCalendar/view/calendar.tpl b/public/plugin/LearningCalendar/view/calendar.tpl index 5fd0db6e954..009fcdecb6d 100644 --- a/public/plugin/LearningCalendar/view/calendar.tpl +++ b/public/plugin/LearningCalendar/view/calendar.tpl @@ -1,104 +1,582 @@ +
+
+
+
+
+ {{ plugin_title }} +
+

{{ header }}

+ {% if description %} +
+ {{ description|raw }} +
+ {% endif %} +
+ +
+
+
{{ 'Total hours'|get_lang }}
+
{{ total_hours }}
+
+
+
{{ 'Minutes per day'|get_lang }}
+
{{ minutes_per_day }}
+
+
+
+
+ +
+
+
+

{{ 'Calendar'|get_lang }}

+

{{ calendar_help }}

+
+ +
+ {% for event in events %} +
+ + {{ event.title }} +
+ {% endfor %} +
+
+ +
+
{{ calendar_cycle_help }}
+
{{ calendar_range_help }}
+
+ +
+ + +
+ + +
+ +
+ +
+
+
+ + + + } - -
-
-
-
-
-
-
-
-
- - {% for event in events %} - - - - - {% endfor %} -
- {{ event.title }}: - - -
-
-
+ function loadEvents() { + return fetch(ajaxUrl + '&a=get_events', { + credentials: 'same-origin', + method: 'GET' + }).then(function (response) { + if (!response.ok) { + throw new Error('Unable to load calendar events.'); + } + + return response.json(); + }).then(function (events) { + buildEventMap(Array.isArray(events) ? events : []); + renderCalendar(); + }).catch(function (error) { + showMessage(error.message, true); + renderCalendar(); + }); + } + + previousButton.addEventListener('click', function () { + currentYear--; + loadEvents(); + }); + + nextButton.addEventListener('click', function () { + currentYear++; + loadEvents(); + }); + + renderCalendar(); + loadEvents(); +})(); + diff --git a/public/plugin/LearningCalendar/view/start.tpl b/public/plugin/LearningCalendar/view/start.tpl index 7bc19acca53..56ac62b385b 100644 --- a/public/plugin/LearningCalendar/view/start.tpl +++ b/public/plugin/LearningCalendar/view/start.tpl @@ -1,3 +1,131 @@ -{{ form }} +
+
+

{{ page_title }}

+

{{ page_subtitle }}

+
-{{ grid }} \ No newline at end of file + {% if form %} +
+
+ +

+ {% if is_form_view %} + {{ 'Settings'|get_lang }} + {% else %} + {{ page_title }} + {% endif %} +

+
+ +
+ {{ form|raw }} +
+
+ {% else %} + {% if calendars is empty %} +
+
+ +
+

{{ empty_message }}

+

{{ page_subtitle }}

+
+ {% else %} +
+ {% for calendar in calendars %} +
+
+
+ + {{ calendar.title }} + + {% if calendar.description %} +
+ {{ calendar.description|striptags }} +
+ {% endif %} +
+ +
+ +
+
+
+ {{ 'Total hours'|get_lang }} +
+
{{ calendar.total_hours }}
+
+
+
+ {{ 'Minutes per day'|get_lang }} +
+
{{ calendar.minutes_per_day }}
+
+
+
+ {{ 'Events'|get_lang }} +
+
{{ calendar.event_count }}
+
+
+
+ {{ 'Users'|get_lang }} +
+
{{ calendar.user_count }}
+
+
+ + +
+ {% endfor %} +
+ {% endif %} + {% endif %} +