MOBILE-3644 glossary: Migrate services

main
Dani Palou 2021-04-08 16:30:00 +02:00
parent c9b99927c4
commit 010475b790
17 changed files with 3071 additions and 2 deletions

View File

@ -0,0 +1,69 @@
// (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 { APP_INITIALIZER, NgModule, Type } from '@angular/core';
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
import { CoreTagAreaDelegate } from '@features/tag/services/tag-area-delegate';
import { CoreCronDelegate } from '@services/cron';
import { CORE_SITE_SCHEMAS } from '@services/sites';
import { SITE_SCHEMA, OFFLINE_SITE_SCHEMA } from './services/database/glossary';
import { AddonModGlossaryProvider } from './services/glossary';
import { AddonModGlossaryHelperProvider } from './services/glossary-helper';
import { AddonModGlossaryOfflineProvider } from './services/glossary-offline';
import { AddonModGlossarySyncProvider } from './services/glossary-sync';
import { AddonModGlossaryEditLinkHandler } from './services/handlers/edit-link';
import { AddonModGlossaryEntryLinkHandler } from './services/handlers/entry-link';
import { AddonModGlossaryIndexLinkHandler } from './services/handlers/index-link';
import { AddonModGlossaryListLinkHandler } from './services/handlers/list-link';
import { AddonModGlossaryModuleHandler } from './services/handlers/module';
import { AddonModGlossaryPrefetchHandler } from './services/handlers/prefetch';
import { AddonModGlossarySyncCronHandler } from './services/handlers/sync-cron';
import { AddonModGlossaryTagAreaHandler } from './services/handlers/tag-area';
export const ADDON_MOD_GLOSSARY_SERVICES: Type<unknown>[] = [
AddonModGlossaryProvider,
AddonModGlossaryOfflineProvider,
AddonModGlossarySyncProvider,
AddonModGlossaryHelperProvider,
];
@NgModule({
imports: [
],
providers: [
{
provide: CORE_SITE_SCHEMAS,
useValue: [SITE_SCHEMA, OFFLINE_SITE_SCHEMA],
multi: true,
},
{
provide: APP_INITIALIZER,
multi: true,
deps: [],
useFactory: () => () => {
CoreCourseModuleDelegate.registerHandler(AddonModGlossaryModuleHandler.instance);
CoreCourseModulePrefetchDelegate.registerHandler(AddonModGlossaryPrefetchHandler.instance);
CoreCronDelegate.register(AddonModGlossarySyncCronHandler.instance);
CoreContentLinksDelegate.registerHandler(AddonModGlossaryIndexLinkHandler.instance);
CoreContentLinksDelegate.registerHandler(AddonModGlossaryListLinkHandler.instance);
CoreContentLinksDelegate.registerHandler(AddonModGlossaryEditLinkHandler.instance);
CoreContentLinksDelegate.registerHandler(AddonModGlossaryEntryLinkHandler.instance);
CoreTagAreaDelegate.registerHandler(AddonModGlossaryTagAreaHandler.instance);
},
},
],
})
export class AddonModGlossaryModule {}

View File

@ -0,0 +1,31 @@
{
"addentry": "Add a new entry",
"aliases": "Keyword(s)",
"attachment": "Attachment",
"browsemode": "Browse entries",
"byalphabet": "Alphabetically",
"byauthor": "Group by author",
"bycategory": "Group by category",
"bynewestfirst": "Newest first",
"byrecentlyupdated": "Recently updated",
"bysearch": "Search",
"cannoteditentry": "Cannot edit entry",
"casesensitive": "This entry is case sensitive",
"categories": "Categories",
"concept": "Concept",
"definition": "Definition",
"entriestobesynced": "Entries to be synced",
"entrypendingapproval": "This entry is pending approval.",
"entryusedynalink": "This entry should be automatically linked",
"errconceptalreadyexists": "This concept already exists. No duplicates allowed in this glossary.",
"errorloadingentries": "An error occurred while loading entries.",
"errorloadingentry": "An error occurred while loading the entry.",
"errorloadingglossary": "An error occurred while loading the glossary.",
"fillfields": "Concept and definition are mandatory fields.",
"fullmatch": "Match whole words only",
"linking": "Auto-linking",
"modulenameplural": "Glossaries",
"noentriesfound": "No entries were found.",
"searchquery": "Search query",
"tagarea_glossary_entries": "Glossary entries"
}

View File

@ -0,0 +1,121 @@
// (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 { CoreSiteSchema } from '@services/sites';
/**
* Database variables for AddonModGlossaryProvider.
*/
export const ENTRIES_TABLE_NAME = 'addon_mod_glossary_entry_glossaryid';
export const SITE_SCHEMA: CoreSiteSchema = {
name: 'AddonModGlossaryProvider',
version: 1,
tables: [
{
name: ENTRIES_TABLE_NAME,
columns: [
{
name: 'entryid',
type: 'INTEGER',
primaryKey: true,
},
{
name: 'glossaryid',
type: 'INTEGER',
},
{
name: 'pagefrom',
type: 'INTEGER',
},
],
},
],
};
/**
* Database variables for AddonModGlossaryOfflineProvider.
*/
export const OFFLINE_ENTRIES_TABLE_NAME = 'addon_mod_glossary_entrues';
export const OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
name: 'AddonModGlossaryOfflineProvider',
version: 1,
tables: [
{
name: OFFLINE_ENTRIES_TABLE_NAME,
columns: [
{
name: 'glossaryid',
type: 'INTEGER',
},
{
name: 'courseid',
type: 'INTEGER',
},
{
name: 'concept',
type: 'TEXT',
},
{
name: 'definition',
type: 'TEXT',
},
{
name: 'definitionformat',
type: 'TEXT',
},
{
name: 'userid',
type: 'INTEGER',
},
{
name: 'timecreated',
type: 'INTEGER',
},
{
name: 'options',
type: 'TEXT',
},
{
name: 'attachments',
type: 'TEXT',
},
],
primaryKeys: ['glossaryid', 'concept', 'timecreated'],
},
],
};
/**
* Glossary entry to get glossaryid from entryid.
*/
export type AddonModGlossaryEntryDBRecord = {
entryid: number;
glossaryid: number;
pagefrom: number;
};
/**
* Glossary offline entry.
*/
export type AddonModGlossaryOfflineEntryDBRecord = {
glossaryid: number;
courseid: number;
concept: string;
definition: string;
definitionformat: string;
userid: number;
timecreated: number;
options: string;
attachments: string;
};

