MOBILE-2235 h5p: Support and use isEnabled function in file delegate
parent
40fc0e2b00
commit
e0849668e3
|
@ -47,4 +47,13 @@ export class AddonModFolderPluginFileHandler implements CorePluginFileHandler {
|
|||
// Component + Filearea + Revision
|
||||
return '/mod_folder/content/0/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -96,7 +96,7 @@ export class AddonModForumPrefetchHandler extends CoreCourseActivityPrefetchHand
|
|||
if (getInlineFiles && post.messageinlinefiles && post.messageinlinefiles.length) {
|
||||
files = files.concat(post.messageinlinefiles);
|
||||
} else if (post.message && !getInlineFiles) {
|
||||
files = files.concat(this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(post.message));
|
||||
files = files.concat(this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(post.message));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -93,7 +93,7 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH
|
|||
if (getInlineFiles && entry.definitioninlinefiles && entry.definitioninlinefiles.length) {
|
||||
files = files.concat(entry.definitioninlinefiles);
|
||||
} else if (entry.definition && !getInlineFiles) {
|
||||
files = files.concat(this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition));
|
||||
files = files.concat(this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition));
|
||||
}
|
||||
});
|
||||
|
||||
|
|
|
@ -54,4 +54,13 @@ export class AddonModImscpPluginFileHandler implements CorePluginFileHandler {
|
|||
// Component + Filearea + Revision
|
||||
return '/mod_imscp/' + args[2] + '/0/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -419,7 +419,8 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
|
|||
return;
|
||||
}
|
||||
answerPage.answerdata.answers.forEach((answer) => {
|
||||
files.push(...this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(answer[0]));
|
||||
files.push(...this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(
|
||||
answer[0]));
|
||||
});
|
||||
});
|
||||
|
||||
|
|
|
@ -47,4 +47,13 @@ export class AddonModPagePluginFileHandler implements CorePluginFileHandler {
|
|||
// Component + Filearea + Revision
|
||||
return '/mod_page/content/0/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -126,7 +126,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
|
|||
files = files.concat(feedback.feedbackinlinefiles);
|
||||
} else if (feedback.feedbacktext && !getInlineFiles) {
|
||||
files = files.concat(
|
||||
this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(feedback.feedbacktext));
|
||||
this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(feedback.feedbacktext));
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
|
|
@ -47,4 +47,13 @@ export class AddonModResourcePluginFileHandler implements CorePluginFileHandler
|
|||
// Component + Filearea + Revision
|
||||
return '/mod_resource/content/0/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -47,4 +47,13 @@ export class AddonModScormPluginFileHandler implements CorePluginFileHandler {
|
|||
// Component + Filearea + Revision
|
||||
return '/mod_scorm/content/0/';
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -237,14 +237,16 @@ export class CoreDelegate {
|
|||
* @return True when registered, false if already registered.
|
||||
*/
|
||||
registerHandler(handler: CoreDelegateHandler): boolean {
|
||||
if (typeof this.handlers[handler[this.handlerNameProperty]] !== 'undefined') {
|
||||
const key = handler[this.handlerNameProperty] || handler.name;
|
||||
|
||||
if (typeof this.handlers[key] !== 'undefined') {
|
||||
this.logger.log(`Handler '${handler[this.handlerNameProperty]}' already registered`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log(`Registered handler '${handler[this.handlerNameProperty]}'`);
|
||||
this.handlers[handler[this.handlerNameProperty]] = handler;
|
||||
this.handlers[key] = handler;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
@ -282,10 +284,12 @@ export class CoreDelegate {
|
|||
}).then((enabled: boolean) => {
|
||||
// Check that site hasn't changed since the check started.
|
||||
if (this.sitesProvider.getCurrentSiteId() === siteId) {
|
||||
const key = handler[this.handlerNameProperty] || handler.name;
|
||||
|
||||
if (enabled) {
|
||||
this.enabledHandlers[handler[this.handlerNameProperty]] = handler;
|
||||
this.enabledHandlers[key] = handler;
|
||||
} else {
|
||||
delete this.enabledHandlers[handler[this.handlerNameProperty]];
|
||||
delete this.enabledHandlers[key];
|
||||
}
|
||||
}
|
||||
}).finally(() => {
|
||||
|
|
|
@ -195,12 +195,12 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref
|
|||
if (typeof instance.introfiles != 'undefined') {
|
||||
return instance.introfiles;
|
||||
} else if (instance.intro) {
|
||||
return this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(instance.intro);
|
||||
return this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(instance.intro);
|
||||
}
|
||||
}
|
||||
|
||||
if (module.description) {
|
||||
return this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(module.description);
|
||||
return this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(module.description);
|
||||
}
|
||||
|
||||
return [];
|
||||
|
|
|
@ -438,7 +438,7 @@ export class CoreCourseHelperProvider {
|
|||
sizePromise = this.prefetchDelegate.getDownloadSize(section.modules, courseId);
|
||||
|
||||
// Check if the section has embedded files in the description.
|
||||
haveEmbeddedFiles = this.domUtils.extractDownloadableFilesFromHtml(section.summary).length > 0;
|
||||
haveEmbeddedFiles = this.filepoolProvider.extractDownloadableFilesFromHtml(section.summary).length > 0;
|
||||
} else {
|
||||
const promises = [],
|
||||
results = {
|
||||
|
@ -454,7 +454,7 @@ export class CoreCourseHelperProvider {
|
|||
}));
|
||||
|
||||
// Check if the section has embedded files in the description.
|
||||
if (!haveEmbeddedFiles && this.domUtils.extractDownloadableFilesFromHtml(s.summary).length > 0) {
|
||||
if (!haveEmbeddedFiles && this.filepoolProvider.extractDownloadableFilesFromHtml(s.summary).length > 0) {
|
||||
haveEmbeddedFiles = true;
|
||||
}
|
||||
}
|
||||
|
@ -1449,7 +1449,7 @@ export class CoreCourseHelperProvider {
|
|||
}));
|
||||
|
||||
// Download the files in the section description.
|
||||
const introFiles = this.domUtils.extractDownloadableFilesFromHtmlAsFakeFileObjects(section.summary),
|
||||
const introFiles = this.filepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects(section.summary),
|
||||
siteId = this.sitesProvider.getCurrentSiteId();
|
||||
|
||||
promises.push(this.filepoolProvider.addFilesToQueue(siteId, introFiles, CoreCourseProvider.COMPONENT, courseId)
|
||||
|
|
|
@ -63,7 +63,7 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler {
|
|||
|
||||
/**
|
||||
* Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by
|
||||
* CoreDomUtilsProvider.extractDownloadableFilesFromHtml.
|
||||
* CoreFilepoolProvider.extractDownloadableFilesFromHtml.
|
||||
*
|
||||
* @param container Container where to get the URLs from.
|
||||
* @return {string[]} List of URLs.
|
||||
|
@ -103,6 +103,15 @@ export class CoreH5PPluginFileHandler implements CorePluginFileHandler {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether or not the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): boolean | Promise<boolean> {
|
||||
return this.h5pProvider.canGetTrustedH5PFileInSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the file should be treated by this handler. It is used in functions where the component isn't used.
|
||||
*
|
||||
|
|
|
@ -516,7 +516,7 @@ export class CoreQuestionHelperProvider {
|
|||
*/
|
||||
prefetchQuestionFiles(question: any, component?: string, componentId?: string | number, siteId?: string, usageId?: number)
|
||||
: Promise<any> {
|
||||
const urls = this.domUtils.extractDownloadableFilesFromHtml(question.html);
|
||||
const urls = this.filepoolProvider.extractDownloadableFilesFromHtml(question.html);
|
||||
|
||||
if (!component) {
|
||||
component = CoreQuestionProvider.COMPONENT;
|
||||
|
|
|
@ -1239,6 +1239,59 @@ export class CoreFilepoolProvider {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the downloadable URLs from an HTML code.
|
||||
*
|
||||
* @param html HTML code.
|
||||
* @return List of file urls.
|
||||
*/
|
||||
extractDownloadableFilesFromHtml(html: string): string[] {
|
||||
let urls = [],
|
||||
elements;
|
||||
|
||||
const element = this.domUtils.convertToElement(html);
|
||||
elements = element.querySelectorAll('a, img, audio, video, source, track');
|
||||
|
||||
for (let i = 0; i < elements.length; i++) {
|
||||
const element = elements[i];
|
||||
let url = element.tagName === 'A' ? element.href : element.src;
|
||||
|
||||
if (url && this.urlUtils.isDownloadableUrl(url) && urls.indexOf(url) == -1) {
|
||||
urls.push(url);
|
||||
}
|
||||
|
||||
// Treat video poster.
|
||||
if (element.tagName == 'VIDEO' && element.getAttribute('poster')) {
|
||||
url = element.getAttribute('poster');
|
||||
if (url && this.urlUtils.isDownloadableUrl(url) && urls.indexOf(url) == -1) {
|
||||
urls.push(url);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Now get other files from plugin file handlers.
|
||||
urls = urls.concat(this.pluginFileDelegate.getDownloadableFilesFromHTML(element));
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract the downloadable URLs from an HTML code and returns them in fake file objects.
|
||||
*
|
||||
* @param html HTML code.
|
||||
* @return List of fake file objects with file URLs.
|
||||
*/
|
||||
extractDownloadableFilesFromHtmlAsFakeFileObjects(html: string): any[] {
|
||||
const urls = this.extractDownloadableFilesFromHtml(html);
|
||||
|
||||
// Convert them to fake file objects.
|
||||
return urls.map((url) => {
|
||||
return {
|
||||
fileurl: url
|
||||
};
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Fill Missing Extension In the File Object if needed.
|
||||
* This is to migrate from old versions.
|
||||
|
|
|
@ -13,18 +13,17 @@
|
|||
// limitations under the License.
|
||||
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreEventsProvider } from './events';
|
||||
import { CoreLoggerProvider } from './logger';
|
||||
import { CoreSitesProvider } from './sites';
|
||||
import { CoreWSExternalFile } from '@providers/ws';
|
||||
import { FileEntry } from '@ionic-native/file';
|
||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||
|
||||
/**
|
||||
* Interface that all plugin file handlers must implement.
|
||||
*/
|
||||
export interface CorePluginFileHandler {
|
||||
/**
|
||||
* A name to identify the handler.
|
||||
*/
|
||||
name: string;
|
||||
export interface CorePluginFileHandler extends CoreDelegateHandler {
|
||||
|
||||
/**
|
||||
* The "component" of the handler. It should match the "component" of pluginfile URLs.
|
||||
|
@ -69,7 +68,7 @@ export interface CorePluginFileHandler {
|
|||
|
||||
/**
|
||||
* Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by
|
||||
* CoreDomUtilsProvider.extractDownloadableFilesFromHtml.
|
||||
* CoreFilepoolProvider.extractDownloadableFilesFromHtml.
|
||||
*
|
||||
* @param container Container where to get the URLs from.
|
||||
* @return {string[]} List of URLs.
|
||||
|
@ -108,12 +107,13 @@ export interface CorePluginFileHandler {
|
|||
* Delegate to register pluginfile information handlers.
|
||||
*/
|
||||
@Injectable()
|
||||
export class CorePluginFileDelegate {
|
||||
protected logger;
|
||||
protected handlers: { [s: string]: CorePluginFileHandler } = {};
|
||||
export class CorePluginFileDelegate extends CoreDelegate {
|
||||
protected handlerNameProperty = 'component';
|
||||
|
||||
constructor(logger: CoreLoggerProvider) {
|
||||
this.logger = logger.getInstance('CorePluginFileDelegate');
|
||||
constructor(loggerProvider: CoreLoggerProvider,
|
||||
sitesProvider: CoreSitesProvider,
|
||||
eventsProvider: CoreEventsProvider) {
|
||||
super('CorePluginFileDelegate', loggerProvider, sitesProvider, eventsProvider);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -167,18 +167,6 @@ export class CorePluginFileDelegate {
|
|||
return Promise.resolve(file);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the handler for a certain pluginfile url.
|
||||
*
|
||||
* @param component Component of the plugin.
|
||||
* @return Handler. Undefined if no handler found for the plugin.
|
||||
*/
|
||||
protected getPluginHandler(component: string): CorePluginFileHandler {
|
||||
if (typeof this.handlers[component] != 'undefined') {
|
||||
return this.handlers[component];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the RegExp of the component and filearea described in the URL.
|
||||
*
|
||||
|
@ -187,7 +175,7 @@ export class CorePluginFileDelegate {
|
|||
*/
|
||||
getComponentRevisionRegExp(args: string[]): RegExp {
|
||||
// Get handler based on component (args[1]).
|
||||
const handler = this.getPluginHandler(args[1]);
|
||||
const handler = <CorePluginFileHandler> this.getHandler(args[1], true);
|
||||
|
||||
if (handler && handler.getComponentRevisionRegExp) {
|
||||
return handler.getComponentRevisionRegExp(args);
|
||||
|
@ -196,7 +184,7 @@ export class CorePluginFileDelegate {
|
|||
|
||||
/**
|
||||
* Given an HTML element, get the URLs of the files that should be downloaded and weren't treated by
|
||||
* CoreDomUtilsProvider.extractDownloadableFilesFromHtml.
|
||||
* CoreFilepoolProvider.extractDownloadableFilesFromHtml.
|
||||
*
|
||||
* @param container Container where to get the URLs from.
|
||||
* @return List of URLs.
|
||||
|
@ -204,8 +192,8 @@ export class CorePluginFileDelegate {
|
|||
getDownloadableFilesFromHTML(container: HTMLElement): string[] {
|
||||
let files = [];
|
||||
|
||||
for (const component in this.handlers) {
|
||||
const handler = this.handlers[component];
|
||||
for (const component in this.enabledHandlers) {
|
||||
const handler = <CorePluginFileHandler> this.enabledHandlers[component];
|
||||
|
||||
if (handler && handler.getDownloadableFilesFromHTML) {
|
||||
files = files.concat(handler.getDownloadableFilesFromHTML(container));
|
||||
|
@ -278,8 +266,8 @@ export class CorePluginFileDelegate {
|
|||
* @return Handler.
|
||||
*/
|
||||
protected getHandlerForFile(file: CoreWSExternalFile): CorePluginFileHandler {
|
||||
for (const component in this.handlers) {
|
||||
const handler = this.handlers[component];
|
||||
for (const component in this.enabledHandlers) {
|
||||
const handler = <CorePluginFileHandler> this.enabledHandlers[component];
|
||||
|
||||
if (handler && handler.shouldHandleFile && handler.shouldHandleFile(file)) {
|
||||
return handler;
|
||||
|
@ -287,25 +275,6 @@ export class CorePluginFileDelegate {
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a handler.
|
||||
*
|
||||
* @param handler The handler to register.
|
||||
* @return True if registered successfully, false otherwise.
|
||||
*/
|
||||
registerHandler(handler: CorePluginFileHandler): boolean {
|
||||
if (typeof this.handlers[handler.component || handler.name] !== 'undefined') {
|
||||
this.logger.log(`Handler '${handler.component}' already registered`);
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
this.logger.log(`Registered handler '${handler.component}'`);
|
||||
this.handlers[handler.component || handler.name] = handler;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Removes the revision number from a file URL.
|
||||
*
|
||||
|
@ -315,7 +284,7 @@ export class CorePluginFileDelegate {
|
|||
*/
|
||||
removeRevisionFromUrl(url: string, args: string[]): string {
|
||||
// Get handler based on component (args[1]).
|
||||
const handler = this.getPluginHandler(args[1]);
|
||||
const handler = <CorePluginFileHandler> this.getHandler(args[1], true);
|
||||
|
||||
if (handler && handler.getComponentRevisionRegExp && handler.getComponentRevisionReplace) {
|
||||
const revisionRegex = handler.getComponentRevisionRegExp(args);
|
||||
|
|
|
@ -22,7 +22,7 @@ import { TranslateService } from '@ngx-translate/core';
|
|||
import { CoreTextUtilsProvider } from './text';
|
||||
import { CoreAppProvider } from '../app';
|
||||
import { CoreConfigProvider } from '../config';
|
||||
import { CorePluginFileDelegate } from '../plugin-file-delegate';
|
||||
import { CoreLoggerProvider } from '../logger';
|
||||
import { CoreUrlUtilsProvider } from './url';
|
||||
import { CoreFileProvider } from '@providers/file';
|
||||
import { CoreConstants } from '@core/constants';
|
||||
|
@ -62,6 +62,7 @@ export class CoreDomUtilsProvider {
|
|||
protected lastInstanceId = 0;
|
||||
protected debugDisplay = false; // Whether to display debug messages. Store it in a variable to make it synchronous.
|
||||
protected displayedAlerts = {}; // To prevent duplicated alerts.
|
||||
protected logger;
|
||||
|
||||
constructor(private translate: TranslateService,
|
||||
private loadingCtrl: LoadingController,
|
||||
|
@ -76,7 +77,9 @@ export class CoreDomUtilsProvider {
|
|||
private sanitizer: DomSanitizer,
|
||||
private popoverCtrl: PopoverController,
|
||||
private fileProvider: CoreFileProvider,
|
||||
private pluginFileDelegate: CorePluginFileDelegate) {
|
||||
loggerProvider: CoreLoggerProvider) {
|
||||
|
||||
this.logger = loggerProvider.getInstance('CoreDomUtilsProvider');
|
||||
|
||||
// Check if debug messages should be displayed.
|
||||
configProvider.get(CoreConstants.SETTINGS_DEBUG_DISPLAY, false).then((debugDisplay) => {
|
||||
|
@ -260,9 +263,13 @@ export class CoreDomUtilsProvider {
|
|||
*
|
||||
* @param html HTML code.
|
||||
* @return List of file urls.
|
||||
* @deprecated since 3.8. Use CoreFilepoolProvider.extractDownloadableFilesFromHtml instead.
|
||||
*/
|
||||
extractDownloadableFilesFromHtml(html: string): string[] {
|
||||
let urls = [];
|
||||
this.logger.error('The function extractDownloadableFilesFromHtml has been moved to CoreFilepoolProvider.' +
|
||||
' Please use that function instead of this one.');
|
||||
|
||||
const urls = [];
|
||||
let elements;
|
||||
|
||||
const element = this.convertToElement(html);
|
||||
|
@ -285,9 +292,6 @@ export class CoreDomUtilsProvider {
|
|||
}
|
||||
}
|
||||
|
||||
// Now get other files from plugin file handlers.
|
||||
urls = urls.concat(this.pluginFileDelegate.getDownloadableFilesFromHTML(element));
|
||||
|
||||
return urls;
|
||||
}
|
||||
|
||||
|
@ -296,6 +300,7 @@ export class CoreDomUtilsProvider {
|
|||
*
|
||||
* @param html HTML code.
|
||||
* @return List of fake file objects with file URLs.
|
||||
* @deprecated since 3.8. Use CoreFilepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects instead.
|
||||
*/
|
||||
extractDownloadableFilesFromHtmlAsFakeFileObjects(html: string): any[] {
|
||||
const urls = this.extractDownloadableFilesFromHtml(html);
|
||||
|
|
|
@ -1,6 +1,10 @@
|
|||
This files describes API changes in the Moodle Mobile app,
|
||||
information provided here is intended especially for developers.
|
||||
|
||||
=== 3.8.0 ===
|
||||
|
||||
- CoreDomUtilsProvider.extractDownloadableFilesFromHtml and CoreDomUtilsProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects have been deprecated. Please use CoreFilepoolProvider.extractDownloadableFilesFromHtml and CoreFilepoolProvider.extractDownloadableFilesFromHtmlAsFakeFileObjects. We had to move them to prevent a circular dependency.
|
||||
|
||||
=== 3.7.1 ===
|
||||
|
||||
- CoreGroupsProvider.getActivityAllowedGroups and CoreGroupsProvider.getActivityAllowedGroupsIfEnabled now return the full response of core_group_get_activity_allowed_groups instead of just the groups.
|
||||
|
|
Loading…
Reference in New Issue