diff --git a/src/addon/mod/forum/providers/sync.ts b/src/addon/mod/forum/providers/sync.ts index b628ce4e0..8d63ccfc5 100644 --- a/src/addon/mod/forum/providers/sync.ts +++ b/src/addon/mod/forum/providers/sync.ts @@ -294,7 +294,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider { const promises = []; results.forEach((result) => { - if (result.updated) { + if (result.updated.length) { updated = true; // Invalidate discussions of updated ratings. diff --git a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html index 2b748a568..ad00c5d0f 100644 --- a/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html +++ b/src/addon/mod/glossary/components/index/addon-mod-glossary-index.html @@ -25,7 +25,7 @@ - + {{ 'core.hasdatatosync' | translate:{$a: moduleName} }} diff --git a/src/addon/mod/glossary/components/index/index.ts b/src/addon/mod/glossary/components/index/index.ts index f896e602f..e05699f71 100644 --- a/src/addon/mod/glossary/components/index/index.ts +++ b/src/addon/mod/glossary/components/index/index.ts @@ -16,6 +16,9 @@ import { Component, Injector, ViewChild } from '@angular/core'; import { Content, PopoverController } from 'ionic-angular'; import { CoreSplitViewComponent } from '@components/split-view/split-view'; import { CoreCourseModuleMainActivityComponent } from '@core/course/classes/main-activity-component'; +import { CoreRatingProvider } from '@core/rating/providers/rating'; +import { CoreRatingOfflineProvider } from '@core/rating/providers/offline'; +import { CoreRatingSyncProvider } from '@core/rating/providers/sync'; import { AddonModGlossaryProvider } from '../../providers/glossary'; import { AddonModGlossaryOfflineProvider } from '../../providers/offline'; import { AddonModGlossarySyncProvider } from '../../providers/sync'; @@ -57,11 +60,16 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity protected getDivider: (entry: any) => string; protected addEntryObserver: any; + hasOfflineRatings: boolean; + protected ratingOfflineObserver: any; + protected ratingSyncObserver: any; + constructor(injector: Injector, private popoverCtrl: PopoverController, private glossaryProvider: AddonModGlossaryProvider, private glossaryOffline: AddonModGlossaryOfflineProvider, - private glossarySync: AddonModGlossarySyncProvider) { + private glossarySync: AddonModGlossarySyncProvider, + private ratingOffline: CoreRatingOfflineProvider) { super(injector); } @@ -74,6 +82,20 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity // When an entry is added, we reload the data. this.addEntryObserver = this.eventsProvider.on(AddonModGlossaryProvider.ADD_ENTRY_EVENT, this.eventReceived.bind(this)); + // Listen for offline ratings saved and synced. + this.ratingOfflineObserver = this.eventsProvider.on(CoreRatingProvider.RATING_SAVED_EVENT, (data) => { + if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module' + && data.instanceId == this.glossary.coursemodule) { + this.hasOfflineRatings = true; + } + }); + this.ratingSyncObserver = this.eventsProvider.on(CoreRatingSyncProvider.SYNCED_EVENT, (data) => { + if (this.glossary && data.component == 'mod_glossary' && data.ratingArea == 'entry' && data.contextLevel == 'module' + && data.instanceId == this.glossary.coursemodule) { + this.hasOfflineRatings = false; + } + }); + this.loadContent(false, true).then(() => { if (!this.glossary) { return; @@ -118,15 +140,23 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity return this.syncActivity(showErrors); } }).then(() => { + const promises = []; - return this.fetchEntries().then(() => { + promises.push(this.fetchEntries().then(() => { // Check if there are responses stored in offline. return this.glossaryOffline.getGlossaryNewEntries(this.glossary.id).then((offlineEntries) => { offlineEntries.sort((a, b) => a.concept.localeCompare(b.fullname)); this.hasOffline = !!offlineEntries.length; this.offlineEntries = offlineEntries || []; }); - }); + })); + + promises.push(this.ratingOffline.hasRatings('mod_glossary', 'entry', 'module', this.glossary.coursemodule) + .then((hasRatings) => { + this.hasOfflineRatings = hasRatings; + })); + + return Promise.all(promises); }).then(() => { // All data obtained, now fill the context menu. this.fillContextMenu(refresh); @@ -189,7 +219,17 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity * @return {Promise} Promise resolved when done. */ protected sync(): Promise { - return this.glossarySync.syncGlossaryEntries(this.glossary.id); + const promises = [ + this.glossarySync.syncGlossaryEntries(this.glossary.id), + this.glossarySync.syncRatings(this.glossary.coursemodule) + ]; + + return Promise.all(promises).then((results) => { + return results.reduce((a, b) => ({ + updated: a.updated || b.updated, + warnings: (a.warnings || []).concat(b.warnings || []), + }), {updated: false}); + }); } /** @@ -345,7 +385,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity openEntry(entryId: number): void { const params = { courseId: this.courseId, - entryId: entryId, + entryId: entryId }; this.splitviewCtrl.push('AddonModGlossaryEntryPage', params); this.selectedEntry = entryId; @@ -400,5 +440,7 @@ export class AddonModGlossaryIndexComponent extends CoreCourseModuleMainActivity super.ngOnDestroy(); this.addEntryObserver && this.addEntryObserver.off(); + this.ratingOfflineObserver && this.ratingOfflineObserver.off(); + this.ratingSyncObserver && this.ratingSyncObserver.off(); } } diff --git a/src/addon/mod/glossary/pages/entry/entry.html b/src/addon/mod/glossary/pages/entry/entry.html index 28dc4be7a..f34e46d27 100644 --- a/src/addon/mod/glossary/pages/entry/entry.html +++ b/src/addon/mod/glossary/pages/entry/entry.html @@ -31,6 +31,8 @@

{{ 'addon.mod_glossary.entrypendingapproval' | translate }}

+ + diff --git a/src/addon/mod/glossary/pages/entry/entry.module.ts b/src/addon/mod/glossary/pages/entry/entry.module.ts index 83b3642cf..cc69e9dc4 100644 --- a/src/addon/mod/glossary/pages/entry/entry.module.ts +++ b/src/addon/mod/glossary/pages/entry/entry.module.ts @@ -18,6 +18,7 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreComponentsModule } from '@components/components.module'; import { CoreDirectivesModule } from '@directives/directives.module'; import { CorePipesModule } from '@pipes/pipes.module'; +import { CoreRatingComponentsModule } from '@core/rating/components/components.module'; import { AddonModGlossaryEntryPage } from './entry'; @NgModule({ @@ -29,7 +30,8 @@ import { AddonModGlossaryEntryPage } from './entry'; CoreDirectivesModule, CorePipesModule, IonicPageModule.forChild(AddonModGlossaryEntryPage), - TranslateModule.forChild() + TranslateModule.forChild(), + CoreRatingComponentsModule ], }) export class AddonModForumDiscussionPageModule {} diff --git a/src/addon/mod/glossary/pages/entry/entry.ts b/src/addon/mod/glossary/pages/entry/entry.ts index 461f0350b..03da1c30d 100644 --- a/src/addon/mod/glossary/pages/entry/entry.ts +++ b/src/addon/mod/glossary/pages/entry/entry.ts @@ -15,6 +15,7 @@ import { Component } from '@angular/core'; import { IonicPage, NavParams } from 'ionic-angular'; import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreRatingInfo } from '@core/rating/providers/rating'; import { AddonModGlossaryProvider } from '../../providers/glossary'; /** @@ -29,9 +30,11 @@ export class AddonModGlossaryEntryPage { component = AddonModGlossaryProvider.COMPONENT; componentId: number; entry: any; + glossary: any; loaded = false; showAuthor = false; showDate = false; + ratingInfo: CoreRatingInfo; protected courseId: number; protected entryId: number; @@ -80,11 +83,13 @@ export class AddonModGlossaryEntryPage { */ protected fetchEntry(refresh?: boolean): Promise { return this.glossaryProvider.getEntry(this.entryId).then((result) => { - this.entry = result; + this.entry = result.entry; + this.ratingInfo = result.ratinginfo; if (!refresh) { // Load the glossary. return this.glossaryProvider.getGlossaryById(this.courseId, this.entry.glossaryid).then((glossary) => { + this.glossary = glossary; this.componentId = glossary.coursemodule; switch (glossary.displayformat) { @@ -109,4 +114,11 @@ export class AddonModGlossaryEntryPage { return Promise.reject(null); }); } + + /** + * Function called when rating is updated online. + */ + ratingUpdated(): void { + this.glossaryProvider.invalidateEntry(this.entryId); + } } diff --git a/src/addon/mod/glossary/providers/entry-link-handler.ts b/src/addon/mod/glossary/providers/entry-link-handler.ts index 137ccca00..953aeb41a 100644 --- a/src/addon/mod/glossary/providers/entry-link-handler.ts +++ b/src/addon/mod/glossary/providers/entry-link-handler.ts @@ -61,8 +61,8 @@ export class AddonModGlossaryEntryLinkHandler extends CoreContentLinksHandlerBas this.domUtils.showErrorModalDefault(error, 'addon.mod_glossary.errorloadingentry', true); return Promise.reject(null); - }).then((entry) => { - return this.courseHelper.getModuleCourseIdByInstance(entry.glossaryid, 'glossary', siteId); + }).then((response) => { + return this.courseHelper.getModuleCourseIdByInstance(response.entry.glossaryid, 'glossary', siteId); }); } diff --git a/src/addon/mod/glossary/providers/glossary.ts b/src/addon/mod/glossary/providers/glossary.ts index da906180c..a4014f0d0 100644 --- a/src/addon/mod/glossary/providers/glossary.ts +++ b/src/addon/mod/glossary/providers/glossary.ts @@ -21,6 +21,7 @@ import { CoreSitesProvider } from '@providers/sites'; import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; +import { CoreRatingInfo } from '@core/rating/providers/rating'; import { AddonModGlossaryOfflineProvider } from './offline'; /** @@ -489,7 +490,7 @@ export class AddonModGlossaryProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the entry. */ - getEntry(entryId: number, siteId?: string): Promise { + getEntry(entryId: number, siteId?: string): Promise<{entry: any, ratinginfo: CoreRatingInfo}> { return this.sitesProvider.getSite(siteId).then((site) => { const params = { id: entryId @@ -500,7 +501,7 @@ export class AddonModGlossaryProvider { return site.read('mod_glossary_get_entry_by_id', params, preSets).then((response) => { if (response && response.entry) { - return response.entry; + return response; } else { return Promise.reject(null); } @@ -615,31 +616,35 @@ export class AddonModGlossaryProvider { * * @param {any} glossary The glossary object. * @param {boolean} [onlyEntriesList] If true, entries won't be invalidated. + * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when data is invalidated. */ - invalidateGlossaryEntries(glossary: any, onlyEntriesList?: boolean): Promise { + invalidateGlossaryEntries(glossary: any, onlyEntriesList?: boolean, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + const promises = []; if (!onlyEntriesList) { - promises.push(this.fetchAllEntries(this.getEntriesByLetter, [glossary.id, 'ALL'], true).then((entries) => { - return this.invalidateEntries(entries); + promises.push(this.fetchAllEntries(this.getEntriesByLetter, [glossary.id, 'ALL'], true, siteId).then((entries) => { + return this.invalidateEntries(entries, siteId); })); } glossary.browsemodes.forEach((mode) => { switch (mode) { case 'letter': - promises.push(this.invalidateEntriesByLetter(glossary.id, 'ALL')); + promises.push(this.invalidateEntriesByLetter(glossary.id, 'ALL', siteId)); break; case 'cat': - promises.push(this.invalidateEntriesByCategory(glossary.id, AddonModGlossaryProvider.SHOW_ALL_CATERGORIES)); + promises.push(this.invalidateEntriesByCategory(glossary.id, AddonModGlossaryProvider.SHOW_ALL_CATERGORIES, + siteId)); break; case 'date': - promises.push(this.invalidateEntriesByDate(glossary.id, 'CREATION', 'DESC')); - promises.push(this.invalidateEntriesByDate(glossary.id, 'UPDATE', 'DESC')); + promises.push(this.invalidateEntriesByDate(glossary.id, 'CREATION', 'DESC', siteId)); + promises.push(this.invalidateEntriesByDate(glossary.id, 'UPDATE', 'DESC', siteId)); break; case 'author': - promises.push(this.invalidateEntriesByAuthor(glossary.id, 'ALL', 'LASTNAME', 'ASC')); + promises.push(this.invalidateEntriesByAuthor(glossary.id, 'ALL', 'LASTNAME', 'ASC', siteId)); break; default: } diff --git a/src/addon/mod/glossary/providers/prefetch-handler.ts b/src/addon/mod/glossary/providers/prefetch-handler.ts index cb4e8ac9d..024fece96 100644 --- a/src/addon/mod/glossary/providers/prefetch-handler.ts +++ b/src/addon/mod/glossary/providers/prefetch-handler.ts @@ -23,6 +23,7 @@ import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; import { CoreUserProvider } from '@core/user/providers/user'; import { AddonModGlossaryProvider } from './glossary'; +import { CoreRatingProvider } from '@core/rating/providers/rating'; /** * Handler to prefetch forums. @@ -42,6 +43,7 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH sitesProvider: CoreSitesProvider, domUtils: CoreDomUtilsProvider, private userProvider: CoreUserProvider, + private ratingProvider: CoreRatingProvider, private glossaryProvider: AddonModGlossaryProvider) { super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); @@ -164,7 +166,11 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH // Fetch user avatars. entries.forEach((entry) => { // Fetch individual entries. - promises.push(this.glossaryProvider.getEntry(entry.id, siteId)); + promises.push(this.glossaryProvider.getEntry(entry.id, siteId).then((entry) => { + // Fetch individual ratings. + return this.ratingProvider.prefetchRatings('module', module.id, glossary.scale, courseId, entry.ratinginfo, + siteId); + })); userIds.push(entry.userid); }); @@ -177,6 +183,8 @@ export class AddonModGlossaryPrefetchHandler extends CoreCourseActivityPrefetchH return Promise.all(promises); })); + + return Promise.all(promises); }); } } diff --git a/src/addon/mod/glossary/providers/sync.ts b/src/addon/mod/glossary/providers/sync.ts index 571745698..3a0dbe0e2 100644 --- a/src/addon/mod/glossary/providers/sync.ts +++ b/src/addon/mod/glossary/providers/sync.ts @@ -29,6 +29,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils'; import { AddonModGlossaryProvider } from './glossary'; import { AddonModGlossaryHelperProvider } from './helper'; import { AddonModGlossaryOfflineProvider } from './offline'; +import { CoreRatingSyncProvider } from '@core/rating/providers/sync'; /** * Service to sync glossaries. @@ -54,7 +55,8 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { private glossaryProvider: AddonModGlossaryProvider, private glossaryHelper: AddonModGlossaryHelperProvider, private glossaryOffline: AddonModGlossaryOfflineProvider, - private logHelper: CoreCourseLogHelperProvider) { + private logHelper: CoreCourseLogHelperProvider, + private ratingSync: CoreRatingSyncProvider) { super('AddonModGlossarySyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils); @@ -79,8 +81,12 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. */ protected syncAllGlossariesFunc(siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const promises = []; + // Sync all new entries - return this.glossaryOffline.getAllNewEntries(siteId).then((entries) => { + promises.push(this.glossaryOffline.getAllNewEntries(siteId).then((entries) => { const promises = {}; // Do not sync same glossary twice. @@ -106,7 +112,11 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { // Promises will be an object so, convert to an array first; return Promise.all(this.utils.objectToArray(promises)); - }); + })); + + promises.push(this.syncRatings(undefined, siteId)); + + return Promise.all(promises); } /** @@ -241,6 +251,50 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider { return this.addOngoingSync(syncId, syncPromise, siteId); } + /** + * Synchronize offline ratings. + * + * @param {number} [cmId] Course module to be synced. If not defined, sync all glossaries. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved if sync is successful, rejected otherwise. + */ + syncRatings(cmId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.ratingSync.syncRatings('mod_glossary', 'entry', 'module', cmId, 0, siteId).then((results) => { + let updated = false; + const warnings = []; + const promises = []; + + results.forEach((result) => { + if (result.updated.length) { + updated = true; + + // Invalidate entry of updated ratings. + result.updated.forEach((itemId) => { + promises.push(this.glossaryProvider.invalidateEntry(itemId, siteId)); + }); + } + if (result.warnings.length) { + promises.push(this.glossaryProvider.getGlossary(result.itemSet.courseId, result.itemSet.instanceId, siteId) + .then((glossary) => { + result.warnings.forEach((warning) => { + warnings.push(this.translate.instant('core.warningofflinedatadeleted', { + component: this.componentTranslate, + name: glossary.name, + error: warning + })); + }); + })); + } + }); + + return this.utils.allPromises(promises).then(() => { + return { updated, warnings }; + }); + }); + } + /** * Delete a new entry. * diff --git a/src/core/rating/providers/sync.ts b/src/core/rating/providers/sync.ts index cffa42d42..0f34ce359 100644 --- a/src/core/rating/providers/sync.ts +++ b/src/core/rating/providers/sync.ts @@ -63,7 +63,7 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { * @return {Promise} Promise resolved if sync is successful, rejected if sync fails. */ syncRatings(component: string, ratingArea: string, contextLevel?: string, instanceId?: number, itemSetId?: number, - siteId?: string): Promise<{itemSet: CoreRatingItemSet, updated: boolean, warnings: string[]}[]> { + siteId?: string): Promise<{itemSet: CoreRatingItemSet, updated: number[], warnings: string[]}[]> { siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.ratingOffline.getItemSets(component, ratingArea, contextLevel, instanceId, itemSetId, siteId) @@ -102,7 +102,7 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { * @return {Promise} Promise resolved when ratings are synced or if it doesn't need to be synced. */ protected syncItemSetIfNeeded(component: string, ratingArea: string, contextLevel: string, instanceId: number, - itemSetId: number, siteId?: string): Promise<{updated: boolean, warnings: string[]}> { + itemSetId: number, siteId?: string): Promise<{updated: number[], warnings: string[]}> { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const syncId = this.getItemSetSyncId(component, ratingArea, contextLevel, instanceId, itemSetId); @@ -126,7 +126,7 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { * @return {Promise} Promise resolved if sync is successful, rejected otherwise. */ protected syncItemSet(component: string, ratingArea: string, contextLevel: string, instanceId: number, itemSetId: number, - siteId?: string): Promise<{updated: boolean, warnings: string[]}> { + siteId?: string): Promise<{updated: number[], warnings: string[]}> { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -139,7 +139,7 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { this.logger.debug(`Try to sync ratings of component '${component}' rating area '${ratingArea}'` + ` context level '${contextLevel}' instance ${instanceId} item set ${itemSetId}`); - let updated = false; + const updated = []; const warnings = []; return this.ratingOffline.getRatings(component, ratingArea, contextLevel, instanceId, itemSetId, siteId).then((ratings) => { @@ -162,7 +162,7 @@ export class CoreRatingSyncProvider extends CoreSyncBaseProvider { return Promise.reject(error); } }).then(() => { - updated = true; + updated.push(rating.itemid); return this.ratingOffline.deleteRating(component, ratingArea, rating.contextlevel, rating.instanceid, rating.itemid, siteId).finally(() => {