forked from EVOgeek/Vmeda.Online
		
	MOBILE-3620 filter: Implement filter services
This commit is contained in:
		
							parent
							
								
									1559bf27b7
								
							
						
					
					
						commit
						b35ce619fb
					
				| @ -124,7 +124,7 @@ export class CoreDelegate<HandlerType extends CoreDelegateHandler> { | ||||
|      * @return Function returned value or default value. | ||||
|      */ | ||||
|     protected executeFunction<T = unknown>(handlerName: string, fnName: string, params?: unknown[]): T | undefined { | ||||
|         return this.execute(this.handlers[handlerName], fnName, params); | ||||
|         return this.execute<T>(this.handlers[handlerName], fnName, params); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
							
								
								
									
										290
									
								
								src/core/features/filter/services/filter-delegate.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										290
									
								
								src/core/features/filter/services/filter-delegate.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,290 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, ViewContainerRef } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreFilterFilter, CoreFilterFormatTextOptions } from './filter'; | ||||
| import { CoreFilterDefaultHandler } from './handlers/default-filter'; | ||||
| import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| 
 | ||||
| /** | ||||
|  * Interface that all filter handlers must implement. | ||||
|  */ | ||||
| export interface CoreFilterHandler extends CoreDelegateHandler { | ||||
|     /** | ||||
|      * Name of the filter. It should match the "filter" field returned in core_filters_get_available_in_context. | ||||
|      */ | ||||
|     filterName: string; | ||||
| 
 | ||||
|     /** | ||||
|      * Filter some text. | ||||
|      * | ||||
|      * @param text The text to filter. | ||||
|      * @param filter The filter. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Filtered text (or promise resolved with the filtered text). | ||||
|      */ | ||||
|     filter(text: string, filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, siteId?: string): string | Promise<string>; | ||||
| 
 | ||||
|     /** | ||||
|      * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was | ||||
|      * filtered. | ||||
|      * | ||||
|      * @param container The HTML container to handle. | ||||
|      * @param filter The filter. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param viewContainerRef The ViewContainerRef where the container is. | ||||
|      * @param component Component. | ||||
|      * @param componentId Component ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return If async, promise resolved when done. | ||||
|      */ | ||||
|     handleHtml?( | ||||
|         container: HTMLElement, | ||||
|         filter: CoreFilterFilter, | ||||
|         options: CoreFilterFormatTextOptions, | ||||
|         viewContainerRef: ViewContainerRef, | ||||
|         component?: string, | ||||
|         componentId?: string | number, | ||||
|         siteId?: string, | ||||
|     ): void | Promise<void>; | ||||
| 
 | ||||
|     /** | ||||
|      * Check if the filter should be applied in a certain site based on some filter options. | ||||
|      * | ||||
|      * @param options Options. | ||||
|      * @param site Site. | ||||
|      * @return Whether filter should be applied. | ||||
|      */ | ||||
|     shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean; | ||||
| } | ||||
| 
 | ||||