View File

@ -0,0 +1,112 @@
// (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 { FileEntry } from '@ionic-native/file/ngx';
import { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { CoreFile } from '@services/file';
import { CoreUtils } from '@services/utils/utils';
import { AddonModGlossaryOffline } from './glossary-offline';
import { AddonModGlossaryNewEntry, AddonModGlossaryNewEntryWithFiles } from './glossary';
import { makeSingleton } from '@singletons';
import { CoreWSExternalFile } from '@services/ws';
/**
* Helper to gather some common functions for glossary.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryHelperProvider {
/**
* Delete stored attachment files for a new entry.
*
* @param glossaryId Glossary ID.
* @param entryName The name of the entry.
* @param timeCreated The time the entry was created.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when deleted.
*/
async deleteStoredFiles(glossaryId: number, entryName: string, timeCreated: number, siteId?: string): Promise<void> {
const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
await CoreUtils.ignoreErrors(CoreFile.removeDir(folderPath));
}
/**
* Get a list of stored attachment files for a new entry. See AddonModGlossaryHelperProvider#storeFiles.
*
* @param glossaryId lossary ID.
* @param entryName The name of the entry.
* @param timeCreated The time the entry was created.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the files.
*/
async getStoredFiles(glossaryId: number, entryName: string, timeCreated: number, siteId?: string): Promise<FileEntry[]> {
const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
return CoreFileUploader.getStoredFiles(folderPath);
}
/**
* Check if the data of an entry has changed.
*
* @param entry Current data.
* @param files Files attached.
* @param original Original content.
* @return True if data has changed, false otherwise.
*/
hasEntryDataChanged(
entry: AddonModGlossaryNewEntry,
files: (CoreWSExternalFile | FileEntry)[],
original?: AddonModGlossaryNewEntryWithFiles,
): boolean {
if (!original || typeof original.concept == 'undefined') {
// There is no original data.
return !!(entry.definition || entry.concept || files.length > 0);
}
if (original.definition != entry.definition || original.concept != entry.concept) {
return true;
}
return CoreFileUploader.areFileListDifferent(files, original.files);
}
/**
* Given a list of files (either online files or local files), store the local files in a local folder
* to be submitted later.
*
* @param glossaryId Glossary ID.
* @param entryName The name of the entry.
* @param timeCreated The time the entry was created.
* @param files List of files.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if success, rejected otherwise.
*/
async storeFiles(
glossaryId: number,
entryName: string,
timeCreated: number,
files: (CoreWSExternalFile | FileEntry)[],
siteId?: string,
): Promise<CoreFileUploaderStoreFilesResult> {
// Get the folder where to store the files.
const folderPath = await AddonModGlossaryOffline.getEntryFolder(glossaryId, entryName, timeCreated, siteId);
return CoreFileUploader.storeFilesToUpload(folderPath, files);
}
}
export const AddonModGlossaryHelper = makeSingleton(AddonModGlossaryHelperProvider);

View File

@ -0,0 +1,258 @@
// (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 { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary';
import { AddonModGlossaryDiscardedEntry, AddonModGlossaryEntryOption } from './glossary';
/**
* Service to handle offline glossary.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryOfflineProvider {
/**
* Delete a new entry.
*
* @param glossaryId Glossary ID.
* @param concept Glossary entry concept.
* @param timeCreated The time the entry was created.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if deleted, rejected if failure.
*/
async deleteNewEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
const site = await CoreSites.getSite(siteId);
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
glossaryid: glossaryId,
concept: concept,
timecreated: timeCreated,
};
await site.getDb().deleteRecords(OFFLINE_ENTRIES_TABLE_NAME, conditions);
}
/**
* Get all the stored new entries from all the glossaries.
*
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with entries.
*/
async getAllNewEntries(siteId?: string): Promise<AddonModGlossaryOfflineEntry[]> {
const site = await CoreSites.getSite(siteId);
const records = await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME);
return records.map(record => this.parseRecord(record));
}
/**
* Get a stored new entry.
*
* @param glossaryId Glossary ID.
* @param concept Glossary entry concept.
* @param timeCreated The time the entry was created.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with entry.
*/
async getNewEntry(
glossaryId: number,
concept: string,
timeCreated: number,
siteId?: string,
): Promise<AddonModGlossaryOfflineEntry> {
const site = await CoreSites.getSite(siteId);
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
glossaryid: glossaryId,
concept: concept,
timecreated: timeCreated,
};
const record = await site.getDb().getRecord<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
return this.parseRecord(record);
}
/**
* Get all the stored add entry data from a certain glossary.
*
* @param glossaryId Glossary ID.
* @param siteId Site ID. If not defined, current site.
* @param userId User the entries belong to. If not defined, current user in site.
* @return Promise resolved with entries.
*/
async getGlossaryNewEntries(glossaryId: number, siteId?: string, userId?: number): Promise<AddonModGlossaryOfflineEntry[]> {
const site = await CoreSites.getSite(siteId);
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
glossaryid: glossaryId,
userid: userId || site.getUserId(),
};
const records = await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
return records.map(record => this.parseRecord(record));
}
/**
* Check if a concept is used offline.
*
* @param glossaryId Glossary ID.
* @param concept Concept to check.
* @param timeCreated Time of the entry we are editing.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with true if concept is found, false otherwise.
*/
async isConceptUsed(glossaryId: number, concept: string, timeCreated?: number, siteId?: string): Promise<boolean> {
try {
const site = await CoreSites.getSite(siteId);
const conditions: Partial<AddonModGlossaryOfflineEntryDBRecord> = {
glossaryid: glossaryId,
concept: concept,
};
const entries =
await site.getDb().getRecords<AddonModGlossaryOfflineEntryDBRecord>(OFFLINE_ENTRIES_TABLE_NAME, conditions);
if (!entries.length) {
return false;
}
if (entries.length > 1 || !timeCreated) {
return true;
}
// If there's only one entry, check that is not the one we are editing.
return CoreUtils.promiseFails(this.getNewEntry(glossaryId, concept, timeCreated, siteId));
} catch {
// No offline data found, return false.
return false;
}
}
/**
* Save a new entry to be sent later.
*
* @param glossaryId Glossary ID.
* @param concept Glossary entry concept.
* @param definition Glossary entry concept definition.
* @param courseId Course ID of the glossary.
* @param options Options for the entry.
* @param attachments Result of CoreFileUploaderProvider#storeFilesToUpload for attachments.
* @param timeCreated The time the entry was created. If not defined, current time.
* @param siteId Site ID. If not defined, current site.
* @param userId User the entry belong to. If not defined, current user in site.
* @param discardEntry The entry provided will be discarded if found.
* @return Promise resolved if stored, rejected if failure.
*/
async addNewEntry(
glossaryId: number,
concept: string,
definition: string,
courseId: number,
options?: Record<string, AddonModGlossaryEntryOption>,
attachments?: CoreFileUploaderStoreFilesResult,
timeCreated?: number,
siteId?: string,
userId?: number,
discardEntry?: AddonModGlossaryDiscardedEntry,
): Promise<false> {
const site = await CoreSites.getSite(siteId);
const entry: AddonModGlossaryOfflineEntryDBRecord = {
glossaryid: glossaryId,
courseid: courseId,
concept: concept,
definition: definition,
definitionformat: 'html',
options: JSON.stringify(options || {}),
attachments: JSON.stringify(attachments),
userid: userId || site.getUserId(),
timecreated: timeCreated || Date.now(),
};
// If editing an offline entry, delete previous first.
if (discardEntry) {
await this.deleteNewEntry(glossaryId, discardEntry.concept, discardEntry.timecreated, site.getId());
}
await site.getDb().insertRecord(OFFLINE_ENTRIES_TABLE_NAME, entry);
return false;
}
/**
* Get the path to the folder where to store files for offline attachments in a glossary.
*
* @param glossaryId Glossary ID.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the path.
*/
async getGlossaryFolder(glossaryId: number, siteId?: string): Promise<string> {
const site = await CoreSites.getSite(siteId);
const siteFolderPath = CoreFile.getSiteFolder(site.getId());
const folderPath = 'offlineglossary/' + glossaryId;
return CoreTextUtils.concatenatePaths(siteFolderPath, folderPath);
}
/**
* Get the path to the folder where to store files for a new offline entry.
*
* @param glossaryId Glossary ID.
* @param concept The name of the entry.
* @param timeCreated Time to allow duplicated entries.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with the path.
*/
async getEntryFolder(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<string> {
const folderPath = await this.getGlossaryFolder(glossaryId, siteId);
return CoreTextUtils.concatenatePaths(folderPath, 'newentry_' + concept + '_' + timeCreated);
}
/**
* Parse "options" and "attachments" columns of a fetched record.
*
* @param records Record object
* @return Record object with columns parsed.
*/
protected parseRecord(record: AddonModGlossaryOfflineEntryDBRecord): AddonModGlossaryOfflineEntry {
return Object.assign(record, {
options: <Record<string, AddonModGlossaryEntryOption>> CoreTextUtils.parseJSON(record.options),
attachments: record.attachments ?
<CoreFileUploaderStoreFilesResult> CoreTextUtils.parseJSON(record.attachments) : undefined,
});
}
}
export const AddonModGlossaryOffline = makeSingleton(AddonModGlossaryOfflineProvider);
/**
* Glossary offline entry with parsed data.
*/
export type AddonModGlossaryOfflineEntry = Omit<AddonModGlossaryOfflineEntryDBRecord, 'options'|'attachments'> & {
options: Record<string, AddonModGlossaryEntryOption>;
attachments?: CoreFileUploaderStoreFilesResult;
};

View File

