// (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 { CoreSitesProvider } from '@providers/sites'; import { CoreUtilsProvider } from '@providers/utils/utils'; import { CoreCoursesProvider } from '@core/courses/providers/courses'; import { CoreSite } from '@classes/site'; /** * Service to handle course completion. */ @Injectable() export class AddonCourseCompletionProvider { protected ROOT_CACHE_KEY = 'mmaCourseCompletion:'; protected logger; constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private coursesProvider: CoreCoursesProvider, private utils: CoreUtilsProvider) { this.logger = logger.getInstance('AddonCourseCompletionProvider'); } /** * Returns whether or not the user can mark a course as self completed. * It can if it's configured in the course and it hasn't been completed yet. * * @param {number} userId User ID. * @param {any} completion Course completion. * @return {boolean} True if user can mark course as self completed, false otherwise. */ canMarkSelfCompleted(userId: number, completion: any): boolean { let selfCompletionActive = false, alreadyMarked = false; if (this.sitesProvider.getCurrentSiteUserId() != userId) { return false; } completion.completions.forEach((criteria) => { if (criteria.type === 1) { // Self completion criteria found. selfCompletionActive = true; alreadyMarked = criteria.complete; } }); return selfCompletionActive && !alreadyMarked; } /** * Get completed status text. The language code returned is meant to be translated. * * @param {any} completion Course completion. * @return {string} Language code of the text to show. */ getCompletedStatusText(completion: any): string { if (completion.completed) { return 'addon.coursecompletion.completed'; } else { // Let's calculate status. let hasStarted = false; completion.completions.forEach((criteria) => { if (criteria.timecompleted || criteria.complete) { hasStarted = true; } }); if (hasStarted) { return 'addon.coursecompletion.inprogress'; } else { return 'addon.coursecompletion.notyetstarted'; } } } /** * Get course completion status for a certain course and user. * * @param {number} courseId Course ID. * @param {number} [userId] User ID. If not defined, use current user. * @param {any} [preSets] Presets to use when calling the WebService. * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise} Promise to be resolved when the completion is retrieved. */ getCompletion(courseId: number, userId?: number, preSets?: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); preSets = preSets || {}; this.logger.debug('Get completion for course ' + courseId + ' and user ' + userId); const data = { courseid: courseId, userid: userId }; preSets.cacheKey = this.getCompletionCacheKey(courseId, userId); preSets.updateFrequency = preSets.updateFrequency || CoreSite.FREQUENCY_SOMETIMES; return site.read('core_completion_get_course_completion_status', data, preSets).then((data) => { if (data.completionstatus) { return data.completionstatus; } return Promise.reject(null); }); }); } /** * Get cache key for get completion WS calls. * * @param {number} courseId Course ID. * @param {number} useIid User ID. * @return {string} Cache key. */ protected getCompletionCacheKey(courseId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'view:' + courseId + ':' + userId; } /** * Invalidates view course completion WS call. * * @param {number} courseId Course ID. * @param {number} [userId] User ID. If not defined, use current user. * @return {Promise} Promise resolved when the list is invalidated. */ invalidateCourseCompletion(courseId: number, userId?: number): Promise { userId = userId || this.sitesProvider.getCurrentSiteUserId(); return this.sitesProvider.getCurrentSite().invalidateWsCacheForKey(this.getCompletionCacheKey(courseId, userId)); } /** * Returns whether or not the view course completion plugin is enabled for the current site. * * @return {boolean} True if plugin enabled, false otherwise. */ isPluginViewEnabled(): boolean { return this.sitesProvider.isLoggedIn(); } /** * Returns whether or not the view course completion plugin is enabled for a certain course. * * @param {number} courseId Course ID. * @param {boolean} [preferCache=true] True if shouldn't call WS if data is cached, false otherwise. * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ isPluginViewEnabledForCourse(courseId: number, preferCache: boolean = true): Promise { if (!courseId) { return Promise.reject(null); } return this.coursesProvider.getUserCourse(courseId, preferCache).then((course) => { if (course) { if (typeof course.enablecompletion != 'undefined' && course.enablecompletion == 0) { // Completion not enabled for the course. return false; } if (typeof course.completionhascriteria != 'undefined' && course.completionhascriteria == 0) { // No criteria, cannot view completion. return false; } } return true; }); } /** * Returns whether or not the view course completion plugin is enabled for a certain user. * * @param {number} courseId Course ID. * @param {number} [userId] User ID. If not defined, use current user. * @return {Promise} Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise. */ isPluginViewEnabledForUser(courseId: number, userId?: number): Promise { // Check if user wants to view his own completion. const currentUserId = this.sitesProvider.getCurrentSiteUserId(); let promise; if (!userId || userId == currentUserId) { // Viewing own completion. Get the course to check if it has completion criteria. promise = this.coursesProvider.getUserCourse(courseId, true).then((course): any => { // If the site is returning the completionhascriteria then the user can view his own completion. // We already checked the value in isPluginViewEnabledForCourse. if (course && typeof course.completionhascriteria != 'undefined') { return true; } return Promise.reject(null); }); } else { promise = Promise.reject(null); } return promise.catch(() => { // User not viewing own completion or the site doesn't tell us if the course has criteria. // The only way to know if completion can be viewed is to call the WS. // Disable emergency cache to be able to detect that the plugin has been disabled (WS will fail). const preSets: any = { emergencyCache: 0 }; return this.getCompletion(courseId, userId, preSets).then(() => { return true; }).catch((error) => { if (this.utils.isWebServiceError(error)) { // The WS returned an error, plugin is not enabled. return false; } else { // Not a WS error. Check if we have a cached value. preSets.omitExpires = true; return this.getCompletion(courseId, userId, preSets).then(() => { return true; }).catch(() => { return false; }); } }); }); } /** * Mark a course as self completed. * * @param {number} courseId Course ID. * @return {Promise} Resolved on success. */ markCourseAsSelfCompleted(courseId: number): Promise { const params = { courseid: courseId }; return this.sitesProvider.getCurrentSite().write('core_completion_mark_course_self_completed', params).then((response) => { if (!response.status) { return Promise.reject(null); } }); } }