Merge pull request #1789 from dpalou/MOBILE-2850

Mobile 2850
main
Juan Leyva 2019-03-04 13:21:57 +01:00 committed by GitHub
commit aac8ee7b3a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
28 changed files with 641 additions and 239 deletions

View File

@ -352,14 +352,13 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
} }
const modal = this.domUtils.showModalLoading('core.sending', true); const modal = this.domUtils.showModalLoading('core.sending', true);
this.choiceProvider.submitResponse(this.choice.id, this.choice.name, this.courseId, responses).then(() => { this.choiceProvider.submitResponse(this.choice.id, this.choice.name, this.courseId, responses).then((online) => {
// Success! // Success!
// Check completion since it could be configured to complete once the user answers the choice. // Check completion since it could be configured to complete once the user answers the choice.
this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata); this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata);
this.domUtils.scrollToTop(this.content); this.domUtils.scrollToTop(this.content);
// Let's refresh the data. return this.dataUpdated(online);
return this.refreshContent(false);
}).catch((message) => { }).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'addon.mod_choice.cannotsubmit', true); this.domUtils.showErrorModalDefault(message, 'addon.mod_choice.cannotsubmit', true);
}).finally(() => { }).finally(() => {
@ -377,7 +376,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
this.choiceProvider.deleteResponses(this.choice.id, this.choice.name, this.courseId).then(() => { this.choiceProvider.deleteResponses(this.choice.id, this.choice.name, this.courseId).then(() => {
this.domUtils.scrollToTop(this.content); this.domUtils.scrollToTop(this.content);
// Success! Let's refresh the data. // Refresh the data. Don't call dataUpdated because deleting an answer doesn't mark the choice as outdated.
return this.refreshContent(false); return this.refreshContent(false);
}).catch((message) => { }).catch((message) => {
this.domUtils.showErrorModalDefault(message, 'addon.mod_choice.cannotsubmit', true); this.domUtils.showErrorModalDefault(message, 'addon.mod_choice.cannotsubmit', true);
@ -389,6 +388,28 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
}); });
} }
/**
* Function to call when some data has changed. It will refresh/prefetch data.
*
* @param {boolean} online Whether the data was sent to server or stored in offline.
* @return {Promise<any>} Promise resolved when done.
*/
protected dataUpdated(online: boolean): Promise<any> {
if (online && this.isPrefetched()) {
// The choice is downloaded, update the data.
return this.choiceSync.prefetchAfterUpdate(this.module, this.courseId).then(() => {
// Update the view.
this.showLoadingAndFetch(false, false);
}).catch(() => {
// Prefetch failed, refresh the data.
return this.refreshContent(false);
});
} else {
// Not downloaded, refresh the data.
return this.refreshContent(false);
}
}
/** /**
* Performs the sync of the activity. * Performs the sync of the activity.
* *

View File

@ -19,6 +19,7 @@ import { CoreAppProvider } from '@providers/app';
import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { AddonModChoiceOfflineProvider } from './offline'; import { AddonModChoiceOfflineProvider } from './offline';
import { CoreSiteWSPreSets } from '@classes/site';
/** /**
* Service that provides some features for choices. * Service that provides some features for choices.
@ -68,9 +69,9 @@ export class AddonModChoiceProvider {
* @param {number} courseId Course ID the choice belongs to. * @param {number} courseId Course ID the choice belongs to.
* @param {number[]} [responses] IDs of the answers. If not defined, delete all the answers of the current user. * @param {number[]} [responses] IDs of the answers. If not defined, delete all the answers of the current user.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the options are deleted. * @return {Promise<boolean>} Promise resolved with boolean: true if response was sent to server, false if stored in device.
*/ */
deleteResponses(choiceId: number, name: string, courseId: number, responses?: number[], siteId?: string): Promise<any> { deleteResponses(choiceId: number, name: string, courseId: number, responses?: number[], siteId?: string): Promise<boolean> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
responses = responses || []; responses = responses || [];
@ -173,20 +174,29 @@ export class AddonModChoiceProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {string} key Name of the property to check. * @param {string} key Name of the property to check.
* @param {any} value Value to search. * @param {any} value Value to search.
* @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise resolved when the choice is retrieved. * @return {Promise<any>} Promise resolved when the choice is retrieved.
*/ */
protected getChoiceByDataKey(siteId: string, courseId: number, key: string, value: any, forceCache: boolean = false) protected getChoiceByDataKey(siteId: string, courseId: number, key: string, value: any, forceCache?: boolean,
: Promise<any> { ignoreCache?: boolean): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
courseids: [courseId] courseids: [courseId]
}; };
const preSets = { const preSets: CoreSiteWSPreSets = {
cacheKey: this.getChoiceDataCacheKey(courseId), cacheKey: this.getChoiceDataCacheKey(courseId),
omitExpires: forceCache omitExpires: forceCache
}; };
if (forceCache) {
preSets.omitExpires = true;
} else if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_choice_get_choices_by_courses', params, preSets).then((response) => { return site.read('mod_choice_get_choices_by_courses', params, preSets).then((response) => {
if (response && response.choices) { if (response && response.choices) {
const currentChoice = response.choices.find((choice) => choice[key] == value); const currentChoice = response.choices.find((choice) => choice[key] == value);
@ -206,11 +216,12 @@ export class AddonModChoiceProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} cmId Course module ID. * @param {number} cmId Course module ID.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise resolved when the choice is retrieved. * @return {Promise<any>} Promise resolved when the choice is retrieved.
*/ */
getChoice(courseId: number, cmId: number, siteId?: string, forceCache: boolean = false): Promise<any> { getChoice(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise<any> {
return this.getChoiceByDataKey(siteId, courseId, 'coursemodule', cmId, forceCache); return this.getChoiceByDataKey(siteId, courseId, 'coursemodule', cmId, forceCache, ignoreCache);
} }
/** /**
@ -219,29 +230,36 @@ export class AddonModChoiceProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} choiceId Choice ID. * @param {number} choiceId Choice ID.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise resolved when the choice is retrieved. * @return {Promise<any>} Promise resolved when the choice is retrieved.
*/ */
getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache: boolean = false): Promise<any> { getChoiceById(courseId: number, choiceId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise<any> {
return this.getChoiceByDataKey(siteId, courseId, 'id', choiceId, forceCache); return this.getChoiceByDataKey(siteId, courseId, 'id', choiceId, forceCache, ignoreCache);
} }
/** /**
* Get choice options. * Get choice options.
* *
* @param {number} choiceId Choice ID. * @param {number} choiceId Choice ID.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with choice options. * @return {Promise<any>} Promise resolved with choice options.
*/ */
getOptions(choiceId: number, siteId?: string): Promise<any> { getOptions(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
choiceid: choiceId choiceid: choiceId
}; };
const preSets = { const preSets: CoreSiteWSPreSets = {
cacheKey: this.getChoiceOptionsCacheKey(choiceId) cacheKey: this.getChoiceOptionsCacheKey(choiceId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_choice_get_choice_options', params, preSets).then((response) => { return site.read('mod_choice_get_choice_options', params, preSets).then((response) => {
if (response.options) { if (response.options) {
return response.options; return response.options;
@ -255,19 +273,25 @@ export class AddonModChoiceProvider {
/** /**
* Get choice results. * Get choice results.
* *
* @param {number} choiceId Choice ID. * @param {number} choiceId Choice ID.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved with choice results. * @return {Promise<any>} Promise resolved with choice results.
*/ */
getResults(choiceId: number, siteId?: string): Promise<any> { getResults(choiceId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
choiceid: choiceId choiceid: choiceId
}; };
const preSets = { const preSets: CoreSiteWSPreSets = {
cacheKey: this.getChoiceResultsCacheKey(choiceId) cacheKey: this.getChoiceResultsCacheKey(choiceId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_choice_get_choice_results', params, preSets).then((response) => { return site.read('mod_choice_get_choice_results', params, preSets).then((response) => {
if (response.options) { if (response.options) {
return response.options; return response.options;

View File

@ -23,7 +23,6 @@ import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler'; import { CoreCourseActivityPrefetchHandlerBase } from '@core/course/classes/activity-prefetch-handler';
import { CoreUserProvider } from '@core/user/providers/user'; import { CoreUserProvider } from '@core/user/providers/user';
import { AddonModChoiceProvider } from './choice'; import { AddonModChoiceProvider } from './choice';
import { AddonModChoiceSyncProvider } from './sync';
/** /**
* Handler to prefetch choices. * Handler to prefetch choices.
@ -38,7 +37,7 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan
constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider, constructor(translate: TranslateService, appProvider: CoreAppProvider, utils: CoreUtilsProvider,
courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider, courseProvider: CoreCourseProvider, filepoolProvider: CoreFilepoolProvider, sitesProvider: CoreSitesProvider,
domUtils: CoreDomUtilsProvider, protected choiceProvider: AddonModChoiceProvider, domUtils: CoreDomUtilsProvider, protected choiceProvider: AddonModChoiceProvider,
protected syncProvider: AddonModChoiceSyncProvider, protected userProvider: CoreUserProvider) { protected userProvider: CoreUserProvider) {
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils);
} }
@ -66,12 +65,12 @@ export class AddonModChoicePrefetchHandler extends CoreCourseActivityPrefetchHan
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
protected prefetchChoice(module: any, courseId: number, single: boolean, siteId: string): Promise<any> { protected prefetchChoice(module: any, courseId: number, single: boolean, siteId: string): Promise<any> {
return this.choiceProvider.getChoice(courseId, module.id, siteId).then((choice) => { return this.choiceProvider.getChoice(courseId, module.id, siteId, false, true).then((choice) => {
const promises = []; const promises = [];
// Get the options and results. // Get the options and results.
promises.push(this.choiceProvider.getOptions(choice.id, siteId)); promises.push(this.choiceProvider.getOptions(choice.id, true, siteId));
promises.push(this.choiceProvider.getResults(choice.id, siteId).then((options) => { promises.push(this.choiceProvider.getResults(choice.id, true, siteId).then((options) => {
// If we can see the users that answered, prefetch their profile and avatar. // If we can see the users that answered, prefetch their profile and avatar.
const subPromises = []; const subPromises = [];
options.forEach((option) => { options.forEach((option) => {

View File

@ -14,7 +14,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
@ -26,13 +25,16 @@ import { CoreEventsProvider } from '@providers/events';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync';
import { CoreSyncProvider } from '@providers/sync'; import { CoreSyncProvider } from '@providers/sync';
import { AddonModChoicePrefetchHandler } from './prefetch-handler';
/** /**
* Service to sync choices. * Service to sync choices.
*/ */
@Injectable() @Injectable()
export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider { export class AddonModChoiceSyncProvider extends CoreCourseActivitySyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_choice_autom_synced'; static AUTO_SYNCED = 'addon_mod_choice_autom_synced';
protected componentTranslate: string; protected componentTranslate: string;
@ -42,9 +44,11 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider {
private eventsProvider: CoreEventsProvider, private choiceProvider: AddonModChoiceProvider, private eventsProvider: CoreEventsProvider, private choiceProvider: AddonModChoiceProvider,
translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider,
courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider, courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider,
private logHelper: CoreCourseLogHelperProvider) { private logHelper: CoreCourseLogHelperProvider, prefetchHandler: AddonModChoicePrefetchHandler,
prefetchDelegate: CoreCourseModulePrefetchDelegate) {
super('AddonModChoiceSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, super('AddonModChoiceSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils); timeUtils, prefetchDelegate, prefetchHandler);
this.componentTranslate = courseProvider.translateModuleName('choice'); this.componentTranslate = courseProvider.translateModuleName('choice');
} }
@ -195,16 +199,8 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider {
}); });
}).then(() => { }).then(() => {
if (courseId) { if (courseId) {
const promises = [ // Data has been sent to server, prefetch choice if needed.
this.choiceProvider.invalidateChoiceData(courseId), return this.prefetchAfterUpdate(module, courseId, undefined, siteId).catch(() => {
choiceId ? this.choiceProvider.invalidateOptions(choiceId) : Promise.resolve(),
choiceId ? this.choiceProvider.invalidateResults(choiceId) : Promise.resolve(),
];
// Data has been sent to server, update choice data.
return Promise.all(promises).then(() => {
return this.choiceProvider.getChoiceById(courseId, choiceId, siteId);
}).catch(() => {
// Ignore errors. // Ignore errors.
}); });
} }

View File

@ -77,15 +77,29 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
// Listen for form submit events. // Listen for form submit events.
this.submitObserver = this.eventsProvider.on(AddonModFeedbackProvider.FORM_SUBMITTED, (data) => { this.submitObserver = this.eventsProvider.on(AddonModFeedbackProvider.FORM_SUBMITTED, (data) => {
if (this.feedback && data.feedbackId == this.feedback.id) { if (this.feedback && data.feedbackId == this.feedback.id) {
// Go to review attempt if an attempt in this quiz was finished and synced.
this.tabsLoaded['analysis'] = false; this.tabsLoaded['analysis'] = false;
this.tabsLoaded['overview'] = false; this.tabsLoaded['overview'] = false;
this.loaded = false; this.loaded = false;
if (data.tab != this.tab) {
this.tabChanged(data.tab); let promise;
// Prefetch data if needed.
if (!data.offline && this.isPrefetched()) {
promise = this.feedbackSync.prefetchAfterUpdate(this.module, this.courseId).catch(() => {
// Ignore errors.
});
} else { } else {
this.loadContent(true); promise = Promise.resolve();
} }
promise.then(() => {
// Load the right tab.
if (data.tab != this.tab) {
this.tabChanged(data.tab);
} else {
this.loadContent(true);
}
});
} }
}, this.siteId); }, this.siteId);
} }

View File

@ -338,8 +338,13 @@ export class AddonModFeedbackFormPage implements OnDestroy {
ngOnDestroy(): void { ngOnDestroy(): void {
if (this.submitted) { if (this.submitted) {
const tab = this.submitted == 'analysis' ? 'analysis' : 'overview'; const tab = this.submitted == 'analysis' ? 'analysis' : 'overview';
// If form has been submitted, the info has been already invalidated but we should update index view. // If form has been submitted, the info has been already invalidated but we should update index view.
this.eventsProvider.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {feedbackId: this.feedback.id, tab: tab}); this.eventsProvider.trigger(AddonModFeedbackProvider.FORM_SUBMITTED, {
feedbackId: this.feedback.id,
tab: tab,
offline: this.completedOffline
});
} }
this.onlineObserver && this.onlineObserver.unsubscribe(); this.onlineObserver && this.onlineObserver.unsubscribe();
} }

View File

@ -20,6 +20,7 @@ import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { AddonModFeedbackOfflineProvider } from './offline'; import { AddonModFeedbackOfflineProvider } from './offline';
import { CoreSiteWSPreSets } from '@classes/site';
/** /**
* Service that provides some features for feedbacks. * Service that provides some features for feedbacks.
@ -215,11 +216,14 @@ export class AddonModFeedbackProvider {
* *
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {number} groupId Group id, 0 means that the function will determine the user group. * @param {number} groupId Group id, 0 means that the function will determine the user group.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {any} [previous] Only for recurrent use. Object with the previous fetched info. * @param {any} [previous] Only for recurrent use. Object with the previous fetched info.
* @return {Promise<any>} Promise resolved when the info is retrieved. * @return {Promise<any>} Promise resolved when the info is retrieved.
*/ */
getAllNonRespondents(feedbackId: number, groupId: number, siteId?: string, previous?: any): Promise<any> { getAllNonRespondents(feedbackId: number, groupId: number, ignoreCache?: boolean, siteId?: string, previous?: any)
: Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (typeof previous == 'undefined') { if (typeof previous == 'undefined') {
previous = { previous = {
@ -228,7 +232,7 @@ export class AddonModFeedbackProvider {
}; };
} }
return this.getNonRespondents(feedbackId, groupId, previous.page, siteId).then((response) => { return this.getNonRespondents(feedbackId, groupId, previous.page, ignoreCache, siteId).then((response) => {
if (previous.users.length < response.total) { if (previous.users.length < response.total) {
previous.users = previous.users.concat(response.users); previous.users = previous.users.concat(response.users);
} }
@ -237,7 +241,7 @@ export class AddonModFeedbackProvider {
// Can load more. // Can load more.
previous.page++; previous.page++;
return this.getAllNonRespondents(feedbackId, groupId, siteId, previous); return this.getAllNonRespondents(feedbackId, groupId, ignoreCache, siteId, previous);
} }
previous.total = response.total; previous.total = response.total;
@ -250,11 +254,14 @@ export class AddonModFeedbackProvider {
* *
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {number} groupId Group id, 0 means that the function will determine the user group. * @param {number} groupId Group id, 0 means that the function will determine the user group.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {any} [previous] Only for recurrent use. Object with the previous fetched info. * @param {any} [previous] Only for recurrent use. Object with the previous fetched info.
* @return {Promise<any>} Promise resolved when the info is retrieved. * @return {Promise<any>} Promise resolved when the info is retrieved.
*/ */
getAllResponsesAnalysis(feedbackId: number, groupId: number, siteId?: string, previous?: any): Promise<any> { getAllResponsesAnalysis(feedbackId: number, groupId: number, ignoreCache?: boolean, siteId?: string, previous?: any)
: Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (typeof previous == 'undefined') { if (typeof previous == 'undefined') {
previous = { previous = {
@ -264,7 +271,7 @@ export class AddonModFeedbackProvider {
}; };
} }
return this.getResponsesAnalysis(feedbackId, groupId, previous.page, siteId).then((responses) => { return this.getResponsesAnalysis(feedbackId, groupId, previous.page, ignoreCache, siteId).then((responses) => {
if (previous.anonattempts.length < responses.totalanonattempts) { if (previous.anonattempts.length < responses.totalanonattempts) {
previous.anonattempts = previous.anonattempts.concat(responses.anonattempts); previous.anonattempts = previous.anonattempts.concat(responses.anonattempts);
} }
@ -277,7 +284,7 @@ export class AddonModFeedbackProvider {
// Can load more. // Can load more.
previous.page++; previous.page++;
return this.getAllResponsesAnalysis(feedbackId, groupId, siteId, previous); return this.getAllResponsesAnalysis(feedbackId, groupId, ignoreCache, siteId, previous);
} }
previous.totalattempts = responses.totalattempts; previous.totalattempts = responses.totalattempts;
@ -292,15 +299,16 @@ export class AddonModFeedbackProvider {
* *
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {number} [groupId] Group ID. * @param {number} [groupId] Group ID.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the feedback is retrieved. * @return {Promise<any>} Promise resolved when the feedback is retrieved.
*/ */
getAnalysis(feedbackId: number, groupId?: number, siteId?: string): Promise<any> { getAnalysis(feedbackId: number, groupId?: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
feedbackid: feedbackId feedbackid: feedbackId
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getAnalysisDataCacheKey(feedbackId, groupId) cacheKey: this.getAnalysisDataCacheKey(feedbackId, groupId)
}; };
@ -308,6 +316,11 @@ export class AddonModFeedbackProvider {
params['groupid'] = groupId; params['groupid'] = groupId;
} }
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_feedback_get_analysis', params, preSets); return site.read('mod_feedback_get_analysis', params, preSets);
}); });
} }
@ -338,11 +351,12 @@ export class AddonModFeedbackProvider {
* *
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {number} attemptId Attempt id to find. * @param {number} attemptId Attempt id to find.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {any} [previous] Only for recurrent use. Object with the previous fetched info. * @param {any} [previous] Only for recurrent use. Object with the previous fetched info.
* @return {Promise<any>} Promise resolved when the info is retrieved. * @return {Promise<any>} Promise resolved when the info is retrieved.
*/ */
getAttempt(feedbackId: number, attemptId: number, siteId?: string, previous?: any): Promise<any> { getAttempt(feedbackId: number, attemptId: number, ignoreCache?: boolean, siteId?: string, previous?: any): Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
if (typeof previous == 'undefined') { if (typeof previous == 'undefined') {
previous = { previous = {
@ -352,7 +366,7 @@ export class AddonModFeedbackProvider {
}; };
} }
return this.getResponsesAnalysis(feedbackId, 0, previous.page, siteId).then((responses) => { return this.getResponsesAnalysis(feedbackId, 0, previous.page, ignoreCache, siteId).then((responses) => {
let attempt; let attempt;
attempt = responses.attempts.find((attempt) => { attempt = responses.attempts.find((attempt) => {
@ -383,7 +397,7 @@ export class AddonModFeedbackProvider {
// Can load more. Check there. // Can load more. Check there.
previous.page++; previous.page++;
return this.getAttempt(feedbackId, attemptId, siteId, previous); return this.getAttempt(feedbackId, attemptId, ignoreCache, siteId, previous);
} }
// Not found and all loaded. Reject. // Not found and all loaded. Reject.
@ -405,18 +419,24 @@ export class AddonModFeedbackProvider {
* Returns the temporary completion timemodified for the current user. * Returns the temporary completion timemodified for the current user.
* *
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the info is retrieved. * @return {Promise<any>} Promise resolved when the info is retrieved.
*/ */
getCurrentCompletedTimeModified(feedbackId: number, siteId?: string): Promise<any> { getCurrentCompletedTimeModified(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
feedbackid: feedbackId feedbackid: feedbackId
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getCurrentCompletedTimeModifiedDataCacheKey(feedbackId) cacheKey: this.getCurrentCompletedTimeModifiedDataCacheKey(feedbackId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_feedback_get_current_completed_tmp', params, preSets).then((response) => { return site.read('mod_feedback_get_current_completed_tmp', params, preSets).then((response) => {
if (response && typeof response.feedback != 'undefined' && typeof response.feedback.timemodified != 'undefined') { if (response && typeof response.feedback != 'undefined' && typeof response.feedback.timemodified != 'undefined') {
return response.feedback.timemodified; return response.feedback.timemodified;
@ -552,20 +572,26 @@ export class AddonModFeedbackProvider {
* @param {string} key Name of the property to check. * @param {string} key Name of the property to check.
* @param {any} value Value to search. * @param {any} value Value to search.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [forceCache=false] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise resolved when the feedback is retrieved. * @return {Promise<any>} Promise resolved when the feedback is retrieved.
*/ */
protected getFeedbackDataByKey(courseId: number, key: string, value: any, siteId?: string, forceCache?: boolean): Promise<any> { protected getFeedbackDataByKey(courseId: number, key: string, value: any, siteId?: string, forceCache?: boolean,
ignoreCache?: boolean): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
courseids: [courseId] courseids: [courseId]
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getFeedbackCacheKey(courseId) cacheKey: this.getFeedbackCacheKey(courseId)
}; };
if (forceCache) { if (forceCache) {
preSets['omitExpires'] = true; preSets.omitExpires = true;
} else if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
} }
return site.read('mod_feedback_get_feedbacks_by_courses', params, preSets).then((response) => { return site.read('mod_feedback_get_feedbacks_by_courses', params, preSets).then((response) => {
@ -589,11 +615,12 @@ export class AddonModFeedbackProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} cmId Course module ID. * @param {number} cmId Course module ID.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise resolved when the feedback is retrieved. * @return {Promise<any>} Promise resolved when the feedback is retrieved.
*/ */
getFeedback(courseId: number, cmId: number, siteId?: string, forceCache?: boolean): Promise<any> { getFeedback(courseId: number, cmId: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise<any> {
return this.getFeedbackDataByKey(courseId, 'coursemodule', cmId, siteId, forceCache); return this.getFeedbackDataByKey(courseId, 'coursemodule', cmId, siteId, forceCache, ignoreCache);
} }
/** /**
@ -603,28 +630,35 @@ export class AddonModFeedbackProvider {
* @param {number} id Feedback ID. * @param {number} id Feedback ID.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false. * @param {boolean} [forceCache] True to always get the value from cache, false otherwise. Default false.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any>} Promise resolved when the feedback is retrieved. * @return {Promise<any>} Promise resolved when the feedback is retrieved.
*/ */
getFeedbackById(courseId: number, id: number, siteId?: string, forceCache?: boolean): Promise<any> { getFeedbackById(courseId: number, id: number, siteId?: string, forceCache?: boolean, ignoreCache?: boolean): Promise<any> {
return this.getFeedbackDataByKey(courseId, 'id', id, siteId, forceCache); return this.getFeedbackDataByKey(courseId, 'id', id, siteId, forceCache, ignoreCache);
} }
/** /**
* Returns the items (questions) in the given feedback. * Returns the items (questions) in the given feedback.
* *
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the info is retrieved. * @return {Promise<any>} Promise resolved when the info is retrieved.
*/ */
getItems(feedbackId: number, siteId?: string): Promise<any> { getItems(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
feedbackid: feedbackId feedbackid: feedbackId
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getItemsDataCacheKey(feedbackId) cacheKey: this.getItemsDataCacheKey(feedbackId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_feedback_get_items', params, preSets); return site.read('mod_feedback_get_items', params, preSets);
}); });
} }
@ -645,20 +679,28 @@ export class AddonModFeedbackProvider {
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {number} [groupId=0] Group id, 0 means that the function will determine the user group. * @param {number} [groupId=0] Group id, 0 means that the function will determine the user group.
* @param {number} [page=0] The page of records to return. * @param {number} [page=0] The page of records to return.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the info is retrieved. * @return {Promise<any>} Promise resolved when the info is retrieved.
*/ */
getNonRespondents(feedbackId: number, groupId: number = 0, page: number = 0, siteId?: string): Promise<any> { getNonRespondents(feedbackId: number, groupId: number = 0, page: number = 0, ignoreCache?: boolean, siteId?: string)
: Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
feedbackid: feedbackId, feedbackid: feedbackId,
groupid: groupId, groupid: groupId,
page: page page: page
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getNonRespondentsDataCacheKey(feedbackId, groupId) cacheKey: this.getNonRespondentsDataCacheKey(feedbackId, groupId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_feedback_get_non_respondents', params, preSets); return site.read('mod_feedback_get_non_respondents', params, preSets);
}); });
} }
@ -725,7 +767,7 @@ export class AddonModFeedbackProvider {
}); });
}).catch(() => { }).catch(() => {
// If getPageItems fail we should calculate it using getItems. // If getPageItems fail we should calculate it using getItems.
return this.getItems(feedbackId, siteId).then((response) => { return this.getItems(feedbackId, false, siteId).then((response) => {
return this.fillValues(feedbackId, response.items, offline, ignoreCache, siteId).then((items) => { return this.fillValues(feedbackId, response.items, offline, ignoreCache, siteId).then((items) => {
// Separate items by pages. // Separate items by pages.
let currentPage = 0; let currentPage = 0;
@ -802,20 +844,26 @@ export class AddonModFeedbackProvider {
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {number} groupId Group id, 0 means that the function will determine the user group. * @param {number} groupId Group id, 0 means that the function will determine the user group.
* @param {number} page The page of records to return. * @param {number} page The page of records to return.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the info is retrieved. * @return {Promise<any>} Promise resolved when the info is retrieved.
*/ */
getResponsesAnalysis(feedbackId: number, groupId: number, page: number, siteId?: string): Promise<any> { getResponsesAnalysis(feedbackId: number, groupId: number, page: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
feedbackid: feedbackId, feedbackid: feedbackId,
groupid: groupId || 0, groupid: groupId || 0,
page: page || 0 page: page || 0
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getResponsesAnalysisDataCacheKey(feedbackId, groupId) cacheKey: this.getResponsesAnalysisDataCacheKey(feedbackId, groupId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_feedback_get_responses_analysis', params, preSets); return site.read('mod_feedback_get_responses_analysis', params, preSets);
}); });
} }
@ -1037,18 +1085,24 @@ export class AddonModFeedbackProvider {
* Returns if feedback has been completed * Returns if feedback has been completed
* *
* @param {number} feedbackId Feedback ID. * @param {number} feedbackId Feedback ID.
* @param {boolean} [ignoreCache=false] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<boolean>} Promise resolved when the info is retrieved. * @return {Promise<boolean>} Promise resolved when the info is retrieved.
*/ */
isCompleted(feedbackId: number, siteId?: string): Promise<boolean> { isCompleted(feedbackId: number, ignoreCache?: boolean, siteId?: string): Promise<boolean> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
feedbackid: feedbackId feedbackid: feedbackId
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getCompletedDataCacheKey(feedbackId) cacheKey: this.getCompletedDataCacheKey(feedbackId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return this.utils.promiseWorks(site.read('mod_feedback_get_last_completed', params, preSets)); return this.utils.promiseWorks(site.read('mod_feedback_get_last_completed', params, preSets));
}); });
} }

View File

@ -173,19 +173,15 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH
*/ */
protected prefetchFeedback(module: any, courseId: number, single: boolean, siteId: string): Promise<any> { protected prefetchFeedback(module: any, courseId: number, single: boolean, siteId: string): Promise<any> {
// Prefetch the feedback data. // Prefetch the feedback data.
return this.feedbackProvider.getFeedback(courseId, module.id).then((feedback) => { return this.feedbackProvider.getFeedback(courseId, module.id, siteId, false, true).then((feedback) => {
const p1 = []; let files = (feedback.pageaftersubmitfiles || []).concat(this.getIntroFilesFromInstance(module, feedback));
p1.push(this.getFiles(module, courseId).then((files) => { return this.feedbackProvider.getFeedbackAccessInformation(feedback.id, false, true, siteId).then((accessData) => {
return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id);
}));
p1.push(this.feedbackProvider.getFeedbackAccessInformation(feedback.id, false, true, siteId).then((accessData) => {
const p2 = []; const p2 = [];
if (accessData.canedititems || accessData.canviewreports) { if (accessData.canedititems || accessData.canviewreports) {
// Get all groups analysis. // Get all groups analysis.
p2.push(this.feedbackProvider.getAnalysis(feedback.id, undefined, siteId)); p2.push(this.feedbackProvider.getAnalysis(feedback.id, undefined, true, siteId));
p2.push(this.groupsProvider.getActivityGroupInfo(feedback.coursemodule, true, undefined, siteId) p2.push(this.groupsProvider.getActivityGroupInfo(feedback.coursemodule, true, undefined, siteId, true)
.then((groupInfo) => { .then((groupInfo) => {
const p3 = [], const p3 = [],
userIds = []; userIds = [];
@ -194,8 +190,8 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH
groupInfo.groups = [{id: 0}]; groupInfo.groups = [{id: 0}];
} }
groupInfo.groups.forEach((group) => { groupInfo.groups.forEach((group) => {
p3.push(this.feedbackProvider.getAnalysis(feedback.id, group.id, siteId)); p3.push(this.feedbackProvider.getAnalysis(feedback.id, group.id, true, siteId));
p3.push(this.feedbackProvider.getAllResponsesAnalysis(feedback.id, group.id, siteId) p3.push(this.feedbackProvider.getAllResponsesAnalysis(feedback.id, group.id, true, siteId)
.then((responses) => { .then((responses) => {
responses.attempts.forEach((attempt) => { responses.attempts.forEach((attempt) => {
userIds.push(attempt.userid); userIds.push(attempt.userid);
@ -203,7 +199,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH
})); }));
if (!accessData.isanonymous) { if (!accessData.isanonymous) {
p3.push(this.feedbackProvider.getAllNonRespondents(feedback.id, group.id, siteId) p3.push(this.feedbackProvider.getAllNonRespondents(feedback.id, group.id, true, siteId)
.then((responses) => { .then((responses) => {
responses.users.forEach((user) => { responses.users.forEach((user) => {
userIds.push(user.userid); userIds.push(user.userid);
@ -219,7 +215,13 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH
})); }));
} }
p2.push(this.feedbackProvider.getItems(feedback.id, siteId)); p2.push(this.feedbackProvider.getItems(feedback.id, true, siteId).then((response) => {
response.items.forEach((item) => {
files = files.concat(item.itemfiles);
});
return this.filepoolProvider.addFilesToQueue(siteId, files, this.component, module.id);
}));
if (accessData.cancomplete && accessData.cansubmit && !accessData.isempty) { if (accessData.cancomplete && accessData.cansubmit && !accessData.isempty) {
// Send empty data, so it will recover last completed feedback attempt values. // Send empty data, so it will recover last completed feedback attempt values.
@ -234,9 +236,7 @@ export class AddonModFeedbackPrefetchHandler extends CoreCourseActivityPrefetchH
} }
return Promise.all(p2); return Promise.all(p2);
})); });
return Promise.all(p1);
}); });
} }
} }

View File

@ -65,7 +65,7 @@ export class AddonModFeedbackShowEntriesLinkHandler extends CoreContentLinksHand
return this.linkHelper.goInSite(navCtrl, 'AddonModFeedbackRespondentsPage', stateParams, siteId); return this.linkHelper.goInSite(navCtrl, 'AddonModFeedbackRespondentsPage', stateParams, siteId);
} }
return this.feedbackProvider.getAttempt(module.instance, params.showcompleted, siteId).then((attempt) => { return this.feedbackProvider.getAttempt(module.instance, params.showcompleted, true, siteId).then((attempt) => {
stateParams = { stateParams = {
moduleId: module.id, moduleId: module.id,
attempt: attempt, attempt: attempt,

View File

@ -15,7 +15,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -25,14 +24,17 @@ import { AddonModFeedbackProvider } from './feedback';
import { CoreEventsProvider } from '@providers/events'; import { CoreEventsProvider } from '@providers/events';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreSyncProvider } from '@providers/sync'; import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreSyncProvider } from '@providers/sync';
import { AddonModFeedbackPrefetchHandler } from './prefetch-handler';
/** /**
* Service to sync feedbacks. * Service to sync feedbacks.
*/ */
@Injectable() @Injectable()
export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider { export class AddonModFeedbackSyncProvider extends CoreCourseActivitySyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_feedback_autom_synced'; static AUTO_SYNCED = 'addon_mod_feedback_autom_synced';
protected componentTranslate: string; protected componentTranslate: string;
@ -42,13 +44,30 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider {
private eventsProvider: CoreEventsProvider, private feedbackProvider: AddonModFeedbackProvider, private eventsProvider: CoreEventsProvider, private feedbackProvider: AddonModFeedbackProvider,
protected translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider, protected translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider,
courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider, courseProvider: CoreCourseProvider, syncProvider: CoreSyncProvider, timeUtils: CoreTimeUtilsProvider,
private logHelper: CoreCourseLogHelperProvider) { private logHelper: CoreCourseLogHelperProvider, prefetchDelegate: CoreCourseModulePrefetchDelegate,
prefetchHandler: AddonModFeedbackPrefetchHandler) {
super('AddonModFeedbackSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, super('AddonModFeedbackSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils); timeUtils, prefetchDelegate, prefetchHandler);
this.componentTranslate = courseProvider.translateModuleName('feedback'); this.componentTranslate = courseProvider.translateModuleName('feedback');
} }
/**
* Conveniece function to prefetch data after an update.
*
* @param {any} module Module.
* @param {number} courseId Course ID.
* @param {RegExp} [regex] If regex matches, don't download the data. Defaults to check files and timers.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
prefetchAfterUpdate(module: any, courseId: number, regex?: RegExp, siteId?: string): Promise<any> {
regex = regex || /^.*files$|^timers/;
return super.prefetchAfterUpdate(module, courseId, regex, siteId);
}
/** /**
* Try to synchronize all the feedbacks in a certain site or in all sites. * Try to synchronize all the feedbacks in a certain site or in all sites.
* *
@ -196,7 +215,7 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider {
return Promise.all(promises); return Promise.all(promises);
} }
return this.feedbackProvider.getCurrentCompletedTimeModified(feedbackId, siteId).then((timemodified) => { return this.feedbackProvider.getCurrentCompletedTimeModified(feedbackId, true, siteId).then((timemodified) => {
// Sort by page. // Sort by page.
responses.sort((a, b) => { responses.sort((a, b) => {
return a.page - b.page; return a.page - b.page;
@ -216,8 +235,8 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider {
}); });
}).then(() => { }).then(() => {
if (result.updated) { if (result.updated) {
// Data has been sent to server. Now invalidate the WS calls. // Data has been sent to server, update data.
return this.feedbackProvider.invalidateAllFeedbackData(feedbackId, siteId).catch(() => { return this.prefetchAfterUpdate(module, courseId, undefined, siteId).catch(() => {
// Ignore errors. // Ignore errors.
}); });
} }

View File

@ -61,6 +61,8 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
protected accessInfo: any; // Lesson access info. protected accessInfo: any; // Lesson access info.
protected password: string; // The password for the lesson. protected password: string; // The password for the lesson.
protected hasPlayed: boolean; // Whether the user has gone to the lesson player (attempted). protected hasPlayed: boolean; // Whether the user has gone to the lesson player (attempted).
protected dataSentObserver; // To detect data sent to server.
protected dataSent = false; // Whether some data was sent to server while playing the lesson.
constructor(injector: Injector, protected lessonProvider: AddonModLessonProvider, @Optional() content: Content, constructor(injector: Injector, protected lessonProvider: AddonModLessonProvider, @Optional() content: Content,
protected groupsProvider: CoreGroupsProvider, protected lessonOffline: AddonModLessonOfflineProvider, protected groupsProvider: CoreGroupsProvider, protected lessonOffline: AddonModLessonOfflineProvider,
@ -228,6 +230,13 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
* @return {boolean} If suceed or not. * @return {boolean} If suceed or not.
*/ */
protected hasSyncSucceed(result: any): boolean { protected hasSyncSucceed(result: any): boolean {
if (result.updated || this.dataSent) {
// Check completion status if something was sent.
this.courseProvider.checkModuleCompletion(this.courseId, this.module.completiondata);
}
this.dataSent = false;
return result.updated; return result.updated;
} }
@ -243,6 +252,10 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
if (this.hasPlayed) { if (this.hasPlayed) {
this.hasPlayed = false; this.hasPlayed = false;
this.dataSentObserver && this.dataSentObserver.off(); // Stop listening for changes.
this.dataSentObserver = undefined;
// Refresh data.
this.showLoadingAndRefresh(true, false); this.showLoadingAndRefresh(true, false);
} }
} }
@ -257,6 +270,16 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
if (this.navCtrl.getActive().component.name == 'AddonModLessonPlayerPage') { if (this.navCtrl.getActive().component.name == 'AddonModLessonPlayerPage') {
this.hasPlayed = true; this.hasPlayed = true;
// Detect if anything was sent to server.
this.dataSentObserver && this.dataSentObserver.off();
this.dataSentObserver = this.eventsProvider.on(AddonModLessonProvider.DATA_SENT_EVENT, (data) => {
// Ignore launch sending because it only affects timers.
if (data.lessonId === this.lesson.id && data.type != 'launch') {
this.dataSent = true;
}
}, this.siteId);
} }
} }
@ -556,7 +579,18 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
protected sync(): Promise<any> { protected sync(): Promise<any> {
return this.lessonSync.syncLesson(this.lesson.id, true); return this.lessonSync.syncLesson(this.lesson.id, true).then((result) => {
if (!result.updated && this.dataSent && this.isPrefetched()) {
// The user sent data to server, but not in the sync process. Check if we need to fetch data.
return this.lessonSync.prefetchAfterUpdate(this.module, this.courseId).catch(() => {
// Ignore errors.
}).then(() => {
return result;
});
}
return result;
});
} }
/** /**
@ -575,4 +609,13 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
return Promise.reject(error); return Promise.reject(error);
}); });
} }
/**
* Component being destroyed.
*/
ngOnDestroy(): void {
super.ngOnDestroy();
this.dataSentObserver && this.dataSentObserver.off();
}
} }

View File

@ -25,7 +25,8 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync';
import { AddonModLessonProvider } from './lesson'; import { AddonModLessonProvider } from './lesson';
import { AddonModLessonOfflineProvider } from './lesson-offline'; import { AddonModLessonOfflineProvider } from './lesson-offline';
import { AddonModLessonPrefetchHandler } from './prefetch-handler'; import { AddonModLessonPrefetchHandler } from './prefetch-handler';
@ -51,7 +52,7 @@ export interface AddonModLessonSyncResult {
* Service to sync lesson. * Service to sync lesson.
*/ */
@Injectable() @Injectable()
export class AddonModLessonSyncProvider extends CoreSyncBaseProvider { export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_lesson_autom_synced'; static AUTO_SYNCED = 'addon_mod_lesson_autom_synced';
@ -92,12 +93,12 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider,
private lessonProvider: AddonModLessonProvider, private lessonOfflineProvider: AddonModLessonOfflineProvider, private lessonProvider: AddonModLessonProvider, private lessonOfflineProvider: AddonModLessonOfflineProvider,
private prefetchHandler: AddonModLessonPrefetchHandler, timeUtils: CoreTimeUtilsProvider, protected prefetchHandler: AddonModLessonPrefetchHandler, timeUtils: CoreTimeUtilsProvider,
private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider, private utils: CoreUtilsProvider, private urlUtils: CoreUrlUtilsProvider,
private logHelper: CoreCourseLogHelperProvider) { private logHelper: CoreCourseLogHelperProvider, prefetchDelegate: CoreCourseModulePrefetchDelegate) {
super('AddonModLessonSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, super('AddonModLessonSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils); timeUtils, prefetchDelegate, prefetchHandler);
this.componentTranslate = courseProvider.translateModuleName('lesson'); this.componentTranslate = courseProvider.translateModuleName('lesson');
@ -288,7 +289,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
courseId = attempts[0].courseid; courseId = attempts[0].courseid;
// Get the info, access info and the lesson password if needed. // Get the info, access info and the lesson password if needed.
return this.lessonProvider.getLessonById(courseId, lessonId, false, siteId).then((lessonData) => { return this.lessonProvider.getLessonById(courseId, lessonId, false, false, siteId).then((lessonData) => {
lesson = lessonData; lesson = lessonData;
return this.prefetchHandler.getLessonPassword(lessonId, false, true, askPassword, siteId); return this.prefetchHandler.getLessonPassword(lessonId, false, true, askPassword, siteId);
@ -364,7 +365,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
// Data already retrieved when syncing attempts. // Data already retrieved when syncing attempts.
promise = Promise.resolve(); promise = Promise.resolve();
} else { } else {
promise = this.lessonProvider.getLessonById(courseId, lessonId, false, siteId).then((lessonData) => { promise = this.lessonProvider.getLessonById(courseId, lessonId, false, false, siteId).then((lessonData) => {
lesson = lessonData; lesson = lessonData;
return this.prefetchHandler.getLessonPassword(lessonId, false, true, askPassword, siteId); return this.prefetchHandler.getLessonPassword(lessonId, false, true, askPassword, siteId);
@ -429,31 +430,9 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
}); });
}).then(() => { }).then(() => {
if (result.updated && courseId) { if (result.updated && courseId) {
// Data has been sent to server. Now invalidate the WS calls. // Data has been sent to server, update data.
const promises = []; return this.prefetchAfterUpdate(module, courseId, undefined, siteId).catch(() => {
promises.push(this.lessonProvider.invalidateAccessInformation(lessonId, siteId));
promises.push(this.lessonProvider.invalidateContentPagesViewed(lessonId, siteId));
promises.push(this.lessonProvider.invalidateQuestionsAttempts(lessonId, siteId));
promises.push(this.lessonProvider.invalidatePagesPossibleJumps(lessonId, siteId));
promises.push(this.lessonProvider.invalidateTimers(lessonId, siteId));
return this.utils.allPromises(promises).catch(() => {
// Ignore errors. // Ignore errors.
}).then(() => {
// Sync successful, update some data that might have been modified.
return this.lessonProvider.getAccessInformation(lessonId, false, false, siteId).then((info) => {
const promises = [],
retake = info.attemptscount;
promises.push(this.lessonProvider.getContentPagesViewedOnline(lessonId, retake, false, false, siteId));
promises.push(this.lessonProvider.getQuestionsAttemptsOnline(lessonId, retake, false, undefined, false,
false, siteId));
return Promise.all(promises);
}).catch(() => {
// Ignore errors.
});
}); });
} }
}).then(() => { }).then(() => {

View File

@ -14,6 +14,7 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreEventsProvider } from '@providers/events';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites'; import { CoreSitesProvider, CoreSiteSchema } from '@providers/sites';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -113,6 +114,7 @@ export interface AddonModLessonGrade {
@Injectable() @Injectable()
export class AddonModLessonProvider { export class AddonModLessonProvider {
static COMPONENT = 'mmaModLesson'; static COMPONENT = 'mmaModLesson';
static DATA_SENT_EVENT = 'addon_mod_lesson_data_sent';
// This page. // This page.
static LESSON_THISPAGE = 0; static LESSON_THISPAGE = 0;
@ -186,7 +188,8 @@ export class AddonModLessonProvider {
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider,
private translate: TranslateService, private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider,
private lessonOfflineProvider: AddonModLessonOfflineProvider, private logHelper: CoreCourseLogHelperProvider) { private lessonOfflineProvider: AddonModLessonOfflineProvider, private logHelper: CoreCourseLogHelperProvider,
private eventsProvider: CoreEventsProvider) {
this.logger = logger.getInstance('AddonModLessonProvider'); this.logger = logger.getInstance('AddonModLessonProvider');
this.sitesProvider.registerSiteSchema(this.siteSchema); this.sitesProvider.registerSiteSchema(this.siteSchema);
@ -1087,7 +1090,17 @@ export class AddonModLessonProvider {
}); });
} }
return this.finishRetakeOnline(lesson.id, password, outOfTime, review, siteId); return this.finishRetakeOnline(lesson.id, password, outOfTime, review, siteId).then((response) => {
this.eventsProvider.trigger(AddonModLessonProvider.DATA_SENT_EVENT, {
lessonId: lesson.id,
type: 'finish',
courseId: courseId,
outOfTime: outOfTime,
review: review
}, this.sitesProvider.getCurrentSiteId());
return response;
});
} }
/** /**
@ -1363,11 +1376,12 @@ export class AddonModLessonProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} cmid Course module ID. * @param {number} cmid Course module ID.
* @param {boolean} [forceCache] Whether it should always return cached data. * @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the lesson is retrieved. * @return {Promise<any>} Promise resolved when the lesson is retrieved.
*/ */
getLesson(courseId: number, cmId: number, forceCache?: boolean, siteId?: string): Promise<any> { getLesson(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getLessonByField(courseId, 'coursemodule', cmId, forceCache, siteId); return this.getLessonByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId);
} }
/** /**
@ -1377,10 +1391,12 @@ export class AddonModLessonProvider {
* @param {string} key Name of the property to check. * @param {string} key Name of the property to check.
* @param {any} value Value to search. * @param {any} value Value to search.
* @param {boolean} [forceCache] Whether it should always return cached data. * @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the lesson is retrieved. * @return {Promise<any>} Promise resolved when the lesson is retrieved.
*/ */
protected getLessonByField(courseId: number, key: string, value: any, forceCache?: boolean, siteId?: string): Promise<any> { protected getLessonByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
@ -1392,6 +1408,9 @@ export class AddonModLessonProvider {
if (forceCache) { if (forceCache) {
preSets.omitExpires = true; preSets.omitExpires = true;
} else if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
} }
return site.read('mod_lesson_get_lessons_by_courses', params, preSets).then((response) => { return site.read('mod_lesson_get_lessons_by_courses', params, preSets).then((response) => {
@ -1416,11 +1435,12 @@ export class AddonModLessonProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} id Lesson ID. * @param {number} id Lesson ID.
* @param {boolean} [forceCache] Whether it should always return cached data. * @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the lesson is retrieved. * @return {Promise<any>} Promise resolved when the lesson is retrieved.
*/ */
getLessonById(courseId: number, id: number, forceCache?: boolean, siteId?: string): Promise<any> { getLessonById(courseId: number, id: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getLessonByField(courseId, 'id', id, forceCache, siteId); return this.getLessonByField(courseId, 'id', id, forceCache, ignoreCache, siteId);
} }
/** /**
@ -2758,7 +2778,14 @@ export class AddonModLessonProvider {
params.pageid = pageId; params.pageid = pageId;
} }
return site.write('mod_lesson_launch_attempt', params); return site.write('mod_lesson_launch_attempt', params).then((response) => {
this.eventsProvider.trigger(AddonModLessonProvider.DATA_SENT_EVENT, {
lessonId: id,
type: 'launch'
}, this.sitesProvider.getCurrentSiteId());
return response;
});
}); });
} }
@ -3028,7 +3055,17 @@ export class AddonModLessonProvider {
}); });
} }
return this.processPageOnline(lesson.id, pageId, data, password, review, siteId); return this.processPageOnline(lesson.id, pageId, data, password, review, siteId).then((response) => {
this.eventsProvider.trigger(AddonModLessonProvider.DATA_SENT_EVENT, {
lessonId: lesson.id,
type: 'process',
courseId: courseId,
pageId: pageId,
review: review
}, this.sitesProvider.getCurrentSiteId());
return response;
});
} }
/** /**

View File

@ -83,7 +83,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
password, password,
result; result;
return this.lessonProvider.getLesson(courseId, module.id, false, siteId).then((lessonData) => { return this.lessonProvider.getLesson(courseId, module.id, false, false, siteId).then((lessonData) => {
lesson = lessonData; lesson = lessonData;
// Get the lesson password if it's needed. // Get the lesson password if it's needed.
@ -190,7 +190,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
const siteId = this.sitesProvider.getCurrentSiteId(); const siteId = this.sitesProvider.getCurrentSiteId();
// Invalidate data to determine if module is downloadable. // Invalidate data to determine if module is downloadable.
return this.lessonProvider.getLesson(courseId, module.id, false, siteId).then((lesson) => { return this.lessonProvider.getLesson(courseId, module.id, true, false, siteId).then((lesson) => {
const promises = []; const promises = [];
promises.push(this.lessonProvider.invalidateLessonData(courseId, siteId)); promises.push(this.lessonProvider.invalidateLessonData(courseId, siteId));
@ -210,7 +210,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
isDownloadable(module: any, courseId: number): boolean | Promise<boolean> { isDownloadable(module: any, courseId: number): boolean | Promise<boolean> {
const siteId = this.sitesProvider.getCurrentSiteId(); const siteId = this.sitesProvider.getCurrentSiteId();
return this.lessonProvider.getLesson(courseId, module.id, false, siteId).then((lesson) => { return this.lessonProvider.getLesson(courseId, module.id, false, false, siteId).then((lesson) => {
if (!this.lessonProvider.isLessonOffline(lesson)) { if (!this.lessonProvider.isLessonOffline(lesson)) {
return false; return false;
} }
@ -260,7 +260,7 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
password, password,
accessInfo; accessInfo;
return this.lessonProvider.getLesson(courseId, module.id, false, siteId).then((lessonData) => { return this.lessonProvider.getLesson(courseId, module.id, false, true, siteId).then((lessonData) => {
lesson = lessonData; lesson = lessonData;
// Get the lesson password if it's needed. // Get the lesson password if it's needed.
@ -360,7 +360,8 @@ export class AddonModLessonPrefetchHandler extends CoreCourseActivityPrefetchHan
if (accessInfo.canviewreports) { if (accessInfo.canviewreports) {
// Prefetch reports data. // Prefetch reports data.
promises.push(this.groupsProvider.getActivityAllowedGroupsIfEnabled(module.id, undefined, siteId).then((groups) => { promises.push(this.groupsProvider.getActivityAllowedGroupsIfEnabled(module.id, undefined, siteId, true)
.then((groups) => {
const subPromises = []; const subPromises = [];
groups.forEach((group) => { groups.forEach((group) => {

View File

@ -50,6 +50,21 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils); super(translate, appProvider, utils, courseProvider, filepoolProvider, sitesProvider, domUtils);
} }
/**
* Download the module.
*
* @param {any} module The module object returned by WS.
* @param {number} courseId Course ID.
* @param {string} [dirPath] Path of the directory where to store all the content files.
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
* @param {boolean} [canStart=true] If true, start a new attempt if needed.
* @return {Promise<any>} Promise resolved when all content is downloaded.
*/
download(module: any, courseId: number, dirPath?: string, single?: boolean, canStart: boolean = true): Promise<any> {
// Same implementation for download and prefetch.
return this.prefetch(module, courseId, single, dirPath, canStart);
}
/** /**
* Get list of files. If not defined, we'll assume they're in module.contents. * Get list of files. If not defined, we'll assume they're in module.contents.
* *
@ -190,7 +205,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
const siteId = this.sitesProvider.getCurrentSiteId(); const siteId = this.sitesProvider.getCurrentSiteId();
return this.quizProvider.getQuiz(courseId, module.id, false, siteId).then((quiz) => { return this.quizProvider.getQuiz(courseId, module.id, false, false, siteId).then((quiz) => {
if (quiz.allowofflineattempts !== 1 || quiz.hasquestions === 0) { if (quiz.allowofflineattempts !== 1 || quiz.hasquestions === 0) {
return false; return false;
} }
@ -220,10 +235,11 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section.
* @param {string} [dirPath] Path of the directory where to store all the content files. * @param {string} [dirPath] Path of the directory where to store all the content files.
* @param {boolean} [canStart=true] If true, start a new attempt if needed.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string): Promise<any> { prefetch(module: any, courseId?: number, single?: boolean, dirPath?: string, canStart: boolean = true): Promise<any> {
return this.prefetchPackage(module, courseId, single, this.prefetchQuiz.bind(this)); return this.prefetchPackage(module, courseId, single, this.prefetchQuiz.bind(this), undefined, canStart);
} }
/** /**
@ -233,9 +249,10 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
* @param {number} courseId Course ID the module belongs to. * @param {number} courseId Course ID the module belongs to.
* @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section. * @param {boolean} single True if we're downloading a single module, false if we're downloading a whole section.
* @param {String} siteId Site ID. * @param {String} siteId Site ID.
* @param {boolean} canStart If true, start a new attempt if needed.
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
protected prefetchQuiz(module: any, courseId: number, single: boolean, siteId: string): Promise<any> { protected prefetchQuiz(module: any, courseId: number, single: boolean, siteId: string, canStart: boolean): Promise<any> {
let attempts: any[], let attempts: any[],
startAttempt = false, startAttempt = false,
quiz, quiz,
@ -244,7 +261,7 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
preflightData; preflightData;
// Get quiz. // Get quiz.
return this.quizProvider.getQuiz(courseId, module.id, false, siteId).then((quizData) => { return this.quizProvider.getQuiz(courseId, module.id, false, true, siteId).then((quizData) => {
quiz = quizData; quiz = quizData;
const promises = [], const promises = [],
@ -272,7 +289,13 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
}).then(() => { }).then(() => {
// Check if we need to start a new attempt. // Check if we need to start a new attempt.
let attempt = attempts[attempts.length - 1]; let attempt = attempts[attempts.length - 1];
if (!attempt || this.quizProvider.isAttemptFinished(attempt.state)) {
if (!canStart && !attempt) {
// No attempts and we won't start a new one, so we don't need preflight data.
return;
}
if (canStart && (!attempt || this.quizProvider.isAttemptFinished(attempt.state))) {
// Check if the user can attempt the quiz. // Check if the user can attempt the quiz.
if (attemptAccessInfo.preventnewattemptreasons.length) { if (attemptAccessInfo.preventnewattemptreasons.length) {
return Promise.reject(this.textUtils.buildMessage(attemptAccessInfo.preventnewattemptreasons)); return Promise.reject(this.textUtils.buildMessage(attemptAccessInfo.preventnewattemptreasons));
@ -331,6 +354,11 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
return Promise.all(promises); return Promise.all(promises);
}).then(() => { }).then(() => {
if (!canStart) {
// Nothing else to do.
return;
}
// If there's nothing to send, mark the quiz as synchronized. // If there's nothing to send, mark the quiz as synchronized.
// We don't return the promises because it should be fast and we don't want to block the user for this. // We don't return the promises because it should be fast and we don't want to block the user for this.
if (!this.syncProvider) { if (!this.syncProvider) {
@ -477,14 +505,49 @@ export class AddonModQuizPrefetchHandler extends CoreCourseActivityPrefetchHandl
return this.prefetchAttempt(quiz, lastAttempt, preflightData, siteId); return this.prefetchAttempt(quiz, lastAttempt, preflightData, siteId);
} }
}).then(() => { }).then(() => {
// Prefetch finished, get current status to determine if we need to change it. // Prefetch finished, set the right status.
return this.setStatusAfterPrefetch(quiz, attempts, true, false, siteId);
});
}
/**
* Set the right status to a quiz after prefetching.
* If the last attempt is finished or there isn't one, set it as not downloaded to show download icon.
*
* @param {any} quiz Quiz.
* @param {any[]} [attempts] List of attempts. If not provided, they will be calculated.
* @param {boolean} [forceCache] Whether it should always return cached data. Only if attempts is undefined.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down). Only if
* attempts is undefined.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
setStatusAfterPrefetch(quiz: any, attempts?: any[], forceCache?: boolean, ignoreCache?: boolean, siteId?: string)
: Promise<any> {
siteId = siteId || this.sitesProvider.getCurrentSiteId();
const promises = [];
let status;
if (!attempts) {
// Get the attempts.
promises.push(this.quizProvider.getUserAttempts(quiz.id, 'all', true, forceCache, ignoreCache, siteId).then((atts) => {
attempts = atts;
}));
}
// Check the current status of the quiz.
promises.push(this.filepoolProvider.getPackageStatus(siteId, this.component, quiz.coursemodule).then((stat) => {
status = stat;
}));
return Promise.all(promises).then(() => {
return this.filepoolProvider.getPackageStatus(siteId, this.component, quiz.coursemodule);
}).then((status) => {
if (status !== CoreConstants.NOT_DOWNLOADED) { if (status !== CoreConstants.NOT_DOWNLOADED) {
// Quiz was downloaded, set the new status. // Quiz was downloaded, set the new status.
// If no attempts or last is finished we'll mark it as not downloaded to show download icon. // If no attempts or last is finished we'll mark it as not downloaded to show download icon.
const isLastFinished = !lastAttempt || this.quizProvider.isAttemptFinished(lastAttempt.state), const lastAttempt = attempts[attempts.length - 1],
isLastFinished = !lastAttempt || this.quizProvider.isAttemptFinished(lastAttempt.state),
newStatus = isLastFinished ? CoreConstants.NOT_DOWNLOADED : CoreConstants.DOWNLOADED; newStatus = isLastFinished ? CoreConstants.NOT_DOWNLOADED : CoreConstants.DOWNLOADED;
return this.filepoolProvider.storePackageStatus(siteId, newStatus, this.component, quiz.coursemodule); return this.filepoolProvider.storePackageStatus(siteId, newStatus, this.component, quiz.coursemodule);

View File

@ -23,9 +23,10 @@ import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time'; import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreQuestionProvider } from '@core/question/providers/question'; import { CoreQuestionProvider } from '@core/question/providers/question';
import { CoreQuestionDelegate } from '@core/question/providers/delegate'; import { CoreQuestionDelegate } from '@core/question/providers/delegate';
import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync';
import { AddonModQuizProvider } from './quiz'; import { AddonModQuizProvider } from './quiz';
import { AddonModQuizOfflineProvider } from './quiz-offline'; import { AddonModQuizOfflineProvider } from './quiz-offline';
import { AddonModQuizPrefetchHandler } from './prefetch-handler'; import { AddonModQuizPrefetchHandler } from './prefetch-handler';
@ -51,7 +52,7 @@ export interface AddonModQuizSyncResult {
* Service to sync quizzes. * Service to sync quizzes.
*/ */
@Injectable() @Injectable()
export class AddonModQuizSyncProvider extends CoreSyncBaseProvider { export class AddonModQuizSyncProvider extends CoreCourseActivitySyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_quiz_autom_synced'; static AUTO_SYNCED = 'addon_mod_quiz_autom_synced';
@ -59,13 +60,14 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider, constructor(loggerProvider: CoreLoggerProvider, sitesProvider: CoreSitesProvider, appProvider: CoreAppProvider,
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
courseProvider: CoreCourseProvider, private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider,
private quizProvider: AddonModQuizProvider, private quizOfflineProvider: AddonModQuizOfflineProvider, private quizProvider: AddonModQuizProvider, private quizOfflineProvider: AddonModQuizOfflineProvider,
private prefetchHandler: AddonModQuizPrefetchHandler, private questionProvider: CoreQuestionProvider, protected prefetchHandler: AddonModQuizPrefetchHandler, private questionProvider: CoreQuestionProvider,
private questionDelegate: CoreQuestionDelegate, private logHelper: CoreCourseLogHelperProvider) { private questionDelegate: CoreQuestionDelegate, private logHelper: CoreCourseLogHelperProvider,
prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider) {
super('AddonModQuizSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, super('AddonModQuizSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils); timeUtils, prefetchDelegate, prefetchHandler);
this.componentTranslate = courseProvider.translateModuleName('quiz'); this.componentTranslate = courseProvider.translateModuleName('quiz');
} }
@ -97,7 +99,11 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
}).then(() => { }).then(() => {
if (updated) { if (updated) {
// Data has been sent. Update prefetched data. // Data has been sent. Update prefetched data.
return this.prefetchHandler.prefetchQuizAndLastAttempt(quiz, false, siteId); return this.courseProvider.getModuleBasicInfoByInstance(quiz.id, 'quiz', siteId).then((module) => {
return this.prefetchAfterUpdateQuiz(module, quiz, courseId, undefined, siteId);
}).catch(() => {
// Ignore errors.
});
} }
}).then(() => { }).then(() => {
return this.setSyncTime(quiz.id, siteId).catch(() => { return this.setSyncTime(quiz.id, siteId).catch(() => {
@ -145,6 +151,41 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
}); });
} }
/**
* Conveniece function to prefetch data after an update.
*
* @param {any} module Module.
* @param {any} quiz Quiz.
* @param {number} courseId Course ID.
* @param {RegExp} [regex] If regex matches, don't download the data. Defaults to check files.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
prefetchAfterUpdateQuiz(module: any, quiz: any, courseId: number, regex?: RegExp, siteId?: string): Promise<any> {
regex = regex || /^.*files$/;
let shouldDownload;
// Get the module updates to check if the data was updated or not.
return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => {
if (result && result.updates && result.updates.length > 0) {
// Only prefetch if files haven't changed.
shouldDownload = !result.updates.find((entry) => {
return entry.name.match(regex);
});
if (shouldDownload) {
return this.prefetchHandler.download(module, courseId, undefined, false, false);
}
}
}).then(() => {
// Prefetch finished or not needed, set the right status.
return this.prefetchHandler.setStatusAfterPrefetch(quiz, undefined, shouldDownload, false, siteId);
});
}
/** /**
* Try to synchronize all the quizzes in a certain site or in all sites. * Try to synchronize all the quizzes in a certain site or in all sites.
* *
@ -185,7 +226,7 @@ export class AddonModQuizSyncProvider extends CoreSyncBaseProvider {
if (!this.syncProvider.isBlocked(AddonModQuizProvider.COMPONENT, quiz.id, siteId)) { if (!this.syncProvider.isBlocked(AddonModQuizProvider.COMPONENT, quiz.id, siteId)) {
// Quiz not blocked, try to synchronize it. // Quiz not blocked, try to synchronize it.
promises.push(this.quizProvider.getQuizById(quiz.courseid, quiz.id, false, siteId).then((quiz) => { promises.push(this.quizProvider.getQuizById(quiz.courseid, quiz.id, false, false, siteId).then((quiz) => {
return this.syncQuizIfNeeded(quiz, false, siteId).then((data) => { return this.syncQuizIfNeeded(quiz, false, siteId).then((data) => {
if (data && data.warnings && data.warnings.length) { if (data && data.warnings && data.warnings.length) {
// Store the warnings to show them when the user opens the quiz. // Store the warnings to show them when the user opens the quiz.

View File

@ -672,10 +672,13 @@ export class AddonModQuizProvider {
* @param {string} key Name of the property to check. * @param {string} key Name of the property to check.
* @param {any} value Value to search. * @param {any} value Value to search.
* @param {boolean} [forceCache] Whether it should always return cached data. * @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the Quiz is retrieved. * @return {Promise<any>} Promise resolved when the Quiz is retrieved.
*/ */
protected getQuizByField(courseId: number, key: string, value: any, forceCache?: boolean, siteId?: string): Promise<any> { protected getQuizByField(courseId: number, key: string, value: any, forceCache?: boolean, ignoreCache?: boolean,
siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
courseids: [courseId] courseids: [courseId]
@ -686,6 +689,9 @@ export class AddonModQuizProvider {
if (forceCache) { if (forceCache) {
preSets.omitExpires = true; preSets.omitExpires = true;
} else if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
} }
return site.read('mod_quiz_get_quizzes_by_courses', params, preSets).then((response) => { return site.read('mod_quiz_get_quizzes_by_courses', params, preSets).then((response) => {
@ -710,11 +716,12 @@ export class AddonModQuizProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} cmId Course module ID. * @param {number} cmId Course module ID.
* @param {boolean} [forceCache] Whether it should always return cached data. * @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the quiz is retrieved. * @return {Promise<any>} Promise resolved when the quiz is retrieved.
*/ */
getQuiz(courseId: number, cmId: number, forceCache?: boolean, siteId?: string): Promise<any> { getQuiz(courseId: number, cmId: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getQuizByField(courseId, 'coursemodule', cmId, forceCache, siteId); return this.getQuizByField(courseId, 'coursemodule', cmId, forceCache, ignoreCache, siteId);
} }
/** /**
@ -723,11 +730,12 @@ export class AddonModQuizProvider {
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} id Quiz ID. * @param {number} id Quiz ID.
* @param {boolean} [forceCache] Whether it should always return cached data. * @param {boolean} [forceCache] Whether it should always return cached data.
* @param {boolean} [ignoreCache] Whether it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the quiz is retrieved. * @return {Promise<any>} Promise resolved when the quiz is retrieved.
*/ */
getQuizById(courseId: number, id: number, forceCache?: boolean, siteId?: string): Promise<any> { getQuizById(courseId: number, id: number, forceCache?: boolean, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getQuizByField(courseId, 'id', id, forceCache, siteId); return this.getQuizByField(courseId, 'id', id, forceCache, ignoreCache, siteId);
} }
/** /**
@ -1223,7 +1231,7 @@ export class AddonModQuizProvider {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Get required data to call the invalidate functions. // Get required data to call the invalidate functions.
return this.getQuiz(courseId, moduleId, false, siteId).then((quiz) => { return this.getQuiz(courseId, moduleId, true, false, siteId).then((quiz) => {
return this.getUserAttempts(quiz.id, 'all', true, false, false, siteId).then((attempts) => { return this.getUserAttempts(quiz.id, 'all', true, false, false, siteId).then((attempts) => {
// Now invalidate it. // Now invalidate it.
const lastAttemptId = attempts.length ? attempts[attempts.length - 1].id : undefined; const lastAttemptId = attempts.length ? attempts[attempts.length - 1].id : undefined;

View File

@ -25,7 +25,7 @@ import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate'; import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreSyncBaseProvider } from '@classes/base-sync'; import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync';
import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm'; import { AddonModScormProvider, AddonModScormAttemptCountResult } from './scorm';
import { AddonModScormOfflineProvider } from './scorm-offline'; import { AddonModScormOfflineProvider } from './scorm-offline';
import { AddonModScormPrefetchHandler } from './prefetch-handler'; import { AddonModScormPrefetchHandler } from './prefetch-handler';
@ -57,7 +57,7 @@ export interface AddonModScormSyncResult {
* Service to sync SCORMs. * Service to sync SCORMs.
*/ */
@Injectable() @Injectable()
export class AddonModScormSyncProvider extends CoreSyncBaseProvider { export class AddonModScormSyncProvider extends CoreCourseActivitySyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_scorm_autom_synced'; static AUTO_SYNCED = 'addon_mod_scorm_autom_synced';
@ -67,12 +67,12 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider {
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, timeUtils: CoreTimeUtilsProvider,
private scormProvider: AddonModScormProvider, private scormOfflineProvider: AddonModScormOfflineProvider, private scormProvider: AddonModScormProvider, private scormOfflineProvider: AddonModScormOfflineProvider,
private prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider, prefetchHandler: AddonModScormPrefetchHandler, private utils: CoreUtilsProvider,
private prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider, prefetchDelegate: CoreCourseModulePrefetchDelegate, private courseProvider: CoreCourseProvider,
private logHelper: CoreCourseLogHelperProvider) { private logHelper: CoreCourseLogHelperProvider) {
super('AddonModScormSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, super('AddonModScormSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils); timeUtils, prefetchDelegate, prefetchHandler);
this.componentTranslate = courseProvider.translateModuleName('scorm'); this.componentTranslate = courseProvider.translateModuleName('scorm');
} }
@ -196,7 +196,7 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider {
if (updated) { if (updated) {
// Update downloaded data. // Update downloaded data.
promise = this.courseProvider.getModuleBasicInfoByInstance(scorm.id, 'scorm', siteId).then((module) => { promise = this.courseProvider.getModuleBasicInfoByInstance(scorm.id, 'scorm', siteId).then((module) => {
return this.prefetchAfterUpdate(module, scorm.course); return this.prefetchAfterUpdate(module, scorm.course, undefined, siteId);
}).catch(() => { }).catch(() => {
// Ignore errors. // Ignore errors.
}); });
@ -361,31 +361,6 @@ export class AddonModScormSyncProvider extends CoreSyncBaseProvider {
}); });
} }
/**
* Prefetch data after an update. It won't prefetch the data if the package file was updated.
*
* @param {any} module Module.
* @param {number} courseId Course ID.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
prefetchAfterUpdate(module: any, courseId: number, siteId?: string): Promise<any> {
// Get the module updates to check if the package was updated or not.
return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => {
if (result && result.updates) {
// Only prefetch if the package file hasn't changed.
const fileChanged = !!result.updates.find((entry) => {
return entry.name == 'packagefiles';
});
if (!fileChanged) {
return this.prefetchHandler.download(module, courseId);
}
}
});
}
/** /**
* Save a snapshot from a synchronization. * Save a snapshot from a synchronization.
* *

View File

@ -188,8 +188,20 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
}); });
} }
return this.surveyProvider.submitAnswers(this.survey.id, this.survey.name, this.courseId, answers).then(() => { return this.surveyProvider.submitAnswers(this.survey.id, this.survey.name, this.courseId, answers).then((online) => {
return this.showLoadingAndRefresh(false); if (online && this.isPrefetched()) {
// The survey is downloaded, update the data.
return this.surveySync.prefetchAfterUpdate(this.module, this.courseId).then(() => {
// Update the view.
this.showLoadingAndFetch(false, false);
}).catch((error) => {
// Prefetch failed, refresh the data.
return this.showLoadingAndRefresh(false);
});
} else {
// Not downloaded, refresh the data.
return this.showLoadingAndRefresh(false);
}
}).finally(() => { }).finally(() => {
modal.dismiss(); modal.dismiss();
}); });

View File

@ -111,7 +111,7 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan
* @return {Promise<any>} Promise resolved when done. * @return {Promise<any>} Promise resolved when done.
*/ */
protected prefetchSurvey(module: any, courseId: number, single: boolean, siteId: string): Promise<any> { protected prefetchSurvey(module: any, courseId: number, single: boolean, siteId: string): Promise<any> {
return this.surveyProvider.getSurvey(courseId, module.id).then((survey) => { return this.surveyProvider.getSurvey(courseId, module.id, true, siteId).then((survey) => {
const promises = [], const promises = [],
files = this.getIntroFilesFromInstance(module, survey); files = this.getIntroFilesFromInstance(module, survey);
@ -120,7 +120,7 @@ export class AddonModSurveyPrefetchHandler extends CoreCourseActivityPrefetchHan
// If survey isn't answered, prefetch the questions. // If survey isn't answered, prefetch the questions.
if (!survey.surveydone) { if (!survey.surveydone) {
promises.push(this.surveyProvider.getQuestions(survey.id)); promises.push(this.surveyProvider.getQuestions(survey.id, true, siteId));
} }
return Promise.all(promises); return Promise.all(promises);

View File

@ -20,6 +20,7 @@ import { CoreAppProvider } from '@providers/app';
import { CoreFilepoolProvider } from '@providers/filepool'; import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { AddonModSurveyOfflineProvider } from './offline'; import { AddonModSurveyOfflineProvider } from './offline';
import { CoreSiteWSPreSets } from '@classes/site';
/** /**
* Service that provides some features for surveys. * Service that provides some features for surveys.
@ -41,18 +42,24 @@ export class AddonModSurveyProvider {
* Get a survey's questions. * Get a survey's questions.
* *
* @param {number} surveyId Survey ID. * @param {number} surveyId Survey ID.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the questions are retrieved. * @return {Promise<any>} Promise resolved when the questions are retrieved.
*/ */
getQuestions(surveyId: number, siteId?: string): Promise<any> { getQuestions(surveyId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
surveyid: surveyId surveyid: surveyId
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getQuestionsCacheKey(surveyId) cacheKey: this.getQuestionsCacheKey(surveyId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_survey_get_questions', params, preSets).then((response) => { return site.read('mod_survey_get_questions', params, preSets).then((response) => {
if (response.questions) { if (response.questions) {
return response.questions; return response.questions;
@ -87,20 +94,26 @@ export class AddonModSurveyProvider {
* Get a survey data. * Get a survey data.
* *
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {string} key Name of the property to check. * @param {string} key Name of the property to check.
* @param {any} value Value to search. * @param {any} value Value to search.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the survey is retrieved. * @return {Promise<any>} Promise resolved when the survey is retrieved.
*/ */
protected getSurveyDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise<any> { protected getSurveyDataByKey(courseId: number, key: string, value: any, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.sitesProvider.getSite(siteId).then((site) => { return this.sitesProvider.getSite(siteId).then((site) => {
const params = { const params = {
courseids: [courseId] courseids: [courseId]
}, },
preSets = { preSets: CoreSiteWSPreSets = {
cacheKey: this.getSurveyCacheKey(courseId) cacheKey: this.getSurveyCacheKey(courseId)
}; };
if (ignoreCache) {
preSets.getFromCache = false;
preSets.emergencyCache = false;
}
return site.read('mod_survey_get_surveys_by_courses', params, preSets).then((response) => { return site.read('mod_survey_get_surveys_by_courses', params, preSets).then((response) => {
if (response && response.surveys) { if (response && response.surveys) {
const currentSurvey = response.surveys.find((survey) => { const currentSurvey = response.surveys.find((survey) => {
@ -120,24 +133,26 @@ export class AddonModSurveyProvider {
* Get a survey by course module ID. * Get a survey by course module ID.
* *
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} cmId Course module ID. * @param {number} cmId Course module ID.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the survey is retrieved. * @return {Promise<any>} Promise resolved when the survey is retrieved.
*/ */
getSurvey(courseId: number, cmId: number, siteId?: string): Promise<any> { getSurvey(courseId: number, cmId: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, siteId); return this.getSurveyDataByKey(courseId, 'coursemodule', cmId, ignoreCache, siteId);
} }
/** /**
* Get a survey by ID. * Get a survey by ID.
* *
* @param {number} courseId Course ID. * @param {number} courseId Course ID.
* @param {number} id Survey ID. * @param {number} id Survey ID.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when the survey is retrieved. * @return {Promise<any>} Promise resolved when the survey is retrieved.
*/ */
getSurveyById(courseId: number, id: number, siteId?: string): Promise<any> { getSurveyById(courseId: number, id: number, ignoreCache?: boolean, siteId?: string): Promise<any> {
return this.getSurveyDataByKey(courseId, 'id', id, siteId); return this.getSurveyDataByKey(courseId, 'id', id, ignoreCache, siteId);
} }
/** /**

View File

@ -15,7 +15,6 @@
import { Injectable } from '@angular/core'; import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger'; import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites'; import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreAppProvider } from '@providers/app'; import { CoreAppProvider } from '@providers/app';
import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreTextUtilsProvider } from '@providers/utils/text'; import { CoreTextUtilsProvider } from '@providers/utils/text';
@ -26,13 +25,16 @@ import { CoreEventsProvider } from '@providers/events';
import { TranslateService } from '@ngx-translate/core'; import { TranslateService } from '@ngx-translate/core';
import { CoreCourseProvider } from '@core/course/providers/course'; import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper'; import { CoreCourseLogHelperProvider } from '@core/course/providers/log-helper';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreCourseActivitySyncBaseProvider } from '@core/course/classes/activity-sync';
import { CoreSyncProvider } from '@providers/sync'; import { CoreSyncProvider } from '@providers/sync';
import { AddonModSurveyPrefetchHandler } from './prefetch-handler';
/** /**
* Service to sync surveys. * Service to sync surveys.
*/ */
@Injectable() @Injectable()
export class AddonModSurveySyncProvider extends CoreSyncBaseProvider { export class AddonModSurveySyncProvider extends CoreCourseActivitySyncBaseProvider {
static AUTO_SYNCED = 'addon_mod_survey_autom_synced'; static AUTO_SYNCED = 'addon_mod_survey_autom_synced';
protected componentTranslate: string; protected componentTranslate: string;
@ -41,10 +43,11 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider {
syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService, syncProvider: CoreSyncProvider, textUtils: CoreTextUtilsProvider, translate: TranslateService,
courseProvider: CoreCourseProvider, private surveyOffline: AddonModSurveyOfflineProvider, courseProvider: CoreCourseProvider, private surveyOffline: AddonModSurveyOfflineProvider,
private eventsProvider: CoreEventsProvider, private surveyProvider: AddonModSurveyProvider, private eventsProvider: CoreEventsProvider, private surveyProvider: AddonModSurveyProvider,
private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider, private logHelper: CoreCourseLogHelperProvider) { private utils: CoreUtilsProvider, timeUtils: CoreTimeUtilsProvider, private logHelper: CoreCourseLogHelperProvider,
prefetchDelegate: CoreCourseModulePrefetchDelegate, prefetchHandler: AddonModSurveyPrefetchHandler) {
super('AddonModSurveySyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, super('AddonModSurveySyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate,
timeUtils); timeUtils, prefetchDelegate, prefetchHandler);
this.componentTranslate = courseProvider.translateModuleName('survey'); this.componentTranslate = courseProvider.translateModuleName('survey');
} }
@ -57,7 +60,7 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider {
* @return {string} Sync ID. * @return {string} Sync ID.
* @protected * @protected
*/ */
getSyncId (surveyId: number, userId: number): string { getSyncId(surveyId: number, userId: number): string {
return surveyId + '#' + userId; return surveyId + '#' + userId;
} }
@ -192,9 +195,7 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider {
}).then(() => { }).then(() => {
if (courseId) { if (courseId) {
// Data has been sent to server, update survey data. // Data has been sent to server, update survey data.
return this.surveyProvider.invalidateSurveyData(courseId, siteId).then(() => { return this.prefetchAfterUpdate(module, courseId, undefined, siteId).catch(() => {
return this.surveyProvider.getSurveyById(courseId, surveyId, siteId);
}).catch(() => {
// Ignore errors. // Ignore errors.
}); });
} }

View File

@ -84,4 +84,20 @@ export class AddonQtypeTrueFalseHandler implements CoreQuestionHandler {
isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean { isSameResponse(question: any, prevAnswers: any, newAnswers: any): boolean {
return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer'); return this.utils.sameAtKeyMissingIsBlank(prevAnswers, newAnswers, 'answer');
} }
/**
* Prepare and add to answers the data to send to server based in the input. Return promise if async.
*
* @param {any} question Question.
* @param {any} answers The answers retrieved from the form. Prepared answers must be stored in this object.
* @param {boolean} [offline] Whether the data should be saved in offline.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {void|Promise<any>} Return a promise resolved when done if async, void if sync.
*/
prepareAnswers(question: any, answers: any, offline: boolean, siteId?: string): void | Promise<any> {
if (question && typeof answers[question.optionsName] != 'undefined' && !answers[question.optionsName]) {
// The user hasn't answered. Delete the answer to prevent marking one of the answers automatically.
delete answers[question.optionsName];
}
}
} }

View File

@ -46,9 +46,6 @@ export class CoreSyncBaseProvider {
// Store sync promises. // Store sync promises.
protected syncPromises: { [siteId: string]: { [uniqueId: string]: Promise<any> } } = {}; protected syncPromises: { [siteId: string]: { [uniqueId: string]: Promise<any> } } = {};
// List of services that will be injected using injector.
// It's done like this so subclasses don't have to send all the services to the parent in the constructor.
constructor(component: string, loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, constructor(component: string, loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
protected appProvider: CoreAppProvider, protected syncProvider: CoreSyncProvider, protected appProvider: CoreAppProvider, protected syncProvider: CoreSyncProvider,
protected textUtils: CoreTextUtilsProvider, protected translate: TranslateService, protected textUtils: CoreTextUtilsProvider, protected translate: TranslateService,

View File

@ -0,0 +1,67 @@
// (C) Copyright 2015 Martin Dougiamas
//
// 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 { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from '@providers/sites';
import { CoreSyncProvider } from '@providers/sync';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreAppProvider } from '@providers/app';
import { CoreTextUtilsProvider } from '@providers/utils/text';
import { CoreTimeUtilsProvider } from '@providers/utils/time';
import { CoreSyncBaseProvider } from '@classes/base-sync';
import { CoreCourseModulePrefetchDelegate } from '@core/course/providers/module-prefetch-delegate';
import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler';
/**
* Base class to create activity sync providers. It provides some common functions.
*/
export class CoreCourseActivitySyncBaseProvider extends CoreSyncBaseProvider {
constructor(component: string, loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider,
protected appProvider: CoreAppProvider, protected syncProvider: CoreSyncProvider,
protected textUtils: CoreTextUtilsProvider, protected translate: TranslateService,
protected timeUtils: CoreTimeUtilsProvider, protected prefetchDelegate: CoreCourseModulePrefetchDelegate,
protected prefetchHandler: CoreCourseModulePrefetchHandlerBase) {
super(component, loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate, timeUtils);
}
/**
* Conveniece function to prefetch data after an update.
*
* @param {any} module Module.
* @param {number} courseId Course ID.
* @param {RegExp} [regex] If regex matches, don't download the data. Defaults to check files.
* @param {string} [siteId] Site ID. If not defined, current site.
* @return {Promise<any>} Promise resolved when done.
*/
prefetchAfterUpdate(module: any, courseId: number, regex?: RegExp, siteId?: string): Promise<any> {
regex = regex || /^.*files$/;
// Get the module updates to check if the data was updated or not.
return this.prefetchDelegate.getModuleUpdates(module, courseId, true, siteId).then((result) => {
if (result && result.updates && result.updates.length > 0) {
// Only prefetch if files haven't changed.
const shouldDownload = !result.updates.find((entry) => {
return entry.name.match(regex);
});
if (shouldDownload) {
return this.prefetchHandler.download(module, courseId);
}
}
});
}
}

View File

@ -23,6 +23,7 @@ import { CoreCourseModuleMainComponent, CoreCourseModuleDelegate } from '@core/c
import { CoreCourseSectionPage } from '@core/course/pages/section/section.ts'; import { CoreCourseSectionPage } from '@core/course/pages/section/section.ts';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
import { AddonBlogProvider } from '@addon/blog/providers/blog'; import { AddonBlogProvider } from '@addon/blog/providers/blog';
import { CoreConstants } from '@core/constants';
/** /**
* Template class to easily create CoreCourseModuleMainComponent of resources (or activities without syncing). * Template class to easily create CoreCourseModuleMainComponent of resources (or activities without syncing).
@ -42,6 +43,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
description: string; // Module description. description: string; // Module description.
refreshIcon: string; // Refresh icon, normally spinner or refresh. refreshIcon: string; // Refresh icon, normally spinner or refresh.
prefetchStatusIcon: string; // Used when calling fillContextMenu. prefetchStatusIcon: string; // Used when calling fillContextMenu.
prefetchStatus: string; // Used when calling fillContextMenu.
prefetchText: string; // Used when calling fillContextMenu. prefetchText: string; // Used when calling fillContextMenu.
size: string; // Used when calling fillContextMenu. size: string; // Used when calling fillContextMenu.
@ -222,6 +224,14 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
this.courseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component); this.courseHelper.fillContextMenu(this, this.module, this.courseId, refresh, this.component);
} }
/**
* Check if the module is prefetched or being prefetched. To make it faster, just use the data calculated by fillContextMenu.
* This means that you need to call fillContextMenu to make this work.
*/
protected isPrefetched(): boolean {
return this.prefetchStatus != CoreConstants.NOT_DOWNLOADABLE && this.prefetchStatus != CoreConstants.NOT_DOWNLOADED;
}
/** /**
* Expand the description. * Expand the description.
*/ */

View File

@ -745,6 +745,7 @@ export class CoreCourseHelperProvider {
return this.getModulePrefetchInfo(module, courseId, invalidateCache, component).then((moduleInfo) => { return this.getModulePrefetchInfo(module, courseId, invalidateCache, component).then((moduleInfo) => {
instance.size = moduleInfo.size > 0 ? moduleInfo.sizeReadable : 0; instance.size = moduleInfo.size > 0 ? moduleInfo.sizeReadable : 0;
instance.prefetchStatusIcon = moduleInfo.statusIcon; instance.prefetchStatusIcon = moduleInfo.statusIcon;
instance.prefetchStatus = moduleInfo.status;
if (moduleInfo.status != CoreConstants.NOT_DOWNLOADABLE) { if (moduleInfo.status != CoreConstants.NOT_DOWNLOADABLE) {
// Module is downloadable, get the text to display to prefetch. // Module is downloadable, get the text to display to prefetch.

View File

@ -124,16 +124,17 @@ export class CoreGroupsProvider {
* @param {number} cmId Course module ID. * @param {number} cmId Course module ID.
* @param {number} [userId] User ID. If not defined, use current user. * @param {number} [userId] User ID. If not defined, use current user.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<any[]>} Promise resolved when the groups are retrieved. If not allowed, empty array will be returned. * @return {Promise<any[]>} Promise resolved when the groups are retrieved. If not allowed, empty array will be returned.
*/ */
getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string): Promise<any[]> { getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string, ignoreCache?: boolean): Promise<any[]> {
siteId = siteId || this.sitesProvider.getCurrentSiteId(); siteId = siteId || this.sitesProvider.getCurrentSiteId();
// Get real groupmode, in case it's forced by the course. // Get real groupmode, in case it's forced by the course.
return this.activityHasGroups(cmId, siteId).then((hasGroups) => { return this.activityHasGroups(cmId, siteId, ignoreCache).then((hasGroups) => {
if (hasGroups) { if (hasGroups) {
// Get the groups available for the user. // Get the groups available for the user.
return this.getActivityAllowedGroups(cmId, userId, siteId); return this.getActivityAllowedGroups(cmId, userId, siteId, ignoreCache);
} }
return []; return [];
@ -147,19 +148,22 @@ export class CoreGroupsProvider {
* @param {boolean} [addAllParts=true] Whether to add the all participants option. Always true for visible groups. * @param {boolean} [addAllParts=true] Whether to add the all participants option. Always true for visible groups.
* @param {number} [userId] User ID. If not defined, use current user. * @param {number} [userId] User ID. If not defined, use current user.
* @param {string} [siteId] Site ID. If not defined, current site. * @param {string} [siteId] Site ID. If not defined, current site.
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
* @return {Promise<CoreGroupInfo>} Promise resolved with the group info. * @return {Promise<CoreGroupInfo>} Promise resolved with the group info.
*/ */
getActivityGroupInfo(cmId: number, addAllParts: boolean = true, userId?: number, siteId?: string): Promise<CoreGroupInfo> { getActivityGroupInfo(cmId: number, addAllParts: boolean = true, userId?: number, siteId?: string, ignoreCache?: boolean)
: Promise<CoreGroupInfo> {
const groupInfo: CoreGroupInfo = { const groupInfo: CoreGroupInfo = {
groups: [] groups: []
}; };
return this.getActivityGroupMode(cmId, siteId).then((groupMode) => { return this.getActivityGroupMode(cmId, siteId, ignoreCache).then((groupMode) => {
groupInfo.separateGroups = groupMode === CoreGroupsProvider.SEPARATEGROUPS; groupInfo.separateGroups = groupMode === CoreGroupsProvider.SEPARATEGROUPS;
groupInfo.visibleGroups = groupMode === CoreGroupsProvider.VISIBLEGROUPS; groupInfo.visibleGroups = groupMode === CoreGroupsProvider.VISIBLEGROUPS;
if (groupInfo.separateGroups || groupInfo.visibleGroups) { if (groupInfo.separateGroups || groupInfo.visibleGroups) {
return this.getActivityAllowedGroups(cmId, userId, siteId); return this.getActivityAllowedGroups(cmId, userId, siteId, ignoreCache);
} }
return []; return [];