MOBILE-3657 workshop: Add workshop activity services
parent
a6574c5b11
commit
8e7b148205
|
@ -792,7 +792,7 @@ export type AddonModH5PActivityWSResultAnswer = {
|
|||
/**
|
||||
* User attempts data with some calculated data.
|
||||
*/
|
||||
export type AddonModH5PActivityUserAttempts = Omit<AddonModH5PActivityWSUserAttempts, 'attempts|scored'> & {
|
||||
export type AddonModH5PActivityUserAttempts = Omit<AddonModH5PActivityWSUserAttempts, 'attempts'|'scored'> & {
|
||||
attempts: AddonModH5PActivityAttempt[]; // The complete attempts list.
|
||||
scored?: { // Attempts used to grade the activity.
|
||||
title: string; // Scored attempts title.
|
||||
|
|
|
@ -35,6 +35,7 @@ import { AddonModScormModule } from './scorm/scorm.module';
|
|||
import { AddonModSurveyModule } from './survey/survey.module';
|
||||
import { AddonModUrlModule } from './url/url.module';
|
||||
import { AddonModWikiModule } from './wiki/wiki.module';
|
||||
import { AddonModWorkshopModule } from './workshop/workshop.module';
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
|
@ -59,6 +60,7 @@ import { AddonModWikiModule } from './wiki/wiki.module';
|
|||
AddonModSurveyModule,
|
||||
AddonModUrlModule,
|
||||
AddonModWikiModule,
|
||||
AddonModWorkshopModule,
|
||||
],
|
||||
})
|
||||
export class AddonModModule { }
|
||||
|
|
|
@ -0,0 +1,214 @@
|
|||
// (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 { CoreSiteSchema } from '@services/sites';
|
||||
import { AddonModWorkshopAction } from '../workshop';
|
||||
|
||||
/**
|
||||
* Database variables for AddonModWorkshopOfflineProvider.
|
||||
*/
|
||||
export const SUBMISSIONS_TABLE = 'addon_mod_workshop_submissions';
|
||||
export const ASSESSMENTS_TABLE = 'addon_mod_workshop_assessments';
|
||||
export const EVALUATE_SUBMISSIONS_TABLE = 'addon_mod_workshop_evaluate_submissions';
|
||||
export const EVALUATE_ASSESSMENTS_TABLE = 'addon_mod_workshop_evaluate_assessments';
|
||||
|
||||
export const ADDON_MOD_WORKSHOP_OFFLINE_SITE_SCHEMA: CoreSiteSchema = {
|
||||
name: 'AddonModWorkshopOfflineProvider',
|
||||
version: 1,
|
||||
tables: [
|
||||
{
|
||||
name: SUBMISSIONS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'workshopid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'action',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'submissionid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'courseid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'title',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'content',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'attachmentsid',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
],
|
||||
primaryKeys: ['workshopid', 'action'],
|
||||
},
|
||||
{
|
||||
name: ASSESSMENTS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'workshopid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'assessmentid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'courseid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'inputdata',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
],
|
||||
primaryKeys: ['workshopid', 'assessmentid'],
|
||||
},
|
||||
{
|
||||
name: EVALUATE_SUBMISSIONS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'workshopid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'submissionid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'courseid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'feedbacktext',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'published',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'gradeover',
|
||||
type: 'TEXT',
|
||||
},
|
||||
],
|
||||
primaryKeys: ['workshopid', 'submissionid'],
|
||||
},
|
||||
{
|
||||
name: EVALUATE_ASSESSMENTS_TABLE,
|
||||
columns: [
|
||||
{
|
||||
name: 'workshopid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'assessmentid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'courseid',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'timemodified',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'feedbacktext',
|
||||
type: 'TEXT',
|
||||
},
|
||||
{
|
||||
name: 'weight',
|
||||
type: 'INTEGER',
|
||||
},
|
||||
{
|
||||
name: 'gradinggradeover',
|
||||
type: 'TEXT',
|
||||
},
|
||||
],
|
||||
primaryKeys: ['workshopid', 'assessmentid'],
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
/**
|
||||
* Data about workshop submissions to sync.
|
||||
*/
|
||||
export type AddonModWorkshopSubmissionDBRecord = {
|
||||
workshopid: number; // Primary key.
|
||||
action: AddonModWorkshopAction; // Primary key.
|
||||
submissionid: number;
|
||||
courseid: number;
|
||||
title: string;
|
||||
content: string;
|
||||
attachmentsid: string;
|
||||
timemodified: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data about workshop assessments to sync.
|
||||
*/
|
||||
export type AddonModWorkshopAssessmentDBRecord = {
|
||||
workshopid: number; // Primary key.
|
||||
assessmentid: number; // Primary key.
|
||||
courseid: number;
|
||||
inputdata: string;
|
||||
timemodified: number;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data about workshop evaluate submissions to sync.
|
||||
*/
|
||||
export type AddonModWorkshopEvaluateSubmissionDBRecord = {
|
||||
workshopid: number; // Primary key.
|
||||
submissionid: number; // Primary key.
|
||||
courseid: number;
|
||||
timemodified: number;
|
||||
feedbacktext: string;
|
||||
published: number;
|
||||
gradeover: string;
|
||||
};
|
||||
|
||||
/**
|
||||
* Data about workshop evaluate assessments to sync.
|
||||
*/
|
||||
export type AddonModWorkshopEvaluateAssessmentDBRecord = {
|
||||
workshopid: number; // Primary key.
|
||||
assessmentid: number; // Primary key.
|
||||
courseid: number;
|
||||
timemodified: number;
|
||||
feedbacktext: string;
|
||||
weight: number;
|
||||
gradinggradeover: string;
|
||||
};
|
|
@ -0,0 +1,39 @@
|
|||
// (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 { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModWorkshopProvider, AddonModWorkshop } from '../workshop';
|
||||
/**
|
||||
* Handler to treat links to workshop.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
|
||||
|
||||
name = 'AddonModWorkshopLinkHandler';
|
||||
|
||||
constructor() {
|
||||
super(AddonModWorkshopProvider.COMPONENT, 'workshop', 'w');
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
isEnabled(siteId: string): Promise<boolean> {
|
||||
return AddonModWorkshop.isPluginEnabled(siteId);
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopIndexLinkHandler = makeSingleton(AddonModWorkshopIndexLinkHandlerService);
|
|
@ -0,0 +1,42 @@
|
|||
// (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 { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModWorkshop } from '../workshop';
|
||||
|
||||
/**
|
||||
* Handler to treat links to workshop list page.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopListLinkHandlerService extends CoreContentLinksModuleListHandler {
|
||||
|
||||
name = 'AddonModWorkshopListLinkHandler';
|
||||
|
||||
constructor() {
|
||||
super('AddonModWorkshop', 'workshop');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the handler is enabled on a site level.
|
||||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return AddonModWorkshop.isPluginEnabled();
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopListLinkHandler = makeSingleton(AddonModWorkshopListLinkHandlerService);
|
|
@ -0,0 +1,79 @@
|
|||
// (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 { CoreConstants } from '@/core/constants';
|
||||
import { Injectable, Type } from '@angular/core';
|
||||
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
|
||||
import { CoreCourseModule } from '@features/course/services/course-helper';
|
||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
|
||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModWorkshopIndexComponent } from '../../components/index';
|
||||
import { AddonModWorkshop } from '../workshop';
|
||||
|
||||
/**
|
||||
* Handler to support workshop modules.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopModuleHandlerService implements CoreCourseModuleHandler {
|
||||
|
||||
static readonly PAGE_NAME = 'mod_workshop';
|
||||
|
||||
name = 'AddonModWorkshop';
|
||||
modName = 'workshop';
|
||||
|
||||
supportedFeatures = {
|
||||
[CoreConstants.FEATURE_GROUPS]: true,
|
||||
[CoreConstants.FEATURE_GROUPINGS]: true,
|
||||
[CoreConstants.FEATURE_MOD_INTRO]: true,
|
||||
[CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
|
||||
[CoreConstants.FEATURE_GRADE_HAS_GRADE]: true,
|
||||
[CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
|
||||
[CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
|
||||
[CoreConstants.FEATURE_PLAGIARISM]: true,
|
||||
};
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return AddonModWorkshop.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getData(module: CoreCourseAnyModuleData): CoreCourseModuleHandlerData {
|
||||
return {
|
||||
icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
|
||||
title: module.name,
|
||||
class: 'addon-mod_workshop-handler',
|
||||
showDownloadButton: true,
|
||||
action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void {
|
||||
options = options || {};
|
||||
options.params = options.params || {};
|
||||
Object.assign(options.params, { module });
|
||||
const routeParams = '/' + courseId + '/' + module.id;
|
||||
|
||||
CoreNavigator.navigateToSitePath(AddonModWorkshopModuleHandlerService.PAGE_NAME + routeParams, options);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
async getMainComponent(): Promise<Type<unknown>> {
|
||||
return AddonModWorkshopIndexComponent;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopModuleHandler = makeSingleton(AddonModWorkshopModuleHandlerService);
|
|
@ -0,0 +1,399 @@
|
|||
// (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 { AddonModDataSyncResult } from '@addons/mod/data/services/data-sync';
|
||||
import { Injectable } from '@angular/core';
|
||||
import { CoreCourseActivityPrefetchHandlerBase } from '@features/course/classes/activity-prefetch-handler';
|
||||
import { CoreCourse, CoreCourseAnyModuleData } from '@features/course/services/course';
|
||||
import { CoreUser } from '@features/user/services/user';
|
||||
import { CoreFilepool } from '@services/filepool';
|
||||
import { CoreGroup, CoreGroups } from '@services/groups';
|
||||
import { CoreSites, CoreSitesReadingStrategy, CoreSitesCommonWSOptions } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreWSExternalFile, CoreWSFile } from '@services/ws';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import {
|
||||
AddonModWorkshopProvider,
|
||||
AddonModWorkshop,
|
||||
AddonModWorkshopPhase,
|
||||
AddonModWorkshopGradesData,
|
||||
AddonModWorkshopData,
|
||||
AddonModWorkshopGetWorkshopAccessInformationWSResponse,
|
||||
} from '../workshop';
|
||||
import { AddonModWorkshopHelper } from '../workshop-helper';
|
||||
import { AddonModWorkshopSync } from '../workshop-sync';
|
||||
|
||||
/**
|
||||
* Handler to prefetch workshops.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopPrefetchHandlerService extends CoreCourseActivityPrefetchHandlerBase {
|
||||
|
||||
name = 'AddonModWorkshop';
|
||||
modName = 'workshop';
|
||||
component = AddonModWorkshopProvider.COMPONENT;
|
||||
updatesNames = new RegExp('^configuration$|^.*files$|^completion|^gradeitems$|^outcomes$|^submissions$|^assessments$' +
|
||||
'|^assessmentgrades$|^usersubmissions$|^userassessments$|^userassessmentgrades$|^userassessmentgrades$');
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async getFiles(module: CoreCourseAnyModuleData, courseId: number): Promise<CoreWSFile[]> {
|
||||
const info = await this.getWorkshopInfoHelper(module, courseId, { omitFail: true });
|
||||
|
||||
return info.files;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper function to get all workshop info just once.
|
||||
*
|
||||
* @param module Module to get the files.
|
||||
* @param courseId Course ID the module belongs to.
|
||||
* @param options Other options.
|
||||
* @return Promise resolved with the info fetched.
|
||||
*/
|
||||
protected async getWorkshopInfoHelper(
|
||||
module: CoreCourseAnyModuleData,
|
||||
courseId: number,
|
||||
options: AddonModWorkshopGetInfoOptions = {},
|
||||
): Promise<{ workshop?: AddonModWorkshopData; groups: CoreGroup[]; files: CoreWSFile[]}> {
|
||||
let groups: CoreGroup[] = [];
|
||||
let files: CoreWSFile[] = [];
|
||||
let workshop: AddonModWorkshopData | undefined;
|
||||
let access: AddonModWorkshopGetWorkshopAccessInformationWSResponse | undefined;
|
||||
|
||||
const modOptions = {
|
||||
cmId: module.id,
|
||||
...options, // Include all options.
|
||||
};
|
||||
|
||||
try {
|
||||
const site = await CoreSites.getSite(options.siteId);
|
||||
const userId = site.getUserId();
|
||||
const workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, options);
|
||||
|
||||
files = this.getIntroFilesFromInstance(module, workshop);
|
||||
files = files.concat(workshop.instructauthorsfiles || []).concat(workshop.instructreviewersfiles || []);
|
||||
|
||||
access = await AddonModWorkshop.getWorkshopAccessInformation(workshop.id, modOptions);
|
||||
if (access.canviewallsubmissions) {
|
||||
const groupInfo = await CoreGroups.getActivityGroupInfo(module.id, false, undefined, options.siteId);
|
||||
if (!groupInfo.groups || groupInfo.groups.length == 0) {
|
||||
groupInfo.groups = [{ id: 0, name: '' }];
|
||||
}
|
||||
groups = groupInfo.groups;
|
||||
}
|
||||
|
||||
const phases = await AddonModWorkshop.getUserPlanPhases(workshop.id, modOptions);
|
||||
|
||||
// Get submission phase info.
|
||||
const submissionPhase = phases[AddonModWorkshopPhase.PHASE_SUBMISSION];
|
||||
const canSubmit = AddonModWorkshopHelper.canSubmit(workshop, access, submissionPhase.tasks);
|
||||
const canAssess = AddonModWorkshopHelper.canAssess(workshop, access);
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
if (canSubmit) {
|
||||
promises.push(AddonModWorkshopHelper.getUserSubmission(workshop.id, {
|
||||
userId,
|
||||
cmId: module.id,
|
||||
}).then((submission) => {
|
||||
if (submission) {
|
||||
files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []);
|
||||
}
|
||||
|
||||
return;
|
||||
}));
|
||||
}
|
||||
|
||||
if (access.canviewallsubmissions && workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) {
|
||||
promises.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions).then(async (submissions) => {
|
||||
|
||||
await Promise.all(submissions.map(async (submission) => {
|
||||
files = files.concat(submission.contentfiles || []).concat(submission.attachmentfiles || []);
|
||||
|
||||
const assessments = await AddonModWorkshop.getSubmissionAssessments(workshop!.id, submission.id, {
|
||||
cmId: module.id,
|
||||
});
|
||||
|
||||
assessments.forEach((assessment) => {
|
||||
files = files.concat(assessment.feedbackattachmentfiles)
|
||||
.concat(assessment.feedbackcontentfiles);
|
||||
});
|
||||
|
||||
if (workshop!.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) {
|
||||
await Promise.all(assessments.map((assessment) =>
|
||||
AddonModWorkshopHelper.getReviewerAssessmentById(workshop!.id, assessment.id)));
|
||||
}
|
||||
}));
|
||||
|
||||
return;
|
||||
}));
|
||||
}
|
||||
|
||||
// Get assessment files.
|
||||
if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) {
|
||||
promises.push(AddonModWorkshopHelper.getReviewerAssessments(workshop.id, modOptions).then((assessments) => {
|
||||
assessments.forEach((assessment) => {
|
||||
files = files.concat(<CoreWSExternalFile[]>assessment.feedbackattachmentfiles)
|
||||
.concat(assessment.feedbackcontentfiles);
|
||||
});
|
||||
|
||||
return;
|
||||
}));
|
||||
}
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
return {
|
||||
workshop,
|
||||
groups,
|
||||
files: files.filter((file) => typeof file !== 'undefined'),
|
||||
};
|
||||
} catch (error) {
|
||||
if (options.omitFail) {
|
||||
// Any error, return the info we have.
|
||||
return {
|
||||
workshop,
|
||||
groups,
|
||||
files: files.filter((file) => typeof file !== 'undefined'),
|
||||
};
|
||||
}
|
||||
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async invalidateContent(moduleId: number, courseId: number): Promise<void> {
|
||||
await AddonModWorkshop.invalidateContent(moduleId, courseId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a module can be downloaded. If the function is not defined, we assume that all modules are downloadable.
|
||||
*
|
||||
* @param module Module.
|
||||
* @param courseId Course ID the module belongs to.
|
||||
* @return Whether the module can be downloaded. The promise should never be rejected.
|
||||
*/
|
||||
async isDownloadable(module: CoreCourseAnyModuleData, courseId: number): Promise<boolean> {
|
||||
const workshop = await AddonModWorkshop.getWorkshop(courseId, module.id, {
|
||||
readingStrategy: CoreSitesReadingStrategy.PreferCache,
|
||||
});
|
||||
|
||||
const accessData = await AddonModWorkshop.getWorkshopAccessInformation(workshop.id, { cmId: module.id });
|
||||
|
||||
// Check if workshop is setup by phase.
|
||||
return accessData.canswitchphase || workshop.phase > AddonModWorkshopPhase.PHASE_SETUP;
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return AddonModWorkshop.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
prefetch(module: CoreCourseAnyModuleData, courseId?: number): Promise<void> {
|
||||
return this.prefetchPackage(module, courseId, this.prefetchWorkshop.bind(this, module, courseId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves all the grades reports for all the groups and then returns only unique grades.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param groups Array of groups in the activity.
|
||||
* @param cmId Module ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return All unique entries.
|
||||
*/
|
||||
protected async getAllGradesReport(
|
||||
workshopId: number,
|
||||
groups: CoreGroup[],
|
||||
cmId: number,
|
||||
siteId: string,
|
||||
): Promise<AddonModWorkshopGradesData[]> {
|
||||
const promises: Promise<AddonModWorkshopGradesData[]>[] = [];
|
||||
|
||||
groups.forEach((group) => {
|
||||
promises.push(AddonModWorkshop.fetchAllGradeReports(workshopId, { groupId: group.id, cmId, siteId }));
|
||||
});
|
||||
|
||||
const grades = await Promise.all(promises);
|
||||
const uniqueGrades: Record<number, AddonModWorkshopGradesData> = {};
|
||||
|
||||
grades.forEach((groupGrades) => {
|
||||
groupGrades.forEach((grade) => {
|
||||
if (grade.submissionid) {
|
||||
uniqueGrades[grade.submissionid] = grade;
|
||||
}
|
||||
});
|
||||
});
|
||||
|
||||
return CoreUtils.objectToArray(uniqueGrades);
|
||||
}
|
||||
|
||||
/**
|
||||
* Prefetch a workshop.
|
||||
*
|
||||
* @param module The module object returned by WS.
|
||||
* @param courseId Course ID the module belongs to.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved when done.
|
||||
*/
|
||||
protected async prefetchWorkshop(module: CoreCourseAnyModuleData, courseId: number, siteId: string): Promise<void> {
|
||||
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
const userIds: number[] = [];
|
||||
const commonOptions = {
|
||||
readingStrategy: CoreSitesReadingStrategy.OnlyNetwork,
|
||||
siteId,
|
||||
};
|
||||
const modOptions = {
|
||||
cmId: module.id,
|
||||
...commonOptions, // Include all common options.
|
||||
};
|
||||
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
const currentUserId = site.getUserId();
|
||||
|
||||
// Prefetch the workshop data.
|
||||
const info = await this.getWorkshopInfoHelper(module, courseId, commonOptions);
|
||||
const workshop = info.workshop!;
|
||||
const promises: Promise<unknown>[] = [];
|
||||
const assessmentIds: number[] = [];
|
||||
|
||||
promises.push(CoreFilepool.addFilesToQueue(siteId, info.files, this.component, module.id));
|
||||
|
||||
promises.push(AddonModWorkshop.getWorkshopAccessInformation(workshop.id, modOptions).then(async (access) => {
|
||||
const phases = await AddonModWorkshop.getUserPlanPhases(workshop.id, modOptions);
|
||||
|
||||
// Get submission phase info.
|
||||
const submissionPhase = phases[AddonModWorkshopPhase.PHASE_SUBMISSION];
|
||||
const canSubmit = AddonModWorkshopHelper.canSubmit(workshop, access, submissionPhase.tasks);
|
||||
const canAssess = AddonModWorkshopHelper.canAssess(workshop, access);
|
||||
const promises2: Promise<unknown>[] = [];
|
||||
|
||||
if (canSubmit) {
|
||||
promises2.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions));
|
||||
// Add userId to the profiles to prefetch.
|
||||
userIds.push(currentUserId);
|
||||
}
|
||||
|
||||
let reportPromise: Promise<unknown> = Promise.resolve();
|
||||
if (access.canviewallsubmissions && workshop.phase >= AddonModWorkshopPhase.PHASE_SUBMISSION) {
|
||||
// eslint-disable-next-line promise/no-nesting
|
||||
reportPromise = this.getAllGradesReport(workshop.id, info.groups, module.id, siteId).then((grades) => {
|
||||
grades.forEach((grade) => {
|
||||
userIds.push(grade.userid);
|
||||
grade.submissiongradeoverby && userIds.push(grade.submissiongradeoverby);
|
||||
|
||||
grade.reviewedby && grade.reviewedby.forEach((assessment) => {
|
||||
userIds.push(assessment.userid);
|
||||
assessmentIds[assessment.assessmentid] = assessment.assessmentid;
|
||||
});
|
||||
|
||||
grade.reviewerof && grade.reviewerof.forEach((assessment) => {
|
||||
userIds.push(assessment.userid);
|
||||
assessmentIds[assessment.assessmentid] = assessment.assessmentid;
|
||||
});
|
||||
});
|
||||
|
||||
return;
|
||||
});
|
||||
}
|
||||
|
||||
if (workshop.phase >= AddonModWorkshopPhase.PHASE_ASSESSMENT && canAssess) {
|
||||
// Wait the report promise to finish to override assessments array if needed.
|
||||
reportPromise = reportPromise.finally(async () => {
|
||||
const revAssessments = await AddonModWorkshopHelper.getReviewerAssessments(workshop.id, {
|
||||
userId: currentUserId,
|
||||
cmId: module.id,
|
||||
siteId,
|
||||
});
|
||||
|
||||
let files: CoreWSExternalFile[] = []; // Files in each submission.
|
||||
|
||||
revAssessments.forEach((assessment) => {
|
||||
if (assessment.submission?.authorid == currentUserId) {
|
||||
promises.push(AddonModWorkshop.getAssessment(
|
||||
workshop.id,
|
||||
assessment.id,
|
||||
modOptions,
|
||||
));
|
||||
}
|
||||
userIds.push(assessment.reviewerid);
|
||||
userIds.push(assessment.gradinggradeoverby);
|
||||
assessmentIds[assessment.id] = assessment.id;
|
||||
|
||||
files = files.concat(assessment.submission?.attachmentfiles || [])
|
||||
.concat(assessment.submission?.contentfiles || []);
|
||||
});
|
||||
|
||||
await CoreFilepool.addFilesToQueue(siteId, files, this.component, module.id);
|
||||
});
|
||||
}
|
||||
|
||||
reportPromise = reportPromise.finally(() => {
|
||||
if (assessmentIds.length > 0) {
|
||||
return Promise.all(assessmentIds.map((assessmentId) =>
|
||||
AddonModWorkshop.getAssessmentForm(workshop.id, assessmentId, modOptions)));
|
||||
}
|
||||
});
|
||||
promises2.push(reportPromise);
|
||||
|
||||
if (workshop.phase == AddonModWorkshopPhase.PHASE_CLOSED) {
|
||||
promises2.push(AddonModWorkshop.getGrades(workshop.id, modOptions));
|
||||
if (access.canviewpublishedsubmissions) {
|
||||
promises2.push(AddonModWorkshop.getSubmissions(workshop.id, modOptions));
|
||||
}
|
||||
}
|
||||
|
||||
await Promise.all(promises2);
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
// Add Basic Info to manage links.
|
||||
promises.push(CoreCourse.getModuleBasicInfoByInstance(workshop.id, 'workshop', siteId));
|
||||
promises.push(CoreCourse.getModuleBasicGradeInfo(module.id, siteId));
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
// Prefetch user profiles.
|
||||
await CoreUser.prefetchProfiles(userIds, courseId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async sync(module: CoreCourseAnyModuleData, courseId: number, siteId?: string): Promise<AddonModDataSyncResult> {
|
||||
return AddonModWorkshopSync.syncWorkshop(module.instance!, siteId);
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopPrefetchHandler = makeSingleton(AddonModWorkshopPrefetchHandlerService);
|
||||
|
||||
/**
|
||||
* Options to pass to getWorkshopInfoHelper.
|
||||
*/
|
||||
export type AddonModWorkshopGetInfoOptions = CoreSitesCommonWSOptions & {
|
||||
omitFail?: boolean; // True to always return even if fails.
|
||||
};
|
|
@ -0,0 +1,43 @@
|
|||
// (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 { CoreCronHandler } from '@services/cron';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { AddonModWorkshopSync } from '../workshop-sync';
|
||||
|
||||
/**
|
||||
* Synchronization cron handler.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopSyncCronHandlerService implements CoreCronHandler {
|
||||
|
||||
name = 'AddonModWorkshopSyncCronHandler';
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
execute(siteId?: string, force?: boolean): Promise<void> {
|
||||
return AddonModWorkshopSync.syncAllWorkshops(siteId, force);
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
getInterval(): number {
|
||||
return AddonModWorkshopSync.syncInterval;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopSyncCronHandler = makeSingleton(AddonModWorkshopSyncCronHandlerService);
|
|
@ -0,0 +1,638 @@
|
|||
// (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 { CoreFileUploader, CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||
import { FileEntry } from '@ionic-native/file';
|
||||
import { CoreFile } from '@services/file';
|
||||
import { CoreFileEntry } from '@services/file-helper';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { makeSingleton, Translate } from '@singletons';
|
||||
import { CoreFormFields } from '@singletons/form';
|
||||
import { AddonModWorkshopAssessmentStrategyFieldErrors } from '../components/assessment-strategy/assessment-strategy';
|
||||
import { AddonWorkshopAssessmentStrategyDelegate } from './assessment-strategy-delegate';
|
||||
import {
|
||||
AddonModWorkshopExampleMode,
|
||||
AddonModWorkshopPhase,
|
||||
AddonModWorkshopUserOptions,
|
||||
AddonModWorkshopProvider,
|
||||
AddonModWorkshopData,
|
||||
AddonModWorkshop,
|
||||
AddonModWorkshopSubmissionData,
|
||||
AddonModWorkshopGetWorkshopAccessInformationWSResponse,
|
||||
AddonModWorkshopPhaseTaskData,
|
||||
AddonModWorkshopSubmissionAssessmentData,
|
||||
AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
AddonModWorkshopAction,
|
||||
AddonModWorkshopOverallFeedbackMode,
|
||||
AddonModWorkshopGetAssessmentFormFieldsParsedData,
|
||||
} from './workshop';
|
||||
import { AddonModWorkshopOffline, AddonModWorkshopOfflineSubmission } from './workshop-offline';
|
||||
|
||||
/**
|
||||
* Helper to gather some common functions for workshop.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopHelperProvider {
|
||||
|
||||
/**
|
||||
* Get a task by code.
|
||||
*
|
||||
* @param tasks Array of tasks.
|
||||
* @param taskCode Unique task code.
|
||||
* @return Task requested
|
||||
*/
|
||||
getTask(tasks: AddonModWorkshopPhaseTaskData[], taskCode: string): AddonModWorkshopPhaseTaskData | undefined {
|
||||
return tasks.find((task) => task.code == taskCode);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check is task code is done.
|
||||
*
|
||||
* @param tasks Array of tasks.
|
||||
* @param taskCode Unique task code.
|
||||
* @return True if task is completed.
|
||||
*/
|
||||
isTaskDone(tasks: AddonModWorkshopPhaseTaskData[], taskCode: string): boolean {
|
||||
const task = this.getTask(tasks, taskCode);
|
||||
|
||||
if (task) {
|
||||
return !!task.completed;
|
||||
}
|
||||
|
||||
// Task not found, assume true.
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if a user can submit a workshop.
|
||||
*
|
||||
* @param workshop Workshop info.
|
||||
* @param access Access information.
|
||||
* @param tasks Array of tasks.
|
||||
* @return True if the user can submit the workshop.
|
||||
*/
|
||||
canSubmit(
|
||||
workshop: AddonModWorkshopData,
|
||||
access: AddonModWorkshopGetWorkshopAccessInformationWSResponse,
|
||||
tasks: AddonModWorkshopPhaseTaskData[],
|
||||
): boolean {
|
||||
const examplesMust = workshop.useexamples &&
|
||||
workshop.examplesmode == AddonModWorkshopExampleMode.EXAMPLES_BEFORE_SUBMISSION;
|
||||
const examplesDone = access.canmanageexamples ||
|
||||
workshop.examplesmode == AddonModWorkshopExampleMode.EXAMPLES_VOLUNTARY ||
|
||||
this.isTaskDone(tasks, 'examples');
|
||||
|
||||
return workshop.phase > AddonModWorkshopPhase.PHASE_SETUP && access.cansubmit && (!examplesMust || examplesDone);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return if a user can assess a workshop.
|
||||
*
|
||||
* @param workshop Workshop info.
|
||||
* @param access Access information.
|
||||
* @return True if the user can assess the workshop.
|
||||
*/
|
||||
canAssess(workshop: AddonModWorkshopData, access: AddonModWorkshopGetWorkshopAccessInformationWSResponse): boolean {
|
||||
const examplesMust = workshop.useexamples &&
|
||||
workshop.examplesmode == AddonModWorkshopExampleMode.EXAMPLES_BEFORE_ASSESSMENT;
|
||||
|
||||
const examplesDone = access.canmanageexamples;
|
||||
|
||||
return !examplesMust || examplesDone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a particular user submission from the submission list.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param options Other options.
|
||||
* @return Resolved with the submission, resolved with false if not found.
|
||||
*/
|
||||
async getUserSubmission(
|
||||
workshopId: number,
|
||||
options: AddonModWorkshopUserOptions = {},
|
||||
): Promise<AddonModWorkshopSubmissionData | undefined> {
|
||||
const userId = options.userId || CoreSites.getCurrentSiteUserId();
|
||||
|
||||
const submissions = await AddonModWorkshop.getSubmissions(workshopId, options);
|
||||
|
||||
return submissions.find((submission) => submission.authorid == userId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a particular submission. It will use prefetched data if fetch fails.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param submissionId Submission ID.
|
||||
* @param options Other options.
|
||||
* @return Resolved with the submission, resolved with false if not found.
|
||||
*/
|
||||
async getSubmissionById(
|
||||
workshopId: number,
|
||||
submissionId: number,
|
||||
options: AddonModWorkshopUserOptions = {},
|
||||
): Promise<AddonModWorkshopSubmissionData> {
|
||||
try {
|
||||
return await AddonModWorkshop.getSubmission(workshopId, submissionId, options);
|
||||
} catch {
|
||||
const submissions = await AddonModWorkshop.getSubmissions(workshopId, options);
|
||||
|
||||
const submission = submissions.find((submission) => submission.id == submissionId);
|
||||
|
||||
if (!submission) {
|
||||
throw new CoreError('Submission not found');
|
||||
}
|
||||
|
||||
return submission;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Return a particular assesment. It will use prefetched data if fetch fails. It will add assessment form data.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param options Other options.
|
||||
* @return Resolved with the assessment.
|
||||
*/
|
||||
async getReviewerAssessmentById(
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
options: AddonModWorkshopUserOptions = {},
|
||||
): Promise<AddonModWorkshopSubmissionAssessmentWithFormData> {
|
||||
let assessment: AddonModWorkshopSubmissionAssessmentWithFormData | undefined;
|
||||
|
||||
try {
|
||||
assessment = await AddonModWorkshop.getAssessment(workshopId, assessmentId, options);
|
||||
} catch (error) {
|
||||
const assessments = await AddonModWorkshop.getReviewerAssessments(workshopId, options);
|
||||
assessment = assessments.find((assessment_1) => assessment_1.id == assessmentId);
|
||||
|
||||
if (!assessment) {
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
assessment.form = await AddonModWorkshop.getAssessmentForm(workshopId, assessmentId, options);
|
||||
|
||||
return assessment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Retrieves the assessment of the given user and all the related data.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param options Other options.
|
||||
* @return Promise resolved when the workshop data is retrieved.
|
||||
*/
|
||||
async getReviewerAssessments(
|
||||
workshopId: number,
|
||||
options: AddonModWorkshopUserOptions = {},
|
||||
): Promise<AddonModWorkshopSubmissionAssessmentWithFormData[]> {
|
||||
options.siteId = options.siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
const assessments: AddonModWorkshopSubmissionAssessmentWithFormData[] =
|
||||
await AddonModWorkshop.getReviewerAssessments(workshopId, options);
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
assessments.forEach((assessment) => {
|
||||
promises.push(this.getSubmissionById(workshopId, assessment.submissionid, options).then((submission) => {
|
||||
assessment.submission = submission;
|
||||
|
||||
return;
|
||||
}));
|
||||
promises.push(AddonModWorkshop.getAssessmentForm(workshopId, assessment.id, options).then((assessmentForm) => {
|
||||
assessment.form = assessmentForm;
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
});
|
||||
await Promise.all(promises);
|
||||
|
||||
return assessments;
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete stored attachment files for a submission.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when deleted.
|
||||
*/
|
||||
async deleteSubmissionStoredFiles(workshopId: number, siteId?: string): Promise<void> {
|
||||
const folderPath = await AddonModWorkshopOffline.getSubmissionFolder(workshopId, siteId);
|
||||
|
||||
// Ignore any errors, CoreFileProvider.removeDir fails if folder doesn't exists.
|
||||
await CoreUtils.ignoreErrors(CoreFile.removeDir(folderPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of files (either online files or local files), store the local files in a local folder
|
||||
* to be submitted later.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param files List of files.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
async storeSubmissionFiles(
|
||||
workshopId: number,
|
||||
files: CoreFileEntry[],
|
||||
siteId?: string,
|
||||
): Promise<CoreFileUploaderStoreFilesResult> {
|
||||
// Get the folder where to store the files.
|
||||
const folderPath = await AddonModWorkshopOffline.getSubmissionFolder(workshopId, siteId);
|
||||
|
||||
return CoreFileUploader.storeFilesToUpload(folderPath, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload or store some files for a submission, depending if the user is offline or not.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param submissionId If not editing, it will refer to timecreated.
|
||||
* @param files List of files.
|
||||
* @param editing If the submission is being edited or added otherwise.
|
||||
* @param offline True if files sould be stored for offline, false to upload them.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if success.
|
||||
*/
|
||||
uploadOrStoreSubmissionFiles(
|
||||
workshopId: number,
|
||||
files: CoreFileEntry[],
|
||||
offline: true,
|
||||
siteId?: string,
|
||||
): Promise<CoreFileUploaderStoreFilesResult>;
|
||||
uploadOrStoreSubmissionFiles(
|
||||
workshopId: number,
|
||||
files: CoreFileEntry[],
|
||||
offline: false,
|
||||
siteId?: string,
|
||||
): Promise<number>;
|
||||
uploadOrStoreSubmissionFiles(
|
||||
workshopId: number,
|
||||
files: CoreFileEntry[],
|
||||
offline: boolean,
|
||||
siteId?: string,
|
||||
): Promise<CoreFileUploaderStoreFilesResult | number> {
|
||||
if (offline) {
|
||||
return this.storeSubmissionFiles(workshopId, files, siteId);
|
||||
}
|
||||
|
||||
return CoreFileUploader.uploadOrReuploadFiles(files, AddonModWorkshopProvider.COMPONENT, workshopId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of stored attachment files for a submission. See AddonModWorkshopHelperProvider#storeFiles.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param submissionId If not editing, it will refer to timecreated.
|
||||
* @param editing If the submission is being edited or added otherwise.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the files.
|
||||
*/
|
||||
async getStoredSubmissionFiles(
|
||||
workshopId: number,
|
||||
siteId?: string,
|
||||
): Promise<FileEntry[]> {
|
||||
const folderPath = await AddonModWorkshopOffline.getSubmissionFolder(workshopId, siteId);
|
||||
|
||||
// Ignore not found files.
|
||||
return CoreUtils.ignoreErrors(CoreFileUploader.getStoredFiles(folderPath), []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of stored attachment files for a submission and online files also. See AddonModWorkshopHelperProvider#storeFiles.
|
||||
*
|
||||
* @param filesObject Files object combining offline and online information.
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the files.
|
||||
*/
|
||||
async getSubmissionFilesFromOfflineFilesObject(
|
||||
filesObject: CoreFileUploaderStoreFilesResult,
|
||||
workshopId: number,
|
||||
siteId?: string,
|
||||
): Promise<CoreFileEntry[]> {
|
||||
const folderPath = await AddonModWorkshopOffline.getSubmissionFolder(workshopId, siteId);
|
||||
|
||||
return CoreFileUploader.getStoredFilesFromOfflineFilesObject(filesObject, folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete stored attachment files for an assessment.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when deleted.
|
||||
*/
|
||||
async deleteAssessmentStoredFiles(workshopId: number, assessmentId: number, siteId?: string): Promise<void> {
|
||||
const folderPath = await AddonModWorkshopOffline.getAssessmentFolder(workshopId, assessmentId, siteId);
|
||||
|
||||
// Ignore any errors, CoreFileProvider.removeDir fails if folder doesn't exists.
|
||||
await CoreUtils.ignoreErrors(CoreFile.removeDir(folderPath));
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of files (either online files or local files), store the local files in a local folder
|
||||
* to be submitted later.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param files List of files.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
async storeAssessmentFiles(
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
files: CoreFileEntry[],
|
||||
siteId?: string,
|
||||
): Promise<CoreFileUploaderStoreFilesResult> {
|
||||
// Get the folder where to store the files.
|
||||
const folderPath = await AddonModWorkshopOffline.getAssessmentFolder(workshopId, assessmentId, siteId);
|
||||
|
||||
return CoreFileUploader.storeFilesToUpload(folderPath, files);
|
||||
}
|
||||
|
||||
/**
|
||||
* Upload or store some files for an assessment, depending if the user is offline or not.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId ID.
|
||||
* @param files List of files.
|
||||
* @param offline True if files sould be stored for offline, false to upload them.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if success.
|
||||
*/
|
||||
uploadOrStoreAssessmentFiles(
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
files: CoreFileEntry[],
|
||||
offline: true,
|
||||
siteId?: string,
|
||||
): Promise<CoreFileUploaderStoreFilesResult>;
|
||||
uploadOrStoreAssessmentFiles(
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
files: CoreFileEntry[],
|
||||
offline: false,
|
||||
siteId?: string,
|
||||
): Promise<number>
|
||||
uploadOrStoreAssessmentFiles(
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
files: CoreFileEntry[],
|
||||
offline: boolean,
|
||||
siteId?: string,
|
||||
): Promise<CoreFileUploaderStoreFilesResult | number> {
|
||||
if (offline) {
|
||||
return this.storeAssessmentFiles(workshopId, assessmentId, files, siteId);
|
||||
}
|
||||
|
||||
return CoreFileUploader.uploadOrReuploadFiles(files, AddonModWorkshopProvider.COMPONENT, workshopId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of stored attachment files for an assessment. See AddonModWorkshopHelperProvider#storeFiles.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the files.
|
||||
*/
|
||||
async getStoredAssessmentFiles(workshopId: number, assessmentId: number, siteId?: string): Promise<FileEntry[]> {
|
||||
const folderPath = await AddonModWorkshopOffline.getAssessmentFolder(workshopId, assessmentId, siteId);
|
||||
|
||||
// Ignore not found files.
|
||||
return CoreUtils.ignoreErrors(CoreFileUploader.getStoredFiles(folderPath), []);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a list of stored attachment files for an assessment and online files also. See AddonModWorkshopHelperProvider#storeFiles.
|
||||
*
|
||||
* @param filesObject Files object combining offline and online information.
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the files.
|
||||
*/
|
||||
async getAssessmentFilesFromOfflineFilesObject(
|
||||
filesObject: CoreFileUploaderStoreFilesResult,
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
siteId?: string,
|
||||
): Promise<CoreFileEntry[]> {
|
||||
const folderPath = await AddonModWorkshopOffline.getAssessmentFolder(workshopId, assessmentId, siteId);
|
||||
|
||||
return CoreFileUploader.getStoredFilesFromOfflineFilesObject(filesObject, folderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies offline data to submission.
|
||||
*
|
||||
* @param submission Submission object to be modified.
|
||||
* @param actions Offline actions to be applied to the given submission.
|
||||
* @return Promise resolved with the files.
|
||||
*/
|
||||
async applyOfflineData(
|
||||
submission?: AddonModWorkshopSubmissionDataWithOfflineData,
|
||||
actions: AddonModWorkshopOfflineSubmission[] = [],
|
||||
): Promise<AddonModWorkshopSubmissionDataWithOfflineData | undefined> {
|
||||
if (actions.length == 0) {
|
||||
return submission;
|
||||
}
|
||||
|
||||
if (typeof submission == 'undefined') {
|
||||
submission = {
|
||||
id: 0,
|
||||
workshopid: 0,
|
||||
title: '',
|
||||
content: '',
|
||||
timemodified: 0,
|
||||
example: false,
|
||||
authorid: 0,
|
||||
timecreated: 0,
|
||||
contenttrust: 0,
|
||||
attachment: 0,
|
||||
published: false,
|
||||
late: 0,
|
||||
};
|
||||
}
|
||||
|
||||
let attachmentsId: CoreFileUploaderStoreFilesResult | undefined;
|
||||
const workshopId = actions[0].workshopid;
|
||||
|
||||
actions.forEach((action) => {
|
||||
switch (action.action) {
|
||||
case AddonModWorkshopAction.ADD:
|
||||
case AddonModWorkshopAction.UPDATE:
|
||||
submission!.title = action.title;
|
||||
submission!.content = action.content;
|
||||
submission!.title = action.title;
|
||||
submission!.courseid = action.courseid;
|
||||
submission!.submissionmodified = action.timemodified / 1000;
|
||||
submission!.offline = true;
|
||||
attachmentsId = action.attachmentsid as CoreFileUploaderStoreFilesResult;
|
||||
break;
|
||||
case AddonModWorkshopAction.DELETE:
|
||||
submission!.deleted = true;
|
||||
submission!.submissionmodified = action.timemodified / 1000;
|
||||
break;
|
||||
default:
|
||||
}
|
||||
});
|
||||
|
||||
// Check offline files for latest attachmentsid.
|
||||
if (attachmentsId) {
|
||||
submission.attachmentfiles =
|
||||
await this.getSubmissionFilesFromOfflineFilesObject(attachmentsId, workshopId);
|
||||
} else {
|
||||
submission.attachmentfiles = [];
|
||||
}
|
||||
|
||||
return submission;
|
||||
}
|
||||
|
||||
/**
|
||||
* Prepare assessment data to be sent to the server.
|
||||
*
|
||||
* @param workshop Workshop object.
|
||||
* @param selectedValues Assessment current values
|
||||
* @param feedbackText Feedback text.
|
||||
* @param feedbackFiles Feedback attachments.
|
||||
* @param form Assessment form original data.
|
||||
* @param attachmentsId The draft file area id for attachments.
|
||||
* @return Promise resolved with the data to be sent. Or rejected with the input errors object.
|
||||
*/
|
||||
async prepareAssessmentData(
|
||||
workshop: AddonModWorkshopData,
|
||||
selectedValues: AddonModWorkshopGetAssessmentFormFieldsParsedData[],
|
||||
feedbackText: string,
|
||||
form: AddonModWorkshopGetAssessmentFormDefinitionData,
|
||||
attachmentsId: CoreFileUploaderStoreFilesResult | number = 0,
|
||||
): Promise<CoreFormFields<unknown>> {
|
||||
|
||||
if (workshop.overallfeedbackmode == AddonModWorkshopOverallFeedbackMode.ENABLED_REQUIRED && !feedbackText) {
|
||||
const errors: AddonModWorkshopAssessmentStrategyFieldErrors =
|
||||
{ feedbackauthor: Translate.instant('core.err_required') };
|
||||
throw errors;
|
||||
}
|
||||
|
||||
const data =
|
||||
(await AddonWorkshopAssessmentStrategyDelegate.prepareAssessmentData(workshop.strategy!, selectedValues, form)) || {};
|
||||
data.feedbackauthor = feedbackText;
|
||||
data.feedbackauthorattachmentsid = attachmentsId;
|
||||
data.nodims = form.dimenssionscount;
|
||||
|
||||
return data;
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the real value of a grade based on real_grade_value.
|
||||
*
|
||||
* @param value Percentual value from 0 to 100.
|
||||
* @param max The maximal grade.
|
||||
* @param decimals Decimals to show in the formatted grade.
|
||||
* @return Real grade formatted.
|
||||
*/
|
||||
protected realGradeValueHelper(value?: number | string, max = 0, decimals = 0): string | undefined {
|
||||
if (typeof value == 'string') {
|
||||
// Already treated.
|
||||
return value;
|
||||
}
|
||||
|
||||
if (value == null || typeof value == 'undefined') {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
if (max == 0) {
|
||||
return '0';
|
||||
}
|
||||
|
||||
value = CoreTextUtils.roundToDecimals(max * value / 100, decimals);
|
||||
|
||||
return CoreUtils.formatFloat(value);
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the real value of a grades of an assessment.
|
||||
*
|
||||
* @param workshop Workshop object.
|
||||
* @param assessment Assessment data.
|
||||
* @return Assessment with real grades.
|
||||
*/
|
||||
realGradeValue(
|
||||
workshop: AddonModWorkshopData,
|
||||
assessment: AddonModWorkshopSubmissionAssessmentWithFormData,
|
||||
): AddonModWorkshopSubmissionAssessmentWithFormData {
|
||||
assessment.grade = this.realGradeValueHelper(assessment.grade, workshop.grade, workshop.gradedecimals);
|
||||
assessment.gradinggrade = this.realGradeValueHelper(assessment.gradinggrade, workshop.gradinggrade, workshop.gradedecimals);
|
||||
|
||||
assessment.gradinggradeover = this.realGradeValueHelper(
|
||||
assessment.gradinggradeover,
|
||||
workshop.gradinggrade,
|
||||
workshop.gradedecimals,
|
||||
);
|
||||
|
||||
return assessment;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check grade should be shown
|
||||
*
|
||||
* @param grade Grade to be shown
|
||||
* @return If grade should be shown or not.
|
||||
*/
|
||||
showGrade(grade?: number|string): boolean {
|
||||
return typeof grade !== 'undefined' && grade !== null;
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopHelper = makeSingleton(AddonModWorkshopHelperProvider);
|
||||
|
||||
export type AddonModWorkshopSubmissionAssessmentWithFormData =
|
||||
Omit<AddonModWorkshopSubmissionAssessmentData, 'grade'|'gradinggrade'|'gradinggradeover'|'feedbackattachmentfiles'> & {
|
||||
form?: AddonModWorkshopGetAssessmentFormDefinitionData;
|
||||
submission?: AddonModWorkshopSubmissionData;
|
||||
offline?: boolean;
|
||||
strategy?: string;
|
||||
grade?: string | number;
|
||||
gradinggrade?: string | number;
|
||||
gradinggradeover?: string | number;
|
||||
ownAssessment?: boolean;
|
||||
feedbackauthor?: string;
|
||||
feedbackattachmentfiles: CoreFileEntry[]; // Feedbackattachmentfiles.
|
||||
};
|
||||
|
||||
export type AddonModWorkshopSubmissionDataWithOfflineData = Omit<AddonModWorkshopSubmissionData, 'attachmentfiles'> & {
|
||||
courseid?: number;
|
||||
submissionmodified?: number;
|
||||
offline?: boolean;
|
||||
deleted?: boolean;
|
||||
attachmentfiles?: CoreFileEntry[];
|
||||
reviewedby?: AddonModWorkshopSubmissionAssessmentWithFormData[];
|
||||
reviewerof?: AddonModWorkshopSubmissionAssessmentWithFormData[];
|
||||
gradinggrade?: number;
|
||||
reviewedbydone?: number;
|
||||
reviewerofdone?: number;
|
||||
reviewedbycount?: number;
|
||||
reviewerofcount?: number;
|
||||
};
|
|
@ -0,0 +1,684 @@
|
|||
// (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 { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||
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 { CoreFormFields } from '@singletons/form';
|
||||
import {
|
||||
AddonModWorkshopAssessmentDBRecord,
|
||||
AddonModWorkshopEvaluateAssessmentDBRecord,
|
||||
AddonModWorkshopEvaluateSubmissionDBRecord,
|
||||
AddonModWorkshopSubmissionDBRecord,
|
||||
ASSESSMENTS_TABLE,
|
||||
EVALUATE_ASSESSMENTS_TABLE,
|
||||
EVALUATE_SUBMISSIONS_TABLE,
|
||||
SUBMISSIONS_TABLE,
|
||||
} from './database/workshop';
|
||||
import { AddonModWorkshopAction } from './workshop';
|
||||
|
||||
/**
|
||||
* Service to handle offline workshop.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopOfflineProvider {
|
||||
|
||||
/**
|
||||
* Get all the workshops ids that have something to be synced.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with workshops id that have something to be synced.
|
||||
*/
|
||||
async getAllWorkshops(siteId?: string): Promise<number[]> {
|
||||
const promiseResults = await Promise.all([
|
||||
this.getAllSubmissions(siteId),
|
||||
this.getAllAssessments(siteId),
|
||||
this.getAllEvaluateSubmissions(siteId),
|
||||
this.getAllEvaluateAssessments(siteId),
|
||||
]);
|
||||
|
||||
const workshopIds: Record<number, number> = {};
|
||||
|
||||
// Get workshops from any offline object all should have workshopid.
|
||||
promiseResults.forEach((offlineObjects) => {
|
||||
offlineObjects.forEach((offlineObject: AddonModWorkshopOfflineSubmission | AddonModWorkshopOfflineAssessment |
|
||||
AddonModWorkshopOfflineEvaluateSubmission | AddonModWorkshopOfflineEvaluateAssessment) => {
|
||||
workshopIds[offlineObject.workshopid] = offlineObject.workshopid;
|
||||
});
|
||||
});
|
||||
|
||||
return Object.values(workshopIds);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if there is an offline data to be synced.
|
||||
*
|
||||
* @param workshopId Workshop ID to remove.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with boolean: true if has offline data, false otherwise.
|
||||
*/
|
||||
async hasWorkshopOfflineData(workshopId: number, siteId?: string): Promise<boolean> {
|
||||
try {
|
||||
const results = await Promise.all([
|
||||
this.getSubmissions(workshopId, siteId),
|
||||
this.getAssessments(workshopId, siteId),
|
||||
this.getEvaluateSubmissions(workshopId, siteId),
|
||||
this.getEvaluateAssessments(workshopId, siteId),
|
||||
]);
|
||||
|
||||
return results.some((result) => result && result.length);
|
||||
} catch {
|
||||
// No offline data found.
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete workshop submission action.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param submissionId Submission ID.
|
||||
* @param action Action to be done.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
async deleteSubmissionAction(
|
||||
workshopId: number,
|
||||
action: AddonModWorkshopAction,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopSubmissionDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
action: action,
|
||||
};
|
||||
|
||||
await site.getDb().deleteRecords(SUBMISSIONS_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete all workshop submission actions.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
async deleteAllSubmissionActions(workshopId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopSubmissionDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
};
|
||||
|
||||
await site.getDb().deleteRecords(SUBMISSIONS_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all the submissions to be synced.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the objects to be synced.
|
||||
*/
|
||||
async getAllSubmissions(siteId?: string): Promise<AddonModWorkshopOfflineSubmission[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const records = await site.getDb().getRecords<AddonModWorkshopSubmissionDBRecord>(SUBMISSIONS_TABLE);
|
||||
|
||||
return records.map(this.parseSubmissionRecord.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the submissions of a workshop to be synced.
|
||||
*
|
||||
* @param workshopId ID of the workshop.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the object to be synced.
|
||||
*/
|
||||
async getSubmissions(workshopId: number, siteId?: string): Promise<AddonModWorkshopOfflineSubmission[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopSubmissionDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
};
|
||||
|
||||
const records = await site.getDb().getRecords<AddonModWorkshopSubmissionDBRecord>(SUBMISSIONS_TABLE, conditions);
|
||||
|
||||
return records.map(this.parseSubmissionRecord.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an specific action of a submission of a workshop to be synced.
|
||||
*
|
||||
* @param workshopId ID of the workshop.
|
||||
* @param action Action to be done.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the object to be synced.
|
||||
*/
|
||||
async getSubmissionAction(
|
||||
workshopId: number,
|
||||
action: AddonModWorkshopAction,
|
||||
siteId?: string,
|
||||
): Promise<AddonModWorkshopOfflineSubmission> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopSubmissionDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
action: action,
|
||||
};
|
||||
|
||||
const record = await site.getDb().getRecord<AddonModWorkshopSubmissionDBRecord>(SUBMISSIONS_TABLE, conditions);
|
||||
|
||||
return this.parseSubmissionRecord(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offline version for adding a submission action to a workshop.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param courseId Course ID the workshop belongs to.
|
||||
* @param title The submission title.
|
||||
* @param content The submission text content.
|
||||
* @param attachmentsId Stored attachments.
|
||||
* @param submissionId Submission Id, if action is add, the time the submission was created.
|
||||
* If set to 0, current time is used.
|
||||
* @param action Action to be done. ['add', 'update', 'delete']
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when submission action is successfully saved.
|
||||
*/
|
||||
async saveSubmission(
|
||||
workshopId: number,
|
||||
courseId: number,
|
||||
title: string,
|
||||
content: string,
|
||||
attachmentsId: CoreFileUploaderStoreFilesResult | undefined,
|
||||
submissionId = 0,
|
||||
action: AddonModWorkshopAction,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const timemodified = CoreTimeUtils.timestamp();
|
||||
|
||||
const submission: AddonModWorkshopSubmissionDBRecord = {
|
||||
workshopid: workshopId,
|
||||
courseid: courseId,
|
||||
title: title,
|
||||
content: content,
|
||||
attachmentsid: JSON.stringify(attachmentsId),
|
||||
action: action,
|
||||
submissionid: submissionId,
|
||||
timemodified: timemodified,
|
||||
};
|
||||
|
||||
await site.getDb().insertRecord(SUBMISSIONS_TABLE, submission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse "attachments" column of a submission record.
|
||||
*
|
||||
* @param record Submission record, modified in place.
|
||||
*/
|
||||
protected parseSubmissionRecord(record: AddonModWorkshopSubmissionDBRecord): AddonModWorkshopOfflineSubmission {
|
||||
return {
|
||||
...record,
|
||||
attachmentsid: CoreTextUtils.parseJSON(record.attachmentsid),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete workshop assessment.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
async deleteAssessment(workshopId: number, assessmentId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopAssessmentDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
assessmentid: assessmentId,
|
||||
};
|
||||
|
||||
await site.getDb().deleteRecords(ASSESSMENTS_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all the assessments to be synced.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the objects to be synced.
|
||||
*/
|
||||
async getAllAssessments(siteId?: string): Promise<AddonModWorkshopOfflineAssessment[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const records = await site.getDb().getRecords<AddonModWorkshopAssessmentDBRecord>(ASSESSMENTS_TABLE);
|
||||
|
||||
return records.map(this.parseAssessmentRecord.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the assessments of a workshop to be synced.
|
||||
*
|
||||
* @param workshopId ID of the workshop.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the object to be synced.
|
||||
*/
|
||||
async getAssessments(workshopId: number, siteId?: string): Promise<AddonModWorkshopOfflineAssessment[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopAssessmentDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
};
|
||||
|
||||
const records = await site.getDb().getRecords<AddonModWorkshopAssessmentDBRecord>(ASSESSMENTS_TABLE, conditions);
|
||||
|
||||
return records.map(this.parseAssessmentRecord.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an specific assessment of a workshop to be synced.
|
||||
*
|
||||
* @param workshopId ID of the workshop.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the object to be synced.
|
||||
*/
|
||||
async getAssessment(workshopId: number, assessmentId: number, siteId?: string): Promise<AddonModWorkshopOfflineAssessment> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopAssessmentDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
assessmentid: assessmentId,
|
||||
};
|
||||
|
||||
const record = await site.getDb().getRecord<AddonModWorkshopAssessmentDBRecord>(ASSESSMENTS_TABLE, conditions);
|
||||
|
||||
return this.parseAssessmentRecord(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offline version for adding an assessment to a workshop.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param courseId Course ID the workshop belongs to.
|
||||
* @param inputData Assessment data.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when assessment is successfully saved.
|
||||
*/
|
||||
async saveAssessment(
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
courseId: number,
|
||||
inputData: CoreFormFields,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const assessment: AddonModWorkshopAssessmentDBRecord = {
|
||||
workshopid: workshopId,
|
||||
courseid: courseId,
|
||||
inputdata: JSON.stringify(inputData),
|
||||
assessmentid: assessmentId,
|
||||
timemodified: CoreTimeUtils.timestamp(),
|
||||
};
|
||||
|
||||
await site.getDb().insertRecord(ASSESSMENTS_TABLE, assessment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse "inpudata" column of an assessment record.
|
||||
*
|
||||
* @param record Assessnent record, modified in place.
|
||||
*/
|
||||
protected parseAssessmentRecord(record: AddonModWorkshopAssessmentDBRecord): AddonModWorkshopOfflineAssessment {
|
||||
return {
|
||||
...record,
|
||||
inputdata: CoreTextUtils.parseJSON(record.inputdata),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete workshop evaluate submission.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param submissionId Submission ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
async deleteEvaluateSubmission(workshopId: number, submissionId: number, siteId?: string): Promise<void> {
|
||||
const conditions: Partial<AddonModWorkshopEvaluateSubmissionDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
submissionid: submissionId,
|
||||
};
|
||||
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
await site.getDb().deleteRecords(EVALUATE_SUBMISSIONS_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all the evaluate submissions to be synced.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the objects to be synced.
|
||||
*/
|
||||
async getAllEvaluateSubmissions(siteId?: string): Promise<AddonModWorkshopOfflineEvaluateSubmission[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const records = await site.getDb().getRecords<AddonModWorkshopEvaluateSubmissionDBRecord>(EVALUATE_SUBMISSIONS_TABLE);
|
||||
|
||||
return records.map(this.parseEvaluateSubmissionRecord.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the evaluate submissions of a workshop to be synced.
|
||||
*
|
||||
* @param workshopId ID of the workshop.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the object to be synced.
|
||||
*/
|
||||
async getEvaluateSubmissions(workshopId: number, siteId?: string): Promise<AddonModWorkshopOfflineEvaluateSubmission[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopEvaluateSubmissionDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
};
|
||||
|
||||
const records =
|
||||
await site.getDb().getRecords<AddonModWorkshopEvaluateSubmissionDBRecord>(EVALUATE_SUBMISSIONS_TABLE, conditions);
|
||||
|
||||
return records.map(this.parseEvaluateSubmissionRecord.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an specific evaluate submission of a workshop to be synced.
|
||||
*
|
||||
* @param workshopId ID of the workshop.
|
||||
* @param submissionId Submission ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the object to be synced.
|
||||
*/
|
||||
async getEvaluateSubmission(
|
||||
workshopId: number,
|
||||
submissionId: number,
|
||||
siteId?: string,
|
||||
): Promise<AddonModWorkshopOfflineEvaluateSubmission> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopEvaluateSubmissionDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
submissionid: submissionId,
|
||||
};
|
||||
|
||||
const record =
|
||||
await site.getDb().getRecord<AddonModWorkshopEvaluateSubmissionDBRecord>(EVALUATE_SUBMISSIONS_TABLE, conditions);
|
||||
|
||||
return this.parseEvaluateSubmissionRecord(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offline version for evaluation a submission to a workshop.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param submissionId Submission ID.
|
||||
* @param courseId Course ID the workshop belongs to.
|
||||
* @param feedbackText The feedback for the author.
|
||||
* @param published Whether to publish the submission for other users.
|
||||
* @param gradeOver The new submission grade (empty for no overriding the grade).
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when submission evaluation is successfully saved.
|
||||
*/
|
||||
async saveEvaluateSubmission(
|
||||
workshopId: number,
|
||||
submissionId: number,
|
||||
courseId: number,
|
||||
feedbackText = '',
|
||||
published?: boolean,
|
||||
gradeOver?: string,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const submission: AddonModWorkshopEvaluateSubmissionDBRecord = {
|
||||
workshopid: workshopId,
|
||||
courseid: courseId,
|
||||
submissionid: submissionId,
|
||||
timemodified: CoreTimeUtils.timestamp(),
|
||||
feedbacktext: feedbackText,
|
||||
published: Number(published),
|
||||
gradeover: JSON.stringify(gradeOver),
|
||||
};
|
||||
|
||||
await site.getDb().insertRecord(EVALUATE_SUBMISSIONS_TABLE, submission);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse "published" and "gradeover" columns of an evaluate submission record.
|
||||
*
|
||||
* @param record Evaluate submission record, modified in place.
|
||||
*/
|
||||
protected parseEvaluateSubmissionRecord(
|
||||
record: AddonModWorkshopEvaluateSubmissionDBRecord,
|
||||
): AddonModWorkshopOfflineEvaluateSubmission {
|
||||
return {
|
||||
...record,
|
||||
published: Boolean(record.published),
|
||||
gradeover: CoreTextUtils.parseJSON(record.gradeover),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete workshop evaluate assessment.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if stored, rejected if failure.
|
||||
*/
|
||||
async deleteEvaluateAssessment(workshopId: number, assessmentId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopEvaluateAssessmentDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
assessmentid: assessmentId,
|
||||
};
|
||||
|
||||
await site.getDb().deleteRecords(EVALUATE_ASSESSMENTS_TABLE, conditions);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the all the evaluate assessments to be synced.
|
||||
*
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the objects to be synced.
|
||||
*/
|
||||
async getAllEvaluateAssessments(siteId?: string): Promise<AddonModWorkshopOfflineEvaluateAssessment[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const records = await site.getDb().getRecords<AddonModWorkshopEvaluateAssessmentDBRecord>(EVALUATE_ASSESSMENTS_TABLE);
|
||||
|
||||
return records.map(this.parseEvaluateAssessmentRecord.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the evaluate assessments of a workshop to be synced.
|
||||
*
|
||||
* @param workshopId ID of the workshop.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the object to be synced.
|
||||
*/
|
||||
async getEvaluateAssessments(workshopId: number, siteId?: string): Promise<AddonModWorkshopOfflineEvaluateAssessment[]> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopEvaluateAssessmentDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
};
|
||||
|
||||
const records =
|
||||
await site.getDb().getRecords<AddonModWorkshopEvaluateAssessmentDBRecord>(EVALUATE_ASSESSMENTS_TABLE, conditions);
|
||||
|
||||
return records.map(this.parseEvaluateAssessmentRecord.bind(this));
|
||||
}
|
||||
|
||||
/**
|
||||
* Get an specific evaluate assessment of a workshop to be synced.
|
||||
*
|
||||
* @param workshopId ID of the workshop.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the object to be synced.
|
||||
*/
|
||||
async getEvaluateAssessment(
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
siteId?: string,
|
||||
): Promise<AddonModWorkshopOfflineEvaluateAssessment> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const conditions: Partial<AddonModWorkshopEvaluateAssessmentDBRecord> = {
|
||||
workshopid: workshopId,
|
||||
assessmentid: assessmentId,
|
||||
};
|
||||
|
||||
const record =
|
||||
await site.getDb().getRecord<AddonModWorkshopEvaluateAssessmentDBRecord>(EVALUATE_ASSESSMENTS_TABLE, conditions);
|
||||
|
||||
return this.parseEvaluateAssessmentRecord(record);
|
||||
}
|
||||
|
||||
/**
|
||||
* Offline version for evaluating an assessment to a workshop.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param courseId Course ID the workshop belongs to.
|
||||
* @param feedbackText The feedback for the reviewer.
|
||||
* @param weight The new weight for the assessment.
|
||||
* @param gradingGradeOver The new grading grade (empty for no overriding the grade).
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when assessment evaluation is successfully saved.
|
||||
*/
|
||||
async saveEvaluateAssessment(
|
||||
workshopId: number,
|
||||
assessmentId: number,
|
||||
courseId: number,
|
||||
feedbackText?: string,
|
||||
weight = 0,
|
||||
gradingGradeOver?: string,
|
||||
siteId?: string,
|
||||
): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const assessment: AddonModWorkshopEvaluateAssessmentDBRecord = {
|
||||
workshopid: workshopId,
|
||||
courseid: courseId,
|
||||
assessmentid: assessmentId,
|
||||
timemodified: CoreTimeUtils.timestamp(),
|
||||
feedbacktext: feedbackText || '',
|
||||
weight: weight,
|
||||
gradinggradeover: JSON.stringify(gradingGradeOver),
|
||||
};
|
||||
|
||||
await site.getDb().insertRecord(EVALUATE_ASSESSMENTS_TABLE, assessment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Parse "gradinggradeover" column of an evaluate assessment record.
|
||||
*
|
||||
* @param record Evaluate assessment record, modified in place.
|
||||
*/
|
||||
protected parseEvaluateAssessmentRecord(
|
||||
record: AddonModWorkshopEvaluateAssessmentDBRecord,
|
||||
): AddonModWorkshopOfflineEvaluateAssessment {
|
||||
return {
|
||||
...record,
|
||||
gradinggradeover: CoreTextUtils.parseJSON(record.gradinggradeover),
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the folder where to store files for offline attachments in a workshop.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the path.
|
||||
*/
|
||||
async getWorkshopFolder(workshopId: number, siteId?: string): Promise<string> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
const siteFolderPath = CoreFile.getSiteFolder(site.getId());
|
||||
const workshopFolderPath = 'offlineworkshop/' + workshopId + '/';
|
||||
|
||||
return CoreTextUtils.concatenatePaths(siteFolderPath, workshopFolderPath);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the folder where to store files for offline submissions.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the path.
|
||||
*/
|
||||
async getSubmissionFolder(workshopId: number, siteId?: string): Promise<string> {
|
||||
const folderPath = await this.getWorkshopFolder(workshopId, siteId);
|
||||
|
||||
return CoreTextUtils.concatenatePaths(folderPath, 'submission');
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the path to the folder where to store files for offline assessment.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param assessmentId Assessment ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with the path.
|
||||
*/
|
||||
async getAssessmentFolder(workshopId: number, assessmentId: number, siteId?: string): Promise<string> {
|
||||
let folderPath = await this.getWorkshopFolder(workshopId, siteId);
|
||||
|
||||
folderPath += 'assessment/';
|
||||
|
||||
return CoreTextUtils.concatenatePaths(folderPath, String(assessmentId));
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopOffline = makeSingleton(AddonModWorkshopOfflineProvider);
|
||||
|
||||
export type AddonModWorkshopOfflineSubmission = Omit<AddonModWorkshopSubmissionDBRecord, 'attachmentsid'> & {
|
||||
attachmentsid?: CoreFileUploaderStoreFilesResult;
|
||||
};
|
||||
|
||||
export type AddonModWorkshopOfflineAssessment = Omit<AddonModWorkshopAssessmentDBRecord, 'inputdata'> & {
|
||||
inputdata: CoreFormFields;
|
||||
};
|
||||
|
||||
export type AddonModWorkshopOfflineEvaluateSubmission =
|
||||
Omit<AddonModWorkshopEvaluateSubmissionDBRecord, 'published' | 'gradeover'> & {
|
||||
published: boolean;
|
||||
gradeover: string;
|
||||
};
|
||||
|
||||
export type AddonModWorkshopOfflineEvaluateAssessment =
|
||||
Omit<AddonModWorkshopEvaluateAssessmentDBRecord, 'gradinggradeover'> & {
|
||||
gradinggradeover: string;
|
||||
};
|
|
@ -0,0 +1,631 @@
|
|||
// (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 { CoreSyncBaseProvider, CoreSyncBlockedError } from '@classes/base-sync';
|
||||
import { CoreNetworkError } from '@classes/errors/network-error';
|
||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
|
||||
import { CoreFileUploaderStoreFilesResult } from '@features/fileuploader/services/fileuploader';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreFileEntry } from '@services/file-helper';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreSync } from '@services/sync';
|
||||
import { CoreTextUtils } from '@services/utils/text';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { Translate, makeSingleton } from '@singletons';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { AddonModWorkshop,
|
||||
AddonModWorkshopAction,
|
||||
AddonModWorkshopData,
|
||||
AddonModWorkshopProvider,
|
||||
AddonModWorkshopSubmissionType,
|
||||
} from './workshop';
|
||||
import { AddonModWorkshopHelper } from './workshop-helper';
|
||||
import { AddonModWorkshopOffline,
|
||||
AddonModWorkshopOfflineAssessment,
|
||||
AddonModWorkshopOfflineEvaluateAssessment,
|
||||
AddonModWorkshopOfflineEvaluateSubmission,
|
||||
AddonModWorkshopOfflineSubmission,
|
||||
} from './workshop-offline';
|
||||
|
||||
/**
|
||||
* Service to sync workshops.
|
||||
*/
|
||||
@Injectable({ providedIn: 'root' })
|
||||
export class AddonModWorkshopSyncProvider extends CoreSyncBaseProvider<AddonModWorkshopSyncResult> {
|
||||
|
||||
static readonly AUTO_SYNCED = 'addon_mod_workshop_autom_synced';
|
||||
static readonly MANUAL_SYNCED = 'addon_mod_workshop_manual_synced';
|
||||
|
||||
protected componentTranslatableString = 'workshop';
|
||||
|
||||
constructor() {
|
||||
super('AddonModWorkshopSyncProvider');
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an workshop has data to synchronize.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved with boolean: true if has data to sync, false otherwise.
|
||||
*/
|
||||
hasDataToSync(workshopId: number, siteId?: string): Promise<boolean> {
|
||||
return AddonModWorkshopOffline.hasWorkshopOfflineData(workshopId, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to synchronize all workshops that need it and haven't been synchronized in a while.
|
||||
*
|
||||
* @param siteId Site ID to sync. If not defined, sync all sites.
|
||||
* @param force Wether to force sync not depending on last execution.
|
||||
* @return Promise resolved when the sync is done.
|
||||
*/
|
||||
syncAllWorkshops(siteId?: string, force?: boolean): Promise<void> {
|
||||
return this.syncOnSites('all workshops', this.syncAllWorkshopsFunc.bind(this, !!force), siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync all workshops on a site.
|
||||
*
|
||||
* @param force Wether to force sync not depending on last execution.
|
||||
* @param siteId Site ID to sync.
|
||||
* @return Promise resolved if sync is successful, rejected if sync fails.
|
||||
*/
|
||||
protected async syncAllWorkshopsFunc(force: boolean, siteId: string): Promise<void> {
|
||||
const workshopIds = await AddonModWorkshopOffline.getAllWorkshops(siteId);
|
||||
|
||||
// Sync all workshops that haven't been synced for a while.
|
||||
const promises = workshopIds.map(async (workshopId) => {
|
||||
const data = force
|
||||
? await this.syncWorkshop(workshopId, siteId)
|
||||
: await this.syncWorkshopIfNeeded(workshopId, siteId);
|
||||
|
||||
if (data && data.updated) {
|
||||
// Sync done. Send event.
|
||||
CoreEvents.trigger(AddonModWorkshopSyncProvider.AUTO_SYNCED, {
|
||||
workshopId: workshopId,
|
||||
warnings: data.warnings,
|
||||
}, siteId);
|
||||
}
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sync a workshop only if a certain time has passed since the last time.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved when the workshop is synced or if it doesn't need to be synced.
|
||||
*/
|
||||
async syncWorkshopIfNeeded(workshopId: number, siteId?: string): Promise<AddonModWorkshopSyncResult | undefined> {
|
||||
const needed = await this.isSyncNeeded(workshopId, siteId);
|
||||
|
||||
if (needed) {
|
||||
return this.syncWorkshop(workshopId, siteId);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Try to synchronize a workshop.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID. If not defined, current site.
|
||||
* @return Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
syncWorkshop(workshopId: number, siteId?: string): Promise<AddonModWorkshopSyncResult> {
|
||||
siteId = siteId || CoreSites.getCurrentSiteId();
|
||||
|
||||
if (this.isSyncing(workshopId, siteId)) {
|
||||
// There's already a sync ongoing for this discussion, return the promise.
|
||||
return this.getOngoingSync(workshopId, siteId)!;
|
||||
}
|
||||
|
||||
// Verify that workshop isn't blocked.
|
||||
if (CoreSync.isBlocked(AddonModWorkshopProvider.COMPONENT, workshopId, siteId)) {
|
||||
this.logger.debug(`Cannot sync workshop '${workshopId}' because it is blocked.`);
|
||||
|
||||
throw new CoreSyncBlockedError(Translate.instant('core.errorsyncblocked', { $a: this.componentTranslate }));
|
||||
}
|
||||
|
||||
this.logger.debug(`Try to sync workshop '${workshopId}' in site ${siteId}'`);
|
||||
|
||||
const syncPromise = this.performSyncWorkshop(workshopId, siteId);
|
||||
|
||||
return this.addOngoingSync(workshopId, syncPromise, siteId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform the workshop sync.
|
||||
*
|
||||
* @param workshopId Workshop ID.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved if sync is successful, rejected otherwise.
|
||||
*/
|
||||
protected async performSyncWorkshop(workshopId: number, siteId: string): Promise<AddonModWorkshopSyncResult> {
|
||||
const result: AddonModWorkshopSyncResult = {
|
||||
warnings: [],
|
||||
updated: false,
|
||||
};
|
||||
|
||||
// Sync offline logs.
|
||||
await CoreUtils.ignoreErrors(CoreCourseLogHelper.syncActivity(AddonModWorkshopProvider.COMPONENT, workshopId, siteId));
|
||||
|
||||
// Get offline submissions to be sent.
|
||||
const syncs = await Promise.all([
|
||||
// Get offline submissions to be sent.
|
||||
CoreUtils.ignoreErrors(AddonModWorkshopOffline.getSubmissions(workshopId, siteId), []),
|
||||
// Get offline submission assessments to be sent.
|
||||
CoreUtils.ignoreErrors(AddonModWorkshopOffline.getAssessments(workshopId, siteId), []),
|
||||
// Get offline submission evaluations to be sent.
|
||||
CoreUtils.ignoreErrors(AddonModWorkshopOffline.getEvaluateSubmissions(workshopId, siteId), []),
|
||||
// Get offline assessment evaluations to be sent.
|
||||
CoreUtils.ignoreErrors(AddonModWorkshopOffline.getEvaluateAssessments(workshopId, siteId), []),
|
||||
]);
|
||||
|
||||
let courseId: number | undefined;
|
||||
|
||||
// Get courseId from the first object
|
||||
for (const x in syncs) {
|
||||
if (syncs[x].length > 0 && syncs[x][0].courseid) {
|
||||
courseId = syncs[x][0].courseid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (!courseId) {
|
||||
// Sync finished, set sync time.
|
||||
await CoreUtils.ignoreErrors(this.setSyncTime(workshopId, siteId));
|
||||
|
||||
// Nothing to sync.
|
||||
return result;
|
||||
}
|
||||
|
||||
if (!CoreApp.isOnline()) {
|
||||
// Cannot sync in offline.
|
||||
throw new CoreNetworkError();
|
||||
}
|
||||
|
||||
const workshop = await AddonModWorkshop.getWorkshopById(courseId, workshopId, { siteId });
|
||||
|
||||
const submissionsActions: AddonModWorkshopOfflineSubmission[] = syncs[0];
|
||||
const assessments: AddonModWorkshopOfflineAssessment[] = syncs[1];
|
||||
const submissionEvaluations: AddonModWorkshopOfflineEvaluateSubmission[] = syncs[2];
|
||||
const assessmentEvaluations: AddonModWorkshopOfflineEvaluateAssessment[] = syncs[3];
|
||||
|
||||
const promises: Promise<void>[] = [];
|
||||
|
||||
promises.push(this.syncSubmission(workshop, submissionsActions, result, siteId).then(() => {
|
||||
result.updated = true;
|
||||
|
||||
return;
|
||||
}));
|
||||
|
||||
assessments.forEach((assessment) => {
|
||||
promises.push(this.syncAssessment(workshop, assessment, result, siteId).then(() => {
|
||||
result.updated = true;
|
||||
|
||||
return;
|
||||
}));
|
||||
});
|
||||
|
||||
submissionEvaluations.forEach((evaluation) => {
|
||||
promises.push(this.syncEvaluateSubmission(workshop, evaluation, result, siteId).then(() => {
|
||||
result.updated = true;
|
||||
|
||||
return;
|
||||
}));
|
||||
});
|
||||
|
||||
assessmentEvaluations.forEach((evaluation) => {
|
||||
promises.push(this.syncEvaluateAssessment(workshop, evaluation, result, siteId).then(() => {
|
||||
result.updated = true;
|
||||
|
||||
return;
|
||||
}));
|
||||
});
|
||||
|
||||
await Promise.all(promises);
|
||||
|
||||
if (result.updated) {
|
||||
// Data has been sent to server. Now invalidate the WS calls.
|
||||
await CoreUtils.ignoreErrors(AddonModWorkshop.invalidateContentById(workshopId, courseId, siteId));
|
||||
}
|
||||
|
||||
// Sync finished, set sync time.
|
||||
await CoreUtils.ignoreErrors(this.setSyncTime(workshopId, siteId));
|
||||
|
||||
// All done, return the warnings.
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize a submission.
|
||||
*
|
||||
* @param workshop Workshop.
|
||||
* @param submissionActions Submission actions offline data.
|
||||
* @param result Object with the result of the sync.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
protected async syncSubmission(
|
||||
workshop: AddonModWorkshopData,
|
||||
submissionActions: AddonModWorkshopOfflineSubmission[],
|
||||
result: AddonModWorkshopSyncResult,
|
||||
siteId: string,
|
||||
): Promise<void> {
|
||||
let discardError: string | undefined;
|
||||
|
||||
// Sort entries by timemodified.
|
||||
submissionActions = submissionActions.sort((a, b) => a.timemodified - b.timemodified);
|
||||
|
||||
let timemodified = 0;
|
||||
let submissionId = submissionActions[0].submissionid;
|
||||
|
||||
if (submissionId > 0) {
|
||||
// Is editing.
|
||||
try {
|
||||
const submission = await AddonModWorkshop.getSubmission(workshop.id, submissionId, {
|
||||
cmId: workshop.coursemodule,
|
||||
siteId,
|
||||
});
|
||||
|
||||
timemodified = submission.timemodified;
|
||||
} catch {
|
||||
timemodified = -1;
|
||||
}
|
||||
}
|
||||
|
||||
if (timemodified < 0 || timemodified >= submissionActions[0].timemodified) {
|
||||
// The entry was not found in Moodle or the entry has been modified, discard the action.
|
||||
result.updated = true;
|
||||
discardError = Translate.instant('addon.mod_workshop.warningsubmissionmodified');
|
||||
|
||||
await AddonModWorkshopOffline.deleteAllSubmissionActions(workshop.id, siteId);
|
||||
|
||||
this.addOfflineDataDeletedWarning(result.warnings, workshop.name, discardError);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
submissionActions.forEach(async (action) => {
|
||||
submissionId = action.submissionid > 0 ? action.submissionid : submissionId;
|
||||
|
||||
try {
|
||||
let attachmentsId: number | undefined;
|
||||
|
||||
// Upload attachments first if any.
|
||||
if (action.attachmentsid) {
|
||||
const files = await AddonModWorkshopHelper.getSubmissionFilesFromOfflineFilesObject(
|
||||
action.attachmentsid,
|
||||
workshop.id,
|
||||
siteId,
|
||||
);
|
||||
|
||||
attachmentsId = await AddonModWorkshopHelper.uploadOrStoreSubmissionFiles(
|
||||
workshop.id,
|
||||
files,
|
||||
false,
|
||||
siteId,
|
||||
);
|
||||
} else {
|
||||
// Remove all files.
|
||||
attachmentsId = await AddonModWorkshopHelper.uploadOrStoreSubmissionFiles(
|
||||
workshop.id,
|
||||
[],
|
||||
false,
|
||||
siteId,
|
||||
);
|
||||
}
|
||||
|
||||
if (workshop.submissiontypefile == AddonModWorkshopSubmissionType.SUBMISSION_TYPE_DISABLED) {
|
||||
attachmentsId = undefined;
|
||||
}
|
||||
|
||||
// Perform the action.
|
||||
switch (action.action) {
|
||||
case AddonModWorkshopAction.ADD:
|
||||
submissionId = await AddonModWorkshop.addSubmissionOnline(
|
||||
workshop.id,
|
||||
action.title,
|
||||
action.content,
|
||||
attachmentsId,
|
||||
siteId,
|
||||
);
|
||||
case AddonModWorkshopAction.UPDATE:
|
||||
await AddonModWorkshop.updateSubmissionOnline(
|
||||
submissionId,
|
||||
action.title,
|
||||
action.content,
|
||||
attachmentsId,
|
||||
siteId,
|
||||
);
|
||||
case AddonModWorkshopAction.DELETE:
|
||||
await AddonModWorkshop.deleteSubmissionOnline(submissionId, siteId);
|
||||
}
|
||||
} catch (error) {
|
||||
if (error && CoreUtils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||
discardError = CoreTextUtils.getErrorMessageFromError(error);
|
||||
}
|
||||
|
||||
// Couldn't connect to server, reject.
|
||||
throw error;
|
||||
|
||||
}
|
||||
// Delete the offline data.
|
||||
result.updated = true;
|
||||
|
||||
await AddonModWorkshopOffline.deleteSubmissionAction(
|
||||
action.workshopid,
|
||||
action.action,
|
||||
siteId,
|
||||
);
|
||||
|
||||
// Delete stored files.
|
||||
if (action.action == AddonModWorkshopAction.ADD || action.action == AddonModWorkshopAction.UPDATE) {
|
||||
|
||||
return AddonModWorkshopHelper.deleteSubmissionStoredFiles(
|
||||
action.workshopid,
|
||||
siteId,
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
if (discardError) {
|
||||
// Submission was discarded, add a warning.
|
||||
this.addOfflineDataDeletedWarning(result.warnings, workshop.name, discardError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize an assessment.
|
||||
*
|
||||
* @param workshop Workshop.
|
||||
* @param assessment Assessment offline data.
|
||||
* @param result Object with the result of the sync.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
protected async syncAssessment(
|
||||
workshop: AddonModWorkshopData,
|
||||
assessmentData: AddonModWorkshopOfflineAssessment,
|
||||
result: AddonModWorkshopSyncResult,
|
||||
siteId: string,
|
||||
): Promise<void> {
|
||||
let discardError: string | undefined;
|
||||
const assessmentId = assessmentData.assessmentid;
|
||||
|
||||
let timemodified = 0;
|
||||
|
||||
try {
|
||||
const assessment = await AddonModWorkshop.getAssessment(workshop.id, assessmentId, {
|
||||
cmId: workshop.coursemodule,
|
||||
siteId,
|
||||
});
|
||||
|
||||
timemodified = assessment.timemodified;
|
||||
} catch {
|
||||
timemodified = -1;
|
||||
}
|
||||
|
||||
if (timemodified < 0 || timemodified >= assessmentData.timemodified) {
|
||||
// The entry was not found in Moodle or the entry has been modified, discard the action.
|
||||
result.updated = true;
|
||||
discardError = Translate.instant('addon.mod_workshop.warningassessmentmodified');
|
||||
|
||||
await AddonModWorkshopOffline.deleteAssessment(workshop.id, assessmentId, siteId);
|
||||
|
||||
this.addOfflineDataDeletedWarning(result.warnings, workshop.name, discardError);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
let attachmentsId = 0;
|
||||
const inputData = assessmentData.inputdata;
|
||||
|
||||
try {
|
||||
let files: CoreFileEntry[] = [];
|
||||
// Upload attachments first if any.
|
||||
if (inputData.feedbackauthorattachmentsid) {
|
||||
files = await AddonModWorkshopHelper.getAssessmentFilesFromOfflineFilesObject(
|
||||
<CoreFileUploaderStoreFilesResult>inputData.feedbackauthorattachmentsid,
|
||||
workshop.id,
|
||||
assessmentId,
|
||||
siteId,
|
||||
);
|
||||
}
|
||||
|
||||
attachmentsId =
|
||||
await AddonModWorkshopHelper.uploadOrStoreAssessmentFiles(workshop.id, assessmentId, files, false, siteId);
|
||||
|
||||
inputData.feedbackauthorattachmentsid = attachmentsId || 0;
|
||||
|
||||
await AddonModWorkshop.updateAssessmentOnline(assessmentId, inputData, siteId);
|
||||
} catch (error) {
|
||||
if (error && CoreUtils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||
discardError = CoreTextUtils.getErrorMessageFromError(error);
|
||||
} else {
|
||||
// Couldn't connect to server, reject.
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the offline data.
|
||||
result.updated = true;
|
||||
|
||||
await AddonModWorkshopOffline.deleteAssessment(workshop.id, assessmentId, siteId);
|
||||
await AddonModWorkshopHelper.deleteAssessmentStoredFiles(workshop.id, assessmentId, siteId);
|
||||
|
||||
if (discardError) {
|
||||
// Assessment was discarded, add a warning.
|
||||
this.addOfflineDataDeletedWarning(result.warnings, workshop.name, discardError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize a submission evaluation.
|
||||
*
|
||||
* @param workshop Workshop.
|
||||
* @param evaluate Submission evaluation offline data.
|
||||
* @param result Object with the result of the sync.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
protected async syncEvaluateSubmission(
|
||||
workshop: AddonModWorkshopData,
|
||||
evaluate: AddonModWorkshopOfflineEvaluateSubmission,
|
||||
result: AddonModWorkshopSyncResult,
|
||||
siteId: string,
|
||||
): Promise<void> {
|
||||
let discardError: string | undefined;
|
||||
const submissionId = evaluate.submissionid;
|
||||
|
||||
let timemodified = 0;
|
||||
|
||||
try {
|
||||
const submission = await AddonModWorkshop.getSubmission(workshop.id, submissionId, {
|
||||
cmId: workshop.coursemodule,
|
||||
siteId,
|
||||
});
|
||||
|
||||
timemodified = submission.timemodified;
|
||||
} catch {
|
||||
timemodified = -1;
|
||||
}
|
||||
|
||||
if (timemodified < 0 || timemodified >= evaluate.timemodified) {
|
||||
// The entry was not found in Moodle or the entry has been modified, discard the action.
|
||||
result.updated = true;
|
||||
discardError = Translate.instant('addon.mod_workshop.warningsubmissionmodified');
|
||||
|
||||
await AddonModWorkshopOffline.deleteEvaluateSubmission(workshop.id, submissionId, siteId);
|
||||
|
||||
this.addOfflineDataDeletedWarning(result.warnings, workshop.name, discardError);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
try {
|
||||
await AddonModWorkshop.evaluateSubmissionOnline(
|
||||
submissionId,
|
||||
evaluate.feedbacktext,
|
||||
evaluate.published,
|
||||
evaluate.gradeover,
|
||||
siteId,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error && CoreUtils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||
discardError = CoreTextUtils.getErrorMessageFromError(error);
|
||||
} else {
|
||||
// Couldn't connect to server, reject.
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the offline data.
|
||||
result.updated = true;
|
||||
|
||||
await AddonModWorkshopOffline.deleteEvaluateSubmission(workshop.id, submissionId, siteId);
|
||||
|
||||
if (discardError) {
|
||||
// Assessment was discarded, add a warning.
|
||||
this.addOfflineDataDeletedWarning(result.warnings, workshop.name, discardError);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Synchronize a assessment evaluation.
|
||||
*
|
||||
* @param workshop Workshop.
|
||||
* @param evaluate Assessment evaluation offline data.
|
||||
* @param result Object with the result of the sync.
|
||||
* @param siteId Site ID.
|
||||
* @return Promise resolved if success, rejected otherwise.
|
||||
*/
|
||||
protected async syncEvaluateAssessment(
|
||||
workshop: AddonModWorkshopData,
|
||||
evaluate: AddonModWorkshopOfflineEvaluateAssessment,
|
||||
result: AddonModWorkshopSyncResult,
|
||||
siteId: string,
|
||||
): Promise<void> {
|
||||
let discardError: string | undefined;
|
||||
const assessmentId = evaluate.assessmentid;
|
||||
|
||||
let timemodified = 0;
|
||||
|
||||
try {
|
||||
const assessment = await AddonModWorkshop.getAssessment(workshop.id, assessmentId, {
|
||||
cmId: workshop.coursemodule,
|
||||
siteId,
|
||||
});
|
||||
|
||||
timemodified = assessment.timemodified;
|
||||
} catch {
|
||||
timemodified = -1;
|
||||
}
|
||||
|
||||
if (timemodified < 0 || timemodified >= evaluate.timemodified) {
|
||||
// The entry was not found in Moodle or the entry has been modified, discard the action.
|
||||
result.updated = true;
|
||||
discardError = Translate.instant('addon.mod_workshop.warningassessmentmodified');
|
||||
|
||||
return AddonModWorkshopOffline.deleteEvaluateAssessment(workshop.id, assessmentId, siteId);
|
||||
}
|
||||
|
||||
try {
|
||||
await AddonModWorkshop.evaluateAssessmentOnline(
|
||||
assessmentId,
|
||||
evaluate.feedbacktext,
|
||||
evaluate.weight,
|
||||
evaluate.gradinggradeover,
|
||||
siteId,
|
||||
);
|
||||
} catch (error) {
|
||||
if (error && CoreUtils.isWebServiceError(error)) {
|
||||
// The WebService has thrown an error, this means it cannot be performed. Discard.
|
||||
discardError = CoreTextUtils.getErrorMessageFromError(error);
|
||||
} else {
|
||||
// Couldn't connect to server, reject.
|
||||
throw error;
|
||||
}
|
||||
}
|
||||
|
||||
// Delete the offline data.
|
||||
result.updated = true;
|
||||
|
||||
await AddonModWorkshopOffline.deleteEvaluateAssessment(workshop.id, assessmentId, siteId);
|
||||
|
||||
if (discardError) {
|
||||
// Assessment was discarded, add a warning.
|
||||
this.addOfflineDataDeletedWarning(result.warnings, workshop.name, discardError);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
export const AddonModWorkshopSync = makeSingleton(AddonModWorkshopSyncProvider);
|
||||
|
||||
export type AddonModWorkshopAutoSyncData = {
|
||||
workshopId: number;
|
||||
warnings: string[];
|
||||
};
|
||||
|
||||
export type AddonModWorkshopSyncResult = {
|
||||
warnings: string[];
|
||||
updated: boolean;
|
||||
};
|
File diff suppressed because it is too large
Load Diff
|
@ -0,0 +1,79 @@
|
|||
// (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 { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
||||
import { Routes } from '@angular/router';
|
||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
|
||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
|
||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
|
||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
import { CoreCronDelegate } from '@services/cron';
|
||||
import { CORE_SITE_SCHEMAS } from '@services/sites';
|
||||
import { AddonModWorkshopAssessmentStrategyModule } from './assessment/assessment.module';
|
||||
import { AddonModWorkshopComponentsModule } from './components/components.module';
|
||||
import { AddonWorkshopAssessmentStrategyDelegateService } from './services/assessment-strategy-delegate';
|
||||
import { ADDON_MOD_WORKSHOP_OFFLINE_SITE_SCHEMA } from './services/database/workshop';
|
||||
import { AddonModWorkshopIndexLinkHandler } from './services/handlers/index-link';
|
||||
import { AddonModWorkshopListLinkHandler } from './services/handlers/list-link';
|
||||
import { AddonModWorkshopModuleHandler, AddonModWorkshopModuleHandlerService } from './services/handlers/module';
|
||||
import { AddonModWorkshopPrefetchHandler } from './services/handlers/prefetch';
|
||||
import { AddonModWorkshopSyncCronHandler } from './services/handlers/sync-cron';
|
||||
import { AddonModWorkshopProvider } from './services/workshop';
|
||||
import { AddonModWorkshopHelperProvider } from './services/workshop-helper';
|
||||
import { AddonModWorkshopOfflineProvider } from './services/workshop-offline';
|
||||
import { AddonModWorkshopSyncProvider } from './services/workshop-sync';
|
||||
|
||||
// List of providers (without handlers).
|
||||
export const ADDON_MOD_WORKSHOP_SERVICES: Type<unknown>[] = [
|
||||
AddonModWorkshopProvider,
|
||||
AddonModWorkshopOfflineProvider,
|
||||
AddonModWorkshopSyncProvider,
|
||||
AddonModWorkshopHelperProvider,
|
||||
AddonWorkshopAssessmentStrategyDelegateService,
|
||||
];
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: AddonModWorkshopModuleHandlerService.PAGE_NAME,
|
||||
loadChildren: () => import('./workshop-lazy.module').then(m => m.AddonModWorkshopLazyModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
imports: [
|
||||
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||
AddonModWorkshopComponentsModule,
|
||||
AddonModWorkshopAssessmentStrategyModule,
|
||||
],
|
||||
providers: [
|
||||
{
|
||||
provide: CORE_SITE_SCHEMAS,
|
||||
useValue: [ADDON_MOD_WORKSHOP_OFFLINE_SITE_SCHEMA],
|
||||
multi: true,
|
||||
},
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => () => {
|
||||
CoreCourseModuleDelegate.registerHandler(AddonModWorkshopModuleHandler.instance);
|
||||
CoreCourseModulePrefetchDelegate.registerHandler(AddonModWorkshopPrefetchHandler.instance);
|
||||
CoreCronDelegate.register(AddonModWorkshopSyncCronHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonModWorkshopIndexLinkHandler.instance);
|
||||
CoreContentLinksDelegate.registerHandler(AddonModWorkshopListLinkHandler.instance);
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AddonModWorkshopModule {}
|
|
@ -142,7 +142,7 @@ import { ADDON_MOD_SCORM_SERVICES } from '@addons/mod/scorm/scorm.module';
|
|||
import { ADDON_MOD_SURVEY_SERVICES } from '@addons/mod/survey/survey.module';
|
||||
import { ADDON_MOD_URL_SERVICES } from '@addons/mod/url/url.module';
|
||||
import { ADDON_MOD_WIKI_SERVICES } from '@addons/mod/wiki/wiki.module';
|
||||
// @todo import { ADDON_MOD_WORKSHOP_SERVICES } from '@addons/mod/workshop/workshop.module';
|
||||
import { ADDON_MOD_WORKSHOP_SERVICES } from '@addons/mod/workshop/workshop.module';
|
||||
import { ADDON_NOTES_SERVICES } from '@addons/notes/notes.module';
|
||||
import { ADDON_NOTIFICATIONS_SERVICES } from '@addons/notifications/notifications.module';
|
||||
import { ADDON_PRIVATEFILES_SERVICES } from '@addons/privatefiles/privatefiles.module';
|
||||
|
@ -150,7 +150,7 @@ import { ADDON_REMOTETHEMES_SERVICES } from '@addons/remotethemes/remotethemes.m
|
|||
|
||||
// Import some addon modules that define components, directives and pipes. Only import the important ones.
|
||||
import { AddonModAssignComponentsModule } from '@addons/mod/assign/components/components.module';
|
||||
// @todo import { AddonModWorkshopComponentsModule } from '@addons/mod/workshop/components/components.module';
|
||||
import { AddonModWorkshopComponentsModule } from '@addons/mod/workshop/components/components.module';
|
||||
|
||||
/**
|
||||
* Service to provide functionalities regarding compiling dynamic HTML and Javascript.
|
||||
|
@ -172,7 +172,7 @@ export class CoreCompileProvider {
|
|||
CoreSharedModule, CoreCourseComponentsModule, CoreCoursesComponentsModule, CoreUserComponentsModule,
|
||||
CoreCourseDirectivesModule, CoreQuestionComponentsModule, AddonModAssignComponentsModule,
|
||||
CoreBlockComponentsModule, CoreEditorComponentsModule, CoreSearchComponentsModule, CoreSitePluginsDirectivesModule,
|
||||
// @todo AddonModWorkshopComponentsModule,
|
||||
AddonModWorkshopComponentsModule,
|
||||
];
|
||||
|
||||
constructor(protected injector: Injector, compilerFactory: JitCompilerFactory) {
|
||||
|
@ -308,7 +308,7 @@ export class CoreCompileProvider {
|
|||
...ADDON_MOD_SURVEY_SERVICES,
|
||||
...ADDON_MOD_URL_SERVICES,
|
||||
...ADDON_MOD_WIKI_SERVICES,
|
||||
// @todo ...ADDON_MOD_WORKSHOP_SERVICES,
|
||||
...ADDON_MOD_WORKSHOP_SERVICES,
|
||||
...ADDON_NOTES_SERVICES,
|
||||
...ADDON_NOTIFICATIONS_SERVICES,
|
||||
...ADDON_PRIVATEFILES_SERVICES,
|
||||
|
|
|
@ -1158,12 +1158,12 @@ export class CoreUtilsProvider {
|
|||
* @param keyPrefix Key prefix if neededs to delete it.
|
||||
* @return Object.
|
||||
*/
|
||||
objectToKeyValueMap(
|
||||
objectToKeyValueMap<T = unknown>(
|
||||
objects: Record<string, unknown>[],
|
||||
keyName: string,
|
||||
valueName: string,
|
||||
keyPrefix?: string,
|
||||
): {[name: string]: unknown} | undefined {
|
||||
): {[name: string]: T} | undefined {
|
||||
if (!objects) {
|
||||
return;
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue