diff --git a/src/addons/block/calendarmonth/services/block-handler.ts b/src/addons/block/calendarmonth/services/block-handler.ts index 793aea1157d..fa717ab6cb7 100644 --- a/src/addons/block/calendarmonth/services/block-handler.ts +++ b/src/addons/block/calendarmonth/services/block-handler.ts @@ -20,6 +20,7 @@ import { CoreCourseBlock } from '@features/course/services/course'; import { Params } from '@angular/router'; import { makeSingleton } from '@singletons'; import { AddonCalendarMainMenuHandlerService } from '@addons/calendar/services/handlers/mainmenu'; +import { ContextLevel } from '@/core/constants'; /** * Block handler. @@ -38,8 +39,8 @@ export class AddonBlockCalendarMonthHandlerService extends CoreBlockBaseHandler * @param instanceId The instance ID associated with the context level. * @returns Data or promise resolved with the data. */ - getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData { - const linkParams: Params = contextLevel == 'course' ? { courseId: instanceId } : {}; + getDisplayData(block: CoreCourseBlock, contextLevel: ContextLevel, instanceId: number): CoreBlockHandlerData { + const linkParams: Params = contextLevel === ContextLevel.COURSE ? { courseId: instanceId } : {}; return { title: 'addon.block_calendarmonth.pluginname', diff --git a/src/addons/block/calendarupcoming/services/block-handler.ts b/src/addons/block/calendarupcoming/services/block-handler.ts index 6a7b79ce61e..be3ff2c1eff 100644 --- a/src/addons/block/calendarupcoming/services/block-handler.ts +++ b/src/addons/block/calendarupcoming/services/block-handler.ts @@ -21,6 +21,7 @@ import { Params } from '@angular/router'; import { makeSingleton } from '@singletons'; import { AddonCalendarMainMenuHandlerService } from '@addons/calendar/services/handlers/mainmenu'; import { CoreSites } from '@services/sites'; +import { ContextLevel } from '@/core/constants'; /** * Block handler. @@ -39,10 +40,10 @@ export class AddonBlockCalendarUpcomingHandlerService extends CoreBlockBaseHandl * @param instanceId The instance ID associated with the context level. * @returns Data or promise resolved with the data. */ - getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData { + getDisplayData(block: CoreCourseBlock, contextLevel: ContextLevel, instanceId: number): CoreBlockHandlerData { const linkParams: Params = { upcoming: true }; - if (contextLevel == 'course' && instanceId !== CoreSites.getCurrentSiteHomeId()) { + if (contextLevel === ContextLevel.COURSE && instanceId !== CoreSites.getCurrentSiteHomeId()) { linkParams.courseId = instanceId; } diff --git a/src/addons/block/comments/services/block-handler.ts b/src/addons/block/comments/services/block-handler.ts index 8a77ac7a446..42b4d72c0b7 100644 --- a/src/addons/block/comments/services/block-handler.ts +++ b/src/addons/block/comments/services/block-handler.ts @@ -19,6 +19,7 @@ import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler import { CoreCourseBlock } from '@features/course/services/course'; import { makeSingleton } from '@singletons'; import { CoreComments } from '@features/comments/services/comments'; +import { ContextLevel } from '@/core/constants'; /** * Block handler. @@ -44,7 +45,7 @@ export class AddonBlockCommentsHandlerService extends CoreBlockBaseHandler { * @param instanceId The instance ID associated with the context level. * @returns Data or promise resolved with the data. */ - getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData { + getDisplayData(block: CoreCourseBlock, contextLevel: ContextLevel, instanceId: number): CoreBlockHandlerData { return { title: 'addon.block_comments.pluginname', class: 'addon-block-comments', diff --git a/src/addons/block/completionstatus/services/block-handler.ts b/src/addons/block/completionstatus/services/block-handler.ts index 13cf265c80a..6039f105470 100644 --- a/src/addons/block/completionstatus/services/block-handler.ts +++ b/src/addons/block/completionstatus/services/block-handler.ts @@ -19,6 +19,7 @@ import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler import { CoreCourseBlock } from '@features/course/services/course'; import { makeSingleton } from '@singletons'; import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion'; +import { ContextLevel } from '@/core/constants'; /** * Block handler. @@ -41,7 +42,7 @@ export class AddonBlockCompletionStatusHandlerService extends CoreBlockBaseHandl */ async getDisplayData( block: CoreCourseBlock, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, ): Promise { if (contextLevel !== 'course') { diff --git a/src/addons/block/globalsearch/services/block-handler.ts b/src/addons/block/globalsearch/services/block-handler.ts index d338a2777e3..5938bbaa1ef 100644 --- a/src/addons/block/globalsearch/services/block-handler.ts +++ b/src/addons/block/globalsearch/services/block-handler.ts @@ -20,6 +20,7 @@ import { makeSingleton } from '@singletons'; import { CoreCourseBlock } from '@features/course/services/course'; import { CORE_SEARCH_PAGE_NAME } from '@features/search/services/handlers/mainmenu'; import { CoreSearchGlobalSearch } from '@features/search/services/global-search'; +import { ContextLevel } from '@/core/constants'; /** * Block handler. @@ -40,8 +41,8 @@ export class AddonBlockGlobalSearchHandlerService extends CoreBlockBaseHandler { /** * @inheritdoc */ - getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData | undefined { - const isCourseSearch = contextLevel === 'course'; + getDisplayData(block: CoreCourseBlock, contextLevel: ContextLevel, instanceId: number): CoreBlockHandlerData | undefined { + const isCourseSearch = contextLevel === ContextLevel.COURSE; return { title: isCourseSearch ? 'core.search' : 'addon.block_globalsearch.pluginname', diff --git a/src/addons/block/searchforums/services/block-handler.ts b/src/addons/block/searchforums/services/block-handler.ts index b1425a34da1..e05a03cb6cd 100644 --- a/src/addons/block/searchforums/services/block-handler.ts +++ b/src/addons/block/searchforums/services/block-handler.ts @@ -20,6 +20,7 @@ import { makeSingleton } from '@singletons'; import { ADDON_MOD_FORUM_SEARCH_PAGE_NAME } from '@addons/mod/forum/constants'; import { CoreCourseBlock } from '@features/course/services/course'; import { CoreSearchGlobalSearch } from '@features/search/services/global-search'; +import { ContextLevel } from '@/core/constants'; /** * Block handler. @@ -42,10 +43,10 @@ export class AddonBlockSearchForumsHandlerService extends CoreBlockBaseHandler { */ async getDisplayData( block: CoreCourseBlock, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, ): Promise { - if (contextLevel !== 'course') { + if (contextLevel !== ContextLevel.COURSE) { return; } diff --git a/src/addons/block/selfcompletion/services/block-handler.ts b/src/addons/block/selfcompletion/services/block-handler.ts index 2d82fe4c0df..d0b0b0b2ea3 100644 --- a/src/addons/block/selfcompletion/services/block-handler.ts +++ b/src/addons/block/selfcompletion/services/block-handler.ts @@ -19,6 +19,7 @@ import { CoreBlockBaseHandler } from '@features/block/classes/base-block-handler import { CoreCourseBlock } from '@features/course/services/course'; import { makeSingleton } from '@singletons'; import { AddonCourseCompletion } from '@addons/coursecompletion/services/coursecompletion'; +import { ContextLevel } from '@/core/constants'; /** * Block handler. @@ -41,10 +42,10 @@ export class AddonBlockSelfCompletionHandlerService extends CoreBlockBaseHandler */ async getDisplayData( block: CoreCourseBlock, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, ): Promise { - if (contextLevel !== 'course') { + if (contextLevel !== ContextLevel.COURSE) { return; } diff --git a/src/addons/blog/pages/entries/entries.html b/src/addons/blog/pages/entries/entries.html index ee8584a0834..67120df9f1f 100644 --- a/src/addons/blog/pages/entries/entries.html +++ b/src/addons/blog/pages/entries/entries.html @@ -30,7 +30,7 @@

{{ title | translate }}

+ [contextInstanceId]="contextInstanceId" [courseId]="entry.courseid" />

{{ 'addon.blog.' + entry.publishTranslated! | translate}} @@ -47,8 +47,8 @@

- + @@ -57,10 +57,9 @@

- - + + {{ 'addon.blog.linktooriginalentry' | translate }} diff --git a/src/addons/blog/pages/entries/entries.ts b/src/addons/blog/pages/entries/entries.ts index 3441163a0af..57527c2a98d 100644 --- a/src/addons/blog/pages/entries/entries.ts +++ b/src/addons/blog/pages/entries/entries.ts @@ -273,7 +273,7 @@ export class AddonBlogEntriesPage implements OnInit { */ refresh(refresher?: HTMLIonRefresherElement): void { const promises = this.entries.map((entry) => - CoreComments.invalidateCommentsData('user', entry.userid, this.component, entry.id, 'format_blog')); + CoreComments.invalidateCommentsData(ContextLevel.USER, entry.userid, this.component, entry.id, 'format_blog')); promises.push(AddonBlog.invalidateEntries(this.filter)); @@ -304,6 +304,6 @@ export class AddonBlogEntriesPage implements OnInit { type AddonBlogPostFormatted = AddonBlogPost & { publishTranslated?: string; // Calculated in the app. Key of the string to translate the publish state of the post. user?: CoreUserProfile; // Calculated in the app. Data of the user that wrote the post. - contextLevel?: string; // Calculated in the app. The context level of the entry. + contextLevel?: ContextLevel; // Calculated in the app. The context level of the entry. contextInstanceId?: number; // Calculated in the app. The context instance id. }; diff --git a/src/addons/calendar/pages/edit-event/edit-event.ts b/src/addons/calendar/pages/edit-event/edit-event.ts index f95dec76200..a48ff91e797 100644 --- a/src/addons/calendar/pages/edit-event/edit-event.ts +++ b/src/addons/calendar/pages/edit-event/edit-event.ts @@ -46,6 +46,7 @@ import { CoreReminders, CoreRemindersService, CoreRemindersUnits } from '@featur import { CoreRemindersSetReminderMenuComponent } from '@features/reminders/components/set-reminder-menu/set-reminder-menu'; import moment from 'moment-timezone'; import { ADDON_CALENDAR_COMPONENT } from '@addons/calendar/constants'; +import { ContextLevel } from '@/core/constants'; /** * Page that displays a form to create/edit an event. @@ -266,7 +267,7 @@ export class AddonCalendarEditEventPage implements OnInit, OnDestroy, CanLeave { const courseFillFullname = async (course: CoreCourseSearchedData | CoreEnrolledCourseData): Promise => { try { - const result = await CoreFilterHelper.getFiltersAndFormatText(course.fullname, 'course', course.id); + const result = await CoreFilterHelper.getFiltersAndFormatText(course.fullname, ContextLevel.COURSE, course.id); course.fullname = result.text; } catch { // Ignore errors. diff --git a/src/addons/competency/pages/competencies/competencies.page.ts b/src/addons/competency/pages/competencies/competencies.page.ts index 9a3f1750b4e..023fc647dd7 100644 --- a/src/addons/competency/pages/competencies/competencies.page.ts +++ b/src/addons/competency/pages/competencies/competencies.page.ts @@ -48,7 +48,7 @@ export class AddonCompetencyCompetenciesPage implements AfterViewInit, OnDestroy >; title = ''; - contextLevel?: string; + contextLevel?: ContextLevel; contextInstanceId?: number; protected logView: () => void; diff --git a/src/addons/competency/pages/competency/competency.page.ts b/src/addons/competency/pages/competency/competency.page.ts index 8273d4428e3..c40a22c13be 100644 --- a/src/addons/competency/pages/competency/competency.page.ts +++ b/src/addons/competency/pages/competency/competency.page.ts @@ -58,7 +58,7 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy { user?: CoreUserSummary; competency?: AddonCompetencyDataForUserCompetencySummaryWSResponse; userCompetency?: AddonCompetencyUserCompetencyPlan | AddonCompetencyUserCompetency | AddonCompetencyUserCompetencyCourse; - contextLevel?: string; + contextLevel?: ContextLevel; contextInstanceId?: number; protected logView: () => void; diff --git a/src/addons/mod/assign/feedback/comments/component/comments.ts b/src/addons/mod/assign/feedback/comments/component/comments.ts index c5f268f2337..7113410cbd8 100644 --- a/src/addons/mod/assign/feedback/comments/component/comments.ts +++ b/src/addons/mod/assign/feedback/comments/component/comments.ts @@ -25,6 +25,7 @@ import { AddonModAssignFeedbackDelegate } from '@addons/mod/assign/services/feed import { AddonModAssignOffline } from '@addons/mod/assign/services/assign-offline'; import { CoreUtils } from '@services/utils/utils'; import { AddonModAssignFeedbackPluginBaseComponent } from '@addons/mod/assign/classes/base-feedback-plugin-component'; +import { ContextLevel } from '@/core/constants'; /** * Component to render a comments feedback plugin. */ @@ -69,7 +70,7 @@ export class AddonModAssignFeedbackCommentsComponent extends AddonModAssignFeedb component: this.component, componentId: this.assign.cmid, filter: true, - contextLevel: 'module', + contextLevel: ContextLevel.MODULE, instanceId: this.assign.cmid, courseId: this.assign.course, }); diff --git a/src/addons/mod/assign/services/assign.ts b/src/addons/mod/assign/services/assign.ts index 413b4ac2616..a0bd7225245 100644 --- a/src/addons/mod/assign/services/assign.ts +++ b/src/addons/mod/assign/services/assign.ts @@ -36,6 +36,7 @@ import { CoreFormFields } from '@singletons/form'; import { CoreFileHelper } from '@services/file-helper'; import { CoreIonicColorNames } from '@singletons/colors'; import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; +import { ContextLevel } from '@/core/constants'; const ROOT_CACHE_KEY = 'mmaModAssign:'; @@ -757,7 +758,7 @@ export class AddonModAssignProvider { promises.push(this.invalidateAssignmentUserMappingsData(assign.id, siteId)); promises.push(this.invalidateAssignmentGradesData(assign.id, siteId)); promises.push(this.invalidateListParticipantsData(assign.id, siteId)); - promises.push(CoreComments.invalidateCommentsByInstance('module', assign.id, siteId)); + promises.push(CoreComments.invalidateCommentsByInstance(ContextLevel.MODULE, assign.id, siteId)); promises.push(this.invalidateAssignmentData(courseId, siteId)); promises.push(CoreGrades.invalidateAllCourseGradesData(courseId)); diff --git a/src/addons/mod/assign/submission/comments/component/comments.ts b/src/addons/mod/assign/submission/comments/component/comments.ts index 46cfe586132..099073d2508 100644 --- a/src/addons/mod/assign/submission/comments/component/comments.ts +++ b/src/addons/mod/assign/submission/comments/component/comments.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { AddonModAssignSubmissionPluginBaseComponent } from '@addons/mod/assign/classes/base-submission-plugin-component'; import { Component, ViewChild } from '@angular/core'; import { CoreCommentsCommentsComponent } from '@features/comments/components/comments/comments'; @@ -43,7 +44,7 @@ export class AddonModAssignSubmissionCommentsComponent extends AddonModAssignSub */ invalidate(): Promise { return CoreComments.invalidateCommentsData( - 'module', + ContextLevel.MODULE, this.assign.cmid, 'assignsubmission_comments', this.submission.id, diff --git a/src/addons/mod/assign/submission/comments/services/handler.ts b/src/addons/mod/assign/submission/comments/services/handler.ts index 3bb5a0162cb..97aa9a4b900 100644 --- a/src/addons/mod/assign/submission/comments/services/handler.ts +++ b/src/addons/mod/assign/submission/comments/services/handler.ts @@ -19,6 +19,7 @@ import { Injectable, Type } from '@angular/core'; import { CoreComments } from '@features/comments/services/comments'; import { makeSingleton } from '@singletons'; import { AddonModAssignSubmissionCommentsComponent } from '../component/comments'; +import { ContextLevel } from '@/core/constants'; /** * Handler for comments submission plugin. @@ -68,7 +69,7 @@ export class AddonModAssignSubmissionCommentsHandlerService implements AddonModA siteId?: string, ): Promise { await CoreComments.getComments( - 'module', + ContextLevel.MODULE, assign.cmid, 'assignsubmission_comments', submission.id, diff --git a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts index f91d185c9bd..942c455186b 100644 --- a/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts +++ b/src/addons/mod/assign/submission/onlinetext/component/onlinetext.ts @@ -21,6 +21,7 @@ import { CoreSites } from '@services/sites'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { AddonModAssignSubmissionOnlineTextPluginData } from '../services/handler'; +import { ContextLevel } from '@/core/constants'; /** * Component to render an onlinetext submission plugin. @@ -86,7 +87,7 @@ export class AddonModAssignSubmissionOnlineTextComponent extends AddonModAssignS component: this.component, componentId: this.assign.cmid, filter: true, - contextLevel: 'module', + contextLevel: ContextLevel.MODULE, instanceId: this.assign.cmid, courseId: this.assign.course, }); diff --git a/src/addons/mod/data/services/handlers/prefetch.ts b/src/addons/mod/data/services/handlers/prefetch.ts index c8a33a75cb6..d34fa71232f 100644 --- a/src/addons/mod/data/services/handlers/prefetch.ts +++ b/src/addons/mod/data/services/handlers/prefetch.ts @@ -26,6 +26,7 @@ import { CoreWSFile } from '@services/ws'; import { makeSingleton } from '@singletons'; import { AddonModDataProvider, AddonModDataEntry, AddonModData, AddonModDataData } from '../data'; import { AddonModDataSync, AddonModDataSyncResult } from '../data-sync'; +import { ContextLevel } from '@/core/constants'; /** * Handler to prefetch databases. @@ -249,7 +250,7 @@ export class AddonModDataPrefetchHandlerService extends CoreCourseActivityPrefet if (commentsEnabled && database.comments) { promises.push(CoreComments.getComments( - 'module', + ContextLevel.MODULE, database.coursemodule, 'mod_data', entry.id, diff --git a/src/addons/mod/glossary/components/index/index.ts b/src/addons/mod/glossary/components/index/index.ts index 56f5c4b87c6..a7dcd26aba3 100644 --- a/src/addons/mod/glossary/components/index/index.ts +++ b/src/addons/mod/glossary/components/index/index.ts @@ -170,15 +170,25 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity // Listen for offline ratings saved and synced. this.observers.push(CoreEvents.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => { - if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module' - && data.instanceId == this.glossary.coursemodule) { + if ( + this.glossary && + data.component == 'mod_glossary' && + data.ratingArea == 'entry' && + data.contextLevel == ContextLevel.MODULE && + data.instanceId == this.glossary.coursemodule + ) { this.hasOfflineRatings = true; this.hasOffline = true; } })); this.observers.push(CoreEvents.on(CoreRatingSyncProvider.SYNCED_EVENT, (data) => { - if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module' - && data.instanceId == this.glossary.coursemodule) { + if ( + this.glossary && + data.component == 'mod_glossary' && + data.ratingArea == 'entry' && + data.contextLevel == ContextLevel.MODULE && + data.instanceId == this.glossary.coursemodule + ) { this.hasOfflineRatings = false; this.hasOffline = this.hasOfflineEntries; } diff --git a/src/addons/mod/glossary/services/handlers/prefetch.ts b/src/addons/mod/glossary/services/handlers/prefetch.ts index 08690f68c2a..713f56d8382 100644 --- a/src/addons/mod/glossary/services/handlers/prefetch.ts +++ b/src/addons/mod/glossary/services/handlers/prefetch.ts @@ -25,6 +25,7 @@ import { CoreWSFile } from '@services/ws'; import { makeSingleton } from '@singletons'; import { AddonModGlossary, AddonModGlossaryEntry, AddonModGlossaryGlossary, AddonModGlossaryProvider } from '../glossary'; import { AddonModGlossarySync, AddonModGlossarySyncResult } from '../glossary-sync'; +import { ContextLevel } from '@/core/constants'; /** * Handler to prefetch forums. @@ -161,7 +162,7 @@ export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPr // Don't fetch individual entries, it's too many WS calls. if (glossary.allowcomments && commentsEnabled) { promises.push(CoreComments.getComments( - 'module', + ContextLevel.MODULE, glossary.coursemodule, 'mod_glossary', entry.id, diff --git a/src/addons/privatefiles/services/privatefiles.ts b/src/addons/privatefiles/services/privatefiles.ts index 3d5e4797d8f..c638bda9c2f 100644 --- a/src/addons/privatefiles/services/privatefiles.ts +++ b/src/addons/privatefiles/services/privatefiles.ts @@ -19,6 +19,7 @@ import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreWSExternalWarning } from '@services/ws'; import { CoreSite } from '@classes/sites/site'; import { makeSingleton } from '@singletons'; +import { ContextLevel } from '@/core/constants'; const ROOT_CACHE_KEY = 'mmaFiles:'; @@ -136,7 +137,7 @@ export class AddonPrivateFilesProvider { contextid: -1, component: 'user', filearea: 'private', - contextlevel: 'user', + contextlevel: ContextLevel.USER, instanceid: CoreSites.getCurrentSite()?.getUserId(), itemid: 0, filepath: '', @@ -428,7 +429,7 @@ export type AddonPrivateFilesGetFilesWSParams = { filepath: string; // File path. filename: string; // File name. modified?: number; // Timestamp to return files changed after this time. - contextlevel?: string; // The context level for the file location. + contextlevel?: ContextLevel; // The context level for the file location. instanceid?: number; // The instance id for where the file is located. }; diff --git a/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts b/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts index 4656beb62e9..ab931d06ad1 100644 --- a/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts +++ b/src/addons/qbehaviour/deferredcbm/component/deferredcbm.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, Input, Output, EventEmitter } from '@angular/core'; import { CoreQuestionBehaviourButton, CoreQuestionQuestion } from '@features/question/services/question-helper'; @@ -30,7 +31,7 @@ export class AddonQbehaviourDeferredCBMComponent { @Input() componentId?: number; // ID of the component the question belongs to. @Input() attemptId?: number; // Attempt ID. @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters. @Input() review?: boolean; // Whether the user is in review mode. diff --git a/src/addons/qbehaviour/informationitem/component/informationitem.ts b/src/addons/qbehaviour/informationitem/component/informationitem.ts index 995bbe28ca1..f3a6a64a200 100644 --- a/src/addons/qbehaviour/informationitem/component/informationitem.ts +++ b/src/addons/qbehaviour/informationitem/component/informationitem.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, Input, Output, EventEmitter } from '@angular/core'; import { CoreQuestionBehaviourButton, CoreQuestionQuestion } from '@features/question/services/question-helper'; @@ -30,7 +31,7 @@ export class AddonQbehaviourInformationItemComponent { @Input() componentId?: number; // ID of the component the question belongs to. @Input() attemptId?: number; // Attempt ID. @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters. @Input() review?: boolean; // Whether the user is in review mode. diff --git a/src/core/components/chart/chart.ts b/src/core/components/chart/chart.ts index 8643f8285a5..dd566186dab 100644 --- a/src/core/components/chart/chart.ts +++ b/src/core/components/chart/chart.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, Input, OnDestroy, OnInit, ElementRef, OnChanges, ViewChild, SimpleChange } from '@angular/core'; import { CoreFilter } from '@features/filter/services/filter'; import { CoreFilterHelper } from '@features/filter/services/filter-helper'; @@ -50,7 +51,7 @@ export class CoreChartComponent implements OnDestroy, OnInit, OnChanges { @Input() legend?: ChartLegendOptions; // Legend options. @Input() height = 300; // Height of the chart element. @Input() filter?: boolean | string; // Whether to filter labels. If not defined, true if contextLevel and instanceId are set. - @Input() contextLevel?: string; // The context level of the text. + @Input() contextLevel?: ContextLevel; // The context level of the text. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the labels for some reason. diff --git a/src/core/components/navigation-bar/navigation-bar.ts b/src/core/components/navigation-bar/navigation-bar.ts index 67b07282417..5c1d5473793 100644 --- a/src/core/components/navigation-bar/navigation-bar.ts +++ b/src/core/components/navigation-bar/navigation-bar.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, EventEmitter, Input, OnChanges, Output, SimpleChange } from '@angular/core'; import { Translate } from '@singletons'; @@ -36,7 +37,7 @@ export class CoreNavigationBarComponent implements OnChanges { @Input() nextTranslate = 'core.next'; // Next translatable text, can admit $a variable. @Input() component?: string; // Component the bar belongs to. @Input() componentId?: number; // Component ID. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index 80355890d3e..fbdf733ae39 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -55,6 +55,7 @@ import { MediaElementController } from '@classes/element-controllers/MediaElemen import { FrameElement, FrameElementController } from '@classes/element-controllers/FrameElementController'; import { CoreUrl } from '@singletons/url'; import { CoreIcons } from '@singletons/icons'; +import { ContextLevel } from '../constants'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective @@ -81,7 +82,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec @Input() singleLine?: boolean | string; // Whether new lines should be removed (all text in single line). Only if clean=true. @Input() highlight?: string; // Text to highlight. @Input() filter?: boolean | string; // Whether to filter the text. If not defined, true if contextLevel and instanceId are set. - @Input() contextLevel?: string; // The context level of the text. + @Input() contextLevel?: ContextLevel; // The context level of the text. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. @Input() wsNotFiltered?: boolean | string; // If true it means the WS didn't filter the text for some reason. @@ -397,11 +398,13 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec const siteId = site?.getId(); - if (site && this.contextLevel == 'course' && this.contextInstanceId !== undefined && this.contextInstanceId <= 0) { + if ( + site && this.contextLevel === ContextLevel.COURSE && this.contextInstanceId !== undefined && this.contextInstanceId <= 0 + ) { this.contextInstanceId = site.getSiteHomeId(); } - if (this.contextLevel === 'course' && this.contextInstanceId === undefined && this.courseId !== undefined) { + if (this.contextLevel === ContextLevel.COURSE && this.contextInstanceId === undefined && this.courseId !== undefined) { this.contextInstanceId = this.courseId; } @@ -422,7 +425,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncDirec if (filter) { const filterResult = await CoreFilterHelper.getFiltersAndFormatText( this.text || '', - this.contextLevel || '', + this.contextLevel || ContextLevel.SYSTEM, this.contextInstanceId ?? -1, options, siteId, diff --git a/src/core/directives/tests/format-text.test.ts b/src/core/directives/tests/format-text.test.ts index 34c08c18b89..e041ea7ceb1 100644 --- a/src/core/directives/tests/format-text.test.ts +++ b/src/core/directives/tests/format-text.test.ts @@ -26,6 +26,7 @@ import { CoreSites } from '@services/sites'; import { CoreUtils } from '@services/utils/utils'; import { mock, mockSingleton, RenderConfig, renderTemplate, renderWrapperComponent } from '@/testing/utils'; +import { ContextLevel } from '@/core/constants'; describe('CoreFormatTextDirective', () => { @@ -120,7 +121,7 @@ describe('CoreFormatTextDirective', () => { expect(CoreFilterHelper.getFiltersAndFormatText).toHaveBeenCalledTimes(1); expect(CoreFilterHelper.getFiltersAndFormatText).toHaveBeenCalledWith( 'Lorem ipsum dolor', - 'course', + ContextLevel.COURSE, 42, expect.anything(), undefined, diff --git a/src/core/features/block/classes/base-block-handler.ts b/src/core/features/block/classes/base-block-handler.ts index 828ae2f1d1b..289ad041683 100644 --- a/src/core/features/block/classes/base-block-handler.ts +++ b/src/core/features/block/classes/base-block-handler.ts @@ -14,6 +14,7 @@ import { CoreCourseBlock } from '@features/course/services/course'; import { CoreBlockHandler, CoreBlockHandlerData } from '../services/block-delegate'; +import { ContextLevel } from '@/core/constants'; /** * Base handler for blocks. @@ -45,7 +46,7 @@ export class CoreBlockBaseHandler implements CoreBlockHandler { */ getDisplayData( block: CoreCourseBlock, // eslint-disable-line @typescript-eslint/no-unused-vars - contextLevel: string, // eslint-disable-line @typescript-eslint/no-unused-vars + contextLevel: ContextLevel, // eslint-disable-line @typescript-eslint/no-unused-vars instanceId: number, // eslint-disable-line @typescript-eslint/no-unused-vars ): undefined | CoreBlockHandlerData | Promise { // To be overridden. diff --git a/src/core/features/block/components/block/block.ts b/src/core/features/block/components/block/block.ts index b505f69373a..a89f4ee0966 100644 --- a/src/core/features/block/components/block/block.ts +++ b/src/core/features/block/components/block/block.ts @@ -18,6 +18,7 @@ import { CoreDynamicComponent } from '@components/dynamic-component/dynamic-comp import { Subscription } from 'rxjs'; import { CoreCourseBlock } from '@/core/features/course/services/course'; import type { ICoreBlockComponent } from '@features/block/classes/base-block-component'; +import { ContextLevel } from '@/core/constants'; /** * Component to render a block. @@ -32,7 +33,7 @@ export class CoreBlockComponent implements OnChanges, OnDestroy { @ViewChild(CoreDynamicComponent) dynamicComponent?: CoreDynamicComponent; @Input() block!: CoreCourseBlock; // The block to render. - @Input() contextLevel!: string; // The context where the block will be used. + @Input() contextLevel!: ContextLevel; // The context where the block will be used. @Input() instanceId!: number; // The instance ID associated with the context level. @Input() extraData!: Record; // Any extra data to be passed to the block. @Input() labelledBy?: string; diff --git a/src/core/features/block/components/pre-rendered-block/pre-rendered-block.ts b/src/core/features/block/components/pre-rendered-block/pre-rendered-block.ts index fda6ef0a7f2..09212a5ab7c 100644 --- a/src/core/features/block/components/pre-rendered-block/pre-rendered-block.ts +++ b/src/core/features/block/components/pre-rendered-block/pre-rendered-block.ts @@ -14,6 +14,7 @@ import { OnInit, Component, HostBinding } from '@angular/core'; import { CoreBlockBaseComponent } from '../../classes/base-block-component'; +import { ContextLevel } from '@/core/constants'; /** * Component to render blocks with pre-rendered HTML. @@ -36,13 +37,11 @@ export class CoreBlockPreRenderedComponent extends CoreBlockBaseComponent implem * @inheritdoc */ async ngOnInit(): Promise { - await super.ngOnInit(); - - this.courseId = this.contextLevel == 'course' ? this.instanceId : undefined; - + this.courseId = this.contextLevel === ContextLevel.COURSE ? this.instanceId : undefined; this.fetchContentDefaultError = 'Error getting ' + this.block.contents?.title + ' data.'; - this.id = `block-${this.block.instanceid}`; + + await super.ngOnInit(); } } diff --git a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts index 0290aa29e33..69ae1e0acbb 100644 --- a/src/core/features/block/components/side-blocks-button/side-blocks-button.ts +++ b/src/core/features/block/components/side-blocks-button/side-blocks-button.ts @@ -20,6 +20,7 @@ import { CoreDomUtils } from '@services/utils/dom'; import { CoreDom } from '@singletons/dom'; import { CoreBlockSideBlocksTourComponent } from '../side-blocks-tour/side-blocks-tour'; import { CoreBlockSideBlocksComponent } from '../side-blocks/side-blocks'; +import { ContextLevel } from '@/core/constants'; /** * Component that displays a button to open blocks. @@ -31,7 +32,7 @@ import { CoreBlockSideBlocksComponent } from '../side-blocks/side-blocks'; }) export class CoreBlockSideBlocksButtonComponent implements OnInit, OnDestroy { - @Input() contextLevel!: string; + @Input() contextLevel!: ContextLevel; @Input() instanceId!: number; @Input() myDashboardPage?: string; diff --git a/src/core/features/block/components/side-blocks/side-blocks.ts b/src/core/features/block/components/side-blocks/side-blocks.ts index 5a317c8003f..e80b9a3b6d0 100644 --- a/src/core/features/block/components/side-blocks/side-blocks.ts +++ b/src/core/features/block/components/side-blocks/side-blocks.ts @@ -22,6 +22,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreCoursesDashboard } from '@features/courses/services/dashboard'; import { CoreTextUtils } from '@services/utils/text'; import { CoreDom } from '@singletons/dom'; +import { ContextLevel } from '@/core/constants'; /** * Component that displays the list of side blocks. @@ -33,7 +34,7 @@ import { CoreDom } from '@singletons/dom'; }) export class CoreBlockSideBlocksComponent implements OnInit { - @Input() contextLevel!: string; + @Input() contextLevel!: ContextLevel; @Input() instanceId!: number; @Input() initialBlockInstanceId?: number; @Input() myDashboardPage?: string; @@ -64,7 +65,7 @@ export class CoreBlockSideBlocksComponent implements OnInit { async invalidateBlocks(): Promise { const promises: Promise[] = []; - if (this.contextLevel === 'course') { + if (this.contextLevel === ContextLevel.COURSE) { promises.push(CoreCourse.invalidateCourseBlocks(this.instanceId)); } else { promises.push(CoreCoursesDashboard.invalidateDashboardBlocks()); @@ -87,7 +88,7 @@ export class CoreBlockSideBlocksComponent implements OnInit { */ async loadContent(): Promise { try { - if (this.contextLevel === 'course') { + if (this.contextLevel === ContextLevel.COURSE) { this.blocks = await CoreBlockHelper.getCourseBlocks(this.instanceId); } else { const blocks = await CoreCoursesDashboard.getDashboardBlocks(undefined, undefined, this.myDashboardPage); diff --git a/src/core/features/block/services/block-delegate.ts b/src/core/features/block/services/block-delegate.ts index ed4de171992..95501d612af 100644 --- a/src/core/features/block/services/block-delegate.ts +++ b/src/core/features/block/services/block-delegate.ts @@ -23,6 +23,7 @@ import { makeSingleton } from '@singletons'; import { CoreBlockDefaultHandler } from './handlers/default-block'; import { CoreNavigationOptions } from '@services/navigator'; import type { ICoreBlockComponent } from '@features/block/classes/base-block-component'; +import { ContextLevel } from '@/core/constants'; /** * Interface that all blocks must implement. @@ -43,7 +44,7 @@ export interface CoreBlockHandler extends CoreDelegateHandler { */ getDisplayData?( block: CoreCourseBlock, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, ): undefined | CoreBlockHandlerData | Promise; } @@ -162,7 +163,7 @@ export class CoreBlockDelegateService extends CoreDelegate { */ async getBlockDisplayData( block: CoreCourseBlock, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, ): Promise { return this.executeFunctionOnEnabled( diff --git a/src/core/features/comments/services/comments-offline.ts b/src/core/features/comments/services/comments-offline.ts index 8199163d3d2..e18901b1d35 100644 --- a/src/core/features/comments/services/comments-offline.ts +++ b/src/core/features/comments/services/comments-offline.ts @@ -17,6 +17,7 @@ import { CoreSites } from '@services/sites'; import { CoreTimeUtils } from '@services/utils/time'; import { makeSingleton } from '@singletons'; import { COMMENTS_TABLE, COMMENTS_DELETED_TABLE, CoreCommentsDBRecord, CoreCommentsDeletedDBRecord } from './database/comments'; +import { ContextLevel } from '@/core/constants'; /** * Service to handle offline comments. @@ -52,7 +53,7 @@ export class CoreCommentsOfflineProvider { * @returns Promise resolved with the comments. */ async getComment( - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, component: string, itemId: number, @@ -86,7 +87,7 @@ export class CoreCommentsOfflineProvider { * @returns Promise resolved with the comments. */ async getComments( - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, component: string, itemId: number, @@ -131,7 +132,7 @@ export class CoreCommentsOfflineProvider { * @returns Promise resolved with the comments. */ async getDeletedComments( - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, component: string, itemId: number, @@ -165,7 +166,7 @@ export class CoreCommentsOfflineProvider { * @returns Promise resolved if deleted, rejected if failure. */ async removeComment( - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, component: string, itemId: number, @@ -195,7 +196,7 @@ export class CoreCommentsOfflineProvider { * @returns Promise resolved if deleted, rejected if failure. */ async removeDeletedComments( - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, component: string, itemId: number, @@ -227,7 +228,7 @@ export class CoreCommentsOfflineProvider { */ async saveComment( content: string, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, component: string, itemId: number, @@ -265,7 +266,7 @@ export class CoreCommentsOfflineProvider { */ async deleteComment( commentId: number, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, component: string, itemId: number, diff --git a/src/core/features/comments/services/comments-sync.ts b/src/core/features/comments/services/comments-sync.ts index 1d37106cd5f..2aa32d8331d 100644 --- a/src/core/features/comments/services/comments-sync.ts +++ b/src/core/features/comments/services/comments-sync.ts @@ -24,6 +24,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreNetworkError } from '@classes/errors/network-error'; import { CoreCommentsDBRecord, CoreCommentsDeletedDBRecord } from './database/comments'; import { CoreSyncResult } from '@services/sync'; +import { ContextLevel } from '@/core/constants'; /** * Service to sync omments. @@ -121,7 +122,7 @@ export class CoreCommentsSyncProvider extends CoreSyncBaseProvider { + async invalidateCommentsByInstance(contextLevel: ContextLevel, instanceId: number, siteId?: string): Promise { const site = await CoreSites.getSite(siteId); await site.invalidateWsCacheForKeyStartingWith(this.getCommentsPrefixCacheKey(contextLevel, instanceId)); @@ -587,7 +588,7 @@ type CoreCommentsAddCommentsWSParams = { export type CoreCommentsCommentBasicData = { id?: number; // Comment ID. - contextlevel: string; // Contextlevel system, course, user... + contextlevel: ContextLevel; // Contextlevel system, course, user... instanceid: number; // The id of item associated with the contextlevel. component: string; // Component. content: string; // Component. @@ -628,7 +629,7 @@ type CoreCommentsDeleteCommentsWSParams = { * Params of core_comment_get_comments WS. */ type CoreCommentsGetCommentsWSParams = { - contextlevel: string; // Contextlevel system, course, user... + contextlevel: ContextLevel; // Contextlevel system, course, user... instanceid: number; // The Instance id of item associated with the context level. component: string; // Component. itemid: number; // Associated id. @@ -652,7 +653,7 @@ export type CoreCommentsGetCommentsWSResponse = { * Data sent by COMMENTS_COUNT_CHANGED_EVENT event. */ export type CoreCommentsCountChangedEventData = { - contextLevel: string; + contextLevel: ContextLevel; instanceId: number; component: string; itemId: number; @@ -664,7 +665,7 @@ export type CoreCommentsCountChangedEventData = { * Data sent by REFRESH_COMMENTS_EVENT event. */ export type CoreCommentsRefreshCommentsEventData = { - contextLevel?: string; + contextLevel?: ContextLevel; instanceId?: number; component?: string; itemId?: number; diff --git a/src/core/features/comments/services/database/comments.ts b/src/core/features/comments/services/database/comments.ts index 38473d69c5d..ced9204f4be 100644 --- a/src/core/features/comments/services/database/comments.ts +++ b/src/core/features/comments/services/database/comments.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { CoreSiteSchema } from '@services/sites'; /** @@ -95,7 +96,7 @@ export const COMMENTS_OFFLINE_SITE_SCHEMA: CoreSiteSchema = { }; export type CoreCommentsDBRecord = { - contextlevel: string; // Primary key. + contextlevel: ContextLevel; // Primary key. instanceid: number; // Primary key. component: string; // Primary key. itemid: number; // Primary key. @@ -106,7 +107,7 @@ export type CoreCommentsDBRecord = { export type CoreCommentsDeletedDBRecord = { commentid: number; // Primary key. - contextlevel: string; + contextlevel: ContextLevel; instanceid: number; component: string; itemid: number; diff --git a/src/core/features/course/classes/activity-prefetch-handler.ts b/src/core/features/course/classes/activity-prefetch-handler.ts index 89dac07fb9b..d0e7db5f9de 100644 --- a/src/core/features/course/classes/activity-prefetch-handler.ts +++ b/src/core/features/course/classes/activity-prefetch-handler.ts @@ -12,7 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { DownloadStatus } from '@/core/constants'; +import { DownloadStatus, ContextLevel } from '@/core/constants'; import { CoreNetworkError } from '@classes/errors/network-error'; import { CoreFilterHelper } from '@features/filter/services/filter-helper'; import { CoreNetwork } from '@services/network'; @@ -118,7 +118,7 @@ export class CoreCourseActivityPrefetchHandlerBase extends CoreCourseModulePrefe await Promise.all([ CoreCourse.getModuleBasicInfo(module.id, { siteId }), CoreCourse.getModule(module.id, courseId, undefined, false, true, siteId), - CoreFilterHelper.getFilters('module', module.id, { courseId }), + CoreFilterHelper.getFilters(ContextLevel.MODULE, module.id, { courseId }), ]); // Call the download function. diff --git a/src/core/features/course/classes/resource-prefetch-handler.ts b/src/core/features/course/classes/resource-prefetch-handler.ts index 89bcced9a7f..5632d13b395 100644 --- a/src/core/features/course/classes/resource-prefetch-handler.ts +++ b/src/core/features/course/classes/resource-prefetch-handler.ts @@ -21,6 +21,7 @@ import { CoreWSFile } from '@services/ws'; import { CoreCourse, CoreCourseAnyModuleData } from '../services/course'; import { CoreCourseModuleData } from '../services/course-helper'; import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler'; +import { ContextLevel } from '@/core/constants'; /** * Base prefetch handler to be registered in CoreCourseModulePrefetchDelegate. It is useful to minimize the amount of @@ -131,7 +132,7 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe )); } - promises.push(CoreFilterHelper.getFilters('module', module.id, { courseId })); + promises.push(CoreFilterHelper.getFilters(ContextLevel.MODULE, module.id, { courseId })); await Promise.all(promises); } diff --git a/src/core/features/course/components/course-format/course-format.ts b/src/core/features/course/components/course-format/course-format.ts index eb641970200..a7788810a4f 100644 --- a/src/core/features/course/components/course-format/course-format.ts +++ b/src/core/features/course/components/course-format/course-format.ts @@ -52,6 +52,7 @@ import { CoreDom } from '@singletons/dom'; import { CoreUserTourDirectiveOptions } from '@directives/user-tour'; import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics'; import { CoreBlockSideBlocksComponent } from '@features/block/components/side-blocks/side-blocks'; +import { ContextLevel } from '@/core/constants'; /** * Component to display course contents using a certain format. If the format isn't found, use default one. @@ -318,7 +319,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { CoreDomUtils.openSideModal({ component: CoreBlockSideBlocksComponent, componentProps: { - contextLevel: 'course', + contextLevel: ContextLevel.COURSE, instanceId: this.course.id, initialBlockInstanceId: this.initialBlockInstanceId, }, diff --git a/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts b/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts index aa2c210da3d..38846e34b9d 100644 --- a/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts +++ b/src/core/features/course/components/module-completion-legacy/module-completion-legacy.ts @@ -25,6 +25,7 @@ import { CoreCourseModuleCompletionBaseComponent } from '@features/course/classe import { CoreCourseHelper } from '@features/course/services/course-helper'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { BehaviorSubject } from 'rxjs'; +import { ContextLevel } from '@/core/constants'; /** * Component to handle activity completion in sites previous to 3.11. @@ -112,7 +113,7 @@ export class CoreCourseModuleCompletionLegacyComponent extends CoreCourseModuleC const result = await CoreFilterHelper.getFiltersAndFormatText( moduleName, - 'module', + ContextLevel.MODULE, this.moduleId, { clean: true, singleLine: true, shortenLength: 50, courseId: this.completion.courseId }, ); diff --git a/src/core/features/course/components/module-description/module-description.ts b/src/core/features/course/components/module-description/module-description.ts index e914b4f54f5..d0bb74b5825 100644 --- a/src/core/features/course/components/module-description/module-description.ts +++ b/src/core/features/course/components/module-description/module-description.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, HostBinding, Input } from '@angular/core'; /** @@ -43,7 +44,7 @@ export class CoreCourseModuleDescriptionComponent { @Input() component?: string; // Component for format text directive. @Input() componentId?: string | number; // Component ID to use in conjunction with the component. @Input() showFull?: string | boolean; // Whether to always display the full description. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. diff --git a/src/core/features/course/services/course-helper.ts b/src/core/features/course/services/course-helper.ts index ea52468c3dc..13d9037798b 100644 --- a/src/core/features/course/services/course-helper.ts +++ b/src/core/features/course/services/course-helper.ts @@ -28,7 +28,7 @@ import { CoreCourseModuleCompletionStatus, CoreCourseGetContentsWSModule, } from './course'; -import { CoreConstants, DownloadStatus, TDownloadStatus } from '@/core/constants'; +import { CoreConstants, DownloadStatus, TDownloadStatus, ContextLevel } from '@/core/constants'; import { CoreLogger } from '@singletons/logger'; import { ApplicationInit, makeSingleton, Translate } from '@singletons'; import { CoreFilepool } from '@services/filepool'; @@ -1636,7 +1636,7 @@ export class CoreCourseHelperProvider { promises.push(CoreCourse.getActivitiesCompletionStatus(course.id)); } - promises.push(CoreFilterHelper.getFilters('course', course.id)); + promises.push(CoreFilterHelper.getFilters(ContextLevel.COURSE, course.id)); await CoreUtils.allPromises(promises); diff --git a/src/core/features/course/services/module-prefetch-delegate.ts b/src/core/features/course/services/module-prefetch-delegate.ts index 195b1b75a80..094b444bdac 100644 --- a/src/core/features/course/services/module-prefetch-delegate.ts +++ b/src/core/features/course/services/module-prefetch-delegate.ts @@ -25,7 +25,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreCourse, CoreCourseAnyModuleData, CoreCourseModuleContentFile } from './course'; import { CoreCache } from '@classes/cache'; import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; -import { DownloadStatus, TDownloadStatus, TDownloadedStatus } from '@/core/constants'; +import { DownloadStatus, TDownloadStatus, TDownloadedStatus, ContextLevel } from '@/core/constants'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { makeSingleton } from '@singletons'; import { CoreEvents, CoreEventSectionStatusChangedData } from '@singletons/events'; @@ -132,7 +132,7 @@ export class CoreCourseModulePrefetchDelegateService extends CoreDelegate; // Extra params to identify the draft. @@ -267,7 +268,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, // Save a draft so the original content is saved. this.lastDraft = newValue ?? ''; CoreEditorOffline.saveDraft( - this.contextLevel || '', + this.contextLevel || ContextLevel.SYSTEM, this.contextInstanceId || 0, this.elementId || '', this.draftExtraParams || {}, @@ -923,7 +924,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, protected async restoreDraft(): Promise { try { const entry = await CoreEditorOffline.resumeDraft( - this.contextLevel || '', + this.contextLevel || ContextLevel.SYSTEM, this.contextInstanceId || 0, this.elementId || '', this.draftExtraParams || {}, @@ -983,7 +984,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, try { await CoreEditorOffline.saveDraft( - this.contextLevel || '', + this.contextLevel || ContextLevel.SYSTEM, this.contextInstanceId || 0, this.elementId || '', this.draftExtraParams || {}, @@ -1011,7 +1012,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit, if (data.form && form && data.form == form) { try { await CoreEditorOffline.deleteDraft( - this.contextLevel || '', + this.contextLevel || ContextLevel.SYSTEM, this.contextInstanceId || 0, this.elementId || '', this.draftExtraParams || {}, diff --git a/src/core/features/editor/services/database/editor.ts b/src/core/features/editor/services/database/editor.ts index 1a0eac575fa..4288ca37f1b 100644 --- a/src/core/features/editor/services/database/editor.ts +++ b/src/core/features/editor/services/database/editor.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { CoreSiteSchema } from '@services/sites'; /** @@ -75,7 +76,7 @@ export const SITE_SCHEMA: CoreSiteSchema = { * Primary data to identify a stored draft. */ export type CoreEditorDraftPrimaryData = { - contextlevel: string; // Context level. + contextlevel: ContextLevel; // Context level. contextinstanceid: number; // The instance ID related to the context. elementid: string; // Element ID. extraparams: string; // Extra params stringified. diff --git a/src/core/features/editor/services/editor-offline.ts b/src/core/features/editor/services/editor-offline.ts index a38f507c893..b6a7e912fe4 100644 --- a/src/core/features/editor/services/editor-offline.ts +++ b/src/core/features/editor/services/editor-offline.ts @@ -20,6 +20,7 @@ import { CoreUtils } from '@services/utils/utils'; import { makeSingleton } from '@singletons'; import { CoreLogger } from '@singletons/logger'; import { CoreEditorDraft, CoreEditorDraftPrimaryData, DRAFT_TABLE } from './database/editor'; +import { ContextLevel } from '@/core/constants'; /** * Service with features regarding rich text editor in offline. @@ -44,7 +45,7 @@ export class CoreEditorOfflineProvider { * @returns Promise resolved when done. */ async deleteDraft( - contextLevel: string, + contextLevel: ContextLevel, contextInstanceId: number, elementId: string, extraParams: Record, @@ -71,7 +72,7 @@ export class CoreEditorOfflineProvider { * @returns Object with the fixed primary data. */ protected fixDraftPrimaryData( - contextLevel: string, + contextLevel: ContextLevel, contextInstanceId: number, elementId: string, extraParams: Record, @@ -96,7 +97,7 @@ export class CoreEditorOfflineProvider { * @returns Promise resolved with the draft data. Undefined if no draft stored. */ async getDraft( - contextLevel: string, + contextLevel: ContextLevel, contextInstanceId: number, elementId: string, extraParams: Record, @@ -123,7 +124,7 @@ export class CoreEditorOfflineProvider { * @returns Promise resolved with the draft data. Undefined if no draft stored. */ async resumeDraft( - contextLevel: string, + contextLevel: ContextLevel, contextInstanceId: number, elementId: string, extraParams: Record, @@ -183,7 +184,7 @@ export class CoreEditorOfflineProvider { * @returns Promise resolved when done. */ async saveDraft( - contextLevel: string, + contextLevel: ContextLevel, contextInstanceId: number, elementId: string, extraParams: Record, diff --git a/src/core/features/filter/services/filter-delegate.ts b/src/core/features/filter/services/filter-delegate.ts index 0db329b32ee..124b60600f0 100644 --- a/src/core/features/filter/services/filter-delegate.ts +++ b/src/core/features/filter/services/filter-delegate.ts @@ -15,11 +15,12 @@ import { Injectable, ViewContainerRef } from '@angular/core'; import { CoreSites } from '@services/sites'; -import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions } from './filter'; +import { CoreFilter, CoreFilterFilter, CoreFilterFormatTextOptions, CoreFilterStateValue } from './filter'; import { CoreFilterDefaultHandler } from './handlers/default-filter'; import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; import { CoreSite } from '@classes/sites/site'; import { makeSingleton } from '@singletons'; +import { ContextLevel } from '@/core/constants'; /** * Interface that all filter handlers must implement. @@ -156,7 +157,7 @@ export class CoreFilterDelegateService extends CoreDelegate { * @param instanceId Instance ID. * @returns Filters. */ - getEnabledFilters(contextLevel: string, instanceId: number): CoreFilterFilter[] { + getEnabledFilters(contextLevel: ContextLevel, instanceId: number): CoreFilterFilter[] { const filters: CoreFilterFilter[] = []; for (const name in this.enabledHandlers) { @@ -168,7 +169,7 @@ export class CoreFilterDelegateService extends CoreDelegate { filter: handler.filterName, inheritedstate: 1, instanceid: instanceId, - localstate: 1, + localstate: CoreFilterStateValue.ON, }); } @@ -244,7 +245,10 @@ export class CoreFilterDelegateService extends CoreDelegate { skipFilters?: string[], ): boolean { - if (filter.localstate == -1 || (filter.localstate == 0 && filter.inheritedstate == -1)) { + if ( + filter.localstate === CoreFilterStateValue.OFF || + (filter.localstate === CoreFilterStateValue.INHERIT && filter.inheritedstate === CoreFilterStateValue.OFF) + ) { // Filter is disabled, ignore it. return false; } @@ -254,7 +258,7 @@ export class CoreFilterDelegateService extends CoreDelegate { return false; } - if (skipFilters && skipFilters.indexOf(filter.filter) != -1) { + if (skipFilters && skipFilters.indexOf(filter.filter) !== -1) { // Skip this filter. return false; } diff --git a/src/core/features/filter/services/filter-helper.ts b/src/core/features/filter/services/filter-helper.ts index 5fe6845affb..42f68eb10fb 100644 --- a/src/core/features/filter/services/filter-helper.ts +++ b/src/core/features/filter/services/filter-helper.ts @@ -23,6 +23,8 @@ import { CoreFilterFormatTextOptions, CoreFilterClassifiedFilters, CoreFiltersGetAvailableInContextWSParamContext, + CoreFilterStateValue, + CoreFilterAllStates, } from './filter'; import { CoreCourse } from '@features/course/services/course'; import { CoreCourses } from '@features/courses/services/courses'; @@ -32,7 +34,7 @@ import { CoreLogger } from '@singletons/logger'; import { CoreSite } from '@classes/sites/site'; import { CoreCourseHelper } from '@features/course/services/course-helper'; import { firstValueFrom } from 'rxjs'; -import { CoreBlockHelper } from '@features/block/services/block-helper'; +import { ContextLevel } from '@/core/constants'; /** * Helper service to provide filter functionalities. @@ -69,32 +71,6 @@ export class CoreFilterHelperProvider { }); } - /** - * Get the contexts of all blocks in a course. - * - * @param courseId Course ID. - * @param siteId Site ID. If not defined, current site. - * @returns Promise resolved with the contexts. - */ - async getBlocksContexts(courseId: number, siteId?: string): Promise { - // Use stale while revalidate, but always use the first value. If data is updated it will be stored in DB. - const blocks = await firstValueFrom(CoreCourse.getCourseBlocksObservable(courseId, { - readingStrategy: CoreSitesReadingStrategy.STALE_WHILE_REVALIDATE, - siteId, - })); - - const contexts: CoreFiltersGetAvailableInContextWSParamContext[] = []; - - blocks.forEach((block) => { - contexts.push({ - contextlevel: 'block', - instanceid: block.instanceid, - }); - }); - - return contexts; - } - /** * Get some filters from memory cache. If not in cache, get them and store them in cache. * @@ -106,7 +82,7 @@ export class CoreFilterHelperProvider { * @returns Promise resolved with the filters. */ protected async getCacheableFilters( - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, getFilters: () => Promise, options: CoreFilterFormatTextOptions, @@ -144,7 +120,7 @@ export class CoreFilterHelperProvider { courseIds.forEach((courseId) => { contexts.push({ - contextlevel: 'course', + contextlevel: ContextLevel.COURSE, instanceid: courseId, }); }); @@ -174,7 +150,7 @@ export class CoreFilterHelperProvider { section.modules.forEach((module) => { if (CoreCourseHelper.canUserViewModule(module, section)) { contexts.push({ - contextlevel: 'module', + contextlevel: ContextLevel.MODULE, instanceid: module.id, }); } @@ -197,12 +173,16 @@ export class CoreFilterHelperProvider { * @returns Promise resolved with the filters. */ async getFilters( - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, - options?: CoreFilterFormatTextOptions, + options: CoreFilterFormatTextOptions = {}, siteId?: string, ): Promise { - options = options || {}; + // Check the right context to use. + const newContext = CoreFilter.getEffectiveContext(contextLevel, instanceId, { courseId: options.courseId }); + contextLevel = newContext.contextLevel; + instanceId = newContext.instanceId; + options.contextLevel = contextLevel; options.instanceId = instanceId; options.filter = false; @@ -220,10 +200,18 @@ export class CoreFilterHelperProvider { return await CoreFilterDelegate.getEnabledFilters(contextLevel, instanceId); } + const filters = await this.getFiltersInContextUsingAllStates(contextLevel, instanceId, options, site); + if (filters) { + return filters; + } + const courseId = options.courseId; let hasFilters = true; - if (contextLevel == 'system' || (contextLevel == 'course' && instanceId == site.getSiteHomeId())) { + if ( + contextLevel === ContextLevel.SYSTEM || + (contextLevel === ContextLevel.COURSE && instanceId == site.getSiteHomeId()) + ) { // No need to check the site filters because we're requesting the same context, so we'd do the same twice. } else { // Check if site has any filter to treat. @@ -236,21 +224,16 @@ export class CoreFilterHelperProvider { options.filter = true; - if (contextLevel == 'module' && courseId) { + if (contextLevel === ContextLevel.MODULE && courseId) { // Get all the modules filters with a single call to decrease the number of WS calls. const getFilters = () => this.getCourseModulesContexts(courseId, siteId); return await this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); - } else if (contextLevel == 'course') { + } else if (contextLevel === ContextLevel.COURSE) { // If enrolled, get all enrolled courses filters with a single call to decrease number of WS calls. const getFilters = () => this.getCourseContexts(instanceId, siteId); - return await this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); - } else if (contextLevel == 'block' && courseId && CoreBlockHelper.canGetCourseBlocks(site)) { - // Get all the course blocks filters with a single call to decrease number of WS calls. - const getFilters = () => this.getBlocksContexts(courseId, siteId); - return await this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); } @@ -262,6 +245,95 @@ export class CoreFilterHelperProvider { } } + /** + * Get filters in context using the all states data. + * + * @param contextLevel The context level. + * @param instanceId Instance ID related to the context. + * @param options Options. + * @param site Site. + * @returns Filters, undefined if all states cannot be used. + */ + protected async getFiltersInContextUsingAllStates( + contextLevel: ContextLevel, + instanceId: number, + options: CoreFilterFormatTextOptions = {}, + site?: CoreSite, + ): Promise { + site = site || CoreSites.getCurrentSite(); + + if (!CoreFilter.canGetAllStatesInSite(site)) { + return; + } + + const allStates = await CoreFilter.getAllStates({ siteId: site?.getId() }); + if ( + contextLevel !== ContextLevel.SYSTEM && + contextLevel !== ContextLevel.COURSECAT && + this.hasCategoryOverride(allStates) + ) { + // A category has an override, we cannot calculate the right filters for child contexts. + return; + } + + const contexts = CoreFilter.getContextsTreeList(contextLevel, instanceId, { courseId: options.courseId }); + const contextId = Object.values(allStates[contextLevel]?.[instanceId] ?? {})[0]?.contextid; + + const filters: Record = {}; + contexts.reverse().forEach((context) => { + const isParentContext = context.contextLevel !== contextLevel; + const filtersInContext = allStates[context.contextLevel]?.[context.instanceId]; + if (!filtersInContext) { + return; + } + + for (const name in filtersInContext) { + const filterInContext = filtersInContext[name]; + if (filterInContext.localstate === CoreFilterStateValue.DISABLED) { + // Ignore disabled filters to make it consistent with available in context. + continue; + } + + filters[name] = { + contextlevel: contextLevel, + instanceid: instanceId, + contextid: contextId, + filter: name, + localstate: isParentContext ? CoreFilterStateValue.INHERIT : filterInContext.localstate, + inheritedstate: isParentContext ? + filterInContext.localstate : + filters[name]?.inheritedstate ?? filterInContext.localstate, + }; + } + }); + + return Object.values(filters); + } + + /** + * Check if there is an override for a category in the states of all filters. + * + * @param states States to check. + * @returns True if has category override, false otherwise. + */ + protected hasCategoryOverride(states: CoreFilterAllStates): boolean { + if (!states[ContextLevel.COURSECAT]) { + return false; + } + + for (const instanceId in states[ContextLevel.COURSECAT]) { + for (const name in states[ContextLevel.COURSECAT][instanceId]) { + if ( + states[ContextLevel.COURSECAT][instanceId][name].localstate !== states[ContextLevel.SYSTEM][0][name].localstate + ) { + return true; + } + } + } + + return false; + } + /** * Get filters and format text. * @@ -274,7 +346,7 @@ export class CoreFilterHelperProvider { */ async getFiltersAndFormatText( text: string, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, options?: CoreFilterFormatTextOptions, siteId?: string, @@ -298,7 +370,7 @@ export class CoreFilterHelperProvider { */ protected getFromMemoryCache( courseId: number, - contextLevel: string, + contextLevel: ContextLevel, instanceId: number, site: CoreSite, ): CoreFilterFilter[] | undefined { @@ -331,7 +403,7 @@ export class CoreFilterHelperProvider { const site = await CoreSites.getSite(siteId); // Get filters at site level. - const filters = await CoreFilter.getAvailableInContext('system', 0, site.getId()); + const filters = await CoreFilter.getAvailableInContext(ContextLevel.SYSTEM, 0, site.getId()); return CoreFilterDelegate.shouldBeApplied(filters, options, site); } @@ -346,7 +418,7 @@ export class CoreFilterHelperProvider { */ protected storeInMemoryCache( courseId: number, - contextLevel: string, + contextLevel: ContextLevel, contexts: CoreFilterClassifiedFilters, siteId: string, ): void { diff --git a/src/core/features/filter/services/filter.ts b/src/core/features/filter/services/filter.ts index b2eb8c3baf4..c64e70c7f69 100644 --- a/src/core/features/filter/services/filter.ts +++ b/src/core/features/filter/services/filter.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { CoreNetwork } from '@services/network'; -import { CoreSites, CoreSitesReadingStrategy } from '@services/sites'; +import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites'; import { CoreSite } from '@classes/sites/site'; import { CoreWSExternalWarning } from '@services/ws'; import { CoreTextUtils } from '@services/utils/text'; @@ -24,6 +24,7 @@ import { makeSingleton } from '@singletons'; import { CoreEvents, CoreEventSiteData } from '@singletons/events'; import { CoreLogger } from '@singletons/logger'; import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site'; +import { ContextLevel } from '@/core/constants'; /** * Service to provide filter functionalities. @@ -61,11 +62,37 @@ export class CoreFilterProvider { }); } + /** + * Check if getting all states is available in site. + * + * @param siteId Site ID. If not defined, current site. + * @returns Whether it's available. + * @since 4.4 + */ + async canGetAllStates(siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + return this.canGetAllStatesInSite(site); + } + + /** + * Check if getting all states is available in site. + * + * @param site Site. If not defined, current site. + * @returns Whether it's available. + * @since 4.4 + */ + canGetAllStatesInSite(site?: CoreSite): boolean { + site = site || CoreSites.getCurrentSite(); + + return !!(site?.wsAvailable('core_filters_get_all_states')); + } + /** * Returns whether or not we can get the available filters: the WS is available and the feature isn't disabled. * * @param siteId Site ID. If not defined, current site. - * @returns Promise resolved with boolean: whethe can get filters. + * @returns Whether can get filters. */ async canGetFilters(siteId?: string): Promise { const disabled = await this.checkFiltersDisabled(siteId); @@ -77,7 +104,7 @@ export class CoreFilterProvider { * Returns whether or not we can get the available filters: the WS is available and the feature isn't disabled. * * @param site Site. If not defined, current site. - * @returns Promise resolved with boolean: whethe can get filters. + * @returns Whether can get filters. */ canGetFiltersInSite(site?: CoreSite): boolean { return !this.checkFiltersDisabledInSite(site); @@ -140,7 +167,7 @@ export class CoreFilterProvider { } filters.forEach((filter) => { - if (hadSystemContext && filter.contextlevel == 'course' && filter.instanceid == site.getSiteHomeId()) { + if (hadSystemContext && filter.contextlevel === ContextLevel.COURSE && filter.instanceid == site.getSiteHomeId()) { if (hadSiteHomeContext) { // We need to return both site home and system. Add site home first. classified[filter.contextlevel][filter.instanceid].push(filter); @@ -150,9 +177,9 @@ export class CoreFilterProvider { } // Simulate the system context based on the inherited data. - filter.contextlevel = 'system'; + filter.contextlevel = ContextLevel.SYSTEM; filter.instanceid = 0; - filter.contextid = -1; + filter.contextid = undefined; filter.localstate = filter.inheritedstate; } @@ -162,6 +189,29 @@ export class CoreFilterProvider { return classified; } + /** + * Given a context level and instance ID, return the proper context to use. + * + * @param contextLevel The context level. + * @param instanceId Instance ID related to the context. + * @param options Options. + * @returns Context to use. + */ + getEffectiveContext( + contextLevel: ContextLevel, + instanceId: number, + options: {courseId?: number} = {}, + ): {contextLevel: ContextLevel; instanceId: number} { + if (contextLevel === ContextLevel.BLOCK || contextLevel === ContextLevel.USER) { + // Blocks and users cannot have specific filters, use the parent context instead. + return options.courseId ? + { contextLevel: ContextLevel.COURSE, instanceId: options.courseId } : + { contextLevel: ContextLevel.SYSTEM, instanceId: 0 }; + } + + return { contextLevel, instanceId }; + } + /** * Given some HTML code, this function returns the text as safe HTML. * @@ -217,6 +267,53 @@ export class CoreFilterProvider { return text; } + /** + * Get cache key for get all states WS call. + * + * @returns Cache key. + */ + protected getAllStatesCacheKey(): string { + return this.ROOT_CACHE_KEY + 'allStates'; + } + + /** + * Get all the states for filters. + * + * @param options Options. + * @returns Promise resolved with the filters classified by context. + * @since 4.4 + */ + async getAllStates(options: CoreSitesCommonWSOptions = {}): Promise { + const site = await CoreSites.getSite(options.siteId); + + const preSets: CoreSiteWSPreSets = { + cacheKey: this.getAllStatesCacheKey(), + updateFrequency: CoreSite.FREQUENCY_RARELY, + // Use stale while revalidate by default, but always use the first value. If data is updated it will be stored in DB. + ...CoreSites.getReadingStrategyPreSets(options.readingStrategy ?? CoreSitesReadingStrategy.STALE_WHILE_REVALIDATE), + }; + + const result = await site.read('core_filters_get_all_states', {}, preSets); + + const classified: CoreFilterAllStates = {}; + + result.filters.forEach((filter) => { + classified[filter.contextlevel] = classified[filter.contextlevel] || {}; + classified[filter.contextlevel][filter.instanceid] = classified[filter.contextlevel][filter.instanceid] || {}; + + classified[filter.contextlevel][filter.instanceid][filter.filter] = { + contextlevel: filter.contextlevel, + instanceid: filter.instanceid, + contextid: filter.contextid, + filter: filter.filter, + localstate: filter.state, + inheritedstate: filter.state, + }; + }); + + return classified; + } + /** * Get cache key for available in contexts WS calls. * @@ -297,7 +394,7 @@ export class CoreFilterProvider { * @param siteId Site ID. If not defined, current site. * @returns Promise resolved with the filters. */ - async getAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise { + async getAvailableInContext(contextLevel: ContextLevel, instanceId: number, siteId?: string): Promise { const result = await this.getAvailableInContexts([{ contextlevel: contextLevel, instanceid: instanceId }], siteId); return result[contextLevel][instanceId] || []; @@ -346,6 +443,45 @@ export class CoreFilterProvider { } } + /** + * Given a context, return the list of contexts used in the filters inheritance tree, from bottom to top. + * E.g. when using module, it will return the module context, course context (if course ID is supplied), category context + * (if categoy ID is supplied) and system context. + * + * @param contextLevel Context level. + * @param instanceId Instance ID. + * @param options Options + * @returns List of contexts. + */ + getContextsTreeList( + contextLevel: ContextLevel, + instanceId: number, + options: {courseId?: number; categoryId?: number} = {}, + ): { contextLevel: ContextLevel; instanceId: number }[] { + // Make sure context has been converted. + const newContext = CoreFilter.getEffectiveContext(contextLevel, instanceId, options); + contextLevel = newContext.contextLevel; + instanceId = newContext.instanceId; + + const contexts = [ + { contextLevel, instanceId }, + ]; + + if (contextLevel === ContextLevel.MODULE && options.courseId) { + contexts.push({ contextLevel: ContextLevel.COURSE, instanceId: options.courseId }); + } + + if ((contextLevel === ContextLevel.MODULE || contextLevel === ContextLevel.COURSE) && options.categoryId) { + contexts.push({ contextLevel: ContextLevel.COURSECAT, instanceId: options.categoryId }); + } + + if (contextLevel !== ContextLevel.SYSTEM) { + contexts.push({ contextLevel: ContextLevel.SYSTEM, instanceId: 0 }); + } + + return contexts; + } + /** * Invalidates all available in context WS calls. * @@ -358,6 +494,18 @@ export class CoreFilterProvider { await site.invalidateWsCacheForKeyStartingWith(this.getAvailableInContextsPrefixCacheKey()); } + /** + * Invalidates get all states WS call. + * + * @param siteId Site ID (empty for current site). + * @returns Promise resolved when the data is invalidated. + */ + async invalidateAllStates(siteId?: string): Promise { + const site = await CoreSites.getSite(siteId); + + await site.invalidateWsCacheForKey(this.getAllStatesCacheKey()); + } + /** * Invalidates available in context WS call. * @@ -382,7 +530,7 @@ export class CoreFilterProvider { * @param siteId Site ID (empty for current site). * @returns Promise resolved when the data is invalidated. */ - async invalidateAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise { + async invalidateAvailableInContext(contextLevel: ContextLevel, instanceId: number, siteId?: string): Promise { await this.invalidateAvailableInContexts([{ contextlevel: contextLevel, instanceid: instanceId }], siteId); } @@ -407,7 +555,7 @@ export class CoreFilterProvider { for (let i = 0; i < contexts.length; i++) { const context = contexts[i]; - if (context.contextlevel != 'system') { + if (context.contextlevel !== ContextLevel.SYSTEM) { continue; } @@ -415,7 +563,7 @@ export class CoreFilterProvider { // Use course site home instead. Check if it's already in the list. result.hadSiteHomeContext = contexts.some((context) => - context.contextlevel == 'course' && context.instanceid == site.getSiteHomeId()); + context.contextlevel === ContextLevel.COURSE && context.instanceid == site.getSiteHomeId()); if (result.hadSiteHomeContext) { // Site home is already in list, remove this context from the list. @@ -423,7 +571,7 @@ export class CoreFilterProvider { } else { // Site home not in list, use it instead of system. contexts[i] = { - contextlevel: 'course', + contextlevel: ContextLevel.COURSE, instanceid: site.getSiteHomeId(), }; } @@ -470,20 +618,20 @@ export type CoreFiltersGetAvailableInContextWSParams = { * Data about a context sent to core_filters_get_available_in_context. */ export type CoreFiltersGetAvailableInContextWSParamContext = { - contextlevel: string; // The context level where the filters are: (coursecat, course, module). + contextlevel: ContextLevel; // The context level where the filters are: (coursecat, course, module). instanceid: number; // The instance id of item associated with the context. }; /** - * Filter object returned by core_filters_get_available_in_context. + * Filter data. */ export type CoreFilterFilter = { - contextlevel: string; // The context level where the filters are: (coursecat, course, module). + contextlevel: ContextLevel; // The context level where the filters are: (coursecat, course, module). instanceid: number; // The instance id of item associated with the context. - contextid: number; // The context id. + contextid?: number; // The context id. It will be undefined in cases where it cannot be calculated in the app. filter: string; // Filter plugin name. - localstate: number; // Filter state: 1 for on, -1 for off, 0 if inherit. - inheritedstate: number; // 1 or 0 to use when localstate is set to inherit. + localstate: CoreFilterStateValue; // Filter state. + inheritedstate: CoreFilterStateValue; // State to use when localstate is set to inherit. }; /** @@ -494,11 +642,41 @@ export type CoreFilterGetAvailableInContextResult = { warnings: CoreWSExternalWarning[]; // List of warnings. }; +/** + * Filter state returned by core_filters_get_all_states. + */ +export type CoreFilterState = { + contextlevel: ContextLevel; // The context level where the filters are: (coursecat, course, module). + instanceid: number; // The instance id of item associated with the context. + contextid: number; // The context id. + filter: string; // Filter plugin name. + state: CoreFilterStateValue; // Filter state. + sortorder: number; // Sort order. +}; + +/** + * Context levels enumeration. + */ +export const enum CoreFilterStateValue { + ON = 1, + INHERIT = 0, + OFF = -1, + DISABLED = -9999, +} + +/** + * Result of core_filters_get_all_states. + */ +export type CoreFilterGetAllStatesWSResponse = { + filters: CoreFilterState[]; // Filter state. + warnings: CoreWSExternalWarning[]; // List of warnings. +}; + /** * Options that can be passed to format text. */ export type CoreFilterFormatTextOptions = { - contextLevel?: string; // The context level where the text is. + contextLevel?: ContextLevel; // The context level where the text is. instanceId?: number; // The instance id related to the context. clean?: boolean; // If true all HTML will be removed. Default false. filter?: boolean; // If true the string will be run through applicable filters as well. Default true. @@ -517,3 +695,14 @@ export type CoreFilterClassifiedFilters = { [instanceid: number]: CoreFilterFilter[]; }; }; + +/** + * All filter states classified by context, instance and filter name. + */ +export type CoreFilterAllStates = { + [contextlevel: string]: { + [instanceid: number]: { + [filtername: string]: CoreFilterFilter; + }; + }; +}; diff --git a/src/core/features/question/classes/base-question-component.ts b/src/core/features/question/classes/base-question-component.ts index 8ddefceea62..5d1e74c3bea 100644 --- a/src/core/features/question/classes/base-question-component.ts +++ b/src/core/features/question/classes/base-question-component.ts @@ -23,6 +23,7 @@ import { CoreWSFile } from '@services/ws'; import { CoreIonicColorNames } from '@singletons/colors'; import { CoreLogger } from '@singletons/logger'; import { CoreQuestionBehaviourButton, CoreQuestionHelper, CoreQuestionQuestion } from '../services/question-helper'; +import { ContextLevel } from '@/core/constants'; /** * Base class for components to render a question. @@ -37,7 +38,7 @@ export class CoreQuestionBaseComponent { const className = this.handlerSchema.displaydata?.class || 'block_' + block.name; diff --git a/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts b/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts index 8a288eaa438..8eed777762a 100644 --- a/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts +++ b/src/core/features/siteplugins/components/question-behaviour/question-behaviour.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { CoreQuestionBehaviourDelegate } from '@features/question/services/behaviour-delegate'; @@ -33,7 +34,7 @@ export class CoreSitePluginsQuestionBehaviourComponent extends CoreSitePluginsCo @Input() componentId?: number; // ID of the component the question belongs to. @Input() attemptId?: number; // Attempt ID. @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters. @Input() review?: boolean; // Whether the user is in review mode. diff --git a/src/core/features/siteplugins/components/question/question.ts b/src/core/features/siteplugins/components/question/question.ts index 3354ebe1161..8c9bd7a39ac 100644 --- a/src/core/features/siteplugins/components/question/question.ts +++ b/src/core/features/siteplugins/components/question/question.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, OnInit, Input, Output, EventEmitter } from '@angular/core'; import { AddonModQuizQuestion } from '@features/question/classes/base-question-component'; @@ -34,7 +35,7 @@ export class CoreSitePluginsQuestionComponent extends CoreSitePluginsCompileInit @Input() componentId?: number; // ID of the component the question belongs to. @Input() attemptId?: number; // Attempt ID. @Input() offlineEnabled?: boolean | string; // Whether the question can be answered in offline. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the question belongs to (if any). It can be used to improve performance with filters. @Input() review?: boolean; // Whether the user is in review mode. diff --git a/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts b/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts index efd1a04c8e6..f67f4802129 100644 --- a/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts +++ b/src/core/features/siteplugins/components/user-profile-field/user-profile-field.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, OnInit, Input } from '@angular/core'; import { FormGroup } from '@angular/forms'; @@ -36,7 +37,7 @@ export class CoreSitePluginsUserProfileFieldComponent extends CoreSitePluginsCom @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true. @Input() registerAuth?: string; // Register auth method. E.g. 'email'. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters. diff --git a/src/core/features/siteplugins/pages/module-index/module-index.ts b/src/core/features/siteplugins/pages/module-index/module-index.ts index 3a37e712d21..31be4473458 100644 --- a/src/core/features/siteplugins/pages/module-index/module-index.ts +++ b/src/core/features/siteplugins/pages/module-index/module-index.ts @@ -21,6 +21,7 @@ import { CoreSitePluginsModuleIndexComponent } from '../../components/module-ind import { CoreSites } from '@services/sites'; import { CoreFilterFormatTextOptions } from '@features/filter/services/filter'; import { CoreFilterHelper } from '@features/filter/services/filter-helper'; +import { ContextLevel } from '@/core/constants'; /** * Page to render the index page of a module site plugin. @@ -57,7 +58,7 @@ export class CoreSitePluginsModuleIndexPage implements OnInit, CanLeave { const filteredTitle = await CoreFilterHelper.getFiltersAndFormatText( this.title.trim(), - 'module', + ContextLevel.MODULE, this.module?.id ?? -1, options, siteId, diff --git a/src/core/features/user/classes/base-profilefield-component.ts b/src/core/features/user/classes/base-profilefield-component.ts index 9a61cf3270e..ae8edbfe6c1 100644 --- a/src/core/features/user/classes/base-profilefield-component.ts +++ b/src/core/features/user/classes/base-profilefield-component.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, Input, OnInit } from '@angular/core'; import { FormGroup, Validators, FormControl } from '@angular/forms'; @@ -32,7 +33,7 @@ export abstract class CoreUserProfileFieldBaseComponent implements O @Input() disabled = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true. @Input() registerAuth?: string; // Register auth method. E.g. 'email'. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters. diff --git a/src/core/features/user/components/user-profile-field/user-profile-field.ts b/src/core/features/user/components/user-profile-field/user-profile-field.ts index e33b5752623..c3b4e82e526 100644 --- a/src/core/features/user/components/user-profile-field/user-profile-field.ts +++ b/src/core/features/user/components/user-profile-field/user-profile-field.ts @@ -19,6 +19,7 @@ import { AuthEmailSignupProfileField } from '@features/login/services/login-help import { CoreUserProfileField } from '@features/user/services/user'; import { CoreUserProfileFieldDelegate } from '@features/user/services/user-profile-field-delegate'; import { CoreUtils } from '@services/utils/utils'; +import { ContextLevel } from '@/core/constants'; /** * Directive to render user profile field. @@ -34,7 +35,7 @@ export class CoreUserProfileFieldComponent implements OnInit { @Input() edit = false; // True if editing the field. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. Required if edit=true or signup=true. @Input() registerAuth?: string; // Register auth method. E.g. 'email'. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() contextInstanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the field belongs to (if any). It can be used to improve performance with filters. @@ -78,7 +79,7 @@ export type CoreUserProfileFieldComponentData = { disabled?: boolean; form?: FormGroup; registerAuth?: string; - contextLevel?: string; + contextLevel?: ContextLevel; contextInstanceId?: number; courseId?: number; }; diff --git a/src/core/features/user/pages/about/about.html b/src/core/features/user/pages/about/about.html index e7b7c66388c..6617eae5dca 100644 --- a/src/core/features/user/pages/about/about.html +++ b/src/core/features/user/pages/about/about.html @@ -29,7 +29,8 @@

{{ user.fullname }}

- +

diff --git a/src/core/features/viewer/components/text/text.ts b/src/core/features/viewer/components/text/text.ts index a6ae057fa0c..a7c82dfed7d 100644 --- a/src/core/features/viewer/components/text/text.ts +++ b/src/core/features/viewer/components/text/text.ts @@ -12,6 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. +import { ContextLevel } from '@/core/constants'; import { Component, Input } from '@angular/core'; import { CoreFileEntry } from '@services/file-helper'; @@ -34,7 +35,7 @@ export class CoreViewerTextComponent { @Input() componentId?: string | number; // Component ID to use in format-text. @Input() files?: CoreFileEntry[]; // List of files. @Input() filter?: boolean; // Whether to filter the text. - @Input() contextLevel?: string; // The context level. + @Input() contextLevel?: ContextLevel; // The context level. @Input() instanceId?: number; // The instance ID related to the context. @Input() courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. @Input() displayCopyButton?: boolean; // Whether to display a button to copy the contents. diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index dd7d148eff3..acec137e397 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -27,6 +27,7 @@ import { CoreUrl } from '@singletons/url'; import { AlertButton } from '@ionic/angular'; import { CorePath } from '@singletons/path'; import { CorePlatform } from '@services/platform'; +import { ContextLevel } from '@/core/constants'; /** * Different type of errors the app can treat. @@ -1048,7 +1049,7 @@ export type CoreTextUtilsViewTextOptions = { componentId?: string | number; // An ID to use in conjunction with the component. files?: CoreWSFile[]; // List of files to display along with the text. filter?: boolean; // Whether the text should be filtered. - contextLevel?: string; // The context level. + contextLevel?: ContextLevel; // The context level. instanceId?: number; // The instance ID related to the context. courseId?: number; // Course ID the text belongs to. It can be used to improve performance with filters. displayCopyButton?: boolean; // Whether to display a button to copy the text.