@ -0,0 +1,368 @@
// (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 { ContextLevel } from '@/core/constants';
import { Injectable } from '@angular/core';
import { FileEntry } from '@ionic-native/file/ngx';
import { CoreSyncBlockedError } from '@classes/base-sync';
import { CoreNetworkError } from '@classes/errors/network-error';
import { CoreCourseActivitySyncBaseProvider } from '@features/course/classes/activity-sync';
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
import { CoreRatingSync } from '@features/rating/services/rating-sync';
import { CoreApp } from '@services/app';
import { CoreSites } from '@services/sites';
import { CoreSync } from '@services/sync';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
import { CoreEvents } from '@singletons/events';
import { AddonModGlossary, AddonModGlossaryProvider } from './glossary';
import { AddonModGlossaryHelper } from './glossary-helper';
import { AddonModGlossaryOffline, AddonModGlossaryOfflineEntry } from './glossary-offline';
import { CoreFileUploader } from '@features/fileuploader/services/fileuploader';
/**
* Service to sync glossaries.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossarySyncProvider extends CoreCourseActivitySyncBaseProvider<AddonModGlossarySyncResult> {
static readonly AUTO_SYNCED = 'addon_mod_glossary_autom_synced';
protected componentTranslatableString = 'glossary';
constructor() {
super('AddonModGlossarySyncProvider');
}
/**
* Try to synchronize all the glossaries in a certain site or in all sites.
*
* @param siteId Site ID to sync. If not defined, sync all sites.
* @param force Wether to force sync not depending on last execution.
* @return Promise resolved if sync is successful, rejected if sync fails.
*/
syncAllGlossaries(siteId?: string, force?: boolean): Promise<void> {
return this.syncOnSites('all glossaries', this.syncAllGlossariesFunc.bind(this, !!force), siteId);
}
/**
* Sync all glossaries on a site.
*
* @param force Wether to force sync not depending on last execution.
* @param siteId Site ID to sync.
* @return Promise resolved if sync is successful, rejected if sync fails.
*/
protected async syncAllGlossariesFunc(force: boolean, siteId: string): Promise<void> {
siteId = siteId || CoreSites.getCurrentSiteId();
await Promise.all([
this.syncAllGlossariesEntries(force, siteId),
this.syncRatings(undefined, force, siteId),
]);
}
/**
* Sync entried of all glossaries on a site.
*
* @param force Wether to force sync not depending on last execution.
* @param siteId Site ID to sync.
* @return Promise resolved if sync is successful, rejected if sync fails.
*/
protected async syncAllGlossariesEntries(force: boolean, siteId: string): Promise<void> {
const entries = await AddonModGlossaryOffline.getAllNewEntries(siteId);
// Do not sync same glossary twice.
const treated: Record<number, boolean> = {};
await Promise.all(entries.map(async (entry) => {
if (treated[entry.glossaryid]) {
return;
}
treated[entry.glossaryid] = true;
const result = force ?
await this.syncGlossaryEntries(entry.glossaryid, entry.userid, siteId) :
await this.syncGlossaryEntriesIfNeeded(entry.glossaryid, entry.userid, siteId);
if (result?.updated) {
// Sync successful, send event.
CoreEvents.trigger(AddonModGlossarySyncProvider.AUTO_SYNCED, {
glossaryId: entry.glossaryid,
userId: entry.userid,
warnings: result.warnings,
}, siteId);
}
}));
}
/**
* Sync a glossary only if a certain time has passed since the last time.
*
* @param glossaryId Glossary ID.
* @param userId User the entry belong to.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when the glossary is synced or if it doesn't need to be synced.
*/
async syncGlossaryEntriesIfNeeded(
glossaryId: number,
userId: number,
siteId?: string,
): Promise<AddonModGlossarySyncResult | undefined> {
siteId = siteId || CoreSites.getCurrentSiteId();
const syncId = this.getGlossarySyncId(glossaryId, userId);
const needed = await this.isSyncNeeded(syncId, siteId);
if (needed) {
return this.syncGlossaryEntries(glossaryId, userId, siteId);
}
}
/**
* Synchronize all offline entries of a glossary.
*
* @param glossaryId Glossary ID to be synced.
* @param userId User the entries belong to.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if sync is successful, rejected otherwise.
*/
syncGlossaryEntries(glossaryId: number, userId?: number, siteId?: string): Promise<AddonModGlossarySyncResult> {
userId = userId || CoreSites.getCurrentSiteUserId();
siteId = siteId || CoreSites.getCurrentSiteId();
const syncId = this.getGlossarySyncId(glossaryId, userId);
if (this.isSyncing(syncId, siteId)) {
// There's already a sync ongoing for this glossary, return the promise.
return this.getOngoingSync(syncId, siteId)!;
}
// Verify that glossary isn't blocked.
if (CoreSync.isBlocked(AddonModGlossaryProvider.COMPONENT, syncId, siteId)) {
this.logger.debug('Cannot sync glossary ' + glossaryId + ' because it is blocked.');
throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
}
this.logger.debug('Try to sync glossary ' + glossaryId + ' for user ' + userId);
const syncPromise = this.performSyncGlossaryEntries(glossaryId, userId, siteId);
return this.addOngoingSync(syncId, syncPromise, siteId);
}
protected async performSyncGlossaryEntries(
glossaryId: number,
userId: number,
siteId: string,
): Promise<AddonModGlossarySyncResult> {
const result: AddonModGlossarySyncResult = {
warnings: [],
updated: false,
};
const syncId = this.getGlossarySyncId(glossaryId, userId);
// Sync offline logs.
await CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModGlossaryProvider.COMPONENT, glossaryId, siteId));
// Get offline responses to be sent.
const entries = await CoreUtils.ignoreErrors(
AddonModGlossaryOffline.getGlossaryNewEntries(glossaryId, siteId, userId),
<AddonModGlossaryOfflineEntry[]> [],
);
if (!entries.length) {
// Nothing to sync.
await CoreUtils.ignoreErrors(this.setSyncTime(syncId, siteId));
return result;
} else if (!CoreApp.isOnline()) {
// Cannot sync in offline.
throw new CoreNetworkError();
}
let courseId: number | undefined;
await Promise.all(entries.map(async (data) => {
courseId = courseId || data.courseid;
try {
// First of all upload the attachments (if any).
const itemId = await this.uploadAttachments(glossaryId, data, siteId);
// Now try to add the entry.
await AddonModGlossary.addEntryOnline(glossaryId, data.concept, data.definition, data.options, itemId, siteId);
result.updated = true;
await this.deleteAddEntry(glossaryId, data.concept, data.timecreated, siteId);
} catch (error) {
if (!CoreUtils.isWebServiceError(error)) {
// Couldn't connect to server, reject.
throw error;
}
// The WebService has thrown an error, this means that responses cannot be submitted. Delete them.
result.updated = true;
await this.deleteAddEntry(glossaryId, data.concept, data.timecreated, siteId);
// Responses deleted, add a warning.
result.warnings.push(Translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: data.concept,
error: CoreTextUtils.getErrorMessageFromError(error),
}));
}
}));
if (result.updated && courseId) {
// Data has been sent to server. Now invalidate the WS calls.
try {
const glossary = await AddonModGlossary.getGlossaryById(courseId, glossaryId);
await AddonModGlossary.invalidateGlossaryEntries(glossary, true);
} catch {
// Ignore errors.
}
}
// Sync finished, set sync time.
await CoreUtils.ignoreErrors(this.setSyncTime(syncId, siteId));
return result;
}
/**
* Synchronize offline ratings.
*
* @param cmId Course module to be synced. If not defined, sync all glossaries.
* @param force Wether to force sync not depending on last execution.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved if sync is successful, rejected otherwise.
*/
async syncRatings(cmId?: number, force?: boolean, siteId?: string): Promise<AddonModGlossarySyncResult> {
siteId = siteId || CoreSites.getCurrentSiteId();
const results = await CoreRatingSync.syncRatings('mod_glossary', 'entry', ContextLevel.MODULE, cmId, 0, force, siteId);
let updated = false;
const warnings: string[] = [];
await CoreUtils.allPromises(results.map(async (result) => {
if (result.updated.length) {
updated = true;
// Invalidate entry of updated ratings.
await Promise.all(result.updated.map((itemId) => AddonModGlossary.invalidateEntry(itemId, siteId)));
}
if (result.warnings.length) {
const glossary = await AddonModGlossary.getGlossary(result.itemSet.courseId, result.itemSet.instanceId, { siteId });
result.warnings.forEach((warning) => {
warnings.push(Translate.instant('core.warningofflinedatadeleted', {
component: this.componentTranslate,
name: glossary.name,
error: warning,
}));
});
}
}));
return { updated, warnings };
}
/**
* Delete a new entry.
*
* @param glossaryId Glossary ID.
* @param concept Glossary entry concept.
* @param timeCreated Time to allow duplicated entries.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved when deleted.
*/
protected async deleteAddEntry(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<void> {
await Promise.all([
AddonModGlossaryOffline.deleteNewEntry(glossaryId, concept, timeCreated, siteId),
AddonModGlossaryHelper.deleteStoredFiles(glossaryId, concept, timeCreated, siteId),
]);
}
/**
* Upload attachments of an offline entry.
*
* @param glossaryId Glossary ID.
* @param entry Offline entry.
* @param siteId Site ID. If not defined, current site.
* @return Promise resolved with draftid if uploaded, resolved with 0 if nothing to upload.
*/
protected async uploadAttachments(glossaryId: number, entry: AddonModGlossaryOfflineEntry, siteId?: string): Promise<number> {
if (!entry.attachments) {
// No attachments.
return 0;
}
// Has some attachments to sync.
let files: (CoreWSExternalFile | FileEntry)[] = entry.attachments.online || [];
if (entry.attachments.offline) {
// Has offline files.
const storedFiles = await CoreUtils.ignoreErrors(
AddonModGlossaryHelper.getStoredFiles(glossaryId, entry.concept, entry.timecreated, siteId),
[], // Folder not found, no files to add.
);
files = files.concat(storedFiles);
}
return CoreFileUploader.uploadOrReuploadFiles(files, AddonModGlossaryProvider.COMPONENT, glossaryId, siteId);
}
/**
* Get the ID of a glossary sync.
*
* @param glossaryId Glossary ID.
* @param userId User the entries belong to.. If not defined, current user.
* @return Sync ID.
*/
protected getGlossarySyncId(glossaryId: number, userId?: number): string {
userId = userId || CoreSites.getCurrentSiteUserId();
return 'glossary#' + glossaryId + '#' + userId;
}
}
export const AddonModGlossarySync = makeSingleton(AddonModGlossarySyncProvider);
/**
* Data returned by a glossary sync.
*/
export type AddonModGlossarySyncResult = {
warnings: string[]; // List of warnings.
updated: boolean; // Whether some data was sent to the server or offline data was updated.
};
/**
* Data passed to AUTO_SYNCED event.
*/
export type AddonModGlossaryAutoSyncData = {
glossaryId: number;
userId: number;
warnings: string[];
};

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,78 @@
// (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 { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreCourse } from '@features/course/services/course';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
import { makeSingleton } from '@singletons';
import { AddonModGlossaryModuleHandlerService } from './module';
/**
* Content links handler for glossary new entry.
* Match mod/glossary/edit.php?cmid=6 with a valid data.
* Currently it only supports new entry.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryEditLinkHandlerService extends CoreContentLinksHandlerBase {
name = 'AddonModGlossaryEditLinkHandler';
featureName = 'CoreCourseModuleDelegate_AddonModGlossary';
pattern = /\/mod\/glossary\/edit\.php.*([?&](cmid)=\d+)/;
/**
* @inheritdoc
*/
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
return [{
action: async (siteId: string) => {
const modal = await CoreDomUtils.showModalLoading();
const cmId = Number(params.cmid);
try {
const module = await CoreCourse.getModuleBasicInfo(cmId, siteId);
await CoreNavigator.navigateToSitePath(
AddonModGlossaryModuleHandlerService.PAGE_NAME + '/edit/0',
{
params: {
cmId: module.id,
courseId: module.course,
},
siteId,
},
);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingglossary', true);
} finally {
// Just in case. In fact we need to dismiss the modal before showing a toast or error message.
modal.dismiss();
}
},
}];
}
/**
* @inheritdoc
*/
async isEnabled(siteId: string, url: string, params: Record<string, string>): Promise<boolean> {
return typeof params.cmid != 'undefined';
}
}
export const AddonModGlossaryEditLinkHandler = makeSingleton(AddonModGlossaryEditLinkHandlerService);

