diff --git a/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts b/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts
index f786edbc2..de9d5a8c9 100644
--- a/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts
+++ b/src/addons/block/recentlyaccessedcourses/components/recentlyaccessedcourses/recentlyaccessedcourses.ts
@@ -16,7 +16,7 @@ import { Component, OnInit, OnDestroy, Input, OnChanges, SimpleChange } from '@a
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 } from '@features/courses/services/courses-helper';
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
@@ -35,7 +35,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
@Input() downloadEnabled = false;
- courses: CoreEnrolledCourseDataWithOptions [] = [];
+ courses: CoreCourseSearchedDataWithExtraInfoAndOptions[] = [];
prefetchCoursesData: CorePrefetchStatusInfo = {
icon: '',
statusTranslatable: 'core.loading',
@@ -112,7 +112,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
protected async invalidateContent(): Promise {
const promises: Promise[] = [];
- promises.push(CoreCourses.invalidateUserCourses().finally(() =>
+ promises.push(CoreCourses.invalidateRecentCourses().finally(() =>
// Invalidate course completion data.
CoreUtils.allPromises(this.courseIds.map((courseId) =>
AddonCourseCompletion.invalidateCourseCompletion(courseId)))));
@@ -136,7 +136,33 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
const showCategories = this.block.configsRecord && this.block.configsRecord.displaycategories &&
this.block.configsRecord.displaycategories.value == '1';
- this.courses = await CoreCoursesHelper.getUserCoursesWithOptions('lastaccess', 10, undefined, showCategories);
+ const recentCourses = await CoreCourses.getRecentCourses();
+ const courseIds = recentCourses.map((course) => course.id);
+
+ // Get the courses using getCoursesByField to get more info about each course.
+ const courses: CoreCourseSearchedDataWithExtraInfoAndOptions[] = await CoreCourses.getCoursesByField(
+ 'ids',
+ courseIds.join(','),
+ );
+
+ // Sort them in the original order.
+ courses.sort((courseA, courseB) => courseIds.indexOf(courseA.id) - courseIds.indexOf(courseB.id));
+
+ // Get course options and extra info.
+ const options = await CoreCourses.getCoursesAdminAndNavOptions(courseIds);
+ courses.forEach((course) => {
+ course.navOptions = options.navOptions[course.id];
+ course.admOptions = options.admOptions[course.id];
+
+ if (!showCategories) {
+ course.categoryname = '';
+ }
+ });
+
+ await CoreCoursesHelper.loadCoursesColorAndImage(courses);
+
+ this.courses = courses;
+
this.initPrefetchCoursesIcons();
}
@@ -148,11 +174,7 @@ export class AddonBlockRecentlyAccessedCoursesComponent extends CoreBlockBaseCom
protected async refreshCourseList(): Promise {
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED);
- try {
- await CoreCourses.invalidateUserCourses();
- } catch (error) {
- // Ignore errors.
- }
+ await CoreUtils.ignoreErrors(CoreCourses.invalidateRecentCourses());
await this.loadContent(true);
}
diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts
index 9a8d1ffc4..5b322d60d 100644
--- a/src/core/features/course/services/course-helper.ts
+++ b/src/core/features/course/services/course-helper.ts
@@ -39,7 +39,6 @@ import {
CoreCourseSearchedData,
CoreEnrolledCourseData,
} from '@features/courses/services/courses';
-import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
import { CoreArray } from '@singletons/array';
import { CoreIonLoadingElement } from '@classes/ion-loading';
import { CoreCourseOffline } from './course-offline';
@@ -424,7 +423,7 @@ export class CoreCourseHelperProvider {
* @return Resolved when downloaded, rejected if error or canceled.
*/
async confirmAndPrefetchCourses(
- courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[],
+ courses: CoreCourseAnyCourseData[],
options: CoreCourseConfirmPrefetchCoursesOptions = {},
): Promise {
const siteId = CoreSites.getCurrentSiteId();
@@ -1302,7 +1301,7 @@ export class CoreCourseHelperProvider {
* @return Promise resolved when done.
*/
async prefetchCourses(
- courses: CoreEnrolledCourseDataWithExtraInfoAndOptions[],
+ courses: CoreCourseAnyCourseData[],
prefetch: CorePrefetchStatusInfo,
options: CoreCoursePrefetchCoursesOptions = {},
): Promise {
diff --git a/src/core/features/courses/components/course-progress/core-courses-course-progress.html b/src/core/features/courses/components/course-progress/core-courses-course-progress.html
index 33cd0cfd4..d36b8cb38 100644
--- a/src/core/features/courses/components/course-progress/core-courses-course-progress.html
+++ b/src/core/features/courses/components/course-progress/core-courses-course-progress.html
@@ -5,7 +5,7 @@
-
+
- {{ 'core.courses.aria:favourite' | translate }}
+ {{ 'core.courses.aria:favourite' | translate }}
{{ 'core.courses.aria:coursename' | translate }}
@@ -61,10 +61,10 @@
- = 0 && course.completionusertracked !== false" lines="none"
+ = 0 && completionUserTracked !== false" lines="none"
class="core-course-progress">
-
+
diff --git a/src/core/features/courses/components/course-progress/course-progress.ts b/src/core/features/courses/components/course-progress/course-progress.ts
index 2c4b9db69..c4892208b 100644
--- a/src/core/features/courses/components/course-progress/course-progress.ts
+++ b/src/core/features/courses/components/course-progress/course-progress.ts
@@ -12,7 +12,7 @@
// See the License for the specific language governing permissions and
// limitations under the License.
-import { Component, Input, OnInit, OnDestroy } from '@angular/core';
+import { Component, Input, OnInit, OnDestroy, OnChanges } from '@angular/core';
import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
@@ -21,7 +21,10 @@ import { CoreCourse, CoreCourseProvider } from '@features/course/services/course
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
import { Translate } from '@singletons';
import { CoreConstants } from '@/core/constants';
-import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '../../services/courses-helper';
+import {
+ CoreCourseAnyCourseDataWithExtraInfoAndOptions,
+ CoreEnrolledCourseDataWithExtraInfoAndOptions,
+} from '../../services/courses-helper';
import { CoreCoursesCourseOptionsMenuComponent } from '../course-options-menu/course-options-menu';
import { CoreUser } from '@features/user/services/user';
@@ -38,9 +41,10 @@ import { CoreUser } from '@features/user/services/user';
templateUrl: 'core-courses-course-progress.html',
styleUrls: ['course-progress.scss'],
})
-export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
+export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy, OnChanges {
- @Input() course!: CoreEnrolledCourseDataWithExtraInfoAndOptions; // The course to render.
+ // The course to render.
+ @Input() course!: CoreCourseAnyCourseDataWithExtraInfoAndOptions;
@Input() showAll = false; // If true, will show all actions, options, star and progress.
@Input() showDownload = true; // If true, will show download button. Only works if the options menu is not shown.
@@ -56,13 +60,16 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
showSpinner = false;
downloadCourseEnabled = false;
courseOptionMenuEnabled = false;
+ isFavourite = false;
+ progress = -1;
+ completionUserTracked: boolean | undefined = false;
protected isDestroyed = false;
protected courseStatusObserver?: CoreEventObserver;
protected siteUpdatedObserver?: CoreEventObserver;
/**
- * Component being initialized.
+ * @inheritdoc
*/
ngOnInit(): void {
@@ -73,7 +80,8 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
}
// This field is only available from 3.6 onwards.
- this.courseOptionMenuEnabled = this.showAll && typeof this.course.isfavourite != 'undefined';
+ this.courseOptionMenuEnabled = this.showAll && 'isfavourite' in this.course &&
+ typeof this.course.isfavourite != 'undefined';
// Refresh the enabled flag if site is updated.
this.siteUpdatedObserver = CoreEvents.on(CoreEvents.SITE_UPDATED, () => {
@@ -88,6 +96,15 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
}, CoreSites.getCurrentSiteId());
}
+ /**
+ * @inheritdoc
+ */
+ ngOnChanges(): void {
+ this.isFavourite = 'isfavourite' in this.course && !!this.course.isfavourite;
+ this.progress = 'progress' in this.course ? this.course.progress || -1 : -1;
+ this.completionUserTracked = 'completionusertracked' in this.course && this.course.completionusertracked;
+ }
+
/**
* Initialize prefetch course.
*/
@@ -255,7 +272,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
hide ? '1' : undefined,
);
- this.course.hidden = hide;
+ ( this.course).hidden = hide;
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
courseId: this.course.id,
course: this.course,
@@ -284,7 +301,8 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy {
try {
await CoreCourses.setFavouriteCourse(this.course.id, favourite);
- this.course.isfavourite = favourite;
+ ( this.course).isfavourite = favourite;
+ this.isFavourite = favourite;
CoreEvents.trigger(CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {
courseId: this.course.id,
course: this.course,
diff --git a/src/core/features/courses/services/courses-helper.ts b/src/core/features/courses/services/courses-helper.ts
index f5e1aaec5..150bfb05a 100644
--- a/src/core/features/courses/services/courses-helper.ts
+++ b/src/core/features/courses/services/courses-helper.ts
@@ -15,7 +15,13 @@
import { Injectable } from '@angular/core';
import { CoreUtils } from '@services/utils/utils';
import { CoreSites } from '@services/sites';
-import { CoreCourses, CoreCourseSearchedData, CoreCourseUserAdminOrNavOptionIndexed, CoreEnrolledCourseData } from './courses';
+import {
+ CoreCourseAnyCourseDataWithOptions,
+ CoreCourses,
+ CoreCourseSearchedData,
+ CoreCourseUserAdminOrNavOptionIndexed,
+ CoreEnrolledCourseData,
+} from './courses';
import { makeSingleton, Translate } from '@singletons';
import { CoreWSExternalFile } from '@services/ws';
import { AddonCourseCompletion } from '@/addons/coursecompletion/services/coursecompletion';
@@ -83,6 +89,31 @@ export class CoreCoursesHelperProvider {
this.loadCourseColorAndImage(course, colors);
}
+ /**
+ * Given a list of courses returned by core_enrol_get_users_courses, load some extra data using the WebService
+ * core_course_get_courses_by_field if available.
+ *
+ * @param courses List of courses.
+ * @param loadCategoryNames Whether load category names or not.
+ * @return Promise resolved when done.
+ */
+ /**
+ * Loads the color of courses or the thumb image.
+ *
+ * @param courses List of courses.
+ * @return Promise resolved when done.
+ */
+ async loadCoursesColorAndImage(courses: CoreCourseSearchedData[]): Promise {
+ if (!courses.length) {
+ return;
+ }
+ const colors = await this.loadCourseSiteColors();
+
+ courses.forEach((course) => {
+ this.loadCourseColorAndImage(course, colors);
+ });
+ }
+
/**
* Given a list of courses returned by core_enrol_get_users_courses, load some extra data using the WebService
* core_course_get_courses_by_field if available.
@@ -301,7 +332,27 @@ export type CoreEnrolledCourseDataWithOptions = CoreEnrolledCourseData & {
admOptions?: CoreCourseUserAdminOrNavOptionIndexed;
};
+/**
+ * Course summary data with admin and navigation option availability.
+ */
+export type CoreCourseSearchedDataWithOptions = CoreCourseSearchedData & {
+ navOptions?: CoreCourseUserAdminOrNavOptionIndexed;
+ admOptions?: CoreCourseUserAdminOrNavOptionIndexed;
+};
+
/**
* Enrolled course data with admin and navigation option availability and extra rendering info.
*/
export type CoreEnrolledCourseDataWithExtraInfoAndOptions = CoreEnrolledCourseDataWithExtraInfo & CoreEnrolledCourseDataWithOptions;
+
+/**
+ * Searched course data with admin and navigation option availability and extra rendering info.
+ */
+export type CoreCourseSearchedDataWithExtraInfoAndOptions = CoreCourseWithImageAndColor & CoreCourseSearchedDataWithOptions;
+
+/**
+ * Any course data with admin and navigation option availability and extra rendering info.
+ */
+export type CoreCourseAnyCourseDataWithExtraInfoAndOptions = CoreCourseWithImageAndColor & CoreCourseAnyCourseDataWithOptions & {
+ categoryname?: string; // Category name,
+};
diff --git a/src/core/features/courses/services/courses.ts b/src/core/features/courses/services/courses.ts
index f16b0e41a..e05585344 100644
--- a/src/core/features/courses/services/courses.ts
+++ b/src/core/features/courses/services/courses.ts
@@ -14,7 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreLogger } from '@singletons/logger';
-import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
+import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { makeSingleton } from '@singletons';
import { CoreStatusWithWarningsWSResponse, CoreWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
@@ -45,6 +45,7 @@ declare module '@singletons/events' {
export class CoreCoursesProvider {
static readonly SEARCH_PER_PAGE = 20;
+ static readonly RECENT_PER_PAGE = 10;
static readonly ENROL_INVALID_KEY = 'CoreCoursesEnrolInvalidKey';
static readonly EVENT_MY_COURSES_CHANGED = 'courses_my_courses_changed'; // User course list changed while app is running.
// A course was hidden/favourite, or user enroled in a course.
@@ -566,7 +567,7 @@ export class CoreCoursesProvider {
customFieldName: string,
customFieldValue: string,
siteId?: string,
- ): Promise {
+ ): Promise {
const site = await CoreSites.getSite(siteId);
const params: CoreCourseGetEnrolledCoursesByTimelineClassificationWSParams = {
classification: 'customfield',
@@ -648,6 +649,40 @@ export class CoreCoursesProvider {
return ({ navOptions: navOptions, admOptions: admOptions });
}
+ /**
+ * Get cache key for get recent courses WS call.
+ *
+ * @param userId User ID.
+ * @return Cache key.
+ */
+ protected getRecentCoursesCacheKey(userId: number): string {
+ return `${ROOT_CACHE_KEY}:recentcourses:${userId}`;
+ }
+
+ /**
+ * Get recent courses.
+ *
+ * @param options Options.
+ * @return Promise resolved with courses.
+ * @since 3.6
+ */
+ async getRecentCourses(options: CoreCourseGetRecentCoursesOptions = {}): Promise {
+ const site = await CoreSites.getSite(options.siteId);
+
+ const userId = options.userId || site.getUserId();
+ const params: CoreCourseGetRecentCoursesWSParams = {
+ userid: userId,
+ offset: options.offset || 0,
+ limit: options.limit || CoreCoursesProvider.RECENT_PER_PAGE,
+ sort: options.sort,
+ };
+ const preSets: CoreSiteWSPreSets = {
+ cacheKey: this.getRecentCoursesCacheKey(userId),
+ };
+
+ return await site.read('core_course_get_recent_courses', params, preSets);
+ }
+
/**
* Get the common part of the cache keys for user administration options WS calls.
*
@@ -995,6 +1030,19 @@ export class CoreCoursesProvider {
return site.invalidateWsCacheForKey(this.getCoursesByFieldCacheKey(field, value));
}
+ /**
+ * Invalidates get recent courses WS call.
+ *
+ * @param userId User ID. If not defined, current user.
+ * @param siteId Site Id. If not defined, use current site.
+ * @return Promise resolved when the data is invalidated.
+ */
+ async invalidateRecentCourses(userId?: number, siteId?: string): Promise {
+ const site = await CoreSites.getSite(siteId);
+
+ await site.invalidateWsCacheForKey(this.getRecentCoursesCacheKey(userId || site.getUserId()));
+ }
+
/**
* Invalidates all user administration options.
*
@@ -1428,13 +1476,15 @@ type CoreCourseGetCoursesWSParams = {
export type CoreCourseGetCoursesWSResponse = CoreCourseGetCoursesData[];
/**
- * Course type exported in CoreCourseGetEnrolledCoursesByTimelineClassificationWSResponse;
+ * Course data exported by course_summary_exporter;
*/
-export type CoreCourseGetEnrolledCoursesByTimelineClassification = CoreCourseBasicData & { // Course.
+export type CoreCourseSummaryData = CoreCourseBasicData & { // Course.
idnumber: string; // Idnumber.
startdate: number; // Startdate.
enddate: number; // Enddate.
visible: boolean; // Visible.
+ showactivitydates: boolean; // Showactivitydates.
+ showcompletionconditions: boolean; // Showcompletionconditions.
fullnamedisplay: string; // Fullnamedisplay.
viewurl: string; // Viewurl.
courseimage: string; // Courseimage.
@@ -1463,7 +1513,7 @@ type CoreCourseGetEnrolledCoursesByTimelineClassificationWSParams = {
* Data returned by core_course_get_enrolled_courses_by_timeline_classification WS.
*/
export type CoreCourseGetEnrolledCoursesByTimelineClassificationWSResponse = {
- courses: CoreCourseGetEnrolledCoursesByTimelineClassification[];
+ courses: CoreCourseSummaryData[];
nextoffset: number; // Offset for the next request.
};
@@ -1588,6 +1638,26 @@ export type EnrolGuestGetInstanceInfoWSResponse = {
warnings?: CoreWSExternalWarning[];
};
+/**
+ * Params of core_course_get_recent_courses WS.
+ */
+export type CoreCourseGetRecentCoursesWSParams = {
+ userid?: number; // Id of the user, default to current user.
+ limit?: number; // Result set limit.
+ offset?: number; // Result set offset.
+ sort?: string; // Sort string.
+};
+
+/**
+ * Options for getRecentCourses.
+ */
+export type CoreCourseGetRecentCoursesOptions = CoreSitesCommonWSOptions & {
+ userId?: number; // Id of the user, default to current user.
+ limit?: number; // Result set limit.
+ offset?: number; // Result set offset.
+ sort?: string; // Sort string.
+};
+
/**
* Course guest enrolment method.
*/