forked from EVOgeek/Vmeda.Online
460 lines
18 KiB
TypeScript
460 lines
18 KiB
TypeScript
// (C) Copyright 2015 Moodle Pty Ltd.
|
|
//
|
|
// 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 { CoreError } from '@classes/errors/error';
|
|
import { SQLiteDBRecordValues } from '@classes/sqlitedb';
|
|
import { CoreFile } from '@services/file';
|
|
import { CoreSites } from '@services/sites';
|
|
import { CoreTextUtils } from '@services/utils/text';
|
|
import { CoreTimeUtils } from '@services/utils/time';
|
|
import { makeSingleton } from '@singletons';
|
|
import { AddonModAssignOutcomes, AddonModAssignSavePluginData } from './assign';
|
|
import {
|
|
AddonModAssignSubmissionsDBRecord,
|
|
AddonModAssignSubmissionsGradingDBRecord,
|
|
SUBMISSIONS_GRADES_TABLE,
|
|
SUBMISSIONS_TABLE,
|
|
} from './database/assign';
|
|
|
|
/**
|
|
* Service to handle offline assign.
|
|
*/
|
|
@Injectable({ providedIn: 'root' })
|
|
export class AddonModAssignOfflineProvider {
|
|
|
|
/**
|
|
* Delete a submission.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param userId User ID. If not defined, site's current user.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved if deleted, rejected if failure.
|
|
*/
|
|
async deleteSubmission(assignId: number, userId?: number, siteId?: string): Promise<void> {
|
|
const site = await CoreSites.instance.getSite(siteId);
|
|
userId = userId || site.getUserId();
|
|
|
|
await site.getDb().deleteRecords(
|
|
SUBMISSIONS_TABLE,
|
|
{ assignid: assignId, userid: userId },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Delete a submission grade.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param userId User ID. If not defined, site's current user.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved if deleted, rejected if failure.
|
|
*/
|
|
async deleteSubmissionGrade(assignId: number, userId?: number, siteId?: string): Promise<void> {
|
|
const site = await CoreSites.instance.getSite(siteId);
|
|
userId = userId || site.getUserId();
|
|
|
|
await site.getDb().deleteRecords(
|
|
SUBMISSIONS_GRADES_TABLE,
|
|
{ assignid: assignId, userid: userId },
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Get all the assignments ids that have something to be synced.
|
|
*
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with assignments id that have something to be synced.
|
|
*/
|
|
async getAllAssigns(siteId?: string): Promise<number[]> {
|
|
const promises:
|
|
Promise<AddonModAssignSubmissionsDBRecordFormatted[] | AddonModAssignSubmissionsGradingDBRecordFormatted[]>[] = [];
|
|
|
|
promises.push(this.getAllSubmissions(siteId));
|
|
promises.push(this.getAllSubmissionsGrade(siteId));
|
|
|
|
const results = await Promise.all(promises);
|
|
// Flatten array.
|
|
const flatten: (AddonModAssignSubmissionsDBRecord | AddonModAssignSubmissionsGradingDBRecord)[] =
|
|
[].concat.apply([], results);
|
|
|
|
// Get assign id.
|
|
let assignIds: number[] = flatten.map((assign) => assign.assignid);
|
|
// Get unique values.
|
|
assignIds = assignIds.filter((id, pos) => assignIds.indexOf(id) == pos);
|
|
|
|
return assignIds;
|
|
}
|
|
|
|
/**
|
|
* Get all the stored submissions from all the assignments.
|
|
*
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with submissions.
|
|
*/
|
|
protected async getAllSubmissions(siteId?: string): Promise<AddonModAssignSubmissionsDBRecordFormatted[]> {
|
|
return this.getAssignSubmissionsFormatted(undefined, siteId);
|
|
}
|
|
|
|
/**
|
|
* Get all the stored submissions for a certain assignment.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with submissions.
|
|
*/
|
|
async getAssignSubmissions(assignId: number, siteId?: string): Promise<AddonModAssignSubmissionsDBRecordFormatted[]> {
|
|
return this.getAssignSubmissionsFormatted({ assignid: assignId }, siteId);
|
|
}
|
|
|
|
/**
|
|
* Convenience helper function to get stored submissions formatted.
|
|
*
|
|
* @param conditions Query conditions.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with submissions.
|
|
*/
|
|
protected async getAssignSubmissionsFormatted(
|
|
conditions: SQLiteDBRecordValues = {},
|
|
siteId?: string,
|
|
): Promise<AddonModAssignSubmissionsDBRecordFormatted[]> {
|
|
const db = await CoreSites.instance.getSiteDb(siteId);
|
|
|
|
const submissions: AddonModAssignSubmissionsDBRecord[] = await db.getRecords(SUBMISSIONS_TABLE, conditions);
|
|
|
|
// Parse the plugin data.
|
|
return submissions.map((submission) => ({
|
|
assignid: submission.assignid,
|
|
userid: submission.userid,
|
|
courseid: submission.courseid,
|
|
plugindata: CoreTextUtils.instance.parseJSON<AddonModAssignSavePluginData>(submission.plugindata, {}),
|
|
onlinetimemodified: submission.onlinetimemodified,
|
|
timecreated: submission.timecreated,
|
|
timemodified: submission.timemodified,
|
|
submitted: submission.submitted,
|
|
submissionstatement: submission.submissionstatement,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Get all the stored submissions grades from all the assignments.
|
|
*
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with submissions grades.
|
|
*/
|
|
protected async getAllSubmissionsGrade(siteId?: string): Promise<AddonModAssignSubmissionsGradingDBRecordFormatted[]> {
|
|
return this.getAssignSubmissionsGradeFormatted(undefined, siteId);
|
|
}
|
|
|
|
/**
|
|
* Get all the stored submissions grades for a certain assignment.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with submissions grades.
|
|
*/
|
|
async getAssignSubmissionsGrade(
|
|
assignId: number,
|
|
siteId?: string,
|
|
): Promise<AddonModAssignSubmissionsGradingDBRecordFormatted[]> {
|
|
return this.getAssignSubmissionsGradeFormatted({ assignid: assignId }, siteId);
|
|
}
|
|
|
|
/**
|
|
* Convenience helper function to get stored submissions grading formatted.
|
|
*
|
|
* @param conditions Query conditions.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with submissions grades.
|
|
*/
|
|
protected async getAssignSubmissionsGradeFormatted(
|
|
conditions: SQLiteDBRecordValues = {},
|
|
siteId?: string,
|
|
): Promise<AddonModAssignSubmissionsGradingDBRecordFormatted[]> {
|
|
const db = await CoreSites.instance.getSiteDb(siteId);
|
|
|
|
const submissions: AddonModAssignSubmissionsGradingDBRecord[] = await db.getRecords(SUBMISSIONS_GRADES_TABLE, conditions);
|
|
|
|
// Parse the plugin data and outcomes.
|
|
return submissions.map((submission) => ({
|
|
assignid: submission.assignid,
|
|
userid: submission.userid,
|
|
courseid: submission.courseid,
|
|
grade: submission.grade,
|
|
attemptnumber: submission.attemptnumber,
|
|
addattempt: submission.addattempt,
|
|
workflowstate: submission.workflowstate,
|
|
applytoall: submission.applytoall,
|
|
outcomes: CoreTextUtils.instance.parseJSON<AddonModAssignOutcomes>(submission.outcomes, {}),
|
|
plugindata: CoreTextUtils.instance.parseJSON<AddonModAssignSavePluginData>(submission.plugindata, {}),
|
|
timemodified: submission.timemodified,
|
|
}));
|
|
}
|
|
|
|
/**
|
|
* Get a stored submission.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param userId User ID. If not defined, site's current user.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with submission.
|
|
*/
|
|
async getSubmission(assignId: number, userId?: number, siteId?: string): Promise<AddonModAssignSubmissionsDBRecordFormatted> {
|
|
userId = userId || CoreSites.instance.getCurrentSiteUserId();
|
|
|
|
const submissions = await this.getAssignSubmissionsFormatted({ assignid: assignId, userid: userId }, siteId);
|
|
|
|
if (submissions.length) {
|
|
return submissions[0];
|
|
}
|
|
|
|
throw new CoreError('No records found.');
|
|
}
|
|
|
|
/**
|
|
* Get the path to the folder where to store files for an offline submission.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param userId User ID. If not defined, site's current user.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the path.
|
|
*/
|
|
async getSubmissionFolder(assignId: number, userId?: number, siteId?: string): Promise<string> {
|
|
const site = await CoreSites.instance.getSite(siteId);
|
|
|
|
userId = userId || site.getUserId();
|
|
const siteFolderPath = CoreFile.instance.getSiteFolder(site.getId());
|
|
const submissionFolderPath = 'offlineassign/' + assignId + '/' + userId;
|
|
|
|
return CoreTextUtils.instance.concatenatePaths(siteFolderPath, submissionFolderPath);
|
|
}
|
|
|
|
/**
|
|
* Get a stored submission grade.
|
|
* Submission grades are not identified using attempt number so it can retrieve the feedback for a previous attempt.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param userId User ID. If not defined, site's current user.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with submission grade.
|
|
*/
|
|
async getSubmissionGrade(
|
|
assignId: number,
|
|
userId?: number,
|
|
siteId?: string,
|
|
): Promise<AddonModAssignSubmissionsGradingDBRecordFormatted> {
|
|
userId = userId || CoreSites.instance.getCurrentSiteUserId();
|
|
|
|
const submissions = await this.getAssignSubmissionsGradeFormatted({ assignid: assignId, userid: userId }, siteId);
|
|
|
|
if (submissions.length) {
|
|
return submissions[0];
|
|
}
|
|
|
|
throw new CoreError('No records found.');
|
|
}
|
|
|
|
/**
|
|
* Get the path to the folder where to store files for a certain plugin in an offline submission.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param pluginName Name of the plugin. Must be unique (both in submission and feedback plugins).
|
|
* @param userId User ID. If not defined, site's current user.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with the path.
|
|
*/
|
|
async getSubmissionPluginFolder(assignId: number, pluginName: string, userId?: number, siteId?: string): Promise<string> {
|
|
const folderPath = await this.getSubmissionFolder(assignId, userId, siteId);
|
|
|
|
return CoreTextUtils.instance.concatenatePaths(folderPath, pluginName);
|
|
}
|
|
|
|
/**
|
|
* Check if the assignment has something to be synced.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved with boolean: whether the assignment has something to be synced.
|
|
*/
|
|
async hasAssignOfflineData(assignId: number, siteId?: string): Promise<boolean> {
|
|
const promises:
|
|
Promise<AddonModAssignSubmissionsDBRecordFormatted[] | AddonModAssignSubmissionsGradingDBRecordFormatted[]>[] = [];
|
|
|
|
|
|
promises.push(this.getAssignSubmissions(assignId, siteId));
|
|
promises.push(this.getAssignSubmissionsGrade(assignId, siteId));
|
|
|
|
try {
|
|
const results = await Promise.all(promises);
|
|
|
|
return results.some((result) => result.length);
|
|
} catch {
|
|
// No offline data found.
|
|
return false;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Mark/Unmark a submission as being submitted.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param courseId Course ID the assign belongs to.
|
|
* @param submitted True to mark as submitted, false to mark as not submitted.
|
|
* @param acceptStatement True to accept the submission statement, false otherwise.
|
|
* @param timemodified The time the submission was last modified in online.
|
|
* @param userId User ID. If not defined, site's current user.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved if marked, rejected if failure.
|
|
*/
|
|
async markSubmitted(
|
|
assignId: number,
|
|
courseId: number,
|
|
submitted: boolean,
|
|
acceptStatement: boolean,
|
|
timemodified: number,
|
|
userId?: number,
|
|
siteId?: string,
|
|
): Promise<number> {
|
|
const site = await CoreSites.instance.getSite(siteId);
|
|
|
|
userId = userId || site.getUserId();
|
|
let submission: AddonModAssignSubmissionsDBRecord;
|
|
try {
|
|
const savedSubmission: AddonModAssignSubmissionsDBRecordFormatted =
|
|
await this.getSubmission(assignId, userId, site.getId());
|
|
submission = Object.assign(savedSubmission, {
|
|
plugindata: savedSubmission.plugindata ? JSON.stringify(savedSubmission.plugindata) : '{}',
|
|
submitted: submitted ? 1 : 0, // Mark the submission.
|
|
submissionstatement: acceptStatement ? 1 : 0, // Mark the submission.
|
|
});
|
|
} catch {
|
|
// No submission, create an empty one.
|
|
const now = CoreTimeUtils.instance.timestamp();
|
|
submission = {
|
|
assignid: assignId,
|
|
courseid: courseId,
|
|
userid: userId,
|
|
onlinetimemodified: timemodified,
|
|
timecreated: now,
|
|
timemodified: now,
|
|
plugindata: '{}',
|
|
submitted: submitted ? 1 : 0, // Mark the submission.
|
|
submissionstatement: acceptStatement ? 1 : 0, // Mark the submission.
|
|
};
|
|
}
|
|
|
|
return await site.getDb().insertRecord(SUBMISSIONS_TABLE, submission);
|
|
}
|
|
|
|
/**
|
|
* Save a submission to be sent later.
|
|
*
|
|
* @param assignId Assignment ID.
|
|
* @param courseId Course ID the assign belongs to.
|
|
* @param pluginData Data to save.
|
|
* @param timemodified The time the submission was last modified in online.
|
|
* @param submitted True if submission has been submitted, false otherwise.
|
|
* @param userId User ID. If not defined, site's current user.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved if stored, rejected if failure.
|
|
*/
|
|
async saveSubmission(
|
|
assignId: number,
|
|
courseId: number,
|
|
pluginData: AddonModAssignSavePluginData,
|
|
timemodified: number,
|
|
submitted: boolean,
|
|
userId?: number,
|
|
siteId?: string,
|
|
): Promise<number> {
|
|
const site = await CoreSites.instance.getSite(siteId);
|
|
|
|
userId = userId || site.getUserId();
|
|
|
|
const now = CoreTimeUtils.instance.timestamp();
|
|
const entry: AddonModAssignSubmissionsDBRecord = {
|
|
assignid: assignId,
|
|
courseid: courseId,
|
|
plugindata: pluginData ? JSON.stringify(pluginData) : '{}',
|
|
userid: userId,
|
|
submitted: submitted ? 1 : 0,
|
|
timecreated: now,
|
|
timemodified: now,
|
|
onlinetimemodified: timemodified,
|
|
};
|
|
|
|
return await site.getDb().insertRecord(SUBMISSIONS_TABLE, entry);
|
|
}
|
|
|
|
/**
|
|
* Save a grading to be sent later.
|
|
*
|
|
* @param assignId Assign ID.
|
|
* @param userId User ID.
|
|
* @param courseId Course ID the assign belongs to.
|
|
* @param grade Grade to submit.
|
|
* @param attemptNumber Number of the attempt being graded.
|
|
* @param addAttempt Admit the user to attempt again.
|
|
* @param workflowState Next workflow State.
|
|
* @param applyToAll If it's a team submission, whether the grade applies to all group members.
|
|
* @param outcomes Object including all outcomes values. If empty, any of them will be sent.
|
|
* @param pluginData Plugin data to save.
|
|
* @param siteId Site ID. If not defined, current site.
|
|
* @return Promise resolved if stored, rejected if failure.
|
|
*/
|
|
async submitGradingForm(
|
|
assignId: number,
|
|
userId: number,
|
|
courseId: number,
|
|
grade: number,
|
|
attemptNumber: number,
|
|
addAttempt: boolean,
|
|
workflowState: string,
|
|
applyToAll: boolean,
|
|
outcomes: AddonModAssignOutcomes,
|
|
pluginData: AddonModAssignSavePluginData,
|
|
siteId?: string,
|
|
): Promise<number> {
|
|
const site = await CoreSites.instance.getSite(siteId);
|
|
|
|
const now = CoreTimeUtils.instance.timestamp();
|
|
const entry: AddonModAssignSubmissionsGradingDBRecord = {
|
|
assignid: assignId,
|
|
userid: userId,
|
|
courseid: courseId,
|
|
grade: grade,
|
|
attemptnumber: attemptNumber,
|
|
addattempt: addAttempt ? 1 : 0,
|
|
workflowstate: workflowState,
|
|
applytoall: applyToAll ? 1 : 0,
|
|
outcomes: outcomes ? JSON.stringify(outcomes) : '{}',
|
|
plugindata: pluginData ? JSON.stringify(pluginData) : '{}',
|
|
timemodified: now,
|
|
};
|
|
|
|
return await site.getDb().insertRecord(SUBMISSIONS_GRADES_TABLE, entry);
|
|
}
|
|
|
|
}
|
|
export const AddonModAssignOffline = makeSingleton(AddonModAssignOfflineProvider);
|
|
|
|
export type AddonModAssignSubmissionsDBRecordFormatted = Omit<AddonModAssignSubmissionsDBRecord, 'plugindata'> & {
|
|
plugindata: AddonModAssignSavePluginData;
|
|
};
|
|
|
|
export type AddonModAssignSubmissionsGradingDBRecordFormatted =
|
|
Omit<AddonModAssignSubmissionsGradingDBRecord, 'plugindata'|'outcomes'> & {
|
|
plugindata: AddonModAssignSavePluginData;
|
|
outcomes: AddonModAssignOutcomes;
|
|
};
|