View File

@ -0,0 +1,75 @@
// (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 { CoreContentLinksHandlerBase } from '@features/contentlinks/classes/base-handler';
import { CoreContentLinksAction } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreCourse } from '@features/course/services/course';
import { CoreNavigator } from '@services/navigator';
import { CoreDomUtils } from '@services/utils/dom';
import { makeSingleton } from '@singletons';
import { AddonModGlossary } from '../glossary';
import { AddonModGlossaryModuleHandlerService } from './module';
/**
* Handler to treat links to glossary entries.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryEntryLinkHandlerService extends CoreContentLinksHandlerBase {
name = 'AddonModGlossaryEntryLinkHandler';
featureName = 'CoreCourseModuleDelegate_AddonModGlossary';
pattern = /\/mod\/glossary\/(showentry|view)\.php.*([&?](eid|g|mode|hook)=\d+)/;
/**
* @inheritdoc
*/
getActions(siteIds: string[], url: string, params: Record<string, string>): CoreContentLinksAction[] {
return [{
action: async (siteId: string) => {
const modal = await CoreDomUtils.showModalLoading();
try {
const entryId = params.mode == 'entry' ? Number(params.hook) : Number(params.eid);
const response = await AddonModGlossary.getEntry(entryId, { siteId });
const module = await CoreCourse.getModuleBasicInfoByInstance(
response.entry.glossaryid,
'glossary',
siteId,
);
await CoreNavigator.navigateToSitePath(
AddonModGlossaryModuleHandlerService.PAGE_NAME + `/entry/${entryId}`,
{
params: {
cmId: module.id,
courseId: module.course,
},
siteId,
},
);
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true);
} finally {
modal.dismiss();
}
},
}];
}
}
export const AddonModGlossaryEntryLinkHandler = makeSingleton(AddonModGlossaryEntryLinkHandlerService);

