commit
5a61613eec
|
@ -1108,10 +1108,13 @@
|
||||||
"core.course.errorgetmodule": "local_moodlemobileapp",
|
"core.course.errorgetmodule": "local_moodlemobileapp",
|
||||||
"core.course.hiddenfromstudents": "moodle",
|
"core.course.hiddenfromstudents": "moodle",
|
||||||
"core.course.hiddenoncoursepage": "moodle",
|
"core.course.hiddenoncoursepage": "moodle",
|
||||||
|
"core.course.manualcompletionnotsynced": "local_moodlemobileapp",
|
||||||
"core.course.nocontentavailable": "local_moodlemobileapp",
|
"core.course.nocontentavailable": "local_moodlemobileapp",
|
||||||
"core.course.overriddennotice": "grades",
|
"core.course.overriddennotice": "grades",
|
||||||
"core.course.sections": "moodle",
|
"core.course.sections": "moodle",
|
||||||
"core.course.useactivityonbrowser": "local_moodlemobileapp",
|
"core.course.useactivityonbrowser": "local_moodlemobileapp",
|
||||||
|
"core.course.warningmanualcompletionmodified": "local_moodlemobileapp",
|
||||||
|
"core.course.warningofflinemanualcompletiondeleted": "local_moodlemobileapp",
|
||||||
"core.coursedetails": "moodle",
|
"core.coursedetails": "moodle",
|
||||||
"core.courses.allowguests": "enrol_guest",
|
"core.courses.allowguests": "enrol_guest",
|
||||||
"core.courses.availablecourses": "moodle",
|
"core.courses.availablecourses": "moodle",
|
||||||
|
|
|
@ -136,7 +136,7 @@ export class AddonModAssignIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* Get assignment data.
|
* Get assignment data.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -308,7 +308,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error && this.utils.isWebServiceError(error)) {
|
if (error && this.utils.isWebServiceError(error)) {
|
||||||
// A WebService has thrown an error, this means it cannot be submitted. Discard the submission.
|
// A WebService has thrown an error, this means it cannot be submitted. Discard the submission.
|
||||||
discardError = error.message || error.error || error.content || error.body;
|
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||||
} else {
|
} else {
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -402,7 +402,7 @@ export class AddonModAssignSyncProvider extends CoreSyncBaseProvider {
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error && this.utils.isWebServiceError(error)) {
|
if (error && this.utils.isWebServiceError(error)) {
|
||||||
// The WebService has thrown an error, this means it cannot be submitted. Discard the offline data.
|
// The WebService has thrown an error, this means it cannot be submitted. Discard the offline data.
|
||||||
discardError = error.message || error.error || error.content || error.body;
|
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||||
} else {
|
} else {
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
|
|
@ -57,7 +57,7 @@ export class AddonModChatIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
* Download chat.
|
* Download chat.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -112,7 +112,7 @@ export class AddonModChoiceIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* Download choice contents.
|
* Download choice contents.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -176,7 +176,7 @@ export class AddonModChoiceSyncProvider extends CoreSyncBaseProvider {
|
||||||
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.componentTranslate,
|
component: this.componentTranslate,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
error: error.error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -154,7 +154,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
* Download data contents.
|
* Download data contents.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -295,10 +295,10 @@ export class AddonModDataSyncProvider extends CoreSyncBaseProvider {
|
||||||
promises.push(actionPromise.catch((error) => {
|
promises.push(actionPromise.catch((error) => {
|
||||||
if (error && error.wserror) {
|
if (error && error.wserror) {
|
||||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||||
discardError = error.error;
|
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||||
} else {
|
} else {
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error && error.error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Delete the offline data.
|
// Delete the offline data.
|
||||||
|
|
|
@ -147,7 +147,7 @@ export class AddonModFeedbackIndexComponent extends CoreCourseModuleMainActivity
|
||||||
* Download feedback contents.
|
* Download feedback contents.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -251,12 +251,12 @@ export class AddonModFeedbackSyncProvider extends CoreSyncBaseProvider {
|
||||||
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.componentTranslate,
|
component: this.componentTranslate,
|
||||||
name: feedback.name,
|
name: feedback.name,
|
||||||
error: error.error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error && error.error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
|
@ -227,7 +227,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider {
|
||||||
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.componentTranslate,
|
component: this.componentTranslate,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
error: error.error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -399,7 +399,7 @@ export class AddonModForumSyncProvider extends CoreSyncBaseProvider {
|
||||||
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.componentTranslate,
|
component: this.componentTranslate,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
error: error.error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -198,7 +198,7 @@ export class AddonModGlossarySyncProvider extends CoreSyncBaseProvider {
|
||||||
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.componentTranslate,
|
component: this.componentTranslate,
|
||||||
name: data.concept,
|
name: data.concept,
|
||||||
error: error.error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -106,7 +106,7 @@ export class AddonModLessonIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* Get the lesson data.
|
* Get the lesson data.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -401,7 +401,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
|
||||||
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.componentTranslate,
|
component: this.componentTranslate,
|
||||||
name: lesson.name,
|
name: lesson.name,
|
||||||
error: error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
@ -485,7 +485,7 @@ export class AddonModLessonSyncProvider extends CoreSyncBaseProvider {
|
||||||
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.componentTranslate,
|
component: this.componentTranslate,
|
||||||
name: lesson.name,
|
name: lesson.name,
|
||||||
error: error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class AddonModLtiIndexComponent extends CoreCourseModuleMainActivityCompo
|
||||||
* Get the LTI data.
|
* Get the LTI data.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -148,7 +148,7 @@ export class AddonModQuizIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
* Get the quiz data.
|
* Get the quiz data.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -137,7 +137,7 @@ export class AddonModScormIndexComponent extends CoreCourseModuleMainActivityCom
|
||||||
* Get the SCORM data.
|
* Get the SCORM data.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -93,7 +93,7 @@ export class AddonModSurveyIndexComponent extends CoreCourseModuleMainActivityCo
|
||||||
* Download survey contents.
|
* Download survey contents.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -173,13 +173,13 @@ export class AddonModSurveySyncProvider extends CoreSyncBaseProvider {
|
||||||
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
result.warnings.push(this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.componentTranslate,
|
component: this.componentTranslate,
|
||||||
name: data.name,
|
name: data.name,
|
||||||
error: error.error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
}));
|
}));
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error && error.error);
|
return Promise.reject(error);
|
||||||
});
|
});
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
if (courseId) {
|
if (courseId) {
|
||||||
|
|
|
@ -213,7 +213,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp
|
||||||
* Get the wiki data.
|
* Get the wiki data.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -291,7 +291,7 @@ export class AddonModWikiSyncProvider extends CoreSyncBaseProvider {
|
||||||
const warning = this.translate.instant('core.warningofflinedatadeleted', {
|
const warning = this.translate.instant('core.warningofflinedatadeleted', {
|
||||||
component: this.translate.instant('addon.mod_wiki.wikipage'),
|
component: this.translate.instant('addon.mod_wiki.wikipage'),
|
||||||
name: page.title,
|
name: page.title,
|
||||||
error: error
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
});
|
});
|
||||||
|
|
||||||
result.discarded.push({
|
result.discarded.push({
|
||||||
|
|
|
@ -177,7 +177,7 @@ export class AddonModWorkshopIndexComponent extends CoreCourseModuleMainActivity
|
||||||
* Download feedback contents.
|
* Download feedback contents.
|
||||||
*
|
*
|
||||||
* @param {boolean} [refresh=false] If it's refreshing content.
|
* @param {boolean} [refresh=false] If it's refreshing content.
|
||||||
* @param {boolean} [sync=false] If the refresh is needs syncing.
|
* @param {boolean} [sync=false] If it should try to sync.
|
||||||
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
* @param {boolean} [showErrors=false] If show errors to the user of hide them.
|
||||||
* @return {Promise<any>} Promise resolved when done.
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -340,7 +340,7 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider {
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error && this.utils.isWebServiceError(error)) {
|
if (error && this.utils.isWebServiceError(error)) {
|
||||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||||
discardError = error.message || error.error;
|
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||||
} else {
|
} else {
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -421,7 +421,7 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider {
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
if (error && this.utils.isWebServiceError(error)) {
|
if (error && this.utils.isWebServiceError(error)) {
|
||||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||||
discardError = error.message || error.error;
|
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||||
} else {
|
} else {
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error);
|
return Promise.reject(error);
|
||||||
|
@ -480,10 +480,10 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider {
|
||||||
evaluate.gradeover, siteId).catch((error) => {
|
evaluate.gradeover, siteId).catch((error) => {
|
||||||
if (error && this.utils.isWebServiceError(error)) {
|
if (error && this.utils.isWebServiceError(error)) {
|
||||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||||
discardError = error.message || error.error;
|
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||||
} else {
|
} else {
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error && error.error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Delete the offline data.
|
// Delete the offline data.
|
||||||
|
@ -539,10 +539,10 @@ export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider {
|
||||||
evaluate.gradinggradeover, siteId).catch((error) => {
|
evaluate.gradinggradeover, siteId).catch((error) => {
|
||||||
if (error && this.utils.isWebServiceError(error)) {
|
if (error && this.utils.isWebServiceError(error)) {
|
||||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||||
discardError = error.message || error.error;
|
discardError = this.textUtils.getErrorMessageFromError(error);
|
||||||
} else {
|
} else {
|
||||||
// Couldn't connect to server, reject.
|
// Couldn't connect to server, reject.
|
||||||
return Promise.reject(error && error.error);
|
return Promise.reject(error);
|
||||||
}
|
}
|
||||||
}).then(() => {
|
}).then(() => {
|
||||||
// Delete the offline data.
|
// Delete the offline data.
|
||||||
|
|
|
@ -14,10 +14,10 @@
|
||||||
|
|
||||||
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
import { Component, Input, Output, EventEmitter, OnChanges, SimpleChange } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreUserProvider } from '@core/user/providers/user';
|
import { CoreUserProvider } from '@core/user/providers/user';
|
||||||
|
import { CoreCourseProvider } from '../../providers/course';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing
|
* Component to handle activity completion. It shows a checkbox with the current status, and allows manually changing
|
||||||
|
@ -41,7 +41,8 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
||||||
completionDescription: string;
|
completionDescription: string;
|
||||||
|
|
||||||
constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider,
|
constructor(private textUtils: CoreTextUtilsProvider, private domUtils: CoreDomUtilsProvider,
|
||||||
private translate: TranslateService, private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider) {
|
private translate: TranslateService, private courseProvider: CoreCourseProvider,
|
||||||
|
private userProvider: CoreUserProvider) {
|
||||||
this.completionChanged = new EventEmitter();
|
this.completionChanged = new EventEmitter();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -68,14 +69,11 @@ export class CoreCourseModuleCompletionComponent implements OnChanges {
|
||||||
e.preventDefault();
|
e.preventDefault();
|
||||||
e.stopPropagation();
|
e.stopPropagation();
|
||||||
|
|
||||||
const modal = this.domUtils.showModalLoading(),
|
const modal = this.domUtils.showModalLoading();
|
||||||
params = {
|
|
||||||
cmid: this.completion.cmid,
|
this.courseProvider.markCompletedManually(this.completion.cmid, this.completion.state === 1 ? 0 : 1,
|
||||||
completed: this.completion.state === 1 ? 0 : 1
|
this.completion.courseId, this.completion.courseName).then((response) => {
|
||||||
},
|
|
||||||
currentSite = this.sitesProvider.getCurrentSite();
|
|
||||||
|
|
||||||
currentSite.write('core_completion_update_activity_completion_status_manually', params).then((response) => {
|
|
||||||
if (!response.status) {
|
if (!response.status) {
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
}
|
}
|
||||||
|
|
|
@ -30,13 +30,14 @@
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div *ngIf="module.visible === 0 || module.availabilityinfo || module.handlerData.extraBadge || module.isStealth">
|
<div *ngIf="module.visible === 0 || module.availabilityinfo || module.handlerData.extraBadge || module.isStealth || (module.completionstatus && module.completionstatus.offline)">
|
||||||
<ion-badge item-end *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor" text-wrap text-start>
|
<ion-badge item-end *ngIf="module.handlerData.extraBadge" [color]="module.handlerData.extraBadgeColor" text-wrap text-start>
|
||||||
<core-format-text [text]="module.handlerData.extraBadge"></core-format-text>
|
<core-format-text [text]="module.handlerData.extraBadge"></core-format-text>
|
||||||
</ion-badge>
|
</ion-badge>
|
||||||
<ion-badge item-end *ngIf="module.visible === 0">{{ 'core.course.hiddenfromstudents' | translate }}</ion-badge>
|
<ion-badge item-end *ngIf="module.visible === 0" text-wrap>{{ 'core.course.hiddenfromstudents' | translate }}</ion-badge>
|
||||||
<ion-badge item-end *ngIf="module.visible !== 0 && module.isStealth">{{ 'core.course.hiddenoncoursepage' | translate }}</ion-badge>
|
<ion-badge item-end *ngIf="module.visible !== 0 && module.isStealth" text-wrap>{{ 'core.course.hiddenoncoursepage' | translate }}</ion-badge>
|
||||||
<ion-badge item-end *ngIf="module.availabilityinfo"><core-format-text [text]="module.availabilityinfo"></core-format-text></ion-badge>
|
<ion-badge item-end *ngIf="module.availabilityinfo" text-wrap><core-format-text [text]="module.availabilityinfo"></core-format-text></ion-badge>
|
||||||
|
<ion-badge item-end *ngIf="module.completionstatus && module.completionstatus.offline" color="warning" text-wrap>{{ 'core.course.manualcompletionnotsynced' | translate }}</ion-badge>
|
||||||
</div>
|
</div>
|
||||||
<core-format-text class="core-module-description" *ngIf="module.description" maxHeight="80" [text]="module.description"></core-format-text>
|
<core-format-text class="core-module-description" *ngIf="module.description" maxHeight="80" [text]="module.description"></core-format-text>
|
||||||
</a>
|
</a>
|
|
@ -13,10 +13,12 @@
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { NgModule } from '@angular/core';
|
||||||
|
import { CoreCronDelegate } from '@providers/cron';
|
||||||
import { CoreCourseProvider } from './providers/course';
|
import { CoreCourseProvider } from './providers/course';
|
||||||
import { CoreCourseHelperProvider } from './providers/helper';
|
import { CoreCourseHelperProvider } from './providers/helper';
|
||||||
import { CoreCourseFormatDelegate } from './providers/format-delegate';
|
import { CoreCourseFormatDelegate } from './providers/format-delegate';
|
||||||
import { CoreCourseModuleDelegate } from './providers/module-delegate';
|
import { CoreCourseModuleDelegate } from './providers/module-delegate';
|
||||||
|
import { CoreCourseOfflineProvider } from './providers/course-offline';
|
||||||
import { CoreCourseModulePrefetchDelegate } from './providers/module-prefetch-delegate';
|
import { CoreCourseModulePrefetchDelegate } from './providers/module-prefetch-delegate';
|
||||||
import { CoreCourseOptionsDelegate } from './providers/options-delegate';
|
import { CoreCourseOptionsDelegate } from './providers/options-delegate';
|
||||||
import { CoreCourseFormatDefaultHandler } from './providers/default-format';
|
import { CoreCourseFormatDefaultHandler } from './providers/default-format';
|
||||||
|
@ -25,6 +27,8 @@ import { CoreCourseFormatSingleActivityModule } from './formats/singleactivity/s
|
||||||
import { CoreCourseFormatSocialModule } from './formats/social/social.module';
|
import { CoreCourseFormatSocialModule } from './formats/social/social.module';
|
||||||
import { CoreCourseFormatTopicsModule } from './formats/topics/topics.module';
|
import { CoreCourseFormatTopicsModule } from './formats/topics/topics.module';
|
||||||
import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module';
|
import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module';
|
||||||
|
import { CoreCourseSyncProvider } from './providers/sync';
|
||||||
|
import { CoreCourseSyncCronHandler } from './providers/sync-cron-handler';
|
||||||
|
|
||||||
// List of providers (without handlers).
|
// List of providers (without handlers).
|
||||||
export const CORE_COURSE_PROVIDERS: any[] = [
|
export const CORE_COURSE_PROVIDERS: any[] = [
|
||||||
|
@ -33,7 +37,9 @@ export const CORE_COURSE_PROVIDERS: any[] = [
|
||||||
CoreCourseFormatDelegate,
|
CoreCourseFormatDelegate,
|
||||||
CoreCourseModuleDelegate,
|
CoreCourseModuleDelegate,
|
||||||
CoreCourseModulePrefetchDelegate,
|
CoreCourseModulePrefetchDelegate,
|
||||||
CoreCourseOptionsDelegate
|
CoreCourseOptionsDelegate,
|
||||||
|
CoreCourseOfflineProvider,
|
||||||
|
CoreCourseSyncProvider
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -51,9 +57,16 @@ export const CORE_COURSE_PROVIDERS: any[] = [
|
||||||
CoreCourseModuleDelegate,
|
CoreCourseModuleDelegate,
|
||||||
CoreCourseModulePrefetchDelegate,
|
CoreCourseModulePrefetchDelegate,
|
||||||
CoreCourseOptionsDelegate,
|
CoreCourseOptionsDelegate,
|
||||||
|
CoreCourseOfflineProvider,
|
||||||
|
CoreCourseSyncProvider,
|
||||||
CoreCourseFormatDefaultHandler,
|
CoreCourseFormatDefaultHandler,
|
||||||
CoreCourseModuleDefaultHandler
|
CoreCourseModuleDefaultHandler,
|
||||||
|
CoreCourseSyncCronHandler
|
||||||
],
|
],
|
||||||
exports: []
|
exports: []
|
||||||
})
|
})
|
||||||
export class CoreCourseModule {}
|
export class CoreCourseModule {
|
||||||
|
constructor(cronDelegate: CoreCronDelegate, syncHandler: CoreCourseSyncCronHandler) {
|
||||||
|
cronDelegate.register(syncHandler);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -18,9 +18,12 @@
|
||||||
"errorgetmodule": "Error getting activity data.",
|
"errorgetmodule": "Error getting activity data.",
|
||||||
"hiddenfromstudents": "Hidden from students",
|
"hiddenfromstudents": "Hidden from students",
|
||||||
"hiddenoncoursepage": "Available but not shown on course page",
|
"hiddenoncoursepage": "Available but not shown on course page",
|
||||||
|
"manualcompletionnotsynced": "Manual completion not synchronised.",
|
||||||
"nocontentavailable": "No content available at the moment.",
|
"nocontentavailable": "No content available at the moment.",
|
||||||
"overriddennotice": "Your final grade from this activity was manually adjusted.",
|
"overriddennotice": "Your final grade from this activity was manually adjusted.",
|
||||||
"refreshcourse": "Refresh course",
|
"refreshcourse": "Refresh course",
|
||||||
"sections": "Sections",
|
"sections": "Sections",
|
||||||
"useactivityonbrowser": "You can still use it using your device's web browser."
|
"useactivityonbrowser": "You can still use it using your device's web browser.",
|
||||||
|
"warningmanualcompletionmodified": "The manual completion of an activity was modified on the site.",
|
||||||
|
"warningofflinemanualcompletiondeleted": "Some offline manual completion of course '{{name}}' has been deleted. {{error}}"
|
||||||
}
|
}
|
|
@ -24,6 +24,7 @@ import { CoreCourseHelperProvider } from '../../providers/helper';
|
||||||
import { CoreCourseFormatDelegate } from '../../providers/format-delegate';
|
import { CoreCourseFormatDelegate } from '../../providers/format-delegate';
|
||||||
import { CoreCourseModulePrefetchDelegate } from '../../providers/module-prefetch-delegate';
|
import { CoreCourseModulePrefetchDelegate } from '../../providers/module-prefetch-delegate';
|
||||||
import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from '../../providers/options-delegate';
|
import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from '../../providers/options-delegate';
|
||||||
|
import { CoreCourseSyncProvider } from '../../providers/sync';
|
||||||
import { CoreCourseFormatComponent } from '../../components/format/format';
|
import { CoreCourseFormatComponent } from '../../components/format/format';
|
||||||
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
import { CoreCoursesProvider } from '@core/courses/providers/courses';
|
||||||
import { CoreTabsComponent } from '@components/tabs/tabs';
|
import { CoreTabsComponent } from '@components/tabs/tabs';
|
||||||
|
@ -62,6 +63,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
protected module: any;
|
protected module: any;
|
||||||
protected completionObserver;
|
protected completionObserver;
|
||||||
protected courseStatusObserver;
|
protected courseStatusObserver;
|
||||||
|
protected syncObserver;
|
||||||
protected firstTabName: string;
|
protected firstTabName: string;
|
||||||
protected isDestroyed = false;
|
protected isDestroyed = false;
|
||||||
|
|
||||||
|
@ -70,7 +72,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider,
|
private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider,
|
||||||
private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider,
|
private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider,
|
||||||
sitesProvider: CoreSitesProvider, private navCtrl: NavController, private injector: Injector,
|
sitesProvider: CoreSitesProvider, private navCtrl: NavController, private injector: Injector,
|
||||||
private prefetchDelegate: CoreCourseModulePrefetchDelegate) {
|
private prefetchDelegate: CoreCourseModulePrefetchDelegate, private syncProvider: CoreCourseSyncProvider) {
|
||||||
this.course = navParams.get('course');
|
this.course = navParams.get('course');
|
||||||
this.sectionId = navParams.get('sectionId');
|
this.sectionId = navParams.get('sectionId');
|
||||||
this.sectionNumber = navParams.get('sectionNumber');
|
this.sectionNumber = navParams.get('sectionNumber');
|
||||||
|
@ -87,7 +89,17 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
if (shouldRefresh) {
|
if (shouldRefresh) {
|
||||||
this.completionObserver = eventsProvider.on(CoreEventsProvider.COMPLETION_MODULE_VIEWED, (data) => {
|
this.completionObserver = eventsProvider.on(CoreEventsProvider.COMPLETION_MODULE_VIEWED, (data) => {
|
||||||
if (data && data.courseId == this.course.id) {
|
if (data && data.courseId == this.course.id) {
|
||||||
this.refreshAfterCompletionChange();
|
this.refreshAfterCompletionChange(true);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
this.syncObserver = eventsProvider.on(CoreCourseSyncProvider.AUTO_SYNCED, (data) => {
|
||||||
|
if (data && data.courseId == this.course.id) {
|
||||||
|
this.refreshAfterCompletionChange(false);
|
||||||
|
|
||||||
|
if (data.warnings && data.warnings[0]) {
|
||||||
|
this.domUtils.showErrorModal(data.warnings[0]);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -113,7 +125,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
this.courseHelper.openModule(this.navCtrl, this.module, this.course.id, this.sectionId);
|
this.courseHelper.openModule(this.navCtrl, this.module, this.course.id, this.sectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.loadData().finally(() => {
|
this.loadData(false, true).finally(() => {
|
||||||
this.dataLoaded = true;
|
this.dataLoaded = true;
|
||||||
|
|
||||||
if (!this.downloadCourseEnabled) {
|
if (!this.downloadCourseEnabled) {
|
||||||
|
@ -146,19 +158,34 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch and load all the data required for the view.
|
* Fetch and load all the data required for the view.
|
||||||
|
*
|
||||||
|
* @param {boolean} [refresh] If it's refreshing content.
|
||||||
|
* @param {boolean} [sync] If it should try to sync.
|
||||||
|
* @return {Promise<any>} Promise resolved when done.
|
||||||
*/
|
*/
|
||||||
protected loadData(refresh?: boolean): Promise<any> {
|
protected loadData(refresh?: boolean, sync?: boolean): Promise<any> {
|
||||||
// First of all, get the course because the data might have changed.
|
// First of all, get the course because the data might have changed.
|
||||||
return this.coursesProvider.getUserCourse(this.course.id).catch(() => {
|
return this.coursesProvider.getUserCourse(this.course.id).catch(() => {
|
||||||
// Error getting the course, probably guest access.
|
// Error getting the course, probably guest access.
|
||||||
}).then((course) => {
|
}).then((course) => {
|
||||||
const promises = [];
|
|
||||||
let promise;
|
|
||||||
|
|
||||||
if (course) {
|
if (course) {
|
||||||
this.course = course;
|
this.course = course;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (sync) {
|
||||||
|
// Try to synchronize the course data.
|
||||||
|
return this.syncProvider.syncCourse(this.course.id).then((result) => {
|
||||||
|
if (result.warnings && result.warnings.length) {
|
||||||
|
this.domUtils.showErrorModal(result.warnings[0]);
|
||||||
|
}
|
||||||
|
}).catch(() => {
|
||||||
|
// For now we don't allow manual syncing, so ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
const promises = [];
|
||||||
|
let promise;
|
||||||
|
|
||||||
// Get the completion status.
|
// Get the completion status.
|
||||||
if (this.course.enablecompletion === false) {
|
if (this.course.enablecompletion === false) {
|
||||||
// Completion not enabled.
|
// Completion not enabled.
|
||||||
|
@ -185,7 +212,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
}
|
}
|
||||||
}).then((sections) => {
|
}).then((sections) => {
|
||||||
|
|
||||||
this.courseHelper.addHandlerDataForModules(sections, this.course.id, completionStatus);
|
this.courseHelper.addHandlerDataForModules(sections, this.course.id, completionStatus, this.course.fullname);
|
||||||
|
|
||||||
// Format the name of each section and check if it has content.
|
// Format the name of each section and check if it has content.
|
||||||
this.sections = sections.map((section) => {
|
this.sections = sections.map((section) => {
|
||||||
|
@ -268,7 +295,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
*/
|
*/
|
||||||
doRefresh(refresher?: any): Promise<any> {
|
doRefresh(refresher?: any): Promise<any> {
|
||||||
return this.invalidateData().finally(() => {
|
return this.invalidateData().finally(() => {
|
||||||
return this.loadData(true).finally(() => {
|
return this.loadData(true, true).finally(() => {
|
||||||
/* Do not call doRefresh on the format component if the refresher is defined in the format component
|
/* Do not call doRefresh on the format component if the refresher is defined in the format component
|
||||||
to prevent an inifinite loop. */
|
to prevent an inifinite loop. */
|
||||||
let promise;
|
let promise;
|
||||||
|
@ -290,7 +317,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
*/
|
*/
|
||||||
onCompletionChange(): void {
|
onCompletionChange(): void {
|
||||||
this.invalidateData().finally(() => {
|
this.invalidateData().finally(() => {
|
||||||
this.refreshAfterCompletionChange();
|
this.refreshAfterCompletionChange(true);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -314,8 +341,10 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Refresh list after a completion change since there could be new activities.
|
* Refresh list after a completion change since there could be new activities.
|
||||||
|
*
|
||||||
|
* @param {boolean} [sync] If it should try to sync.
|
||||||
*/
|
*/
|
||||||
protected refreshAfterCompletionChange(): void {
|
protected refreshAfterCompletionChange(sync?: boolean): void {
|
||||||
// Save scroll position to restore it once done.
|
// Save scroll position to restore it once done.
|
||||||
const scrollElement = this.content.getScrollElement(),
|
const scrollElement = this.content.getScrollElement(),
|
||||||
scrollTop = scrollElement.scrollTop || 0,
|
scrollTop = scrollElement.scrollTop || 0,
|
||||||
|
@ -324,7 +353,7 @@ export class CoreCourseSectionPage implements OnDestroy {
|
||||||
this.dataLoaded = false;
|
this.dataLoaded = false;
|
||||||
this.domUtils.scrollToTop(this.content); // Scroll top so the spinner is seen.
|
this.domUtils.scrollToTop(this.content); // Scroll top so the spinner is seen.
|
||||||
|
|
||||||
this.loadData().then(() => {
|
this.loadData(true, sync).then(() => {
|
||||||
return this.formatComponent.doRefresh(undefined, undefined, true);
|
return this.formatComponent.doRefresh(undefined, undefined, true);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
this.dataLoaded = true;
|
this.dataLoaded = true;
|
||||||
|
|
|
@ -0,0 +1,145 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to handle offline data for courses.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreCourseOfflineProvider {
|
||||||
|
|
||||||
|
// Variables for database.
|
||||||
|
static MANUAL_COMPLETION_TABLE = 'course_manual_completion';
|
||||||
|
protected tablesSchema = [
|
||||||
|
{
|
||||||
|
name: CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE,
|
||||||
|
columns: [
|
||||||
|
{
|
||||||
|
name: 'cmid',
|
||||||
|
type: 'INTEGER',
|
||||||
|
primaryKey: true
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'completed',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'courseid',
|
||||||
|
type: 'INTEGER'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'coursename',
|
||||||
|
type: 'TEXT'
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'timecompleted',
|
||||||
|
type: 'INTEGER'
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
];
|
||||||
|
|
||||||
|
constructor(private sitesProvider: CoreSitesProvider) {
|
||||||
|
this.sitesProvider.createTablesFromSchema(this.tablesSchema);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Delete a manual completion stored.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID to remove the completion.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when deleted, rejected if failure.
|
||||||
|
*/
|
||||||
|
deleteManualCompletion(cmId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
return site.getDb().deleteRecords(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, {cmid: cmId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all offline manual completions for a certain course.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with the list of completions.
|
||||||
|
*/
|
||||||
|
getAllManualCompletions(siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
return site.getDb().getRecords(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get all offline manual completions for a certain course.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any[]>} Promise resolved with the list of completions.
|
||||||
|
*/
|
||||||
|
getCourseManualCompletions(courseId: number, siteId?: string): Promise<any[]> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
return site.getDb().getRecords(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, {courseid: courseId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the offline manual completion for a certain module.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID to remove the completion.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved with the completion, rejected if failure or not found.
|
||||||
|
*/
|
||||||
|
getManualCompletion(cmId: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
|
||||||
|
return site.getDb().getRecord(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, {cmid: cmId});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline version for manually marking a module as completed.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID to store the completion.
|
||||||
|
* @param {number} completed Whether the module is completed or not.
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @param {string} [courseName] Course name. Recommended, it is used to display a better warning message.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<{status: boolean, offline: boolean}>} Promise resolved when completion is successfully stored.
|
||||||
|
*/
|
||||||
|
markCompletedManually(cmId: number, completed: number, courseId: number, courseName?: string, siteId?: string)
|
||||||
|
: Promise<{status: boolean, offline: boolean}> {
|
||||||
|
|
||||||
|
// Store the offline data.
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const entry = {
|
||||||
|
cmid: cmId,
|
||||||
|
completed: completed,
|
||||||
|
courseid: courseId,
|
||||||
|
coursename: courseName || '',
|
||||||
|
timecompleted: Date.now()
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.getDb().insertRecord(CoreCourseOfflineProvider.MANUAL_COMPLETION_TABLE, entry);
|
||||||
|
}).then(() => {
|
||||||
|
return {
|
||||||
|
status: true,
|
||||||
|
offline: true
|
||||||
|
};
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
|
@ -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 { CoreAppProvider } from '@providers/app';
|
||||||
import { CoreEventsProvider } from '@providers/events';
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
import { CoreLoggerProvider } from '@providers/logger';
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
@ -21,6 +22,7 @@ import { CoreTimeUtilsProvider } from '@providers/utils/time';
|
||||||
import { CoreUtilsProvider } from '@providers/utils/utils';
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
import { CoreSiteWSPreSets } from '@classes/site';
|
import { CoreSiteWSPreSets } from '@classes/site';
|
||||||
import { CoreConstants } from '../../constants';
|
import { CoreConstants } from '../../constants';
|
||||||
|
import { CoreCourseOfflineProvider } from './course-offline';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service that provides some features regarding a course.
|
* Service that provides some features regarding a course.
|
||||||
|
@ -75,7 +77,8 @@ export class CoreCourseProvider {
|
||||||
];
|
];
|
||||||
|
|
||||||
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider,
|
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider,
|
||||||
private utils: CoreUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private translate: TranslateService) {
|
private utils: CoreUtilsProvider, private timeUtils: CoreTimeUtilsProvider, private translate: TranslateService,
|
||||||
|
private courseOffline: CoreCourseOfflineProvider, private appProvider: CoreAppProvider) {
|
||||||
this.logger = logger.getInstance('CoreCourseProvider');
|
this.logger = logger.getInstance('CoreCourseProvider');
|
||||||
|
|
||||||
this.sitesProvider.createTableFromSchema(this.courseStatusTableSchema);
|
this.sitesProvider.createTableFromSchema(this.courseStatusTableSchema);
|
||||||
|
@ -118,9 +121,14 @@ export class CoreCourseProvider {
|
||||||
* @param {number} courseId Course ID.
|
* @param {number} courseId Course ID.
|
||||||
* @param {string} [siteId] Site ID. If not defined, current site.
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
* @param {number} [userId] User ID. If not defined, current user.
|
* @param {number} [userId] User ID. If not defined, current user.
|
||||||
|
* @param {boolean} [forceCache] True if it should return cached data. Has priority over ignoreCache.
|
||||||
|
* @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down).
|
||||||
|
* @param {boolean} [includeOffline=true] True if it should load offline data in the completion status.
|
||||||
* @return {Promise<any>} Promise resolved with the completion statuses: object where the key is module ID.
|
* @return {Promise<any>} Promise resolved with the completion statuses: object where the key is module ID.
|
||||||
*/
|
*/
|
||||||
getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number): Promise<any> {
|
getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number, forceCache: boolean = false,
|
||||||
|
ignoreCache: boolean = false, includeOffline: boolean = true): Promise<any> {
|
||||||
|
|
||||||
return this.sitesProvider.getSite(siteId).then((site) => {
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
userId = userId || site.getUserId();
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
|
@ -130,16 +138,48 @@ export class CoreCourseProvider {
|
||||||
courseid: courseId,
|
courseid: courseId,
|
||||||
userid: userId
|
userid: userId
|
||||||
},
|
},
|
||||||
preSets = {
|
preSets: CoreSiteWSPreSets = {
|
||||||
cacheKey: this.getActivitiesCompletionCacheKey(courseId, userId)
|
cacheKey: this.getActivitiesCompletionCacheKey(courseId, userId)
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (forceCache) {
|
||||||
|
preSets.omitExpires = true;
|
||||||
|
} else if (ignoreCache) {
|
||||||
|
preSets.getFromCache = false;
|
||||||
|
preSets.emergencyCache = false;
|
||||||
|
}
|
||||||
|
|
||||||
return site.read('core_completion_get_activities_completion_status', params, preSets).then((data) => {
|
return site.read('core_completion_get_activities_completion_status', params, preSets).then((data) => {
|
||||||
if (data && data.statuses) {
|
if (data && data.statuses) {
|
||||||
return this.utils.arrayToObject(data.statuses, 'cmid');
|
return this.utils.arrayToObject(data.statuses, 'cmid');
|
||||||
}
|
}
|
||||||
|
|
||||||
return Promise.reject(null);
|
return Promise.reject(null);
|
||||||
|
}).then((completionStatus) => {
|
||||||
|
if (!includeOffline) {
|
||||||
|
return completionStatus;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Now get the offline completion (if any).
|
||||||
|
return this.courseOffline.getCourseManualCompletions(courseId, site.id).then((offlineCompletions) => {
|
||||||
|
offlineCompletions.forEach((offlineCompletion) => {
|
||||||
|
|
||||||
|
if (offlineCompletion && typeof completionStatus[offlineCompletion.cmid] != 'undefined') {
|
||||||
|
const onlineCompletion = completionStatus[offlineCompletion.cmid];
|
||||||
|
|
||||||
|
// If the activity uses manual completion, override the value with the offline one.
|
||||||
|
if (onlineCompletion.tracking === 1) {
|
||||||
|
onlineCompletion.state = offlineCompletion.completed;
|
||||||
|
onlineCompletion.offline = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
return completionStatus;
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
return completionStatus;
|
||||||
|
});
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -647,6 +687,70 @@ export class CoreCourseProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline version for manually marking a module as completed.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID.
|
||||||
|
* @param {number} completed Whether the module is completed or not.
|
||||||
|
* @param {number} courseId Course ID the module belongs to.
|
||||||
|
* @param {string} [courseName] Course name. Recommended, it is used to display a better warning message.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when completion is successfully sent or stored.
|
||||||
|
*/
|
||||||
|
markCompletedManually(cmId: number, completed: number, courseId: number, courseName?: string, siteId?: string)
|
||||||
|
: Promise<any> {
|
||||||
|
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
// Convenience function to store a completion to be synchronized later.
|
||||||
|
const storeOffline = (): Promise<any> => {
|
||||||
|
return this.courseOffline.markCompletedManually(cmId, completed, courseId, courseName, siteId);
|
||||||
|
};
|
||||||
|
|
||||||
|
// The offline function requires a courseId and it could be missing because it's a calculated field.
|
||||||
|
if (!this.appProvider.isOnline() && courseId) {
|
||||||
|
// App is offline, store the action.
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Try to send it to server.
|
||||||
|
return this.markCompletedManuallyOnline(cmId, completed, siteId).then((result) => {
|
||||||
|
// Data sent to server, if there is some offline data delete it now.
|
||||||
|
return this.courseOffline.deleteManualCompletion(cmId, siteId).catch(() => {
|
||||||
|
// Ignore errors, shouldn't happen.
|
||||||
|
}).then(() => {
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error) || !courseId) {
|
||||||
|
// The WebService has thrown an error, this means that responses cannot be submitted.
|
||||||
|
return Promise.reject(error);
|
||||||
|
} else {
|
||||||
|
// Couldn't connect to server, store it offline.
|
||||||
|
return storeOffline();
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Offline version for manually marking a module as completed.
|
||||||
|
*
|
||||||
|
* @param {number} cmId The module ID.
|
||||||
|
* @param {number} completed Whether the module is completed or not.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when completion is successfully sent.
|
||||||
|
*/
|
||||||
|
markCompletedManuallyOnline(cmId: number, completed: number, siteId?: string): Promise<any> {
|
||||||
|
return this.sitesProvider.getSite(siteId).then((site) => {
|
||||||
|
const params = {
|
||||||
|
cmid: cmId,
|
||||||
|
completed: completed
|
||||||
|
};
|
||||||
|
|
||||||
|
return site.write('core_completion_update_activity_completion_status_manually', params);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Change the course status, setting it to the previous status.
|
* Change the course status, setting it to the previous status.
|
||||||
*
|
*
|
||||||
|
|
|
@ -131,9 +131,10 @@ export class CoreCourseHelperProvider {
|
||||||
* @param {any[]} sections List of sections to treat modules.
|
* @param {any[]} sections List of sections to treat modules.
|
||||||
* @param {number} courseId Course ID of the modules.
|
* @param {number} courseId Course ID of the modules.
|
||||||
* @param {any[]} [completionStatus] List of completion status.
|
* @param {any[]} [completionStatus] List of completion status.
|
||||||
|
* @param {string} [courseName] Course name. Recommended if completionStatus is supplied.
|
||||||
* @return {boolean} Whether the sections have content.
|
* @return {boolean} Whether the sections have content.
|
||||||
*/
|
*/
|
||||||
addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any): boolean {
|
addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any, courseName?: string): boolean {
|
||||||
let hasContent = false;
|
let hasContent = false;
|
||||||
|
|
||||||
sections.forEach((section) => {
|
sections.forEach((section) => {
|
||||||
|
@ -150,6 +151,7 @@ export class CoreCourseHelperProvider {
|
||||||
// Check if activity has completions and if it's marked.
|
// Check if activity has completions and if it's marked.
|
||||||
module.completionstatus = completionStatus[module.id];
|
module.completionstatus = completionStatus[module.id];
|
||||||
module.completionstatus.courseId = courseId;
|
module.completionstatus.courseId = courseId;
|
||||||
|
module.completionstatus.courseName = courseName;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check if the module is stealth.
|
// Check if the module is stealth.
|
||||||
|
|
|
@ -0,0 +1,47 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreCronHandler } from '@providers/cron';
|
||||||
|
import { CoreCourseSyncProvider } from './sync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronization cron handler.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreCourseSyncCronHandler implements CoreCronHandler {
|
||||||
|
name = 'CoreCourseSyncCronHandler';
|
||||||
|
|
||||||
|
constructor(private courseSync: CoreCourseSyncProvider) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute the process.
|
||||||
|
* Receives the ID of the site affected, undefined for all sites.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] ID of the site affected, undefined for all sites.
|
||||||
|
* @return {Promise<any>} Promise resolved when done, rejected if failure.
|
||||||
|
*/
|
||||||
|
execute(siteId?: string): Promise<any> {
|
||||||
|
return this.courseSync.syncAllCourses(siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the time between consecutive executions.
|
||||||
|
*
|
||||||
|
* @return {number} Time between consecutive executions (in ms).
|
||||||
|
*/
|
||||||
|
getInterval(): number {
|
||||||
|
return this.courseSync.syncInterval;
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,201 @@
|
||||||
|
// (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 { Injectable } from '@angular/core';
|
||||||
|
import { CoreLoggerProvider } from '@providers/logger';
|
||||||
|
import { CoreSyncBaseProvider } from '@classes/base-sync';
|
||||||
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
|
import { CoreAppProvider } from '@providers/app';
|
||||||
|
import { CoreUtilsProvider } from '@providers/utils/utils';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
|
import { CoreCourseOfflineProvider } from './course-offline';
|
||||||
|
import { CoreCourseProvider } from './course';
|
||||||
|
import { CoreEventsProvider } from '@providers/events';
|
||||||
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
|
import { CoreSyncProvider } from '@providers/sync';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Service to sync course offline data. This only syncs the offline data of the course itself, not the offline data of
|
||||||
|
* the activities in the course.
|
||||||
|
*/
|
||||||
|
@Injectable()
|
||||||
|
export class CoreCourseSyncProvider extends CoreSyncBaseProvider {
|
||||||
|
|
||||||
|
static AUTO_SYNCED = 'core_course_autom_synced';
|
||||||
|
|
||||||
|
constructor(protected sitesProvider: CoreSitesProvider, loggerProvider: CoreLoggerProvider,
|
||||||
|
protected appProvider: CoreAppProvider, private courseOffline: CoreCourseOfflineProvider,
|
||||||
|
private eventsProvider: CoreEventsProvider, private courseProvider: CoreCourseProvider,
|
||||||
|
translate: TranslateService, private utils: CoreUtilsProvider, protected textUtils: CoreTextUtilsProvider,
|
||||||
|
syncProvider: CoreSyncProvider) {
|
||||||
|
|
||||||
|
super('CoreCourseSyncProvider', loggerProvider, sitesProvider, appProvider, syncProvider, textUtils, translate);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Try to synchronize all the courses in a certain site or in all sites.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
syncAllCourses(siteId?: string): Promise<any> {
|
||||||
|
return this.syncOnSites('courses', this.syncAllCoursesFunc.bind(this), undefined, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync all courses on a site.
|
||||||
|
*
|
||||||
|
* @param {string} [siteId] Site ID to sync. If not defined, sync all sites.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected if sync fails.
|
||||||
|
*/
|
||||||
|
protected syncAllCoursesFunc(siteId?: string): Promise<any> {
|
||||||
|
return this.courseOffline.getAllManualCompletions(siteId).then((completions) => {
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
// Sync all courses.
|
||||||
|
completions.forEach((completion) => {
|
||||||
|
promises.push(this.syncCourseIfNeeded(completion.courseid, siteId).then((result) => {
|
||||||
|
if (result && result.updated) {
|
||||||
|
// Sync successful, send event.
|
||||||
|
this.eventsProvider.trigger(CoreCourseSyncProvider.AUTO_SYNCED, {
|
||||||
|
courseId: completion.courseid,
|
||||||
|
warnings: result.warnings
|
||||||
|
}, siteId);
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sync a course if it's needed.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID to be synced.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved when the course is synced or it doesn't need to be synced.
|
||||||
|
*/
|
||||||
|
syncCourseIfNeeded(courseId: number, siteId?: string): Promise<any> {
|
||||||
|
// Usually we call isSyncNeeded to check if a certain time has passed.
|
||||||
|
// However, since we barely send data for now just sync the course.
|
||||||
|
return this.syncCourse(courseId, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Synchronize a course.
|
||||||
|
*
|
||||||
|
* @param {number} courseId Course ID to be synced.
|
||||||
|
* @param {string} [siteId] Site ID. If not defined, current site.
|
||||||
|
* @return {Promise<any>} Promise resolved if sync is successful, rejected otherwise.
|
||||||
|
*/
|
||||||
|
syncCourse(courseId: number, siteId?: string): Promise<any> {
|
||||||
|
siteId = siteId || this.sitesProvider.getCurrentSiteId();
|
||||||
|
|
||||||
|
if (this.isSyncing(courseId, siteId)) {
|
||||||
|
// There's already a sync ongoing for this discussion, return the promise.
|
||||||
|
return this.getOngoingSync(courseId, siteId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.logger.debug(`Try to sync course '${courseId}'`);
|
||||||
|
|
||||||
|
const result = {
|
||||||
|
warnings: [],
|
||||||
|
updated: false
|
||||||
|
};
|
||||||
|
|
||||||
|
// Get offline responses to be sent.
|
||||||
|
const syncPromise = this.courseOffline.getCourseManualCompletions(courseId, siteId).catch(() => {
|
||||||
|
// No offline data found, return empty list.
|
||||||
|
return [];
|
||||||
|
}).then((completions) => {
|
||||||
|
if (!completions || !completions.length) {
|
||||||
|
// Nothing to sync.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!this.appProvider.isOnline()) {
|
||||||
|
// Cannot sync in offline.
|
||||||
|
return Promise.reject(null);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Get the current completion status to check if any completion was modified in web.
|
||||||
|
return this.courseProvider.getActivitiesCompletionStatus(courseId, siteId, undefined, false, true, false)
|
||||||
|
.then((onlineCompletions) => {
|
||||||
|
|
||||||
|
const promises = [];
|
||||||
|
|
||||||
|
// Send all the completions.
|
||||||
|
completions.forEach((entry) => {
|
||||||
|
const onlineComp = onlineCompletions[entry.cmid];
|
||||||
|
|
||||||
|
// Check if the completion was modified in online. If so, discard it.
|
||||||
|
if (onlineComp && onlineComp.timecompleted * 1000 > entry.timecompleted) {
|
||||||
|
promises.push(this.courseOffline.deleteManualCompletion(entry.cmid, siteId).then(() => {
|
||||||
|
|
||||||
|
// Completion deleted, add a warning if the completion status doesn't match.
|
||||||
|
if (onlineComp.state != entry.completed) {
|
||||||
|
result.warnings.push(this.translate.instant('core.course.warningofflinemanualcompletiondeleted', {
|
||||||
|
name: entry.coursename || courseId,
|
||||||
|
error: this.translate.instant('core.course.warningmanualcompletionmodified')
|
||||||
|
}));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
promises.push(this.courseProvider.markCompletedManuallyOnline(entry.cmid, entry.completed, siteId).then(() => {
|
||||||
|
result.updated = true;
|
||||||
|
|
||||||
|
return this.courseOffline.deleteManualCompletion(entry.cmid, siteId);
|
||||||
|
}).catch((error) => {
|
||||||
|
if (this.utils.isWebServiceError(error)) {
|
||||||
|
// The WebService has thrown an error, this means that the completion cannot be submitted. Delete it.
|
||||||
|
result.updated = true;
|
||||||
|
|
||||||
|
return this.courseOffline.deleteManualCompletion(entry.cmid, siteId).then(() => {
|
||||||
|
// Completion deleted, add a warning.
|
||||||
|
result.warnings.push(this.translate.instant('core.course.warningofflinemanualcompletiondeleted', {
|
||||||
|
name: entry.coursename || courseId,
|
||||||
|
error: this.textUtils.getErrorMessageFromError(error)
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
// Couldn't connect to server, reject.
|
||||||
|
return Promise.reject(error);
|
||||||
|
}));
|
||||||
|
});
|
||||||
|
|
||||||
|
return Promise.all(promises);
|
||||||
|
});
|
||||||
|
}).then(() => {
|
||||||
|
if (result.updated) {
|
||||||
|
// Update data.
|
||||||
|
return this.courseProvider.invalidateSections(courseId, siteId).then(() => {
|
||||||
|
return this.courseProvider.getActivitiesCompletionStatus(courseId);
|
||||||
|
}).catch(() => {
|
||||||
|
// Ignore errors.
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}).then(() => {
|
||||||
|
// Sync finished, set sync time.
|
||||||
|
return this.setSyncTime(courseId, siteId);
|
||||||
|
}).then(() => {
|
||||||
|
// All done, return the data.
|
||||||
|
return result;
|
||||||
|
});
|
||||||
|
|
||||||
|
return this.addOngoingSync(courseId, syncPromise, siteId);
|
||||||
|
}
|
||||||
|
}
|
|
@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
|
||||||
import { TranslateService } from '@ngx-translate/core';
|
import { TranslateService } from '@ngx-translate/core';
|
||||||
import { CoreSitesProvider } from '@providers/sites';
|
import { CoreSitesProvider } from '@providers/sites';
|
||||||
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
import { CoreDomUtilsProvider } from '@providers/utils/dom';
|
||||||
|
import { CoreTextUtilsProvider } from '@providers/utils/text';
|
||||||
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
|
||||||
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
import { CoreContentLinksAction } from '@core/contentlinks/providers/delegate';
|
||||||
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
|
import { CoreLoginHelperProvider } from '@core/login/providers/helper';
|
||||||
|
@ -34,7 +35,8 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase {
|
||||||
|
|
||||||
constructor(private sitesProvider: CoreSitesProvider, private coursesProvider: CoreCoursesProvider,
|
constructor(private sitesProvider: CoreSitesProvider, private coursesProvider: CoreCoursesProvider,
|
||||||
private loginHelper: CoreLoginHelperProvider, private domUtils: CoreDomUtilsProvider,
|
private loginHelper: CoreLoginHelperProvider, private domUtils: CoreDomUtilsProvider,
|
||||||
private translate: TranslateService, private courseProvider: CoreCourseProvider) {
|
private translate: TranslateService, private courseProvider: CoreCourseProvider,
|
||||||
|
private textUtils: CoreTextUtilsProvider) {
|
||||||
super();
|
super();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -150,7 +152,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
error = error.message || error.error || error.content || error.body || error;
|
error = this.textUtils.getErrorMessageFromError(error) || error;
|
||||||
}
|
}
|
||||||
if (!error) {
|
if (!error) {
|
||||||
error = this.translate.instant('core.courses.notenroled');
|
error = this.translate.instant('core.courses.notenroled');
|
||||||
|
@ -232,7 +234,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase {
|
||||||
|
|
||||||
if (typeof password != 'undefined') {
|
if (typeof password != 'undefined') {
|
||||||
// The user attempted a password. Show an error message.
|
// The user attempted a password. Show an error message.
|
||||||
this.domUtils.showErrorModal(error.message);
|
this.domUtils.showErrorModal(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.domUtils.showPrompt(body, title, placeholder).then((password) => {
|
return this.domUtils.showPrompt(body, title, placeholder).then((password) => {
|
||||||
|
|
|
@ -276,7 +276,7 @@ export class CoreLoginEmailSignupPage {
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.domUtils.showErrorModalDefault(error && error.error, 'core.login.usernotaddederror', true);
|
this.domUtils.showErrorModalDefault(error, 'core.login.usernotaddederror', true);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
});
|
});
|
||||||
|
|
|
@ -70,7 +70,7 @@ export class CoreLoginForgottenPasswordPage {
|
||||||
this.navCtrl.pop();
|
this.navCtrl.pop();
|
||||||
}
|
}
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.domUtils.showErrorModal(error.error);
|
this.domUtils.showErrorModal(error);
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
});
|
});
|
||||||
|
|
|
@ -88,7 +88,7 @@ export class CoreLoginSitePolicyPage {
|
||||||
this.policyLoaded = true;
|
this.policyLoaded = true;
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.domUtils.showErrorModalDefault(error && error.error, 'Error getting site policy.');
|
this.domUtils.showErrorModalDefault(error, 'Error getting site policy.');
|
||||||
this.cancel();
|
this.cancel();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -118,7 +118,7 @@ export class CoreLoginSitePolicyPage {
|
||||||
return this.loginHelper.goToSiteInitialPage();
|
return this.loginHelper.goToSiteInitialPage();
|
||||||
});
|
});
|
||||||
}).catch((error) => {
|
}).catch((error) => {
|
||||||
this.domUtils.showErrorModalDefault(error.message, 'Error accepting site policy.');
|
this.domUtils.showErrorModalDefault(error, 'Error accepting site policy.');
|
||||||
}).finally(() => {
|
}).finally(() => {
|
||||||
modal.dismiss();
|
modal.dismiss();
|
||||||
});
|
});
|
||||||
|
|
|
@ -1001,9 +1001,9 @@ export class CoreLoginHelperProvider {
|
||||||
*/
|
*/
|
||||||
treatUserTokenError(siteUrl: string, error: any): void {
|
treatUserTokenError(siteUrl: string, error: any): void {
|
||||||
if (error.errorcode == 'forcepasswordchangenotice') {
|
if (error.errorcode == 'forcepasswordchangenotice') {
|
||||||
this.openChangePassword(siteUrl, error.error || error.message || error.body || error.content);
|
this.openChangePassword(siteUrl, this.textUtils.getErrorMessageFromError(error));
|
||||||
} else if (error.errorcode == 'legacymoodleversion') {
|
} else if (error.errorcode == 'legacymoodleversion') {
|
||||||
this.showLegacyNoticeModal(error.error);
|
this.showLegacyNoticeModal(this.textUtils.getErrorMessageFromError(error));
|
||||||
} else {
|
} else {
|
||||||
this.domUtils.showErrorModal(error);
|
this.domUtils.showErrorModal(error);
|
||||||
}
|
}
|
||||||
|
|
|
@ -388,7 +388,7 @@ export class CoreSitesProvider {
|
||||||
const promise = this.http.post(siteUrl + '/login/token.php', data).timeout(CoreConstants.WS_TIMEOUT).toPromise();
|
const promise = this.http.post(siteUrl + '/login/token.php', data).timeout(CoreConstants.WS_TIMEOUT).toPromise();
|
||||||
|
|
||||||
return promise.catch((error) => {
|
return promise.catch((error) => {
|
||||||
return Promise.reject(error.message);
|
return Promise.reject(error);
|
||||||
}).then((data: any) => {
|
}).then((data: any) => {
|
||||||
if (data.errorcode && (data.errorcode == 'enablewsdescription' || data.errorcode == 'requirecorrectaccess')) {
|
if (data.errorcode && (data.errorcode == 'enablewsdescription' || data.errorcode == 'requirecorrectaccess')) {
|
||||||
return Promise.reject({ errorcode: data.errorcode, error: data.error });
|
return Promise.reject({ errorcode: data.errorcode, error: data.error });
|
||||||
|
|
|
@ -1005,15 +1005,10 @@ export class CoreDomUtilsProvider {
|
||||||
if (error.coreCanceled) {
|
if (error.coreCanceled) {
|
||||||
// It's a canceled error, don't display an error.
|
// It's a canceled error, don't display an error.
|
||||||
return;
|
return;
|
||||||
} else if (typeof error.content != 'undefined') {
|
}
|
||||||
error = error.content;
|
|
||||||
} else if (typeof error.body != 'undefined') {
|
error = this.textUtils.getErrorMessageFromError(error);
|
||||||
error = error.body;
|
if (!error) {
|
||||||
} else if (typeof error.message != 'undefined') {
|
|
||||||
error = error.message;
|
|
||||||
} else if (typeof error.error != 'undefined') {
|
|
||||||
error = error.error;
|
|
||||||
} else {
|
|
||||||
// No common properties found, just stringify it.
|
// No common properties found, just stringify it.
|
||||||
error = JSON.stringify(error);
|
error = JSON.stringify(error);
|
||||||
extraInfo = ''; // No need to add extra info because it's already in the error.
|
extraInfo = ''; // No need to add extra info because it's already in the error.
|
||||||
|
@ -1058,7 +1053,7 @@ export class CoreDomUtilsProvider {
|
||||||
let errorMessage = error;
|
let errorMessage = error;
|
||||||
|
|
||||||
if (error && typeof error != 'string') {
|
if (error && typeof error != 'string') {
|
||||||
errorMessage = error.message || error.error || error.content || error.body;
|
errorMessage = this.textUtils.getErrorMessageFromError(error);
|
||||||
}
|
}
|
||||||
|
|
||||||
return this.showErrorModal(typeof errorMessage == 'string' ? error : defaultError, needsTranslate, autocloseTime);
|
return this.showErrorModal(typeof errorMessage == 'string' ? error : defaultError, needsTranslate, autocloseTime);
|
||||||
|
|
|
@ -400,6 +400,16 @@ export class CoreTextUtilsProvider {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get the error message from an error object.
|
||||||
|
*
|
||||||
|
* @param {any} error Error object.
|
||||||
|
* @return {string} Error message, undefined if not found.
|
||||||
|
*/
|
||||||
|
getErrorMessageFromError(error: any): string {
|
||||||
|
return error && (error.message || error.error || error.content || error.body);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get the pluginfile URL to replace @@PLUGINFILE@@ wildcards.
|
* Get the pluginfile URL to replace @@PLUGINFILE@@ wildcards.
|
||||||
*
|
*
|
||||||
|
|
Loading…
Reference in New Issue