forked from EVOgeek/Vmeda.Online
		
	MOBILE-2345 lesson: Implement lesson and offline providers
This commit is contained in:
		
							parent
							
								
									a4b1dd0c73
								
							
						
					
					
						commit
						9abc6748ae
					
				
							
								
								
									
										85
									
								
								src/addon/mod/lesson/lang/en.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										85
									
								
								src/addon/mod/lesson/lang/en.json
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,85 @@ | ||||
| { | ||||
|     "answer": "Answer", | ||||
|     "attempt": "Attempt: {{$a}}", | ||||
|     "attemptheader": "Attempt", | ||||
|     "attemptsremaining": "You have {{$a}} attempt(s) remaining", | ||||
|     "averagescore": "Average score", | ||||
|     "averagetime": "Average time", | ||||
|     "branchtable": "Content", | ||||
|     "cannotfindattempt": "Error: could not find attempt", | ||||
|     "cannotfinduser": "Error: could not find users", | ||||
|     "clusterjump": "Unseen question within a cluster", | ||||
|     "completed": "Completed", | ||||
|     "congratulations": "Congratulations - end of lesson reached", | ||||
|     "continue": "Continue", | ||||
|     "continuetonextpage": "Continue to next page.", | ||||
|     "defaultessayresponse": "Your essay will be graded by your teacher.", | ||||
|     "detailedstats": "Detailed statistics", | ||||
|     "didnotanswerquestion": "Did not answer this question.", | ||||
|     "displayofgrade": "Display of grade (for students only)", | ||||
|     "displayscorewithessays": "<p>You earned {{$a.score}} out of {{$a.tempmaxgrade}} for the automatically graded questions.</p><p>Your {{$a.essayquestions}} essay question(s) will be graded and added into your final score at a later date.</p><p>Your current grade without the essay question(s) is {{$a.score}} out of {{$a.grade}}.</p>", | ||||
|     "displayscorewithoutessays": "Your score is {{$a.score}} (out of {{$a.grade}}).", | ||||
|     "emptypassword": "Password cannot be empty", | ||||
|     "enterpassword": "Please enter the password:", | ||||
|     "eolstudentoutoftimenoanswers": "You did not answer any questions.  You have received a 0 for this lesson.", | ||||
|     "errorprefetchrandombranch": "This lesson contains a jump to a random content page. It can't be attempted in the app until it has been started in a web browser.", | ||||
|     "errorreviewretakenotlast": "This attempt can no longer be reviewed because another attempt has been finished.", | ||||
|     "finish": "Finish", | ||||
|     "finishretakeoffline": "This attempt was finished offline.", | ||||
|     "firstwrong": "You have answered incorrectly. Would you like to attempt the question again? (If you now answer the question correctly, it will not count towards your final score.)", | ||||
|     "gotoendoflesson": "Go to the end of the lesson", | ||||
|     "grade": "Grade", | ||||
|     "highscore": "High score", | ||||
|     "hightime": "High time", | ||||
|     "leftduringtimed": "You have left during a timed lesson.<br />Please click on Continue to restart the lesson.", | ||||
|     "leftduringtimednoretake": "You have left during a timed lesson and you are<br />not allowed to retake or continue the lesson.", | ||||
|     "lessonmenu": "Lesson menu", | ||||
|     "lessonstats": "Lesson statistics", | ||||
|     "linkedmedia": "Linked media", | ||||
|     "loginfail": "Login failed, please try again...", | ||||
|     "lowscore": "Low score", | ||||
|     "lowtime": "Low time", | ||||
|     "maximumnumberofattemptsreached": "Maximum number of attempts reached - Moving to next page", | ||||
|     "modattemptsnoteacher": "Student review only works for students.", | ||||
|     "noanswer": "One or more questions have no answer given.  Please go back and submit an answer.", | ||||
|     "nolessonattempts": "No attempts have been made on this lesson.", | ||||
|     "nolessonattemptsgroup": "No attempts have been made by {{$a}} group members on this lesson.", | ||||
|     "notcompleted": "Not completed", | ||||
|     "numberofcorrectanswers": "Number of correct answers: {{$a}}", | ||||
|     "numberofpagesviewed": "Number of questions answered: {{$a}}", | ||||
|     "numberofpagesviewednotice": "Number of questions answered: {{$a.nquestions}} (You should answer at least {{$a.minquestions}})", | ||||
|     "ongoingcustom": "You have earned {{$a.score}} point(s) out of {{$a.currenthigh}} point(s) thus far.", | ||||
|     "ongoingnormal": "You have answered {{$a.correct}} correctly out of {{$a.viewed}} attempts.", | ||||
|     "or": "OR", | ||||
|     "overview": "Overview", | ||||
|     "preview": "Preview", | ||||
|     "progressbarteacherwarning2": "You will not see the progress bar because you can edit this lesson", | ||||
|     "progresscompleted": "You have completed {{$a}}% of the lesson", | ||||
|     "question": "Question", | ||||
|     "rawgrade": "Raw grade", | ||||
|     "reports": "Reports", | ||||
|     "response": "Response", | ||||
|     "retakefinishedinsync": "An offline attempt was synchronised. Do you want to review it?", | ||||
|     "retakelabelfull": "{{retake}}: {{grade}} {{timestart}} ({{duration}})", | ||||
|     "retakelabelshort": "{{retake}}: {{grade}} {{timestart}}", | ||||
|     "review": "Review", | ||||
|     "reviewlesson": "Review lesson", | ||||
|     "reviewquestionback": "Yes, I'd like to try again", | ||||
|     "reviewquestioncontinue": "No, I just want to go on to the next question", | ||||
|     "secondpluswrong": "Not quite.  Would you like to try again?", | ||||
|     "submit": "Submit", | ||||
|     "teacherjumpwarning": "An {{$a.cluster}} jump or an {{$a.unseen}} jump is being used in this lesson.  The next page jump will be used instead.  Login as a student to test these jumps.", | ||||
|     "teacherongoingwarning": "Ongoing score is only displayed for student.  Login as a student to test ongoing score", | ||||
|     "teachertimerwarning": "Timer only works for students.  Test the timer by logging in as a student.", | ||||
|     "thatsthecorrectanswer": "That's the correct answer", | ||||
|     "thatsthewronganswer": "That's the wrong answer", | ||||
|     "timeremaining": "Time remaining", | ||||
|     "timetaken": "Time taken", | ||||
|     "unseenpageinbranch": "Unseen question within a content page", | ||||
|     "warningretakefinished": "The attempt was finished on the site.", | ||||
|     "welldone": "Well done!", | ||||
|     "youhaveseen": "You have seen more than one page of this lesson already.<br />Do you want to start at the last page you saw?", | ||||
|     "youranswer": "Your answer", | ||||
|     "yourcurrentgradeisoutof": "Your current grade is {{$a.grade}} out of {{$a.total}}", | ||||
|     "youshouldview": "You should answer at least: {{$a}}" | ||||
| } | ||||
							
								
								
									
										29
									
								
								src/addon/mod/lesson/lesson.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								src/addon/mod/lesson/lesson.module.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,29 @@ | ||||