View File

@ -0,0 +1,33 @@
// (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 { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
import { makeSingleton } from '@singletons';
/**
* Handler to treat links to glossary index.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
name = 'AddonModGlossaryIndexLinkHandler';
constructor() {
super('AddonModGlossary', 'glossary', 'g');
}
}
export const AddonModGlossaryIndexLinkHandler = makeSingleton(AddonModGlossaryIndexLinkHandlerService);

View File

@ -0,0 +1,33 @@
// (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 { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler';
import { makeSingleton } from '@singletons';
/**
* Handler to treat links to glossary list page.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryListLinkHandlerService extends CoreContentLinksModuleListHandler {
name = 'AddonModGlossaryListLinkHandler';
constructor() {
super('AddonModGlossary', 'glossary');
}
}
export const AddonModGlossaryListLinkHandler = makeSingleton(AddonModGlossaryListLinkHandlerService);

View File

@ -0,0 +1,92 @@
// (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 { CoreConstants } from '@/core/constants';
import { Injectable, Type } from '@angular/core';
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
import { CoreCourseModule } from '@features/course/services/course-helper';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
import { makeSingleton } from '@singletons';
import { AddonModGlossaryIndexComponent } from '../../components/index/index';
/**
* Handler to support glossary modules.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryModuleHandlerService implements CoreCourseModuleHandler {
static readonly PAGE_NAME = 'mod_glossary';
name = 'AddonModGlossary';
modName = 'glossary';
supportedFeatures = {
[CoreConstants.FEATURE_GROUPS]: false,
[CoreConstants.FEATURE_GROUPINGS]: false,
[CoreConstants.FEATURE_MOD_INTRO]: true,
[CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
[CoreConstants.FEATURE_COMPLETION_HAS_RULES]: true,
[CoreConstants.FEATURE_GRADE_HAS_GRADE]: true,
[CoreConstants.FEATURE_GRADE_OUTCOMES]: true,
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
[CoreConstants.FEATURE_RATE]: true,
[CoreConstants.FEATURE_PLAGIARISM]: true,
};
/**
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* @inheritdoc
*/
getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData {
return {
icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
title: module.name,
class: 'addon-mod_glossary-handler',
showDownloadButton: true,
action: (event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions) => {
options = options || {};
options.params = options.params || {};
Object.assign(options.params, { module });
const routeParams = '/' + courseId + '/' + module.id;
CoreNavigator.navigateToSitePath(AddonModGlossaryModuleHandlerService.PAGE_NAME + routeParams, options);
},
};
}
/**
* @inheritdoc
*/
async getMainComponent(): Promise<Type<unknown>> {
return AddonModGlossaryIndexComponent;
}
/**
* @inheritdoc
*/
displayRefresherInSingleActivity(): boolean {
return false;
}
}
export const AddonModGlossaryModuleHandler = makeSingleton(AddonModGlossaryModuleHandlerService);