| /** | ||||
|  * Delegate to register filters. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreFilterDelegateService extends CoreDelegate<CoreFilterHandler> { | ||||
| 
 | ||||
|     protected featurePrefix = 'CoreFilterDelegate_'; | ||||
|     protected handlerNameProperty = 'filterName'; | ||||
| 
 | ||||
|     constructor(protected defaultHandler: CoreFilterDefaultHandler) { | ||||
|         super('CoreFilterDelegate', true); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Apply a list of filters to some content. | ||||
|      * | ||||
|      * @param text The text to filter. | ||||
|      * @param filters Filters to apply. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param skipFilters Names of filters that shouldn't be applied. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the filtered text. | ||||
|      */ | ||||
|     async filterText( | ||||
|         text: string, | ||||
|         filters?: CoreFilterFilter[], | ||||
|         options?: CoreFilterFormatTextOptions, | ||||
|         skipFilters?: string[], | ||||
|         siteId?: string, | ||||
|     ): Promise<string> { | ||||
| 
 | ||||
|         // Wait for filters to be initialized.
 | ||||
|         await this.handlersInitPromise; | ||||
| 
 | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         filters = filters || []; | ||||
|         options = options || {}; | ||||
| 
 | ||||
|         for (let i = 0; i < filters.length; i++) { | ||||
|             const filter = filters[i]; | ||||
|             if (!this.isEnabledAndShouldApply(filter, options, site, skipFilters)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 const newText = await this.executeFunctionOnEnabled<string>( | ||||
|                     filter.filter, | ||||
|                     'filter', | ||||
|                     [text, filter, options, siteId], | ||||
|                 ); | ||||
| 
 | ||||
|                 text = newText || text; | ||||
|             } catch (error) { | ||||
|                 this.logger.error('Error applying filter' + filter.filter, error); | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         // Remove <nolink> tags for XHTML compatibility.
 | ||||
|         text = text.replace(/<\/?nolink>/gi, ''); | ||||
| 
 | ||||
|         return text; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get filters that have an enabled handler. | ||||
|      * | ||||
|      * @param contextLevel Context level of the filters. | ||||
|      * @param instanceId Instance ID. | ||||
|      * @return Filters. | ||||
|      */ | ||||
|     getEnabledFilters(contextLevel: string, instanceId: number): CoreFilterFilter[] { | ||||
|         const filters: CoreFilterFilter[] = []; | ||||
| 
 | ||||
|         for (const name in this.enabledHandlers) { | ||||
|             const handler = <CoreFilterHandler> this.enabledHandlers[name]; | ||||
| 
 | ||||
|             filters.push({ | ||||
|                 contextid: -1, | ||||
|                 contextlevel: contextLevel, | ||||
|                 filter: handler.filterName, | ||||
|                 inheritedstate: 1, | ||||
|                 instanceid: instanceId, | ||||
|                 localstate: 1, | ||||
|             }); | ||||
|         } | ||||
| 
 | ||||
|         return filters; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Let filters handle an HTML element. | ||||
|      * | ||||
|      * @param container The HTML container to handle. | ||||
|      * @param filters Filters to apply. | ||||
|      * @param viewContainerRef The ViewContainerRef where the container is. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param skipFilters Names of filters that shouldn't be applied. | ||||
|      * @param component Component. | ||||
|      * @param componentId Component ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved when done. | ||||
|      */ | ||||
|     async handleHtml( | ||||
|         container: HTMLElement, | ||||
|         filters: CoreFilterFilter[], | ||||
|         viewContainerRef?: ViewContainerRef, | ||||
|         options?: CoreFilterFormatTextOptions, | ||||
|         skipFilters?: string[], | ||||
|         component?: string, | ||||
|         componentId?: string | number, | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
| 
 | ||||
|         // Wait for filters to be initialized.
 | ||||
|         await this.handlersInitPromise; | ||||
| 
 | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         filters = filters || []; | ||||
|         options = options || {}; | ||||
| 
 | ||||
|         for (let i = 0; i < filters.length; i++) { | ||||
|             const filter = filters[i]; | ||||
|             if (!this.isEnabledAndShouldApply(filter, options, site, skipFilters)) { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             try { | ||||
|                 await this.executeFunctionOnEnabled<void>( | ||||
|                     filter.filter, | ||||
|                     'handleHtml', | ||||
|                     [container, filter, options, viewContainerRef, component, componentId, siteId], | ||||
|                 ); | ||||
|             } catch (error) { | ||||
|                 this.logger.error('Error handling HTML' + filter.filter, error); | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a filter is enabled and should be applied. | ||||
|      * | ||||
|      * @param filters Filters to apply. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param site Site. | ||||
|      * @param skipFilters Names of filters that shouldn't be applied. | ||||
|      * @return Whether the filter is enabled and should be applied. | ||||
|      */ | ||||
|     isEnabledAndShouldApply( | ||||
|         filter: CoreFilterFilter, | ||||
|         options: CoreFilterFormatTextOptions, | ||||
|         site: CoreSite, | ||||
|         skipFilters?: string[], | ||||
|     ): boolean { | ||||
| 
 | ||||
|         if (filter.localstate == -1 || (filter.localstate == 0 && filter.inheritedstate == -1)) { | ||||
|             // Filter is disabled, ignore it.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (!this.shouldFilterBeApplied(filter, options, site)) { | ||||
|             // Filter shouldn't be applied.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         if (skipFilters && skipFilters.indexOf(filter.filter) != -1) { | ||||
|             // Skip this filter.
 | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if at least 1 filter should be applied in a certain site and with certain options. | ||||
|      * | ||||
|      * @param filter Filter to check. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param site Site. If not defined, current site. | ||||
|      * @return Promise resolved with true: whether the filter should be applied. | ||||
|      */ | ||||
|     async shouldBeApplied(filters: CoreFilterFilter[], options: CoreFilterFormatTextOptions, site?: CoreSite): Promise<boolean> { | ||||
|         // Wait for filters to be initialized.
 | ||||
|         await this.handlersInitPromise; | ||||
| 
 | ||||
|         for (let i = 0; i < filters.length; i++) { | ||||
|             if (this.shouldFilterBeApplied(filters[i], options, site)) { | ||||
|                 return true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         return false; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check whether a filter should be applied in a certain site and with certain options. | ||||
|      * | ||||
|      * @param filter Filter to check. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param site Site. If not defined, current site. | ||||
|      * @return Whether the filter should be applied. | ||||
|      */ | ||||
|     protected shouldFilterBeApplied(filter: CoreFilterFilter, options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { | ||||
|         if (!this.hasHandler(filter.filter, true)) { | ||||
|             return false; | ||||
|         } | ||||
| 
 | ||||
|         return !!(this.executeFunctionOnEnabled<boolean>(filter.filter, 'shouldBeApplied', [options, site])); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreFilterDelegate extends makeSingleton(CoreFilterDelegateService) {} | ||||
							
								
								
									
										358
									
								
								src/core/features/filter/services/filter-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										358
									
								
								src/core/features/filter/services/filter-helper.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,358 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreFilterDelegate } from './filter-delegate'; | ||||
| import { | ||||
|     CoreFilter, | ||||
|     CoreFilterFilter, | ||||
|     CoreFilterFormatTextOptions, | ||||
|     CoreFilterClassifiedFilters, | ||||
|     CoreFiltersGetAvailableInContextWSParamContext, | ||||
| } from './filter'; | ||||
| // import { CoreCourseProvider } from '@features/course/providers/course';
 | ||||
| // import { CoreCoursesProvider } from '@features/courses/providers/courses';
 | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { CoreEvents, CoreEventSiteData } from '@singletons/events'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| 
 | ||||
| /** | ||||
|  * Helper service to provide filter functionalities. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreFilterHelperProvider { | ||||
| 
 | ||||
|     protected logger: CoreLogger; | ||||
| 
 | ||||
|     /** | ||||
|      * When a module context is requested, we request all the modules in a course to decrease WS calls. If there are a lot of | ||||
|      * modules, checking the cache of all contexts can be really slow, so we use this memory cache to speed up the process. | ||||
|      */ | ||||
|     protected moduleContextsCache: { | ||||
|         [siteId: string]: { | ||||
|             [courseId: number]: { | ||||
|                 [contextLevel: string]: { | ||||
|                     contexts: CoreFilterClassifiedFilters; | ||||
|                     time: number; | ||||
|                 }; | ||||
|             }; | ||||
|         }; | ||||
|     } = {}; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logger = CoreLogger.getInstance('CoreFilterHelperProvider'); | ||||
| 
 | ||||
|         CoreEvents.on(CoreEvents.WS_CACHE_INVALIDATED, (data: CoreEventSiteData) => { | ||||
|             delete this.moduleContextsCache[data.siteId || '']; | ||||
|         }); | ||||
| 
 | ||||
|         CoreEvents.on(CoreEvents.SITE_STORAGE_DELETED, (data: CoreEventSiteData) => { | ||||
|             delete this.moduleContextsCache[data.siteId || '']; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the contexts of all blocks in a course. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the contexts. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     async getBlocksContexts(courseId: number, siteId?: string): Promise<CoreFiltersGetAvailableInContextWSParamContext[]> { | ||||
|         return []; | ||||
|         // @todo
 | ||||
|         // const blocks = await this.courseProvider.getCourseBlocks(courseId, 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. | ||||
|      * | ||||
|      * @param contextLevel The context level. | ||||
|      * @param instanceId Instance ID related to the context. | ||||
|      * @param options Options for format text. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the filters. | ||||
|      */ | ||||
|     protected async getCacheableFilters( | ||||
|         contextLevel: string, | ||||
|         instanceId: number, | ||||
|         getFilters: () => Promise<CoreFiltersGetAvailableInContextWSParamContext[]>, | ||||
|         options: CoreFilterFormatTextOptions, | ||||
|         site: CoreSite, | ||||
|     ): Promise<CoreFilterFilter[]> { | ||||
| 
 | ||||
|         // Check the memory cache first.
 | ||||
|         const result = this.getFromMemoryCache(options.courseId ?? -1, contextLevel, instanceId, site); | ||||
|         if (result) { | ||||
|             return result; | ||||
|         } | ||||
| 
 | ||||
|         const siteId = site.getId(); | ||||
| 
 | ||||
|         const contexts = await getFilters(); | ||||
| 
 | ||||
|         const filters = await CoreFilter.instance.getAvailableInContexts(contexts, siteId); | ||||
| 
 | ||||
|         this.storeInMemoryCache(options.courseId ?? -1, contextLevel, filters, siteId); | ||||
| 
 | ||||
|         return filters[contextLevel][instanceId] || []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * If user is enrolled in the course, return contexts of all enrolled courses to decrease number of WS requests. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the contexts. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     async getCourseContexts(courseId: number, siteId?: string): Promise<CoreFiltersGetAvailableInContextWSParamContext[]> { | ||||
|         // @todo
 | ||||
|         return []; | ||||
|         // const courseIds = await this.coursesProvider.getCourseIdsIfEnrolled(courseId, siteId);
 | ||||
| 
 | ||||
|         // const contexts: CoreFiltersGetAvailableInContextWSParamContext[] = [];
 | ||||
| 
 | ||||
|         // courseIds.forEach((courseId) => {
 | ||||
|         //     contexts.push({
 | ||||
|         //         contextlevel: 'course',
 | ||||
|         //         instanceid: courseId
 | ||||
|         //     });
 | ||||
|         // });
 | ||||
| 
 | ||||
|         // return contexts;
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the contexts of all course modules in a course. | ||||
|      * | ||||
|      * @param courseId Course ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the contexts. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     async getCourseModulesContexts(courseId: number, siteId?: string): Promise<CoreFiltersGetAvailableInContextWSParamContext[]> { | ||||
|         // @todo
 | ||||
|         return []; | ||||
| 
 | ||||
|         // const sections = await this.courseProvider.getSections(courseId, false, true, undefined, siteId);
 | ||||
| 
 | ||||
|         // const contexts: CoreFiltersGetAvailableInContextWSParamContext[] = [];
 | ||||
| 
 | ||||
|         // sections.forEach((section) => {
 | ||||
|         //     if (section.modules) {
 | ||||
|         //         section.modules.forEach((module) => {
 | ||||
|         //             if (module.uservisible) {
 | ||||
|         //                 contexts.push({
 | ||||
|         //                     contextlevel: 'module',
 | ||||
|         //                     instanceid: module.id
 | ||||
|         //                 });
 | ||||
|         //             }
 | ||||
|         //         });
 | ||||
|         //     }
 | ||||
|         // });
 | ||||
| 
 | ||||
|         // return contexts;
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the filters in a certain context, performing some checks like the site version. | ||||
|      * It's recommended to use this function instead of canGetFilters + getEnabledFilters because this function will check if | ||||
|      * it's really needed to call the WS. | ||||
|      * | ||||
|      * @param contextLevel The context level. | ||||
|      * @param instanceId Instance ID related to the context. | ||||
|      * @param options Options for format text. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the filters. | ||||
|      */ | ||||
|     async getFilters( | ||||
|         contextLevel: string, | ||||
|         instanceId: number, | ||||
|         options?: CoreFilterFormatTextOptions, | ||||
|         siteId?: string, | ||||
|     ): Promise<CoreFilterFilter[]> { | ||||
|         options = options || {}; | ||||
|         options.contextLevel = contextLevel; | ||||
|         options.instanceId = instanceId; | ||||
|         options.filter = false; | ||||
| 
 | ||||
|         try { | ||||
|             const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|             siteId = site.getId(); | ||||
| 
 | ||||
|             const canGet = await CoreFilter.instance.canGetFilters(siteId); | ||||
|             if (!canGet) { | ||||
|                 options.filter = true; | ||||
| 
 | ||||
|                 // We cannot check which filters are available, apply them all.
 | ||||
|                 return CoreFilterDelegate.instance.getEnabledFilters(contextLevel, instanceId); | ||||
|             } | ||||
| 
 | ||||
|             let hasFilters = true; | ||||
| 
 | ||||
|             if (contextLevel == 'system' || (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.
 | ||||
|                 hasFilters = await this.siteHasFiltersToTreat(options, siteId); | ||||
|             } | ||||
| 
 | ||||
|             if (!hasFilters) { | ||||
|                 return []; | ||||
|             } | ||||
| 
 | ||||
|             options.filter = true; | ||||
| 
 | ||||
|             if (contextLevel == 'module' && options.courseId) { | ||||
|                 // Get all the modules filters with a single call to decrease the number of WS calls.
 | ||||
|                 const getFilters = this.getCourseModulesContexts.bind(this, options.courseId, siteId); | ||||
| 
 | ||||
|                 return this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); | ||||
| 
 | ||||
|             } else if (contextLevel == 'course') { | ||||
|                 // If enrolled, get all enrolled courses filters with a single call to decrease number of WS calls.
 | ||||
|                 const getFilters = this.getCourseContexts.bind(this, instanceId, siteId); | ||||
| 
 | ||||
|                 return this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); | ||||
|             } else if (contextLevel == 'block' && options.courseId) { // @todo && this.courseProvider.canGetCourseBlocks(site)
 | ||||
|                 // Get all the course blocks filters with a single call to decrease number of WS calls.
 | ||||
|                 const getFilters = this.getBlocksContexts.bind(this, options.courseId, siteId); | ||||
| 
 | ||||
|                 return this.getCacheableFilters(contextLevel, instanceId, getFilters, options, site); | ||||
|             } | ||||
| 
 | ||||
|             return CoreFilter.instance.getAvailableInContext(contextLevel, instanceId, siteId); | ||||
|         } catch (error) { | ||||
|             this.logger.error('Error getting filters, return an empty array', error, contextLevel, instanceId); | ||||
| 
 | ||||
|             return []; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get filters and format text. | ||||
|      * | ||||
|      * @param text Text to filter. | ||||
|      * @param contextLevel The context level. | ||||
|      * @param instanceId Instance ID related to the context. | ||||
|      * @param options Options for format text. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the formatted text and the filters. | ||||
|      */ | ||||
|     async getFiltersAndFormatText( | ||||
|         text: string, | ||||
|         contextLevel: string, | ||||
|         instanceId: number, | ||||
|         options?: CoreFilterFormatTextOptions, | ||||
|         siteId?: string, | ||||
|     ): Promise<{text: string; filters: CoreFilterFilter[]}> { | ||||
| 
 | ||||
|         const filters = await this.getFilters(contextLevel, instanceId, options, siteId); | ||||
| 
 | ||||
|         text = await CoreFilter.instance.formatText(text, options, filters, siteId); | ||||
| 
 | ||||
|         return { text, filters: filters }; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get module context filters from the memory cache. | ||||
|      * | ||||
|      * @param courseId Course the module belongs to. | ||||
|      * @param contextLevel Context level. | ||||
|      * @param instanceId Instance ID. | ||||
|      * @param site Site. | ||||
|      * @return The filters, undefined if not found. | ||||
|      */ | ||||
|     protected getFromMemoryCache( | ||||
|         courseId: number, | ||||
|         contextLevel: string, | ||||
|         instanceId: number, | ||||
|         site: CoreSite, | ||||
|     ): CoreFilterFilter[] | undefined { | ||||
| 
 | ||||
|         const siteId = site.getId(); | ||||
| 
 | ||||
|         // Check if we have the context in the memory cache.
 | ||||
|         if (!this.moduleContextsCache[siteId]?.[courseId]?.[contextLevel]) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         const cachedData = this.moduleContextsCache[siteId][courseId][contextLevel]; | ||||
| 
 | ||||
|         if (!CoreApp.instance.isOnline() || Date.now() <= cachedData.time + site.getExpirationDelay(CoreSite.FREQUENCY_RARELY)) { | ||||
|             // We can use cache, return the filters if found.
 | ||||
|             return cachedData.contexts[contextLevel] && cachedData.contexts[contextLevel][instanceId]; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if site has available any filter that should be treated by the app. | ||||
|      * | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean: whether it has filters to treat. | ||||
|      */ | ||||
|     async siteHasFiltersToTreat(options?: CoreFilterFormatTextOptions, siteId?: string): Promise<boolean> { | ||||
|         options = options || {}; | ||||
| 
 | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         // Get filters at site level.
 | ||||
|         const filters = await CoreFilter.instance.getAvailableInContext('system', 0, site.getId()); | ||||
| 
 | ||||
|         return CoreFilterDelegate.instance.shouldBeApplied(filters, options, site); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Store filters in the memory cache. | ||||
|      * | ||||
|      * @param contexts Filters to store, classified by contextlevel and instanceid | ||||
|      * @param siteId Site ID. | ||||
|      */ | ||||
|     protected storeInMemoryCache( | ||||
|         courseId: number, | ||||
|         contextLevel: string, | ||||
|         contexts: CoreFilterClassifiedFilters, | ||||
|         siteId: string, | ||||
|     ): void { | ||||
| 
 | ||||
|         this.moduleContextsCache[siteId] = this.moduleContextsCache[siteId] || {}; | ||||
|         this.moduleContextsCache[siteId][courseId] = this.moduleContextsCache[siteId][courseId] || {}; | ||||
|         this.moduleContextsCache[siteId][courseId][contextLevel] = { | ||||
|             contexts: contexts, | ||||
|             time: Date.now(), | ||||
|         }; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreFilterHelper extends makeSingleton(CoreFilterHelperProvider) {} | ||||
							
								
								
									
										543
									
								
								src/core/features/filter/services/filter.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										543
									
								
								src/core/features/filter/services/filter.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,543 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreApp } from '@services/app'; | ||||
| import { CoreSites } from '@services/sites'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| import { CoreWSExternalWarning } from '@services/ws'; | ||||
| import { CoreTextUtils } from '@services/utils/text'; | ||||
| import { CoreFilterDelegate } from './filter-delegate'; | ||||
| import { makeSingleton } from '@singletons'; | ||||
| import { CoreEvents, CoreEventSiteData } from '@singletons/events'; | ||||
| import { CoreLogger } from '@singletons/logger'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to provide filter functionalities. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreFilterProvider { | ||||
| 
 | ||||
|     protected readonly ROOT_CACHE_KEY = 'mmFilter:'; | ||||
| 
 | ||||
|     protected logger: CoreLogger; | ||||
| 
 | ||||
|     /** | ||||
|      * Store the contexts in memory to speed up the process, it can take a lot of time otherwise. | ||||
|      */ | ||||
|     protected contextsCache: { | ||||
|         [siteId: string]: { | ||||
|             [contextlevel: string]: { | ||||
|                 [instanceid: number]: { | ||||
|                     filters: CoreFilterFilter[]; | ||||
|                     time: number; | ||||
|                 }; | ||||
|             }; | ||||
|         }; | ||||
|     } = {}; | ||||
| 
 | ||||
|     constructor() { | ||||
|         this.logger = CoreLogger.getInstance('CoreFilterProvider'); | ||||
| 
 | ||||
|         CoreEvents.on(CoreEvents.WS_CACHE_INVALIDATED, (data: CoreEventSiteData) => { | ||||
|             delete this.contextsCache[data.siteId || '']; | ||||
|         }); | ||||
| 
 | ||||
|         CoreEvents.on(CoreEvents.SITE_STORAGE_DELETED, (data: CoreEventSiteData) => { | ||||
|             delete this.contextsCache[data.siteId || '']; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns whether or not WS get available in context is available. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with true if ws is available, false otherwise. | ||||
|      * @since 3.4 | ||||
|      */ | ||||
|     async canGetAvailableInContext(siteId?: string): Promise<boolean> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         return this.canGetAvailableInContextInSite(site); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns whether or not WS get available in context is available in a certain site. | ||||
|      * | ||||
|      * @param site Site. If not defined, current site. | ||||
|      * @return Promise resolved with true if ws is available, false otherwise. | ||||
|      * @since 3.4 | ||||
|      */ | ||||
|     canGetAvailableInContextInSite(site?: CoreSite): boolean { | ||||
|         site = site || CoreSites.instance.getCurrentSite(); | ||||
| 
 | ||||
|         return !!(site?.wsAvailable('core_filters_get_available_in_context')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|      * @return Promise resolved with boolean: whethe can get filters. | ||||
|      */ | ||||
|     async canGetFilters(siteId?: string): Promise<boolean> { | ||||
|         const wsAvailable = await this.canGetAvailableInContext(siteId); | ||||
|         const disabled = await this.checkFiltersDisabled(siteId); | ||||
| 
 | ||||
|         return wsAvailable && !disabled; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * 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. | ||||
|      * @return Promise resolved with boolean: whethe can get filters. | ||||
|      */ | ||||
|     canGetFiltersInSite(site?: CoreSite): boolean { | ||||
|         return this.canGetAvailableInContextInSite(site) && this.checkFiltersDisabledInSite(site); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns whether or not checking the available filters is disabled in the site. | ||||
|      * | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with boolean: whether it's disabled. | ||||
|      */ | ||||
|     async checkFiltersDisabled(siteId?: string): Promise<boolean> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         return this.checkFiltersDisabledInSite(site); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Returns whether or not checking the available filters is disabled in the site. | ||||
|      * | ||||
|      * @param site Site. If not defined, current site. | ||||
|      * @return Whether it's disabled. | ||||
|      */ | ||||
|     checkFiltersDisabledInSite(site?: CoreSite): boolean { | ||||
|         site = site || CoreSites.instance.getCurrentSite(); | ||||
| 
 | ||||
|         return !!(site?.isFeatureDisabled('CoreFilterDelegate')); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Classify a list of filters into each context. | ||||
|      * | ||||
|      * @param contexts List of contexts. | ||||
|      * @param filters List of filters. | ||||
|      * @param hadSystemContext Whether the list of contexts originally had system context. | ||||
|      * @param hadSiteHomeContext Whether the list of contexts originally had site home context. | ||||
|      * @param site Site instance. | ||||
|      * @return Classified filters. | ||||
|      */ | ||||
|     protected classifyFilters( | ||||
|         contexts: CoreFiltersGetAvailableInContextWSParamContext[], | ||||
|         filters: CoreFilterFilter[], | ||||
|         hadSystemContext: boolean, | ||||
|         hadSiteHomeContext: boolean, | ||||
|         site: CoreSite, | ||||
|     ): CoreFilterClassifiedFilters { | ||||
|         const classified: CoreFilterClassifiedFilters = {}; | ||||
| 
 | ||||
|         // Initialize all contexts.
 | ||||
|         contexts.forEach((context) => { | ||||
|             classified[context.contextlevel] = classified[context.contextlevel] || {}; | ||||
|             classified[context.contextlevel][context.instanceid] = []; | ||||
|         }); | ||||
| 
 | ||||
|         if (contexts.length == 1 && !hadSystemContext) { | ||||
|             // Only 1 context, no need to iterate over the filters.
 | ||||
|             classified[contexts[0].contextlevel][contexts[0].instanceid] = filters; | ||||
| 
 | ||||
|             return classified; | ||||
|         } | ||||
| 
 | ||||
|         filters.forEach((filter) => { | ||||
|             if (hadSystemContext && filter.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); | ||||
| 
 | ||||
|                     // Now copy the object so it can be modified.
 | ||||
|                     filter = Object.assign({}, filter); | ||||
|                 } | ||||
| 
 | ||||
|                 // Simulate the system context based on the inherited data.
 | ||||
|                 filter.contextlevel = 'system'; | ||||
|                 filter.instanceid = 0; | ||||
|                 filter.contextid = -1; | ||||
|                 filter.localstate = filter.inheritedstate; | ||||
|             } | ||||
| 
 | ||||
|             classified[filter.contextlevel][filter.instanceid].push(filter); | ||||
|         }); | ||||
| 
 | ||||
|         return classified; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given some HTML code, this function returns the text as safe HTML. | ||||
|      * | ||||
|      * @param text The text to be formatted. | ||||
|      * @param options Formatting options. | ||||
|      * @param filters The filters to apply. Required if filter is set to true. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the formatted text. | ||||
|      */ | ||||
|     async formatText( | ||||
|         text: string, | ||||
|         options?: CoreFilterFormatTextOptions, | ||||
|         filters?: CoreFilterFilter[], | ||||
|         siteId?: string, | ||||
|     ): Promise<string> { | ||||
| 
 | ||||
|         if (!text || typeof text != 'string') { | ||||
|             // No need to do any filters and cleaning.
 | ||||
|             return ''; | ||||
|         } | ||||
| 
 | ||||
|         // Clone object if needed so we can modify it.
 | ||||
|         options = options ? Object.assign({}, options) : {}; | ||||
| 
 | ||||
|         if (typeof options.clean == 'undefined') { | ||||
|             options.clean = false; | ||||
|         } | ||||
| 
 | ||||
|         if (typeof options.filter == 'undefined') { | ||||
|             options.filter = true; | ||||
|         } | ||||
| 
 | ||||
|         if (!options.contextLevel) { | ||||
|             options.filter = false; | ||||
|         } | ||||
| 
 | ||||
|         if (options.filter) { | ||||
|             text = await CoreFilterDelegate.instance.filterText(text, filters, options, [], siteId); | ||||
|         } | ||||
| 
 | ||||
|         if (options.clean) { | ||||
|             text = CoreTextUtils.instance.cleanTags(text, options.singleLine); | ||||
|         } | ||||
| 
 | ||||
|         if (options.shortenLength && options.shortenLength > 0) { | ||||
|             text = CoreTextUtils.instance.shortenText(text, options.shortenLength); | ||||
|         } | ||||
| 
 | ||||
|         if (options.highlight) { | ||||
|             text = CoreTextUtils.instance.highlightText(text, options.highlight); | ||||
|         } | ||||
| 
 | ||||
|         return text; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get cache key for available in contexts WS calls. | ||||
|      * | ||||
|      * @param contexts The contexts to check. | ||||
|      * @return Cache key. | ||||
|      */ | ||||
|     protected getAvailableInContextsCacheKey(contexts: CoreFiltersGetAvailableInContextWSParamContext[]): string { | ||||
|         return this.getAvailableInContextsPrefixCacheKey() + JSON.stringify(contexts); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get prefixed cache key for available in contexts WS calls. | ||||
|      * | ||||
|      * @return Cache key. | ||||
|      */ | ||||
|     protected getAvailableInContextsPrefixCacheKey(): string { | ||||
|         return this.ROOT_CACHE_KEY + 'availableInContexts:'; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the filters available in several contexts. | ||||
|      * | ||||
|      * @param contexts The contexts to check. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the filters classified by context. | ||||
|      */ | ||||
|     async getAvailableInContexts( | ||||
|         contexts: CoreFiltersGetAvailableInContextWSParamContext[], | ||||
|         siteId?: string, | ||||
|     ): Promise<CoreFilterClassifiedFilters> { | ||||
| 
 | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         siteId = site.getId(); | ||||
| 
 | ||||
|         const cacheResult = this.getFromMemoryCache(contexts, site); | ||||
| 
 | ||||
|         if (cacheResult) { | ||||
|             return cacheResult; | ||||
|         } | ||||
| 
 | ||||
|         const contextsToSend = contexts.slice(); // Copy the contexts array to be able to modify it.
 | ||||
| 
 | ||||
|         const { hadSystemContext, hadSiteHomeContext } = this.replaceSystemContext(contextsToSend, site); | ||||
| 
 | ||||
|         const data: CoreFiltersGetAvailableInContextWSParams = { | ||||
|             contexts: contextsToSend, | ||||
|         }; | ||||
|         const preSets = { | ||||
|             cacheKey: this.getAvailableInContextsCacheKey(contextsToSend), | ||||
|             updateFrequency: CoreSite.FREQUENCY_RARELY, | ||||
|             splitRequest: { | ||||
|                 param: 'contexts', | ||||
|                 maxLength: 300, | ||||
|             }, | ||||
|         }; | ||||
| 
 | ||||
|         const result = await site.read<CoreFilterGetAvailableInContextResult>( | ||||
|             'core_filters_get_available_in_context', | ||||
|             data, | ||||
|             preSets, | ||||
|         ); | ||||
| 
 | ||||
|         const classified = this.classifyFilters(contexts, result.filters, hadSystemContext, hadSiteHomeContext, site); | ||||
| 
 | ||||
|         this.storeInMemoryCache(classified, siteId); | ||||
| 
 | ||||
|         return classified; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get the filters available in a certain context. | ||||
|      * | ||||
|      * @param contextLevel The context level to check: system, user, coursecat, course, module, block, ... | ||||
|      * @param instanceId The instance ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Promise resolved with the filters. | ||||
|      */ | ||||
|     async getAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise<CoreFilterFilter[]> { | ||||
|         const result = await this.getAvailableInContexts([{ contextlevel: contextLevel, instanceid: instanceId }], siteId); | ||||
| 
 | ||||
|         return result[contextLevel][instanceId] || []; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get contexts filters from the memory cache. | ||||
|      * | ||||
|      * @param contexts Contexts to get. | ||||
|      * @param site Site. | ||||
|      * @return The filters classified by context and instance. | ||||
|      */ | ||||
|     protected getFromMemoryCache( | ||||
|         contexts: CoreFiltersGetAvailableInContextWSParamContext[], | ||||
|         site: CoreSite, | ||||
|     ): CoreFilterClassifiedFilters | undefined { | ||||
| 
 | ||||
|         if (!this.contextsCache[site.getId()]) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         // Check if we have the contexts in the memory cache.
 | ||||
|         const siteContexts = this.contextsCache[site.getId()]; | ||||
|         const isOnline = CoreApp.instance.isOnline(); | ||||
|         const result: CoreFilterClassifiedFilters = {}; | ||||
|         let allFound = true; | ||||
| 
 | ||||
|         for (let i = 0; i < contexts.length; i++) { | ||||
|             const context = contexts[i]; | ||||
|             const cachedCtxt = siteContexts[context.contextlevel]?.[context.instanceid]; | ||||
| 
 | ||||
|             // Check the context isn't "expired". The time stored in this cache will not match the one in the site cache.
 | ||||
|             if (cachedCtxt && (!isOnline || | ||||
|                     Date.now() <= cachedCtxt.time + site.getExpirationDelay(CoreSite.FREQUENCY_RARELY))) { | ||||
| 
 | ||||
|                 result[context.contextlevel] = result[context.contextlevel] || {}; | ||||
|                 result[context.contextlevel][context.instanceid] = cachedCtxt.filters; | ||||
|             } else { | ||||
|                 allFound = false; | ||||
|                 break; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (allFound) { | ||||
|             return result; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates all available in context WS calls. | ||||
|      * | ||||
|      * @param siteId Site ID (empty for current site). | ||||
|      * @return Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     async invalidateAllAvailableInContext(siteId?: string): Promise<void> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         await site.invalidateWsCacheForKeyStartingWith(this.getAvailableInContextsPrefixCacheKey()); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates available in context WS call. | ||||
|      * | ||||
|      * @param contexts The contexts to check. | ||||
|      * @param siteId Site ID (empty for current site). | ||||
|      * @return Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     async invalidateAvailableInContexts( | ||||
|         contexts: CoreFiltersGetAvailableInContextWSParamContext[], | ||||
|         siteId?: string, | ||||
|     ): Promise<void> { | ||||
|         const site = await CoreSites.instance.getSite(siteId); | ||||
| 
 | ||||
|         await site.invalidateWsCacheForKey(this.getAvailableInContextsCacheKey(contexts)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Invalidates available in context WS call. | ||||
|      * | ||||
|      * @param contextLevel The context level to check. | ||||
|      * @param instanceId The instance ID. | ||||
|      * @param siteId Site ID (empty for current site). | ||||
|      * @return Promise resolved when the data is invalidated. | ||||
|      */ | ||||
|     async invalidateAvailableInContext(contextLevel: string, instanceId: number, siteId?: string): Promise<void> { | ||||
|         await this.invalidateAvailableInContexts([{ contextlevel: contextLevel, instanceid: instanceId }], siteId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a list of context to send to core_filters_get_available_in_context, search if the system context is in the list | ||||
|      * and, if so, replace it with a workaround. | ||||
|      * | ||||
|      * @param contexts The contexts to check. | ||||
|      * @param site Site instance. | ||||
|      * @return Whether the filters had system context and whether they had the site home context. | ||||
|      */ | ||||
|     protected replaceSystemContext( | ||||
|         contexts: CoreFiltersGetAvailableInContextWSParamContext[], | ||||
|         site: CoreSite, | ||||
|     ): { hadSystemContext: boolean; hadSiteHomeContext: boolean } { | ||||
|         const result = { | ||||
|             hadSystemContext: false, | ||||
|             hadSiteHomeContext: false, | ||||
|         }; | ||||
| 
 | ||||
|         // Check if any of the contexts is "system". We cannot use system context, so we'll have to use a wrokaround.
 | ||||
|         for (let i = 0; i < contexts.length; i++) { | ||||
|             const context = contexts[i]; | ||||
| 
 | ||||
|             if (context.contextlevel != 'system') { | ||||
|                 continue; | ||||
|             } | ||||
| 
 | ||||
|             result.hadSystemContext = true; | ||||
| 
 | ||||
|             // 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()); | ||||
| 
 | ||||
|             if (result.hadSiteHomeContext) { | ||||
|                 // Site home is already in list, remove this context from the list.
 | ||||
|                 contexts.splice(i, 1); | ||||
|             } else { | ||||
|                 // Site home not in list, use it instead of system.
 | ||||
|                 contexts[i] = { | ||||
|                     contextlevel: 'course', | ||||
|                     instanceid: site.getSiteHomeId(), | ||||
|                 }; | ||||
|             } | ||||
| 
 | ||||
|             break; | ||||
|         } | ||||
| 
 | ||||
|         return result; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Store filters in the memory cache. | ||||
|      * | ||||
|      * @param filters Filters to store, classified by contextlevel and instanceid | ||||
|      * @param siteId Site ID. | ||||
|      */ | ||||
|     protected storeInMemoryCache(filters: CoreFilterClassifiedFilters, siteId: string): void { | ||||
|         this.contextsCache[siteId] = this.contextsCache[siteId] || {}; | ||||
| 
 | ||||
|         for (const contextLevel in filters) { | ||||
|             this.contextsCache[siteId][contextLevel] = this.contextsCache[siteId][contextLevel] || {}; | ||||
| 
 | ||||
|             for (const instanceId in filters[contextLevel]) { | ||||
|                 this.contextsCache[siteId][contextLevel][instanceId] = { | ||||
|                     filters: filters[contextLevel][instanceId], | ||||
|                     time: Date.now(), | ||||
|                 }; | ||||
|             } | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| export class CoreFilter extends makeSingleton(CoreFilterProvider) {} | ||||
| 
 | ||||
| /** | ||||
|  * Params of core_filters_get_available_in_context WS. | ||||
|  */ | ||||
| export type CoreFiltersGetAvailableInContextWSParams = { | ||||
|     contexts: CoreFiltersGetAvailableInContextWSParamContext[]; // The list of contexts to check.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * 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).
 | ||||
|     instanceid: number; // The instance id of item associated with the context.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Filter object returned by core_filters_get_available_in_context. | ||||
|  */ | ||||
| export type CoreFilterFilter = { | ||||
|     contextlevel: string; // 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.
 | ||||
|     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.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Result of core_filters_get_available_in_context. | ||||
|  */ | ||||
| export type CoreFilterGetAvailableInContextResult = { | ||||
|     filters: CoreFilterFilter[]; // Available filters.
 | ||||
|     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.
 | ||||
|     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.
 | ||||
|     singleLine?: boolean; // If true then new lines will be removed (all the text in a single line).
 | ||||
|     shortenLength?: number; // Number of characters to shorten the text.
 | ||||
|     highlight?: string; // Text to highlight.
 | ||||
|     wsNotFiltered?: boolean; // If true it means the WS didn't filter the text for some reason.
 | ||||
|     courseId?: number; // Course ID the text belongs to. It can be used to improve performance.
 | ||||
| }; | ||||
| 
 | ||||
| /** | ||||
|  * Filters classified by context and instance. | ||||
|  */ | ||||
| export type CoreFilterClassifiedFilters = { | ||||
|     [contextlevel: string]: { | ||||
|         [instanceid: number]: CoreFilterFilter[]; | ||||
|     }; | ||||
| }; | ||||
							
								
								
									
										94
									
								
								src/core/features/filter/services/handlers/default-filter.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										94
									
								
								src/core/features/filter/services/handlers/default-filter.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,94 @@ | ||||
| // (C) Copyright 2015 Moodle Pty Ltd.
 | ||||
| //
 | ||||
| // Licensed under the Apache License, Version 2.0 (the "License");
 | ||||
| // you may not use this file except in compliance with the License.
 | ||||
| // You may obtain a copy of the License at
 | ||||
| //
 | ||||
| //     http://www.apache.org/licenses/LICENSE-2.0
 | ||||
| //
 | ||||
| // Unless required by applicable law or agreed to in writing, software
 | ||||
| // distributed under the License is distributed on an "AS IS" BASIS,
 | ||||
| // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 | ||||
| // See the License for the specific language governing permissions and
 | ||||
| // limitations under the License.
 | ||||
| 
 | ||||
| import { Injectable, ViewContainerRef } from '@angular/core'; | ||||
| 
 | ||||
| import { CoreFilterHandler } from '../filter-delegate'; | ||||
| import { CoreFilterFilter, CoreFilterFormatTextOptions } from '../filter'; | ||||
| import { CoreSite } from '@classes/site'; | ||||
| 
 | ||||
| /** | ||||
|  * Default handler used when the module doesn't have a specific implementation. | ||||
|  */ | ||||
| @Injectable({ providedIn: 'root' }) | ||||
| export class CoreFilterDefaultHandler implements CoreFilterHandler { | ||||
| 
 | ||||
|     name = 'CoreFilterDefaultHandler'; | ||||
|     filterName = 'default'; | ||||
| 
 | ||||
|     /** | ||||
|      * Filter some text. | ||||
|      * | ||||
|      * @param text The text to filter. | ||||
|      * @param filter The filter. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return Filtered text (or promise resolved with the filtered text). | ||||
|      */ | ||||
|     filter( | ||||
|         text: string, | ||||
|         filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|     ): string | Promise<string> { | ||||
|         return text; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Handle HTML. This function is called after "filter", and it will receive an HTMLElement containing the text that was | ||||
|      * filtered. | ||||
|      * | ||||
|      * @param container The HTML container to handle. | ||||
|      * @param filter The filter. | ||||
|      * @param options Options passed to the filters. | ||||
|      * @param viewContainerRef The ViewContainerRef where the container is. | ||||
|      * @param component Component. | ||||
|      * @param componentId Component ID. | ||||
|      * @param siteId Site ID. If not defined, current site. | ||||
|      * @return If async, promise resolved when done. | ||||
|      */ | ||||
|     handleHtml( | ||||
|         container: HTMLElement, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         filter: CoreFilterFilter, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         options: CoreFilterFormatTextOptions, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         viewContainerRef: ViewContainerRef, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         component?: string, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         componentId?: string | number, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|         siteId?: string, // eslint-disable-line @typescript-eslint/no-unused-vars
 | ||||
|     ): void | Promise<void> { | ||||
|         // To be overridden.
 | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Whether or not the handler is enabled on a site level. | ||||
|      * | ||||
|      * @return Whether or not the handler is enabled on a site level. | ||||
|      */ | ||||
|     async isEnabled(): Promise<boolean> { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if the filter should be applied in a certain site based on some filter options. | ||||
|      * | ||||
|      * @param options Options. | ||||
|      * @param site Site. | ||||
|      * @return Whether filter should be applied. | ||||
|      */ | ||||
|     // eslint-disable-next-line @typescript-eslint/no-unused-vars
 | ||||
|     shouldBeApplied(options: CoreFilterFormatTextOptions, site?: CoreSite): boolean { | ||||
|         return true; | ||||
|     } | ||||
| 
 | ||||
| } | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user