| // (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 { NgModule } from '@angular/core'; | ||||
| import { AddonModLessonProvider } from './providers/lesson'; | ||||
| import { AddonModLessonOfflineProvider } from './providers/lesson-offline'; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     declarations: [ | ||||
|     ], | ||||
|     imports: [ | ||||
|     ], | ||||
|     providers: [ | ||||
|         AddonModLessonProvider, | ||||
|         AddonModLessonOfflineProvider | ||||
|     ] | ||||
| }) | ||||
| export class AddonModLessonModule { } | ||||
							
								
								
									
										598
									
								
								src/addon/mod/lesson/providers/lesson-offline.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										598
									
								
								src/addon/mod/lesson/providers/lesson-offline.ts
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,598 @@ | ||||
| // (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 { CoreTextUtilsProvider } from '@providers/utils/text'; | ||||
| import { CoreTimeUtilsProvider } from '@providers/utils/time'; | ||||
| import { CoreUtilsProvider } from '@providers/utils/utils'; | ||||
| import { AddonModLessonProvider } from './lesson'; | ||||
| 
 | ||||
| /** | ||||
|  * Service to handle offline lesson. | ||||
|  */ | ||||
| @Injectable() | ||||
| export class AddonModLessonOfflineProvider { | ||||
| 
 | ||||
|     protected logger; | ||||
| 
 | ||||
|     // Variables for database. We use lowercase in the names to match the WS responses.
 | ||||
|     protected RETAKES_TABLE = 'addon_mod_lesson_retakes'; | ||||
|     protected PAGE_ATTEMPTS_TABLE = 'addon_mod_lesson_page_attempts'; | ||||
|     protected tablesSchema = [ | ||||
|         { | ||||
|             name: this.RETAKES_TABLE, | ||||
|             columns: [ | ||||
|                 { | ||||
|                     name: 'lessonid', | ||||
|                     type: 'INTEGER', | ||||
|                     primaryKey: true // Only 1 offline retake per lesson.
 | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'retake', // Retake number.
 | ||||
|                     type: 'INTEGER', | ||||
|                     notNull: true | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'courseid', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'finished', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'outoftime', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'timemodified', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'lastquestionpage', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|             ] | ||||
|         }, | ||||
|         { | ||||
|             name: this.PAGE_ATTEMPTS_TABLE, | ||||
|             columns: [ | ||||
|                 { | ||||
|                     name: 'lessonid', | ||||
|                     type: 'INTEGER', | ||||
|                     notNull: true | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'retake', // Retake number.
 | ||||
|                     type: 'INTEGER', | ||||
|                     notNull: true | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'pageid', | ||||
|                     type: 'INTEGER', | ||||
|                     notNull: true | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'timemodified', | ||||
|                     type: 'INTEGER', | ||||
|                     notNull: true | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'courseid', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'data', | ||||
|                     type: 'TEXT' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'type', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'newpageid', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'correct', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'answerid', | ||||
|                     type: 'INTEGER' | ||||
|                 }, | ||||
|                 { | ||||
|                     name: 'useranswer', | ||||
|                     type: 'TEXT' | ||||
|                 }, | ||||
|             ], | ||||
|             primaryKeys: ['lessonid', 'retake', 'pageid', 'timemodified'] // A user can attempt several times per page and retake.
 | ||||
|         } | ||||
|     ]; | ||||
| 
 | ||||
|     constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private timeUtils: CoreTimeUtilsProvider, | ||||
|             private textUtils: CoreTextUtilsProvider, private utils: CoreUtilsProvider) { | ||||
|         this.logger = logger.getInstance('AddonModLessonOfflineProvider'); | ||||
| 
 | ||||
|         this.sitesProvider.createTablesFromSchema(this.tablesSchema); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete an offline attempt. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} retake Lesson retake number. | ||||
|      * @param {number} pageId Page ID. | ||||
|      * @param {number} timemodified The timemodified of the attempt. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     deleteAttempt(lessonId: number, retake: number, pageId: number, timemodified: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.getDb().deleteRecords(this.PAGE_ATTEMPTS_TABLE, { | ||||
|                 lessonid: lessonId, | ||||
|                 retake: retake, | ||||
|                 pageid: pageId, | ||||
|                 timemodified: timemodified | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete offline lesson retake. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     deleteRetake(lessonId: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.getDb().deleteRecords(this.RETAKES_TABLE, {lessonid: lessonId}); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Delete offline attempts for a retake and page. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} retake Lesson retake number. | ||||
|      * @param {number} pageId Page ID. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved when done. | ||||
|      */ | ||||
|     deleteRetakeAttemptsForPage(lessonId: number, retake: number, pageId: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.getDb().deleteRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake, pageid: pageId}); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Mark a retake as finished. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} courseId Course ID the lesson belongs to. | ||||
|      * @param {number} retake Retake number. | ||||
|      * @param {boolean} finished  Whether retake is finished. | ||||
|      * @param {boolean} outOfTime If the user ran out of time. | ||||
|      * @param {string} [siteId]   Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>}           Promise resolved in success, rejected otherwise. | ||||
|      */ | ||||
|     finishRetake(lessonId: number, courseId: number, retake: number, finished?: boolean, outOfTime?: boolean, siteId?: string) | ||||
|             : Promise<any> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             // Get current stored retake (if any). If not found, it will create a new one.
 | ||||
|             return this.getRetakeWithFallback(lessonId, courseId, retake, site.id).then((entry) => { | ||||
|                 entry.finished = finished ? 1 : 0; | ||||
|                 entry.outoftime = outOfTime ? 1 : 0; | ||||
|                 entry.timemodified = this.timeUtils.timestamp(); | ||||
| 
 | ||||
|                 return site.getDb().insertRecord(this.RETAKES_TABLE, entry); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all the offline page attempts in a certain site. | ||||
|      * | ||||
|      * @param {string} [siteId] Site ID. If not set, use current site. | ||||
|      * @return {Promise<any>} Promise resolved when the offline attempts are retrieved. | ||||
|      */ | ||||
|     getAllAttempts(siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSiteDb(siteId).then((db) => { | ||||
|             return db.getAllRecords(this.PAGE_ATTEMPTS_TABLE); | ||||
|         }).then((attempts) => { | ||||
|             return this.parsePageAttempts(attempts); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all the lessons that have offline data in a certain site. | ||||
|      * | ||||
|      * @param {string} [siteId] Site ID. If not set, use current site. | ||||
|      * @return {Promise<any>} Promise resolved with an object containing the lessons. | ||||
|      */ | ||||
|     getAllLessonsWithData(siteId?: string): Promise<any> { | ||||
|         const promises = [], | ||||
|             lessons = {}; | ||||
| 
 | ||||
|         // Get the lessons from page attempts.
 | ||||
|         promises.push(this.getAllAttempts(siteId).then((entries) => { | ||||
|             this.getLessonsFromEntries(lessons, entries); | ||||
|         }).catch(() => { | ||||
|             // Ignore errors.
 | ||||
|         })); | ||||
| 
 | ||||
|         // Get the lessons from retakes.
 | ||||
|         promises.push(this.getAllRetakes(siteId).then((entries) => { | ||||
|             this.getLessonsFromEntries(lessons, entries); | ||||
|         }).catch(() => { | ||||
|             // Ignore errors.
 | ||||
|         })); | ||||
| 
 | ||||
|         return Promise.all(promises).then(() => { | ||||
|             return this.utils.objectToArray(lessons); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get all the offline retakes in a certain site. | ||||
|      * | ||||
|      * @param {string} [siteId] Site ID. If not set, use current site. | ||||
|      * @return {Promise<any>} Promise resolved when the offline retakes are retrieved. | ||||
|      */ | ||||
|     getAllRetakes(siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSiteDb(siteId).then((db) => { | ||||
|             return db.getAllRecords(this.RETAKES_TABLE); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve the last offline attempt stored in a retake. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} retake Retake number. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved with the attempt (undefined if no attempts). | ||||
|      */ | ||||
|     getLastQuestionPageAttempt(lessonId: number, retake: number, siteId?: string): Promise<any> { | ||||
|         siteId = siteId || this.sitesProvider.getCurrentSiteId(); | ||||
| 
 | ||||
|         return this.getRetakeWithFallback(lessonId, 0, retake, siteId).then((retakeData) => { | ||||
|             if (!retakeData.lastquestionpage) { | ||||
|                 // No question page attempted.
 | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             return this.getRetakeAttemptsForPage(lessonId, retake, retakeData.lastquestionpage, siteId).then((attempts) => { | ||||
|                 // Return the attempt with highest timemodified.
 | ||||
|                 return attempts.reduce((a, b) => { | ||||
|                     return a.timemodified > b.timemodified ? a : b; | ||||
|                 }); | ||||
|             }); | ||||
|         }).catch(() => { | ||||
|             // Error, return undefined.
 | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve all offline attempts for a lesson. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any[]>} Promise resolved with the attempts. | ||||
|      */ | ||||
|     getLessonAttempts(lessonId: number, siteId?: string): Promise<any[]> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.getDb().getRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId}); | ||||
|         }).then((attempts) => { | ||||
|             return this.parsePageAttempts(attempts); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Given a list of DB entries (either retakes or page attempts), get the list of lessons. | ||||
|      * | ||||
|      * @param {any} lessons Object where to store the lessons. | ||||
|      * @param {any[]} entries List of DB entries. | ||||
|      */ | ||||
|     protected getLessonsFromEntries(lessons: any, entries: any[]): void { | ||||
|         entries.forEach((entry) => { | ||||
|             if (!lessons[entry.lessonid]) { | ||||
|                 lessons[entry.lessonid] = { | ||||
|                     id: entry.lessonid, | ||||
|                     courseId: entry.courseid | ||||
|                 }; | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get attempts for question pages and retake in a lesson. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} retake Retake number. | ||||
|      * @param {boolean} [correct] True to only fetch correct attempts, false to get them all. | ||||
|      * @param {number} [pageId] If defined, only get attempts on this page. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any[]>}  Promise resolved with the attempts. | ||||
|      */ | ||||
|     getQuestionsAttempts(lessonId: number, retake: number, correct?: boolean, pageId?: number, siteId?: string): Promise<any[]> { | ||||
|         let promise; | ||||
| 
 | ||||
|         if (pageId) { | ||||
|             // Page ID is set, only get the attempts for that page.
 | ||||
|             promise = this.getRetakeAttemptsForPage(lessonId, retake, pageId, siteId); | ||||
|         } else { | ||||
|             // Page ID not specified, get all the attempts.
 | ||||
|             promise = this.getRetakeAttemptsForType(lessonId, retake, AddonModLessonProvider.TYPE_QUESTION, siteId); | ||||
|         } | ||||
| 
 | ||||
|         return promise.then((attempts) => { | ||||
|             if (correct) { | ||||
|                 return attempts.filter((attempt) => { | ||||
|                     return !!attempt.correct; | ||||
|                 }); | ||||
|             } | ||||
| 
 | ||||
|             return attempts; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve a retake from site DB. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved with the retake. | ||||
|      */ | ||||
|     getRetake(lessonId: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.getDb().getRecord(this.RETAKES_TABLE, {lessonid: lessonId}); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve all offline attempts for a retake. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} retake   Retake number. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any[]>} Promise resolved with the retake attempts. | ||||
|      */ | ||||
|     getRetakeAttempts(lessonId: number, retake: number, siteId?: string): Promise<any[]> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.getDb().getRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake}); | ||||
|         }).then((attempts) => { | ||||
|             return this.parsePageAttempts(attempts); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve offline attempts for a retake and page. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} retake   Lesson retake number. | ||||
|      * @param {number} pageId   Page ID. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>}         Promise resolved with the retake attempts. | ||||
|      */ | ||||
|     getRetakeAttemptsForPage(lessonId: number, retake: number, pageId: number, siteId?: string): Promise<any[]> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.getDb().getRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake, pageid: pageId}); | ||||
|         }).then((attempts) => { | ||||
|             return this.parsePageAttempts(attempts); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Retrieve offline attempts for certain pages for a retake. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} retake   Retake number. | ||||
|      * @param {number} type     Type of the pages to get: TYPE_QUESTION or TYPE_STRUCTURE. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>}         Promise resolved with the retake attempts. | ||||
|      */ | ||||
|     getRetakeAttemptsForType(lessonId: number, retake: number, type: number, siteId?: string): Promise<any> { | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             return site.getDb().getRecords(this.PAGE_ATTEMPTS_TABLE, {lessonid: lessonId, retake: retake, type: type}); | ||||
|         }).then((attempts) => { | ||||
|             return this.parsePageAttempts(attempts); | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get stored retake. If not found or doesn't match the retake number, return a new one. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} courseId Course ID the lesson belongs to. | ||||
|      * @param {number} retake Retake number. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved with the retake. | ||||
|      */ | ||||
|     protected getRetakeWithFallback(lessonId: number, courseId: number, retake: number, siteId?: string): Promise<any> { | ||||
|         // Get current stored retake.
 | ||||
|         return this.getRetake(lessonId, siteId).then((retakeData) => { | ||||
|             if (retakeData.retake != retake) { | ||||
|                 // The stored retake doesn't match the retake number, create a new one.
 | ||||
|                 return Promise.reject(null); | ||||
|             } | ||||
| 
 | ||||
|             return retakeData; | ||||
|         }).catch(() => { | ||||
|             // No retake, create a new one.
 | ||||
|             return { | ||||
|                 lessonid: lessonId, | ||||
|                 retake: retake, | ||||
|                 courseid: courseId, | ||||
|                 finished: 0 | ||||
|             }; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if there is a finished retake for a certain lesson. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<boolean>} Promise resolved with boolean. | ||||
|      */ | ||||
|     hasFinishedRetake(lessonId: number, siteId?: string): Promise<boolean> { | ||||
|         return this.getRetake(lessonId, siteId).then((retake) => { | ||||
|             return !!retake.finished; | ||||
|         }).catch(() => { | ||||
|             return false; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if a lesson has offline data. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<boolean>} Promise resolved with boolean. | ||||
|      */ | ||||
|     hasOfflineData(lessonId: number, siteId?: string): Promise<boolean> { | ||||
|         const promises = []; | ||||
|         let hasData = false; | ||||
| 
 | ||||
|         promises.push(this.getRetake(lessonId, siteId).then(() => { | ||||
|             hasData = true; | ||||
|         }).catch(() => { | ||||
|             // Ignore errors.
 | ||||
|         })); | ||||
| 
 | ||||
|         promises.push(this.getLessonAttempts(lessonId, siteId).then((attempts) => { | ||||
|             hasData = hasData || !!attempts.length; | ||||
|         }).catch(() => { | ||||
|             // Ignore errors.
 | ||||
|         })); | ||||
| 
 | ||||
|         return Promise.all(promises).then(() => { | ||||
|             return hasData; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Check if there are offline attempts for a retake. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} retake Retake number. | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<boolean>} Promise resolved with a boolean. | ||||
|      */ | ||||
|     hasRetakeAttempts(lessonId: number, retake: number, siteId?: string): Promise<boolean> { | ||||
|         return this.getRetakeAttempts(lessonId, retake, siteId).then((list) => { | ||||
|             return !!list.length; | ||||
|         }).catch(() => { | ||||
|             return false; | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Parse some properties of a page attempt. | ||||
|      * | ||||
|      * @param {any} attempt The attempt to treat. | ||||
|      * @return {any} The treated attempt. | ||||
|      */ | ||||
|     protected parsePageAttempt(attempt: any): any { | ||||
|         attempt.data = this.textUtils.parseJSON(attempt.data); | ||||
|         attempt.useranswer = this.textUtils.parseJSON(attempt.useranswer); | ||||
| 
 | ||||
|         return attempt; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Parse some properties of some page attempts. | ||||
|      * | ||||
|      * @param {any[]} attempts The attempts to treat. | ||||
|      * @return {any[]} The treated attempts. | ||||
|      */ | ||||
|     protected parsePageAttempts(attempts: any[]): any[] { | ||||
|         attempts.forEach((attempt) => { | ||||
|             this.parsePageAttempt(attempt); | ||||
|         }); | ||||
| 
 | ||||
|         return attempts; | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Process a lesson page, saving its data. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} courseId Course ID the lesson belongs to. | ||||
|      * @param {number} retake Retake number. | ||||
|      * @param {any} page Page. | ||||
|      * @param {any} data Data to save. | ||||
|      * @param {number} newPageId New page ID (calculated). | ||||
|      * @param {number} [answerId] The answer ID that the user answered. | ||||
|      * @param {boolean} [correct] If answer is correct. Only for question pages. | ||||
|      * @param {any} [userAnswer] The user's answer (userresponse from checkAnswer). | ||||
|      * @param {string} [siteId] Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved in success, rejected otherwise. | ||||
|      */ | ||||
|     processPage(lessonId: number, courseId: number, retake: number, page: any, data: any, newPageId: number, answerId?: number, | ||||
|             correct?: boolean, userAnswer?: any, siteId?: string): Promise<any> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             const entry = { | ||||
|                 lessonid: lessonId, | ||||
|                 retake: retake, | ||||
|                 pageid: page.id, | ||||
|                 timemodified: this.timeUtils.timestamp(), | ||||
|                 courseid: courseId, | ||||
|                 data: data ? JSON.stringify(data) : null, | ||||
|                 type: page.type, | ||||
|                 newpageid: newPageId, | ||||
|                 correct: correct ? 1 : 0, | ||||
|                 answerid: Number(answerId), | ||||
|                 useranswer: userAnswer ? JSON.stringify(userAnswer) : null, | ||||
|             }; | ||||
| 
 | ||||
|             return site.getDb().insertRecord(this.PAGE_ATTEMPTS_TABLE, entry); | ||||
|         }).then(() => { | ||||
|             if (page.type == AddonModLessonProvider.TYPE_QUESTION) { | ||||
|                 // It's a question page, set it as last question page attempted.
 | ||||
|                 return this.setLastQuestionPageAttempted(lessonId, courseId, retake, page.id, siteId); | ||||
|             } | ||||
|         }); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Set the last question page attempted in a retake. | ||||
|      * | ||||
|      * @param {number} lessonId Lesson ID. | ||||
|      * @param {number} courseId Course ID the lesson belongs to. | ||||
|      * @param {number} retake Retake number. | ||||
|      * @param {number} lastPage ID of the last question page attempted. | ||||
|      * @param {string} [siteId]  Site ID. If not defined, current site. | ||||
|      * @return {Promise<any>} Promise resolved in success, rejected otherwise. | ||||
|      */ | ||||
|     setLastQuestionPageAttempted(lessonId: number, courseId: number, retake: number, lastPage: number, siteId?: string) | ||||
|             : Promise<any> { | ||||
| 
 | ||||
|         return this.sitesProvider.getSite(siteId).then((site) => { | ||||
|             // Get current stored retake (if any). If not found, it will create a new one.
 | ||||
|             return this.getRetakeWithFallback(lessonId, courseId, retake, site.id).then((entry) => { | ||||
|                 entry.lastquestionpage = lastPage; | ||||
|                 entry.timemodified = this.timeUtils.timestamp(); | ||||
| 
 | ||||
|                 return site.getDb().insertRecord(this.RETAKES_TABLE, entry); | ||||
|             }); | ||||
|         }); | ||||
|     } | ||||
| } | ||||
							
								
								
									
										3233
									
								
								src/addon/mod/lesson/providers/lesson.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										3233
									
								
								src/addon/mod/lesson/providers/lesson.ts
									
									
									
									
									
										Normal file
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							| @ -22,6 +22,12 @@ import { CoreCoursesProvider } from '@core/courses/providers/courses'; | ||||
|  */ | ||||
| @Injectable() | ||||
| export class CoreGradesProvider { | ||||
| 
 | ||||
|     static TYPE_NONE = 0; // Moodle's GRADE_TYPE_NONE.
 | ||||
|     static TYPE_VALUE = 1; // Moodle's GRADE_TYPE_VALUE.
 | ||||
|     static TYPE_SCALE = 2; // Moodle's GRADE_TYPE_SCALE.
 | ||||
|     static TYPE_TEXT = 3; // Moodle's GRADE_TYPE_TEXT.
 | ||||
| 
 | ||||
|     protected ROOT_CACHE_KEY = 'mmGrades:'; | ||||
| 
 | ||||
|     protected logger; | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user