View File

@ -0,0 +1,235 @@
// (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 { CoreComments } from '@features/comments/services/comments';
import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
import { CoreUser } from '@features/user/services/user';
import { CoreFilepool } from '@services/filepool';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreWSExternalFile } from '@services/ws';
import { makeSingleton } from '@singletons';
import { AddonModGlossary, AddonModGlossaryEntry, AddonModGlossaryGlossary, AddonModGlossaryProvider } from '../glossary';
import { AddonModGlossarySync, AddonModGlossarySyncResult } from '../glossary-sync';
/**
* Handler to prefetch forums.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase {
name = 'AddonModGlossary';
modName = 'glossary';
component = AddonModGlossaryProvider.COMPONENT;
updatesNames = /^configuration$|^.*files$|^entries$/;
/**
* @inheritdoc
*/
async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSExternalFile[]> {
try {
const glossary = await AddonModGlossary.getGlossary(courseId, module.id);
const entries = await AddonModGlossary.fetchAllEntries(
AddonModGlossary.getEntriesByLetter.bind(AddonModGlossary.instance, glossary.id, 'ALL'),
{
cmId: module.id,
},
);
return this.getFilesFromGlossaryAndEntries(module, glossary, entries);
} catch {
// Glossary not found, return empty list.
return [];
}
}
/**
* Get the list of downloadable files. It includes entry embedded files.
*
* @param module Module to get the files.
* @param glossary Glossary
* @param entries Entries of the Glossary.
* @return List of Files.
*/
protected getFilesFromGlossaryAndEntries(
module: CoreCourseAnyModuleData,
glossary: AddonModGlossaryGlossary,
entries: AddonModGlossaryEntry[],
): CoreWSExternalFile[] {
let files = this.getIntroFilesFromInstance(module, glossary);
const getInlineFiles = CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.2');
// Get entries files.
entries.forEach((entry) => {
files = files.concat(entry.attachments || []);
if (getInlineFiles && entry.definitioninlinefiles && entry.definitioninlinefiles.length) {
files = files.concat(entry.definitioninlinefiles);
} else if (entry.definition && !getInlineFiles) {
files = files.concat(CoreFilepool.extractDownloadableFilesFromHtmlAsFakeFileObjects(entry.definition));
}
});
return files;
}
/**
* @inheritdoc
*/
invalidateContent(moduleId: number, courseId: number): Promise<void> {
return AddonModGlossary.invalidateContent(moduleId, courseId);
}
/**
* @inheritdoc
*/
prefetch(module: CoreCourseAnyModuleData, courseId?: number): Promise<void> {
return this.prefetchPackage(module, courseId, this.prefetchGlossary.bind(this, module, courseId));
}
/**
* Prefetch a glossary.
*
* @param module The module object returned by WS.
* @param courseId Course ID the module belongs to.
* @param siteId Site ID.
* @return Promise resolved when done.
*/
protected async prefetchGlossary(module: CoreCourseAnyModuleData, courseId: number, siteId: string): Promise<void> {
siteId = siteId || CoreSites.getCurrentSiteId();
const options = {
cmId: module.id,
readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
siteId,
};
// Prefetch the glossary data.
const glossary = await AddonModGlossary.getGlossary(courseId, module.id, { siteId });
const promises: Promise<unknown>[] = [];
glossary.browsemodes.forEach((mode) => {
switch (mode) {
case 'letter': // Always done. Look bellow.
break;
case 'cat':
promises.push(AddonModGlossary.fetchAllEntries(
AddonModGlossary.getEntriesByCategory.bind(
AddonModGlossary.instance,
glossary.id,
AddonModGlossaryProvider.SHOW_ALL_CATEGORIES,
),
options,
));
break;
case 'date':
promises.push(AddonModGlossary.fetchAllEntries(
AddonModGlossary.getEntriesByDate.bind(
AddonModGlossary.instance,
glossary.id,
'CREATION',
'DESC',
),
options,
));
promises.push(AddonModGlossary.fetchAllEntries(
AddonModGlossary.getEntriesByDate.bind(
AddonModGlossary.instance,
glossary.id,
'UPDATE',
'DESC',
),
options,
));
break;
case 'author':
promises.push(AddonModGlossary.fetchAllEntries(
AddonModGlossary.getEntriesByAuthor.bind(
AddonModGlossary.instance,
glossary.id,
'ALL',
'LASTNAME',
'ASC',
),
options,
));
break;
default:
}
});
// Fetch all entries to get information from.
promises.push(AddonModGlossary.fetchAllEntries(
AddonModGlossary.getEntriesByLetter.bind(AddonModGlossary.instance, glossary.id, 'ALL'),
options,
).then((entries) => {
const promises: Promise<unknown>[] = [];
const commentsEnabled = !CoreComments.areCommentsDisabledInSite();
entries.forEach((entry) => {
// Don't fetch individual entries, it's too many WS calls.
if (glossary.allowcomments && commentsEnabled) {
promises.push(CoreComments.getComments(
'module',
glossary.coursemodule,
'mod_glossary',
entry.id,
'glossary_entry',
0,
siteId,
));
}
});
const files = this.getFilesFromGlossaryAndEntries(module, glossary, entries);
promises.push(CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id));
// Prefetch user avatars.
promises.push(CoreUser.prefetchUserAvatars(entries, 'userpictureurl', siteId));
return Promise.all(promises);
}));
// Get all categories.
promises.push(AddonModGlossary.getAllCategories(glossary.id, options));
// Prefetch data for link handlers.
promises.push(CoreCourse.getModuleBasicInfo(module.id, siteId));
promises.push(CoreCourse.getModuleBasicInfoByInstance(glossary.id, 'glossary', siteId));
await Promise.all(promises);
}
/**
* @inheritdoc
*/
async sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise<AddonModGlossarySyncResult> {
const results = await Promise.all([
AddonModGlossarySync.syncGlossaryEntries(module.instance!, undefined, siteId),
AddonModGlossarySync.syncRatings(module.id, undefined, siteId),
]);
return {
updated: results[0].updated || results[1].updated,
warnings: results[0].warnings.concat(results[1].warnings),
};
}
}
export const AddonModGlossaryPrefetchHandler = makeSingleton(AddonModGlossaryPrefetchHandlerService);

