MOBILE-3644 glossary: Migrate services
parent
c9b99927c4
commit
010475b790
|
@ -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 {}
|
|
@ -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"
|
||||||
|
}
|
|
@ -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;
|
||||||
|
};
|
|
@ -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);
|
|
@ -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;
|
||||||
|
};
|
|
@ -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
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -32,6 +32,7 @@ import { AddonModSurveyModule } from './survey/survey.module';
|
||||||
import { AddonModScormModule } from './scorm/scorm.module';
|
import { AddonModScormModule } from './scorm/scorm.module';
|
||||||
import { AddonModChoiceModule } from './choice/choice.module';
|
import { AddonModChoiceModule } from './choice/choice.module';
|
||||||
import { AddonModWikiModule } from './wiki/wiki.module';
|
import { AddonModWikiModule } from './wiki/wiki.module';
|
||||||
|
import { AddonModGlossaryModule } from './glossary/glossary.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
|
@ -53,6 +54,7 @@ import { AddonModWikiModule } from './wiki/wiki.module';
|
||||||
AddonModScormModule,
|
AddonModScormModule,
|
||||||
AddonModChoiceModule,
|
AddonModChoiceModule,
|
||||||
AddonModWikiModule,
|
AddonModWikiModule,
|
||||||
|
AddonModGlossaryModule,
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AddonModModule { }
|
export class AddonModModule { }
|
||||||
|
|
|
@ -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';
|
// @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_FOLDER_SERVICES } from '@addons/mod/folder/folder.module';
|
||||||
import { ADDON_MOD_FORUM_SERVICES } from '@addons/mod/forum/forum.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_H5P_ACTIVITY_SERVICES } from '@addons/mod/h5pactivity/h5pactivity.module';
|
||||||
import { ADDON_MOD_IMSCP_SERVICES } from '@addons/mod/imscp/imscp.module';
|
import { ADDON_MOD_IMSCP_SERVICES } from '@addons/mod/imscp/imscp.module';
|
||||||
import { ADDON_MOD_LESSON_SERVICES } from '@addons/mod/lesson/lesson.module';
|
import { ADDON_MOD_LESSON_SERVICES } from '@addons/mod/lesson/lesson.module';
|
||||||
|
@ -296,7 +296,7 @@ export class CoreCompileProvider {
|
||||||
// @todo ...ADDON_MOD_FEEDBACK_SERVICES,
|
// @todo ...ADDON_MOD_FEEDBACK_SERVICES,
|
||||||
...ADDON_MOD_FOLDER_SERVICES,
|
...ADDON_MOD_FOLDER_SERVICES,
|
||||||
...ADDON_MOD_FORUM_SERVICES,
|
...ADDON_MOD_FORUM_SERVICES,
|
||||||
// @todo ...ADDON_MOD_GLOSSARY_SERVICES,
|
...ADDON_MOD_GLOSSARY_SERVICES,
|
||||||
...ADDON_MOD_H5P_ACTIVITY_SERVICES,
|
...ADDON_MOD_H5P_ACTIVITY_SERVICES,
|
||||||
...ADDON_MOD_IMSCP_SERVICES,
|
...ADDON_MOD_IMSCP_SERVICES,
|
||||||
...ADDON_MOD_LESSON_SERVICES,
|
...ADDON_MOD_LESSON_SERVICES,
|
||||||
|
|
Loading…
Reference in New Issue