diff --git a/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts b/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts index b6aef6614..6bbad0d5d 100644 --- a/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts +++ b/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts @@ -21,11 +21,16 @@ import { CoreCourses, CoreCourseSummaryData, } from '@features/courses/services/courses'; -import { CoreCourseSearchedDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper'; +import { + CoreCourseSearchedDataWithExtraInfoAndOptions, + CoreCoursesHelper, + CoreEnrolledCourseDataWithOptions, +} from '@features/courses/services/courses-helper'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion'; import { CoreBlockBaseComponent } from '@features/block/classes/base-block-component'; import { CoreUtils } from '@services/utils/utils'; +import { CoreSite } from '@classes/site'; /** * Component to render a recent courses block. @@ -38,11 +43,12 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom @Input() downloadEnabled = false; - courses: (Omit & CoreCourseSearchedDataWithExtraInfoAndOptions)[] = []; + courses: AddonBlockRecentlyAccessedCourse[] = []; downloadCourseEnabled = false; scrollElementId!: string; + protected site!: CoreSite; protected isDestroyed = false; protected coursesObserver?: CoreEventObserver; protected updateSiteObserver?: CoreEventObserver; @@ -50,6 +56,8 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom constructor() { super('AddonBlockRecentlyAccessedCoursesComponent'); + + this.site = CoreSites.getRequiredCurrentSite(); } /** @@ -68,14 +76,14 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom this.updateSiteObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => { this.downloadCourseEnabled = !CoreCourses.isDownloadCourseDisabledInSite(); - }, CoreSites.getCurrentSiteId()); + }, this.site.getId()); this.coursesObserver = CoreEvents.on( CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, (data) => { this.refreshCourseList(data); }, - CoreSites.getCurrentSiteId(), + this.site.getId(), ); super.ngOnInit(); @@ -99,8 +107,12 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom protected async invalidateCourses(courseIds: number[]): Promise { const promises: Promise[] = []; + const invalidateCoursePromise = this.site.isVersionGreaterEqualThan('3.8') + ? CoreCourses.invalidateRecentCourses() + : CoreCourses.invalidateUserCourses(); + // Invalidate course completion data. - promises.push(CoreCourses.invalidateRecentCourses().finally(() => + promises.push(invalidateCoursePromise.finally(() => CoreUtils.allPromises(courseIds.map((courseId) => AddonCourseCompletion.invalidateCourseCompletion(courseId))))); @@ -123,6 +135,13 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom const showCategories = this.block.configsRecord && this.block.configsRecord.displaycategories && this.block.configsRecord.displaycategories.value == '1'; + // WS is failing on 3.7 and 3.6, use a fallback. + if (!this.site.isVersionGreaterEqualThan('3.8')) { + this.courses = await CoreCoursesHelper.getUserCoursesWithOptions('lastaccess', 10, undefined, showCategories); + + return; + } + const recentCourses = await CoreCourses.getRecentCourses(); const courseIds = recentCourses.map((course) => course.id); @@ -191,3 +210,9 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom } } + +type AddonBlockRecentlyAccessedCourse = + (Omit & CoreCourseSearchedDataWithExtraInfoAndOptions) | + (CoreEnrolledCourseDataWithOptions & { + categoryname?: string; // Category name, + }); diff --git a/src/addons/block/starredcourses/components/starredcourses/starredcourses.ts b/src/addons/block/starredcourses/components/starredcourses/starredcourses.ts index 38fd77081..8e58120ad 100644 --- a/src/addons/block/starredcourses/components/starredcourses/starredcourses.ts +++ b/src/addons/block/starredcourses/components/starredcourses/starredcourses.ts @@ -16,11 +16,17 @@ import { Component, OnInit, OnDestroy, Input } from '@angular/core'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreSites } from '@services/sites'; import { CoreCoursesProvider, CoreCoursesMyCoursesUpdatedEventData, CoreCourses } from '@features/courses/services/courses'; -import { CoreCoursesHelper, CoreEnrolledCourseDataWithOptions } from '@features/courses/services/courses-helper'; +import { + CoreCourseSearchedDataWithExtraInfoAndOptions, + CoreCoursesHelper, + CoreEnrolledCourseDataWithOptions, +} from '@features/courses/services/courses-helper'; import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate'; import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion'; import { CoreBlockBaseComponent } from '@features/block/classes/base-block-component'; import { CoreUtils } from '@services/utils/utils'; +import { CoreSite } from '@classes/site'; +import { AddonBlockStarredCourse, AddonBlockStarredCourses } from '../../services/starredcourses'; /** * Component to render a starred courses block. @@ -33,11 +39,12 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im @Input() downloadEnabled = false; - courses: CoreEnrolledCourseDataWithOptions [] = []; + courses: AddonBlockStarredCoursesCourse[] = []; downloadCourseEnabled = false; scrollElementId!: string; + protected site!: CoreSite; protected isDestroyed = false; protected coursesObserver?: CoreEventObserver; protected updateSiteObserver?: CoreEventObserver; @@ -45,6 +52,8 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im constructor() { super('AddonBlockStarredCoursesComponent'); + + this.site = CoreSites.getRequiredCurrentSite(); } /** @@ -94,8 +103,12 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im protected async invalidateCourses(courseIds: number[]): Promise { const promises: Promise[] = []; + const invalidateCoursePromise = this.site.isVersionGreaterEqualThan('4.0') + ? CoreCourses.invalidateUserCourses() + : AddonBlockStarredCourses.invalidateStarredCourses(); + // Invalidate course completion data. - promises.push(CoreCourses.invalidateUserCourses().finally(() => + promises.push(invalidateCoursePromise.finally(() => CoreUtils.allPromises(courseIds.map((courseId) => AddonCourseCompletion.invalidateCourseCompletion(courseId))))); @@ -118,8 +131,36 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im const showCategories = this.block.configsRecord && this.block.configsRecord.displaycategories && this.block.configsRecord.displaycategories.value == '1'; - // @TODO: Sort won't coincide with website because timemodified is not informed. - this.courses = await CoreCoursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite', showCategories); + if (this.site.isVersionGreaterEqualThan('4.0')) { + this.courses = await CoreCoursesHelper.getUserCoursesWithOptions('timemodified', 0, 'isfavourite', showCategories); + + return; + } + + // Timemodified not present, use the block WS to retrieve the info. + const starredCourses = await AddonBlockStarredCourses.getStarredCourses(); + + const courseIds = starredCourses.map((course) => course.id); + + // Get the courses using getCoursesByField to get more info about each course. + const courses = await CoreCourses.getCoursesByField('ids', courseIds.join(',')); + + this.courses = starredCourses.map((recentCourse) => { + const course = courses.find((course) => recentCourse.id == course.id); + + return Object.assign(recentCourse, course); + }); + + // Get course options and extra info. + const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds); + this.courses.forEach((course) => { + course.navOptions = options.navOptions[course.id]; + course.admOptions = options.admOptions[course.id]; + + if (!showCategories) { + course.categoryname = ''; + } + }); } /** @@ -155,7 +196,6 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im } await this.invalidateCourses([course.id]); - } } @@ -169,3 +209,9 @@ export class AddonBlockStarredCoursesComponent extends CoreBlockBaseComponent im } } + +type AddonBlockStarredCoursesCourse = + (AddonBlockStarredCourse & CoreCourseSearchedDataWithExtraInfoAndOptions) | + (CoreEnrolledCourseDataWithOptions & { + categoryname?: string; // Category name, + }); diff --git a/src/addons/block/starredcourses/services/starredcourses.ts b/src/addons/block/starredcourses/services/starredcourses.ts new file mode 100644 index 000000000..839b05dd5 --- /dev/null +++ b/src/addons/block/starredcourses/services/starredcourses.ts @@ -0,0 +1,101 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreSites } from '@services/sites'; +import { CoreSiteWSPreSets } from '@classes/site'; +import { makeSingleton } from '@singletons'; + +const ROOT_CACHE_KEY = 'AddonBlockStarredCourses:'; + +/** + * Service that provides some features regarding starred courses. + */ +@Injectable( { providedIn: 'root' }) +export class AddonBlockStarredCoursesProvider { + + /** + * Get cache key for get starred courrses value WS call. + * + * @return Cache key. + */ + protected getStarredCoursesCacheKey(): string { + return ROOT_CACHE_KEY + ':starredcourses'; + } + + /** + * Get starred courrses. + * + * @param siteId Site ID. If not defined, use current site. + * @return Promise resolved when the info is retrieved. + */ + async getStarredCourses(siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getStarredCoursesCacheKey(), + }; + + return await site.read('block_starredcourses_get_starred_courses', undefined, preSets); + } + + /** + * Invalidates get starred courrses WS call. + * + * @param siteId Site ID to invalidate. If not defined, use current site. + * @return Promise resolved when the data is invalidated. + */ + async invalidateStarredCourses(siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getStarredCoursesCacheKey()); + } + +} +export const AddonBlockStarredCourses = makeSingleton(AddonBlockStarredCoursesProvider); + +/** + * Params of block_starredcourses_get_starred_courses WS. + */ +export type AddonBlockStarredCoursesGetStarredCoursesWSParams = { + limit?: number; // Limit. + offset?: number; // Offset. +}; + +/** + * Data returned by block_starredcourses_get_starred_courses WS. + */ +export type AddonBlockStarredCourse = { + id: number; // Id. + fullname: string; // Fullname. + shortname: string; // Shortname. + idnumber: string; // Idnumber. + summary: string; // Summary. + summaryformat: number; // Summary format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN). + startdate: number; // Startdate. + enddate: number; // Enddate. + visible: boolean; // Visible. + showactivitydates: boolean; // Showactivitydates. + showcompletionconditions: boolean; // Showcompletionconditions. + fullnamedisplay: string; // Fullnamedisplay. + viewurl: string; // Viewurl. + courseimage: string; // Courseimage. + progress?: number; // Progress. + hasprogress: boolean; // Hasprogress. + isfavourite: boolean; // Isfavourite. + hidden: boolean; // Hidden. + timeaccess?: number; // Timeaccess. + showshortname: boolean; // Showshortname. + coursecategory: string; // Coursecategory. +}; diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index 49590c2af..acea7dd52 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -1308,6 +1308,7 @@ export type CoreCourseSummary = { coursecategory: string; // @since 3.7. Coursecategory. showactivitydates: boolean | null; // @since 3.11. Whether the activity dates are shown or not. showcompletionconditions: boolean | null; // @since 3.11. Whether the activity completion conditions are shown or not. + timemodified?: number; // @since 4.0. Last time course settings were updated (timestamp). }; /** diff --git a/src/core/features/courses/services/courses-helper.ts b/src/core/features/courses/services/courses-helper.ts index df8e4e92c..62f98ed70 100644 --- a/src/core/features/courses/services/courses-helper.ts +++ b/src/core/features/courses/services/courses-helper.ts @@ -236,7 +236,7 @@ export class CoreCoursesHelperProvider { courses = courses.filter((course) => !!course.isfavourite); break; default: - // Filter not implemented. + // Filter not implemented. } switch (sort) { @@ -251,10 +251,10 @@ export class CoreCoursesHelperProvider { case 'lastaccess': courses.sort((a, b) => (b.lastaccess || 0) - (a.lastaccess || 0)); break; - // @todo Time modified property is not defined in CoreEnrolledCourseDataWithOptions, so it Won't do anything. - // case 'timemodified': - // courses.sort((a, b) => b.timemodified - a.timemodified); - // break; + // Time modified property is defined on Moodle 4.0. + case 'timemodified': + courses.sort((a, b) => (b.timemodified || 0) - (a.timemodified || 0)); + break; case 'shortname': courses.sort((a, b) => { const compareA = a.shortname.toLowerCase(); diff --git a/src/core/features/courses/services/courses.ts b/src/core/features/courses/services/courses.ts index c28874747..002d3fc28 100644 --- a/src/core/features/courses/services/courses.ts +++ b/src/core/features/courses/services/courses.ts @@ -1328,6 +1328,7 @@ export type CoreEnrolledCourseData = CoreEnrolledCourseBasicData & { overviewfiles?: CoreWSExternalFile[]; showactivitydates?: boolean; // @since 3.11. Whether the activity dates are shown or not. showcompletionconditions?: boolean; // @since 3.11. Whether the activity completion conditions are shown or not. + timemodified?: number; // @since 4.0. Last time course settings were updated (timestamp). }; /**