View File

@ -0,0 +1,44 @@
// (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 { CoreCronHandler } from '@services/cron';
import { makeSingleton } from '@singletons';
import { AddonModGlossarySync } from '../glossary-sync';
/**
* Synchronization cron handler.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossarySyncCronHandlerService implements CoreCronHandler {
name = 'AddonModGlossarySyncCronHandler';
/**
* @inheritdoc
*/
execute(siteId?: string, force?: boolean): Promise<void> {
return AddonModGlossarySync.syncAllGlossaries(siteId, force);
}
/**
* @inheritdoc
*/
getInterval(): number {
return AddonModGlossarySync.syncInterval;
}
}
export const AddonModGlossarySyncCronHandler = makeSingleton(AddonModGlossarySyncCronHandlerService);

View File

@ -0,0 +1,53 @@
// (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, Type } from '@angular/core';
import { CoreTagFeedComponent } from '@features/tag/components/feed/feed';
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper';
import { makeSingleton } from '@singletons';
/**
* Handler to support tags.
*/
@Injectable({ providedIn: 'root' })
export class AddonModGlossaryTagAreaHandlerService implements CoreTagAreaHandler {
name = 'AddonModGlossaryTagAreaHandler';
type = 'mod_glossary/glossary_entries';
/**
* @inheritdoc
*/
async isEnabled(): Promise<boolean> {
return true;
}
/**
* @inheritdoc
*/
parseContent(content: string): CoreTagFeedElement[] {
return CoreTagHelper.parseFeedContent(content);
}
/**
* @inheritdoc
*/
getComponent(): Type<unknown> {
return CoreTagFeedComponent;
}
}
export const AddonModGlossaryTagAreaHandler = makeSingleton(AddonModGlossaryTagAreaHandlerService);

View File

@ -32,6 +32,7 @@ import { AddonModSurveyModule } from './survey/survey.module';
import { AddonModScormModule } from './scorm/scorm.module';
import { AddonModChoiceModule } from './choice/choice.module';
import { AddonModWikiModule } from './wiki/wiki.module';
import { AddonModGlossaryModule } from './glossary/glossary.module';
@NgModule({
imports: [
@ -53,6 +54,7 @@ import { AddonModWikiModule } from './wiki/wiki.module';
AddonModScormModule,
AddonModChoiceModule,
AddonModWikiModule,
AddonModGlossaryModule,
],
})
export class AddonModModule { }

View File

@ -130,7 +130,7 @@ import { ADDON_MOD_DATA_SERVICES } from '@addons/mod/data/data.module';
// @todo import { ADDON_MOD_FEEDBACK_SERVICES } from '@addons/mod/feedback/feedback.module';
import { ADDON_MOD_FOLDER_SERVICES } from '@addons/mod/folder/folder.module';
import { ADDON_MOD_FORUM_SERVICES } from '@addons/mod/forum/forum.module';
// @todo import { ADDON_MOD_GLOSSARY_SERVICES } from '@addons/mod/glossary/glossary.module';
import { ADDON_MOD_GLOSSARY_SERVICES } from '@addons/mod/glossary/glossary.module';
import { ADDON_MOD_H5P_ACTIVITY_SERVICES } from '@addons/mod/h5pactivity/h5pactivity.module';
import { ADDON_MOD_IMSCP_SERVICES } from '@addons/mod/imscp/imscp.module';
import { ADDON_MOD_LESSON_SERVICES } from '@addons/mod/lesson/lesson.module';
@ -296,7 +296,7 @@ export class CoreCompileProvider {
// @todo ...ADDON_MOD_FEEDBACK_SERVICES,
...ADDON_MOD_FOLDER_SERVICES,
...ADDON_MOD_FORUM_SERVICES,
// @todo ...ADDON_MOD_GLOSSARY_SERVICES,
...ADDON_MOD_GLOSSARY_SERVICES,
...ADDON_MOD_H5P_ACTIVITY_SERVICES,
...ADDON_MOD_IMSCP_SERVICES,
...ADDON_MOD_LESSON_SERVICES,