diff --git a/src/addon/competency/competency.module.ts b/src/addon/competency/competency.module.ts new file mode 100644 index 000000000..a2a7b5dac --- /dev/null +++ b/src/addon/competency/competency.module.ts @@ -0,0 +1,68 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { AddonCompetencyProvider } from './providers/competency'; +import { AddonCompetencyHelperProvider } from './providers/helper'; +import { AddonCompetencyCourseOptionHandler } from './providers/course-option-handler'; +import { AddonCompetencyMainMenuHandler } from './providers/mainmenu-handler'; +import { AddonCompetencyUserHandler } from './providers/user-handler'; +import { AddonCompetencyComponentsModule } from './components/components.module'; +import { CoreCourseProvider } from '../../core/course/providers/course'; +import { CoreCourseOptionsDelegate } from '../../core/course/providers/options-delegate'; +import { CoreMainMenuDelegate } from '../../core/mainmenu/providers/delegate'; +import { CoreUserDelegate } from '../../core/user/providers/user-delegate'; +import { CoreUserProvider } from '../../core/user/providers/user'; +import { CoreEventsProvider } from '../../providers/events'; +import { CoreSitesProvider } from '../../providers/sites'; +import { CoreCoursesProvider } from '../../core/courses/providers/courses'; + +@NgModule({ + declarations: [ + ], + imports: [ + AddonCompetencyComponentsModule + ], + providers: [ + AddonCompetencyProvider, + AddonCompetencyHelperProvider, + AddonCompetencyCourseOptionHandler, + AddonCompetencyMainMenuHandler, + AddonCompetencyUserHandler + ] +}) +export class AddonCompetencyModule { + constructor(mainMenuDelegate: CoreMainMenuDelegate, mainMenuHandler: AddonCompetencyMainMenuHandler, + courseOptionsDelegate: CoreCourseOptionsDelegate, courseOptionHandler: AddonCompetencyCourseOptionHandler, + userDelegate: CoreUserDelegate, userHandler: AddonCompetencyUserHandler, + eventsProvider: CoreEventsProvider, sitesProvider: CoreSitesProvider) { + + mainMenuDelegate.registerHandler(mainMenuHandler); + courseOptionsDelegate.registerHandler(courseOptionHandler); + userDelegate.registerHandler(userHandler); + + eventsProvider.on(CoreEventsProvider.LOGOUT, () => { + courseOptionHandler.clearCoursesNavCache(); + userHandler.clearUsersNavCache(); + }, sitesProvider.getCurrentSiteId()); + + eventsProvider.on(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED, () => { + courseOptionHandler.clearCoursesNavCache(); + }, sitesProvider.getCurrentSiteId()); + + eventsProvider.on(CoreUserProvider.PROFILE_REFRESHED, () => { + userHandler.clearUsersNavCache(); + }, sitesProvider.getCurrentSiteId()); + } +} diff --git a/src/addon/competency/components/components.module.ts b/src/addon/competency/components/components.module.ts new file mode 100644 index 000000000..651b2a9aa --- /dev/null +++ b/src/addon/competency/components/components.module.ts @@ -0,0 +1,45 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../components/components.module'; +import { CoreDirectivesModule } from '../../../directives/directives.module'; +import { CorePipesModule } from '../../../pipes/pipes.module'; +import { AddonCompetencyCourseComponent } from './course/course'; + +@NgModule({ + declarations: [ + AddonCompetencyCourseComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule + ], + providers: [ + ], + exports: [ + AddonCompetencyCourseComponent + ], + entryComponents: [ + AddonCompetencyCourseComponent + ] +}) +export class AddonCompetencyComponentsModule {} diff --git a/src/addon/competency/components/course/course.html b/src/addon/competency/components/course/course.html new file mode 100644 index 000000000..c1344b369 --- /dev/null +++ b/src/addon/competency/components/course/course.html @@ -0,0 +1,73 @@ + + + + + + + + {{ 'addon.competency.coursecompetencyratingsarepushedtouserplans' | translate }} + + + {{ 'addon.competency.coursecompetencyratingsarenotpushedtouserplans' | translate }} + + + {{ 'addon.competency.progress' | translate }}: + {{ 'addon.competency.xcompetenciesproficientoutofyincourse' | translate:{$a: {x: competencies.statistics.proficientcompetencycount, y: competencies.statistics.competencycount} } }} ({{ competencies.statistics.proficientcompetencypercentageformatted }}%) + + + + {{ 'addon.competency.competenciesmostoftennotproficientincourse' | translate }}: +

+ + {{ comp.shortname }} - {{ comp.idnumber }} + +

+
+
+ +

{{ 'addon.competency.competencies' | translate }}

+ + + + + + + + +

+
+
+ + +
+ + + {{competency.competency.shortname}} {{competency.competency.idnumber}} + {{ competency.usercompetencycourse.gradename }}
+
+ +
+ +
+
+ {{ 'addon.competency.path' | translate }}: + {{ competency.comppath.framework.name }} + +  / {{ ancestor.name }} + +
+
+ {{ 'addon.competency.activities' | translate }}: + + {{ 'addon.competency.noactivities' | translate }} + + + + + +
+
+
+
+
+
diff --git a/src/addon/competency/components/course/course.ts b/src/addon/competency/components/course/course.ts new file mode 100644 index 000000000..955462760 --- /dev/null +++ b/src/addon/competency/components/course/course.ts @@ -0,0 +1,112 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, ViewChild, Input, Optional } from '@angular/core'; +import { Content, NavController } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '../../../../providers/app'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { AddonCompetencyProvider } from '../../providers/competency'; +import { AddonCompetencyHelperProvider } from '../../providers/helper'; + +/** + * Component that displays the competencies of a course. + */ +@Component({ + selector: 'addon-competency-course', + templateUrl: 'course.html', +}) +export class AddonCompetencyCourseComponent { + @ViewChild(Content) content: Content; + + @Input() courseId: number; + @Input() userId: number; + + competenciesLoaded = false; + competencies: any; + user: any; + + constructor(private navCtrl: NavController, private translate: TranslateService, + private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, + private competencyProvider: AddonCompetencyProvider, private helperProvider: AddonCompetencyHelperProvider) { + } + + /** + * View loaded. + */ + ngOnInit(): void { + this.fetchCourseCompetencies().finally(() => { + this.competenciesLoaded = true; + }); + } + + /** + * Fetches the competencies and updates the view. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchCourseCompetencies(): Promise { + return this.competencyProvider.getCourseCompetencies(this.courseId, this.userId).then((competencies) => { + this.competencies = competencies; + + // Get the user profile image. + this.helperProvider.getProfile(this.userId).then((user) => { + this.user = user; + }); + }, (message) => { + if (message) { + this.domUtils.showErrorModal(message); + } else { + this.domUtils.showErrorModal('Error getting course competencies data.'); + } + + return Promise.reject(null); + }); + } + + /** + * Opens a competency. + * + * @param {number} competencyId + */ + openCompetency(competencyId: number): void { + if (this.appProvider.isWide()) { + this.navCtrl.push('AddonCompetencyCompetenciesPage', {competencyId, courseId: this.courseId, userId: this.userId}); + } else { + this.navCtrl.push('AddonCompetencyCompetencyPage', {competencyId, courseId: this.courseId, userId: this.userId}); + } + } + + /** + * Opens the summary of a competency. + * + * @param {number} competencyId + */ + openCompetencySummary(competencyId: number): void { + this.navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId}); + } + + /** + * Refreshes the competencies. + * + * @param {any} refresher Refresher. + */ + refreshCourseCompetencies(refresher: any): void { + this.competencyProvider.invalidateCourseCompetencies(this.courseId, this.userId).finally(() => { + this.fetchCourseCompetencies().finally(() => { + refresher.complete(); + }); + }); + } +} diff --git a/src/addon/competency/lang/en.json b/src/addon/competency/lang/en.json new file mode 100644 index 000000000..a36bacbd7 --- /dev/null +++ b/src/addon/competency/lang/en.json @@ -0,0 +1,47 @@ +{ + "activities": "Activities", + "competencies": "Competencies", + "competenciesmostoftennotproficientincourse": "Competencies most often not proficient in this course", + "coursecompetencies": "Course competencies", + "coursecompetencyratingsarenotpushedtouserplans": "Competency ratings in this course do not affect learning plans.", + "coursecompetencyratingsarepushedtouserplans": "Competency ratings in this course are updated immediately in learning plans.", + "crossreferencedcompetencies": "Cross-referenced competencies", + "duedate": "Due date", + "errornocompetenciesfound": "No competencies found", + "evidence": "Evidence", + "evidence_competencyrule": "The rule of the competency was met.", + "evidence_coursecompleted": "The course '{{$a}}' was completed.", + "evidence_coursemodulecompleted": "The activity '{{$a}}' was completed.", + "evidence_courserestored": "The rating was restored along with the course '{{$a}}'.", + "evidence_evidenceofpriorlearninglinked": "The evidence of prior learning '{{$a}}' was linked.", + "evidence_evidenceofpriorlearningunlinked": "The evidence of prior learning '{{$a}}' was unlinked.", + "evidence_manualoverride": "The competency rating was manually set.", + "evidence_manualoverrideincourse": "The competency rating was manually set in the course '{{$a}}'.", + "evidence_manualoverrideinplan": "The competency rating was manually set in the learning plan '{{$a}}'.", + "learningplancompetencies": "Learning plan competencies", + "learningplans": "Learning plans", + "myplans": "My learning plans", + "noactivities": "No activities", + "nocompetencies": "No competencies", + "nocrossreferencedcompetencies": "No other competencies have been cross-referenced to this competency.", + "noevidence": "No evidence", + "noplanswerecreated": "No learning plans were created.", + "path": "Path:", + "planstatusactive": "Active", + "planstatuscomplete": "Complete", + "planstatusdraft": "Draft", + "planstatusinreview": "In review", + "planstatuswaitingforreview": "Waiting for review", + "proficient": "Proficient", + "progress": "Progress", + "rating": "Rating", + "reviewstatus": "Review status", + "status": "Status", + "template": "Learning plan template", + "usercompetencystatus_idle": "Idle", + "usercompetencystatus_inreview": "In review", + "usercompetencystatus_waitingforreview": "Waiting for review", + "userplans": "Learning plans", + "xcompetenciesproficientoutofy": "{{$a.x}} out of {{$a.y}} competencies are proficient", + "xcompetenciesproficientoutofyincourse": "You are proficient in {{$a.x}} out of {{$a.y}} competencies in this course." +} \ No newline at end of file diff --git a/src/addon/competency/pages/competencies/competencies.html b/src/addon/competency/pages/competencies/competencies.html new file mode 100644 index 000000000..158f421c1 --- /dev/null +++ b/src/addon/competency/pages/competencies/competencies.html @@ -0,0 +1,21 @@ + + + {{ title }} + + + + + + + + + + + {{ competency.competency.shortname }} {{competency.competency.idnumber}} + {{ competency.usercompetency.gradename }} + {{ competency.usercompetencycourse.gradename }} + + + + + \ No newline at end of file diff --git a/src/addon/competency/pages/competencies/competencies.module.ts b/src/addon/competency/pages/competencies/competencies.module.ts new file mode 100644 index 000000000..6effd59fd --- /dev/null +++ b/src/addon/competency/pages/competencies/competencies.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CorePipesModule } from '../../../../pipes/pipes.module'; +import { AddonCompetencyCompetenciesPage } from './competencies'; + +@NgModule({ + declarations: [ + AddonCompetencyCompetenciesPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonCompetencyCompetenciesPage), + TranslateModule.forChild() + ], +}) +export class AddonCompetencyCompetenciesPageModule {} diff --git a/src/addon/competency/pages/competencies/competencies.ts b/src/addon/competency/pages/competencies/competencies.ts new file mode 100644 index 000000000..b016b349f --- /dev/null +++ b/src/addon/competency/pages/competencies/competencies.ts @@ -0,0 +1,143 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, ViewChild } from '@angular/core'; +import { IonicPage, NavController, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreSplitViewComponent } from '../../../../components/split-view/split-view'; +import { AddonCompetencyProvider } from '../../providers/competency'; + +/** + * Page that displays the list of competencies of a learning plan. + */ +@IonicPage({ segment: 'addon-competency-competencies' }) +@Component({ + selector: 'page-addon-competency-competencies', + templateUrl: 'competencies.html', +}) +export class AddonCompetencyCompetenciesPage { + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + + protected planId: number; + protected courseId: number; + protected competencyId: number; + protected userId: number; + + competenciesLoaded = false; + competencies = []; + title: string; + + constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, + private domUtils: CoreDomUtilsProvider, private competencyProvider: AddonCompetencyProvider) { + this.planId = navParams.get('planId'); + this.courseId = navParams.get('courseId'); + this.competencyId = navParams.get('competencyId'); + this.userId = navParams.get('userId'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + if (this.competencyId) { + // There is a competency to load. + this.openCompetency(this.competencyId); + } + + this.fetchCompetencies().then(() => { + if (!this.competencyId && this.splitviewCtrl.isOn() && this.competencies.length > 0) { + // Take first and load it. + this.openCompetency(this.competencies[0].id); + } + }).finally(() => { + this.competenciesLoaded = true; + }); + } + + /** + * Fetches the competencies and updates the view. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchCompetencies(): Promise { + let promise; + + if (this.planId) { + promise = this.competencyProvider.getLearningPlan(this.planId); + } else if (this.courseId) { + promise = this.competencyProvider.getCourseCompetencies(this.courseId, this.userId); + } else { + promise = Promise.reject(null); + } + + return promise.then((response) => { + if (response.competencycount <= 0) { + return Promise.reject(this.translate.instant('addon.competency.errornocompetenciesfound')); + } + + if (this.planId) { + this.title = response.plan.name; + this.userId = response.plan.userid; + } else { + this.title = this.translate.instant('addon.competency.coursecompetencies'); + } + this.competencies = response.competencies; + }).catch((message) => { + if (message) { + this.domUtils.showErrorModal(message); + } else { + this.domUtils.showErrorModal('Error getting competencies data.'); + } + + return Promise.reject(null); + }); + } + + /** + * Opens a competency. + * + * @param {number} competencyId + */ + openCompetency(competencyId: number): void { + this.competencyId = competencyId; + let params; + if (this.planId) { + params = {competencyId, planId: this.planId}; + } else { + params = {competencyId, courseId: this.courseId, userId: this.userId}; + } + this.splitviewCtrl.push('AddonCompetencyCompetencyPage', params); + } + + /** + * Refreshes the competencies. + * + * @param {any} refresher Refresher. + */ + refreshCompetencies(refresher: any): void { + let promise; + if (this.planId) { + promise = this.competencyProvider.invalidateLearningPlan(this.planId); + } else { + promise = this.competencyProvider.invalidateCourseCompetencies(this.courseId, this.userId); + } + + return promise.finally(() => { + this.fetchCompetencies().finally(() => { + refresher.complete(); + }); + }); + } +} diff --git a/src/addon/competency/pages/competency/competency.html b/src/addon/competency/pages/competency/competency.html new file mode 100644 index 000000000..0e01be423 --- /dev/null +++ b/src/addon/competency/pages/competency/competency.html @@ -0,0 +1,95 @@ + + + {{ competency.competency.competency.shortname }} {{ competency.competency.competency.idnumber }} + + + + + + + + + + + + + + + +

+
+
+ + + + + + + {{ 'addon.competency.path' | translate }}: + {{ competency.competency.comppath.framework.name }} + +  / {{ ancestor.name }} + + + + {{ 'addon.competency.crossreferencedcompetencies' | translate }}: +
{{ 'addon.competency.nocrossreferencedcompetencies' | translate }}
+ +
+ + {{ 'addon.competency.activities' | translate }}: + + {{ 'addon.competency.noactivities' | translate }} + + + + + + + + {{ 'addon.competency.reviewstatus' | translate }}: + {{ competency.usercompetency.statusname }} + + + {{ 'addon.competency.proficient' | translate }}: + + {{ 'core.yes' | translate }} + + + {{ 'core.no' | translate }} + + + + {{ 'addon.competency.rating' | translate }}: + {{ competency.usercompetency.gradename }} + +
+ +
+

{{ 'addon.competency.evidence' | translate }}

+

+ {{ 'addon.competency.noevidence' | translate }} +

+ + + + + +

{{ evidence.actionuser.fullname }}

+

{{ evidence.timemodified | coreToLocaleString }}

+
+ +

{{ evidence.gradename }}

+

{{ evidence.description }}

+
+
+
+
+
+
diff --git a/src/addon/competency/pages/competency/competency.module.ts b/src/addon/competency/pages/competency/competency.module.ts new file mode 100644 index 000000000..e188fc222 --- /dev/null +++ b/src/addon/competency/pages/competency/competency.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CorePipesModule } from '../../../../pipes/pipes.module'; +import { AddonCompetencyCompetencyPage } from './competency'; + +@NgModule({ + declarations: [ + AddonCompetencyCompetencyPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonCompetencyCompetencyPage), + TranslateModule.forChild() + ], +}) +export class AddonCompetencyCompetencyPageModule {} diff --git a/src/addon/competency/pages/competency/competency.ts b/src/addon/competency/pages/competency/competency.ts new file mode 100644 index 000000000..e8b2ff2f0 --- /dev/null +++ b/src/addon/competency/pages/competency/competency.ts @@ -0,0 +1,184 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Optional } from '@angular/core'; +import { IonicPage, NavController, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreSitesProvider } from '../../../../providers/sites'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreSplitViewComponent } from '../../../../components/split-view/split-view'; +import { AddonCompetencyProvider } from '../../providers/competency'; + +/** + * Page that displays a learning plan. + */ +@IonicPage({ segment: 'addon-competency-competency' }) +@Component({ + selector: 'page-addon-competency-competency', + templateUrl: 'competency.html', +}) +export class AddonCompetencyCompetencyPage { + competencyLoaded = false; + competencyId: number; + planId: number; + courseId: number; + userId: number; + planStatus: number; + coursemodules: any; + user: any; + competency: any; + + constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, + private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, + @Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider) { + this.competencyId = navParams.get('competencyId'); + this.planId = navParams.get('planId'); + this.courseId = navParams.get('courseId'); + this.userId = navParams.get('userId'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchCompetency().then(() => { + if (this.planId) { + this.competencyProvider.logCompetencyInPlanView(this.planId, this.competencyId, this.planStatus, this.userId); + } else { + this.competencyProvider.logCompetencyInCourseView(this.courseId, this.competencyId, this.userId); + } + }).finally(() => { + this.competencyLoaded = true; + }); + } + + /** + * Fetches the competency and updates the view. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchCompetency(): Promise { + let promise; + if (this.planId) { + this.planStatus = null; + promise = this.competencyProvider.getCompetencyInPlan(this.planId, this.competencyId); + } else if (this.courseId) { + promise = this.competencyProvider.getCompetencyInCourse(this.courseId, this.competencyId, this.userId); + } else { + promise = Promise.reject(null); + } + + return promise.then((competency) => { + this.competency = competency.usercompetencysummary; + + if (this.planId) { + this.planStatus = competency.plan.status; + this.competency.usercompetency.statusname = this.getStatusName(this.competency.usercompetency.status); + } else { + this.competency.usercompetency = this.competency.usercompetencycourse; + this.coursemodules = competency.coursemodules; + } + + if (this.competency.user.id != this.sitesProvider.getCurrentSiteUserId()) { + this.competency.user.profileimageurl = this.competency.user.profileimageurl || true; + + // Get the user profile image from the returned object. + this.user = this.competency.user; + } + + this.competency.evidence.forEach((evidence) => { + if (evidence.descidentifier) { + const key = 'addon.competency.' + evidence.descidentifier; + evidence.description = this.translate.instant(key, {$a: evidence.desca}); + } + }); + }, (message) => { + if (message) { + this.domUtils.showErrorModal(message); + } else { + this.domUtils.showErrorModal('Error getting competency data.'); + } + + return Promise.reject(null); + }); + } + + /** + * Convenience function to get the review status name translated. + * + * @param {number} status + * @return {any} + */ + protected getStatusName(status: number): any { + let statusTranslateName; + switch (status) { + case AddonCompetencyProvider.REVIEW_STATUS_IDLE: + statusTranslateName = 'idle'; + break; + case AddonCompetencyProvider.REVIEW_STATUS_IN_REVIEW: + statusTranslateName = 'inreview'; + break; + case AddonCompetencyProvider.REVIEW_STATUS_WAITING_FOR_REVIEW: + statusTranslateName = 'waitingforreview'; + break; + default: + // We can use the current status name. + return status; + } + + return this.translate.instant('addon.competency.usercompetencystatus_' + statusTranslateName); + } + + /** + * Refreshes the competency. + * + * @param {any} refresher Refresher. + */ + refreshCompetency(refresher: any): void { + let promise; + if (this.planId) { + promise = this.competencyProvider.invalidateCompetencyInPlan(this.planId, this.competencyId); + } else { + promise = this.competencyProvider.invalidateCompetencyInCourse(this.courseId, this.competencyId); + } + + return promise.finally(() => { + this.fetchCompetency().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Opens the summary of a competency. + * + * @param {number} competencyId + */ + openCompetencySummary(competencyId: number): void { + // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav. + const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; + navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId}); + } + + /** + * Opens the profile of a user. + * + * @param {number} userId + */ + openUserProfile(userId: number): void { + // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav. + const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; + navCtrl.push('CoreUserProfilePage', {userId, courseId: this.courseId}); + } +} diff --git a/src/addon/competency/pages/competencysummary/competencysummary.html b/src/addon/competency/pages/competencysummary/competencysummary.html new file mode 100644 index 000000000..5e93f372d --- /dev/null +++ b/src/addon/competency/pages/competencysummary/competencysummary.html @@ -0,0 +1,24 @@ + + + {{ competency.competency.shortname }} {{ competency.competency.idnumber }} + + + + + + + + + + + + + {{ 'addon.competency.path' | translate }}: + {{ competency.comppath.framework.name }} + +  / {{ ancestor.name }} + + + + + diff --git a/src/addon/competency/pages/competencysummary/competencysummary.module.ts b/src/addon/competency/pages/competencysummary/competencysummary.module.ts new file mode 100644 index 000000000..59563b5ff --- /dev/null +++ b/src/addon/competency/pages/competencysummary/competencysummary.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CorePipesModule } from '../../../../pipes/pipes.module'; +import { AddonCompetencyCompetencySummaryPage } from './competencysummary'; + +@NgModule({ + declarations: [ + AddonCompetencyCompetencySummaryPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonCompetencyCompetencySummaryPage), + TranslateModule.forChild() + ], +}) +export class AddonCompetencyCompetencySummaryPageModule {} diff --git a/src/addon/competency/pages/competencysummary/competencysummary.ts b/src/addon/competency/pages/competencysummary/competencysummary.ts new file mode 100644 index 000000000..f75247f31 --- /dev/null +++ b/src/addon/competency/pages/competencysummary/competencysummary.ts @@ -0,0 +1,94 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Optional } from '@angular/core'; +import { IonicPage, NavController, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreSplitViewComponent } from '../../../../components/split-view/split-view'; +import { AddonCompetencyProvider } from '../../providers/competency'; + +/** + * Page that displays a learning plan. + */ +@IonicPage({ segment: 'addon-competency-competency-summary' }) +@Component({ + selector: 'page-addon-competency-competency-summary', + templateUrl: 'competencysummary.html', +}) +export class AddonCompetencyCompetencySummaryPage { + competencyLoaded = false; + competencyId: number; + competency: any; + + constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, + private domUtils: CoreDomUtilsProvider, @Optional() private svComponent: CoreSplitViewComponent, + private competencyProvider: AddonCompetencyProvider) { + this.competencyId = navParams.get('competencyId'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchCompetency().then(() => { + this.competencyProvider.logCompetencyView(this.competencyId); + }).finally(() => { + this.competencyLoaded = true; + }); + } + + /** + * Fetches the competency summary and updates the view. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchCompetency(): Promise { + return this.competencyProvider.getCompetencySummary(this.competencyId).then((competency) => { + this.competency = competency; + }, (message) => { + if (message) { + this.domUtils.showErrorModal(message); + } else { + this.domUtils.showErrorModal('Error getting competency summary data.'); + } + + return Promise.reject(null); + }); + } + + /** + * Refreshes the competency summary. + * + * @param {any} refresher Refresher. + */ + refreshCompetency(refresher: any): void { + this.competencyProvider.invalidateCompetencySummary(this.competencyId).finally(() => { + this.fetchCompetency().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Opens the summary of a competency. + * + * @param {number} competencyId + */ + openCompetencySummary(competencyId: number): void { + // Decide which navCtrl to use. If this page is inside a split view, use the split view's master nav. + const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; + navCtrl.push('AddonCompetencyCompetencySummaryPage', {competencyId}); + } +} diff --git a/src/addon/competency/pages/coursecompetencies/coursecompetencies.html b/src/addon/competency/pages/coursecompetencies/coursecompetencies.html new file mode 100644 index 000000000..e20ac1987 --- /dev/null +++ b/src/addon/competency/pages/coursecompetencies/coursecompetencies.html @@ -0,0 +1,6 @@ + + + {{ 'addon.competency.coursecompetencies' | translate }} + + + \ No newline at end of file diff --git a/src/addon/competency/pages/coursecompetencies/coursecompetencies.module.ts b/src/addon/competency/pages/coursecompetencies/coursecompetencies.module.ts new file mode 100644 index 000000000..277d005bf --- /dev/null +++ b/src/addon/competency/pages/coursecompetencies/coursecompetencies.module.ts @@ -0,0 +1,37 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CorePipesModule } from '../../../../pipes/pipes.module'; +import { AddonCompetencyComponentsModule } from '../../components/components.module'; +import { AddonCompetencyCourseCompetenciesPage } from './coursecompetencies'; + +@NgModule({ + declarations: [ + AddonCompetencyCourseCompetenciesPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonCompetencyCourseCompetenciesPage), + TranslateModule.forChild(), + AddonCompetencyComponentsModule + ], +}) +export class AddonCompetencyCourseCompetenciesPageModule {} diff --git a/src/addon/competency/pages/coursecompetencies/coursecompetencies.ts b/src/addon/competency/pages/coursecompetencies/coursecompetencies.ts new file mode 100644 index 000000000..b93dd5577 --- /dev/null +++ b/src/addon/competency/pages/coursecompetencies/coursecompetencies.ts @@ -0,0 +1,42 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, ViewChild } from '@angular/core'; +import { IonicPage, NavController, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '../../../../providers/app'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { AddonCompetencyProvider } from '../../providers/competency'; +import { AddonCompetencyHelperProvider } from '../../providers/helper'; + +/** + * Page that displays the list of competencies of a course. + */ +@IonicPage({ segment: 'addon-competency-coursecompetencies' }) +@Component({ + selector: 'page-addon-competency-coursecompetencies', + templateUrl: 'coursecompetencies.html', +}) +export class AddonCompetencyCourseCompetenciesPage { + + protected courseId: number; + protected userId: number; + + constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, + private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, + private competencyProvider: AddonCompetencyProvider, private helperProvider: AddonCompetencyHelperProvider) { + this.courseId = navParams.get('courseId'); + this.userId = navParams.get('userId'); + } +} diff --git a/src/addon/competency/pages/plan/plan.html b/src/addon/competency/pages/plan/plan.html new file mode 100644 index 000000000..74b7756c1 --- /dev/null +++ b/src/addon/competency/pages/plan/plan.html @@ -0,0 +1,56 @@ + + + {{plan.plan.name}} + + + + + + + + + + + + + + + +

+
+
+ + + + {{ 'addon.competency.status' | translate }}: + {{ plan.plan.statusname }} + + + {{ 'addon.competency.duedate' | translate }}: + {{ plan.plan.duedate | coreToLocaleString }} + + + {{ 'addon.competency.template' | translate }}: + {{ plan.plan.template.shortname }} + + + {{ 'addon.competency.progress' | translate }}: + {{ 'addon.competency.xcompetenciesproficientoutofy' | translate: {$a: {x: plan.proficientcompetencycount, y: plan.competencycount} } }} + + + + + + {{ 'addon.competency.learningplancompetencies' | translate }} + + + {{ 'addon.competency.nocompetencies' | translate }} + + + {{competency.competency.shortname}} {{competency.competency.idnumber}} + {{ competency.usercompetency.gradename }} + + + +
+
diff --git a/src/addon/competency/pages/plan/plan.module.ts b/src/addon/competency/pages/plan/plan.module.ts new file mode 100644 index 000000000..cf22ce9b7 --- /dev/null +++ b/src/addon/competency/pages/plan/plan.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CorePipesModule } from '../../../../pipes/pipes.module'; +import { AddonCompetencyPlanPage } from './plan'; + +@NgModule({ + declarations: [ + AddonCompetencyPlanPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonCompetencyPlanPage), + TranslateModule.forChild() + ], +}) +export class AddonCompetencyPlanPageModule {} diff --git a/src/addon/competency/pages/plan/plan.ts b/src/addon/competency/pages/plan/plan.ts new file mode 100644 index 000000000..20fd55fa9 --- /dev/null +++ b/src/addon/competency/pages/plan/plan.ts @@ -0,0 +1,132 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, Optional } from '@angular/core'; +import { IonicPage, NavController, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreAppProvider } from '../../../../providers/app'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreSplitViewComponent } from '../../../../components/split-view/split-view'; +import { AddonCompetencyProvider } from '../../providers/competency'; +import { AddonCompetencyHelperProvider } from '../../providers/helper'; + +/** + * Page that displays a learning plan. + */ +@IonicPage({ segment: 'addon-competency-plan' }) +@Component({ + selector: 'page-addon-competency-plan', + templateUrl: 'plan.html', +}) +export class AddonCompetencyPlanPage { + protected planId: number; + planLoaded = false; + plan: any; + user: any; + + constructor(private navCtrl: NavController, navParams: NavParams, private translate: TranslateService, + private appProvider: CoreAppProvider, private domUtils: CoreDomUtilsProvider, + @Optional() private svComponent: CoreSplitViewComponent, private competencyProvider: AddonCompetencyProvider, + private competencyHelperProvider: AddonCompetencyHelperProvider) { + this.planId = navParams.get('planId'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + this.fetchLearningPlan().finally(() => { + this.planLoaded = true; + }); + } + + /** + * Fetches the learning plan and updates the view. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchLearningPlan(): Promise { + return this.competencyProvider.getLearningPlan(this.planId).then((plan) => { + plan.plan.statusname = this.getStatusName(plan.plan.status); + // Get the user profile image. + this.competencyHelperProvider.getProfile(plan.plan.userid).then((user) => { + this.user = user; + }); + this.plan = plan; + }, (message) => { + if (message) { + this.domUtils.showErrorModal(message); + } else { + this.domUtils.showErrorModal('Error getting learning plan data.'); + } + + return Promise.reject(null); + }); + } + + /** + * Navigates to a particular competency. + * + * @param {number} competencyId + */ + openCompetency(competencyId: number): void { + const navCtrl = this.svComponent ? this.svComponent.getMasterNav() : this.navCtrl; + navCtrl.push('AddonCompetencyCompetenciesPage', {competencyId, planId: this.planId}); + } + + /** + * Convenience function to get the status name translated. + * + * @param {number} status + * @return {any} + */ + protected getStatusName(status: number): any { + let statusTranslateName; + switch (status) { + case AddonCompetencyProvider.STATUS_DRAFT: + statusTranslateName = 'draft'; + break; + case AddonCompetencyProvider.REVIEW_STATUS_IN_REVIEW: + statusTranslateName = 'inreview'; + break; + case AddonCompetencyProvider.REVIEW_STATUS_WAITING_FOR_REVIEW: + statusTranslateName = 'waitingforreview'; + break; + case AddonCompetencyProvider.STATUS_ACTIVE: + statusTranslateName = 'active'; + break; + case AddonCompetencyProvider.STATUS_COMPLETE: + statusTranslateName = 'complete'; + break; + default: + // We can use the current status name. + return status; + } + + return this.translate.instant('addon.competency.planstatus' + statusTranslateName); + } + + /** + * Refreshes the learning plan. + * + * @param {any} refresher Refresher. + */ + refreshLearningPlan(refresher: any): void { + this.competencyProvider.invalidateLearningPlan(this.planId).finally(() => { + this.fetchLearningPlan().finally(() => { + refresher.complete(); + }); + }); + } +} diff --git a/src/addon/competency/pages/planlist/planlist.html b/src/addon/competency/pages/planlist/planlist.html new file mode 100644 index 000000000..120b15ad2 --- /dev/null +++ b/src/addon/competency/pages/planlist/planlist.html @@ -0,0 +1,22 @@ + + + {{ 'addon.competency.userplans' | translate }} + + + + + + + + + + + + +

{{ plan.name }}

+

{{ 'addon.competency.duedate' | translate }}: {{ plan.duedate | coreToLocaleString }}

+
+
+
+
+
\ No newline at end of file diff --git a/src/addon/competency/pages/planlist/planlist.module.ts b/src/addon/competency/pages/planlist/planlist.module.ts new file mode 100644 index 000000000..637461ae8 --- /dev/null +++ b/src/addon/competency/pages/planlist/planlist.module.ts @@ -0,0 +1,35 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { NgModule } from '@angular/core'; +import { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; +import { CorePipesModule } from '../../../../pipes/pipes.module'; +import { AddonCompetencyPlanListPage } from './planlist'; + +@NgModule({ + declarations: [ + AddonCompetencyPlanListPage, + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + CorePipesModule, + IonicPageModule.forChild(AddonCompetencyPlanListPage), + TranslateModule.forChild() + ], +}) +export class AddonCompetencyPlanListPageModule {} diff --git a/src/addon/competency/pages/planlist/planlist.ts b/src/addon/competency/pages/planlist/planlist.ts new file mode 100644 index 000000000..bc77fd229 --- /dev/null +++ b/src/addon/competency/pages/planlist/planlist.ts @@ -0,0 +1,103 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Component, ViewChild } from '@angular/core'; +import { IonicPage, NavParams } from 'ionic-angular'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreSplitViewComponent } from '../../../../components/split-view/split-view'; +import { AddonCompetencyProvider } from '../../providers/competency'; + +/** + * Page that displays the list of learning plans. + */ +@IonicPage({ segment: 'addon-competency-planlist' }) +@Component({ + selector: 'page-addon-competency-planlist', + templateUrl: 'planlist.html', +}) +export class AddonCompetencyPlanListPage { + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + + protected userId: number; + protected planId: number; + plansLoaded = false; + plans = []; + + constructor(navParams: NavParams, private translate: TranslateService, private domUtils: CoreDomUtilsProvider, + private competencyProvider: AddonCompetencyProvider) { + this.userId = navParams.get('userId'); + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + if (this.planId) { + // There is a learning plan to load. + this.openPlan(this.planId); + } + + this.fetchLearningPlans().then(() => { + if (!this.planId && this.splitviewCtrl.isOn() && this.plans.length > 0) { + // Take first and load it. + this.openPlan(this.plans[0].id); + } + }).finally(() => { + this.plansLoaded = true; + }); + } + + /** + * Fetches the learning plans and updates the view. + * + * @return {Promise} Promise resolved when done. + */ + protected fetchLearningPlans(): Promise { + return this.competencyProvider.getLearningPlans(this.userId).then((plans) => { + this.plans = plans; + }).catch((message) => { + if (message) { + this.domUtils.showErrorModal(message); + } else { + this.domUtils.showErrorModal('Error getting learning plans data.'); + } + + return Promise.reject(null); + }); + } + + /** + * Refreshes the learning plans. + * + * @param {any} refresher Refresher. + */ + refreshLearningPlans(refresher: any): void { + this.competencyProvider.invalidateLearningPlans(this.userId).finally(() => { + this.fetchLearningPlans().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Opens a learning plan. + * + * @param {number} planId Learning plan to load. + */ + openPlan(planId: number): void { + this.planId = planId; + this.splitviewCtrl.push('AddonCompetencyPlanPage', { planId }); + } +} diff --git a/src/addon/competency/providers/competency.ts b/src/addon/competency/providers/competency.ts new file mode 100644 index 000000000..c8112b79a --- /dev/null +++ b/src/addon/competency/providers/competency.ts @@ -0,0 +1,572 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreLoggerProvider } from '../../../providers/logger'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreUserProvider } from '../../../core/user/providers/user'; + +/** + * Service to handle caompetency learning plans. + */ +@Injectable() +export class AddonCompetencyProvider { + + static STATUS_DRAFT = 0; + static STATUS_ACTIVE = 1; + static STATUS_COMPLETE = 2; + static STATUS_WAITIN_GFOR_REVIEW = 3; + static STATUS_IN_REVIEW = 4; + static REVIEW_STATUS_IDLE = 0; + static REVIEW_STATUS_WAITING_FOR_REVIEW = 1; + static REVIEW_STATUS_IN_REVIEW = 2; + protected ROOT_CACHE_KEY = 'mmaCompetency:'; + + protected logger; + + constructor(loggerProvider: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) { + this.logger = loggerProvider.getInstance('AddonCompetencyProvider'); + } + + /** + * Get cache key for user learning plans data WS calls. + * + * @param {number} userId User ID. + * @return {string} Cache key. + */ + protected getLearningPlansCacheKey(userId: number): string { + return this.ROOT_CACHE_KEY + 'userplans:' + userId; + } + + /** + * Get cache key for learning plan data WS calls. + * + * @param {number} planId Plan ID. + * @return {string} Cache key. + */ + protected getLearningPlanCacheKey(planId: number): string { + return this.ROOT_CACHE_KEY + 'learningplan:' + planId; + } + + /** + * Get cache key for competency in plan data WS calls. + * + * @param {number} planId Plan ID. + * @param {number} competencyId Competency ID. + * @return {string} Cache key. + */ + protected getCompetencyInPlanCacheKey(planId: number, competencyId: number): string { + return this.ROOT_CACHE_KEY + 'plancompetency:' + planId + ':' + competencyId; + } + + /** + * Get cache key for competency in course data WS calls. + * + * @param {number} courseId Course ID. + * @param {number} competencyId Competency ID. + * @param {number} userId User ID. + * @return {string} Cache key. + */ + protected getCompetencyInCourseCacheKey(courseId: number, competencyId: number, userId: number): string { + return this.ROOT_CACHE_KEY + 'coursecompetency:' + userId + ':' + courseId + ':' + competencyId; + } + + /** + * Get cache key for competency summary data WS calls. + * + * @param {number} competencyId Competency ID. + * @param {number} userId User ID. + * @return {string} Cache key. + */ + protected getCompetencySummaryCacheKey(competencyId: number, userId: number): string { + return this.ROOT_CACHE_KEY + 'competencysummary:' + userId + ':' + competencyId; + } + + /** + * Get cache key for course competencies data WS calls. + * + * @param {number} courseId Course ID. + * @return {string} Cache key. + */ + protected getCourseCompetenciesCacheKey(courseId: number): string { + return this.ROOT_CACHE_KEY + 'coursecompetencies:' + courseId; + } + + /** + * Check if competency learning plans WS is available. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} True if competency learning plans WS is available, false otherwise. + */ + isPluginEnabled(siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + if (site.wsAvailable('core_competency_list_course_competencies') && site.wsAvailable('tool_lp_data_for_plans_page')) { + return this.getLearningPlans(0, siteId); + } + + return false; + }); + } + + /** + * Returns whether competencies are enabled. + * + * @param {number} courseId Course ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} competencies if enabled for the given course, false otherwise. + */ + isPluginForCourseEnabled(courseId: number, siteId?: string): Promise { + if (!this.sitesProvider.isLoggedIn()) { + return Promise.resolve(false); + } + + if (!this.isPluginEnabled(siteId)) { + return Promise.resolve(false); + } + + return this.getCourseCompetencies(courseId, 0, siteId).catch(() => { + return false; + }); + } + + /** + * Get plans for a certain user. + * + * @param {number} [userId] ID of the user. If not defined, current user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the plans are retrieved. + */ + getLearningPlans(userId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + this.logger.debug('Get plans for user ' + userId); + + const params = { + userid: userId + }, + preSets = { + cacheKey: this.getLearningPlansCacheKey(userId) + }; + + return site.read('tool_lp_data_for_plans_page', params, preSets).then((response) => { + if (response.plans) { + return response.plans; + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get a certain plan. + * + * @param {number} planId ID of the plan. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the plans are retrieved. + */ + getLearningPlan(planId: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + + this.logger.debug('Get plan ' + planId); + + const params = { + planid: planId + }, + preSets = { + cacheKey: this.getLearningPlanCacheKey(planId) + }; + + return site.read('tool_lp_data_for_plan_page', params, preSets).then((response) => { + if (response.plan) { + return response; + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get a certain competency in a plan. + * + * @param {number} planId ID of the plan. + * @param {number} competencyId ID of the competency. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the plans are retrieved. + */ + getCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + + this.logger.debug('Get competency ' + competencyId + ' in plan ' + planId); + + const params = { + planid: planId, + competencyid: competencyId + }, + preSets = { + cacheKey: this.getCompetencyInPlanCacheKey(planId, competencyId) + }; + + return site.read('tool_lp_data_for_user_competency_summary_in_plan', params, preSets).then((response) => { + if (response.usercompetencysummary) { + return response; + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get a certain competency in a course. + * + * @param {number} courseId ID of the course. + * @param {number} competencyId ID of the competency. + * @param {number} [userId] ID of the user. If not defined, current user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the plans are retrieved. + */ + getCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + this.logger.debug('Get competency ' + competencyId + ' in course ' + courseId); + + const params = { + courseid: courseId, + competencyid: competencyId, + userid: userId + }, + preSets = { + cacheKey: this.getCompetencyInCourseCacheKey(courseId, competencyId, userId) + }; + + return site.read('tool_lp_data_for_user_competency_summary_in_course', params, preSets).then((response) => { + if (response.usercompetencysummary) { + return response; + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get a certain competency summary. + * + * @param {number} competencyId ID of the competency. + * @param {number} [userId] ID of the user. If not defined, current user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the plans are retrieved. + */ + getCompetencySummary(competencyId: number, userId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + this.logger.debug('Get competency ' + competencyId + ' summary for user' + userId); + + const params = { + competencyid: competencyId, + userid: userId + }, + preSets = { + cacheKey: this.getCompetencySummaryCacheKey(competencyId, userId) + }; + + return site.read('tool_lp_data_for_user_competency_summary', params, preSets).then((response) => { + if (response.competency) { + return response.competency; + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get an specific competency summary. + * + * @param {number} courseId ID of the course. + * @param {number} [userId] ID of the user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the course competencies are retrieved. + */ + getCourseCompetencies(courseId: number, userId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + + this.logger.debug('Get course competencies for course ' + courseId); + + const params = { + courseid: courseId + }, + preSets = { + cacheKey: this.getCourseCompetenciesCacheKey(courseId) + }; + + return site.read('tool_lp_data_for_course_competencies_page', params, preSets).then((response) => { + if (response.competencies) { + return response; + } + + return Promise.reject(null); + }); + + }).then((response) => { + + if (!userId || userId == this.sitesProvider.getCurrentSiteUserId()) { + return response; + } + + const promises = response.competencies.map((competency) => + this.getCompetencyInCourse(courseId, competency.competency.id, userId, siteId) + ); + + return Promise.all(promises).then((responses: any[]) => { + responses.forEach((resp, index) => { + response.competencies[index].usercompetencycourse = resp.usercompetencysummary.usercompetencycourse; + }); + + return response; + }); + }); + } + + /** + * Invalidates User Learning Plans data. + * + * @param {number} [userId] ID of the user. If not defined, current user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateLearningPlans(userId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + return site.invalidateWsCacheForKey(this.getLearningPlansCacheKey(userId)); + }); + } + + /** + * Invalidates Learning Plan data. + * + * @param {number} planId ID of the plan. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateLearningPlan(planId: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getLearningPlanCacheKey(planId)); + }); + } + + /** + * Invalidates Competency in Plan data. + * + * @param {number} planId ID of the plan. + * @param {number} competencyId ID of the competency. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateCompetencyInPlan(planId: number, competencyId: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getCompetencyInPlanCacheKey(planId, competencyId)); + }); + } + + /** + * Invalidates Competency in Course data. + * + * @param {number} courseId ID of the course. + * @param {number} competencyId ID of the competency. + * @param {number} [userId] ID of the user. If not defined, current user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateCompetencyInCourse(courseId: number, competencyId: number, userId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + return site.invalidateWsCacheForKey(this.getCompetencyInCourseCacheKey(courseId, competencyId, userId)); + }); + } + + /** + * Invalidates Competency Summary data. + * + * @param {number} competencyId ID of the competency. + * @param {number} [userId] ID of the user. If not defined, current user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateCompetencySummary(competencyId: number, userId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + return site.invalidateWsCacheForKey(this.getCompetencySummaryCacheKey(competencyId, userId)); + }); + } + + /** + * Invalidates Course Competencies data. + * + * @param {number} courseId ID of the course. + * @param {number} [userId] ID of the user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateCourseCompetencies(courseId: number, userId?: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getCourseCompetenciesCacheKey(courseId)); + }).then((response) => { + if (!userId || userId == this.sitesProvider.getCurrentSiteUserId()) { + return; + } + + /* Competencies for other users are fetched with getCompetencyInCourse (and saved in their own cache). + We need to fecth the list of competencies to know which ones to invalidate. We can pass 0 as userId + to getCourseCompetencies, we just need the competency IDs and this way we avid extra WS calls. */ + return this.getCourseCompetencies(courseId, 0, siteId).then((competencies) => { + const promises = competencies.competencies.map((competency) => { + return this.invalidateCompetencyInCourse(courseId, competency.competency.id, userId, siteId); + }); + + return Promise.all(promises); + }); + }); + } + + /** + * Report the competency as being viewed in plan. + * + * @param {number} planId ID of the plan. + * @param {number} competencyId ID of the competency. + * @param {number} planStatus Current plan Status to decide what action should be logged. + * @param {number} [userId] User ID. If not defined, current user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. + */ + logCompetencyInPlanView(planId: number, competencyId: number, planStatus: number, userId?: number, siteId?: string) + : Promise { + if (planId && competencyId) { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + const params = { + planid: planId, + competencyid: competencyId, + userid: userId + }, + preSets = { + typeExpected: 'boolean' + }; + + if (planStatus == AddonCompetencyProvider.STATUS_COMPLETE) { + return site.write('core_competency_user_competency_plan_viewed', params, preSets); + } else { + return site.write('core_competency_user_competency_viewed_in_plan', params, preSets); + } + }); + } + + return Promise.reject(null); + } + + /** + * Report the competency as being viewed in course. + * + * @param {number} courseId ID of the course. + * @param {number} competencyId ID of the competency. + * @param {number} [userId] User ID. If not defined, current user. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. + */ + logCompetencyInCourseView(courseId: number, competencyId: number, userId?: number, siteId?: string): Promise { + if (courseId && competencyId) { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + userId = userId || site.getUserId(); + + const params = { + courseid: courseId, + competencyid: competencyId, + userid: userId + }; + const preSets = { + typeExpected: 'boolean' + }; + + return site.write('core_competency_user_competency_viewed_in_course', params, preSets); + }); + } + + return Promise.reject(null); + } + + /** + * Report the competency as being viewed. + * + * @param {number} competencyId ID of the competency. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the WS call is successful. + */ + logCompetencyView(competencyId: number, siteId?: string): Promise { + if (competencyId) { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + id: competencyId, + }; + const preSets = { + typeExpected: 'boolean' + }; + + return site.write('core_competency_competency_viewed', params, preSets); + }); + } + + return Promise.reject(null); + } +} diff --git a/src/addon/competency/providers/course-option-handler.ts b/src/addon/competency/providers/course-option-handler.ts new file mode 100644 index 000000000..74a37f6d9 --- /dev/null +++ b/src/addon/competency/providers/course-option-handler.ts @@ -0,0 +1,109 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { NavController } from 'ionic-angular'; +import { CoreCourseOptionsHandler, CoreCourseOptionsHandlerData } from '../../../core/course/providers/options-delegate'; +import { CoreCourseProvider } from '../../../core/course/providers/course'; +import { AddonCompetencyCourseComponent } from '../components/course/course'; +import { AddonCompetencyProvider } from '../providers/competency'; + +/** + * Course nav handler. + */ +@Injectable() +export class AddonCompetencyCourseOptionHandler implements CoreCourseOptionsHandler { + name = 'AddonCompetency'; + priority = 700; + + protected coursesNavEnabledCache = {}; + + constructor(private competencyProvider: AddonCompetencyProvider) {} + + /** + * Clear courses nav cache. + */ + clearCoursesNavCache(): void { + this.coursesNavEnabledCache = {}; + } + + /** + * Whether or not the handler is enabled ona site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.competencyProvider.isPluginEnabled(); + } + + /** + * Whether or not the handler is enabled for a certain course. + * + * @param {number} courseId The course ID. + * @param {any} accessData Access type and data. Default, guest, ... + * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return {boolean|Promise} True or promise resolved with true if enabled. + */ + isEnabledForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): boolean | Promise { + if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) { + return false; // Not enabled for guests. + } + + if (navOptions && typeof navOptions.competencies != 'undefined') { + return navOptions.competencies; + } + + if (typeof this.coursesNavEnabledCache[courseId] != 'undefined') { + return this.coursesNavEnabledCache[courseId]; + } + + return this.competencyProvider.isPluginForCourseEnabled(courseId).then((competencies) => { + const enabled = competencies ? !competencies.canmanagecoursecompetencies : false; + this.coursesNavEnabledCache[courseId] = enabled; + + return enabled; + }); + } + + /** + * Returns the data needed to render the handler. + * + * @param {number} courseId The course ID. + * @return {CoreCourseOptionsHandlerData} Data. + */ + getDisplayData(courseId: number): CoreCourseOptionsHandlerData { + return { + title: 'addon.competency.competencies', + class: 'addon-competency-course-handler', + component: AddonCompetencyCourseComponent + }; + } + + /** + * Should invalidate the data to determine if the handler is enabled for a certain course. + * + * @param {number} courseId The course ID. + * @param {any} [navOptions] Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions. + * @return {Promise} Promise resolved when done. + */ + invalidateEnabledForCourse(courseId: number, navOptions?: any, admOptions?: any): Promise { + if (navOptions && typeof navOptions.competencies != 'undefined') { + // No need to invalidate anything. + return Promise.resolve(); + } + + return this.competencyProvider.invalidateCourseCompetencies(courseId); + } +} diff --git a/src/addon/competency/providers/helper.ts b/src/addon/competency/providers/helper.ts new file mode 100644 index 000000000..494958140 --- /dev/null +++ b/src/addon/competency/providers/helper.ts @@ -0,0 +1,46 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreUserProvider } from '../../../core/user/providers/user'; + +/** + * Service that provides some features regarding the user profile. + */ +@Injectable() +export class AddonCompetencyHelperProvider { + + constructor(private sitesProvider: CoreSitesProvider, private userProvider: CoreUserProvider) { + } + + /** + * Convenient helper to get the user profile image. + * + * @param {number} userId User Id + * @return {Promise} User profile Image URL or true if default icon. + */ + getProfile(userId: number): Promise { + if (!userId || userId == this.sitesProvider.getCurrentSiteUserId()) { + return Promise.resolve(false); + } + + // Get the user profile to retrieve the user image. + return this.userProvider.getProfile(userId, null, true).then((user) => { + user.profileimageurl = user.profileimageurl || true; + + return user; + }); + } +} diff --git a/src/addon/competency/providers/mainmenu-handler.ts b/src/addon/competency/providers/mainmenu-handler.ts new file mode 100644 index 000000000..d17ad43c9 --- /dev/null +++ b/src/addon/competency/providers/mainmenu-handler.ts @@ -0,0 +1,60 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { AddonCompetencyProvider } from './competency'; +import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../../core/mainmenu/providers/delegate'; + +/** + * Handler to inject an option into main menu. + */ +@Injectable() +export class AddonCompetencyMainMenuHandler implements CoreMainMenuHandler { + name = 'AddonCompetency'; + priority = 900; + + constructor(private competencyProvider: AddonCompetencyProvider) { } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.competencyProvider.isPluginEnabled().then((enabled) => { + if (!enabled) { + return false; + } + + // Check the user has at least one learn plan available. + return this.competencyProvider.getLearningPlans().then((plans) => { + return plans.length > 0; + }); + }); + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreMainMenuHandlerData} Data needed to render the handler. + */ + getDisplayData(): CoreMainMenuHandlerData { + return { + icon: 'map', + title: 'addon.competency.myplans', + page: 'AddonCompetencyPlanListPage', + class: 'addon-competency-handler' + }; + } +} diff --git a/src/addon/competency/providers/user-handler.ts b/src/addon/competency/providers/user-handler.ts new file mode 100644 index 000000000..fa7f24f2c --- /dev/null +++ b/src/addon/competency/providers/user-handler.ts @@ -0,0 +1,128 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Injectable } from '@angular/core'; +import { CoreUserDelegate, CoreUserProfileHandler, CoreUserProfileHandlerData } from '../../../core/user/providers/user-delegate'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreContentLinksHelperProvider } from '../../../core/contentlinks/providers/helper'; +import { AddonCompetencyProvider } from './competency'; + +/** + * Profile competencies handler. + */ +@Injectable() +export class AddonCompetencyUserHandler implements CoreUserProfileHandler { + name = 'AddonCompetency'; + priority = 900; + type = CoreUserDelegate.TYPE_NEW_PAGE; + participantsNavEnabledCache = {}; + usersNavEnabledCache = {}; + + constructor(private linkHelper: CoreContentLinksHelperProvider, protected sitesProvider: CoreSitesProvider, + private competencyProvider: AddonCompetencyProvider) { + } + + /** + * Clear users nav cache. + */ + clearUsersNavCache(): void { + this.participantsNavEnabledCache = {}; + this.usersNavEnabledCache = {}; + } + + /** + * Whether or not the handler is enabled on a site level. + * @return {boolean|Promise} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean | Promise { + return this.competencyProvider.isPluginEnabled(); + } + + /** + * Check if handler is enabled for this user in this context. + * + * @param {any} user User to check. + * @param {number} courseId Course ID. + * @param {any} [navOptions] Course navigation options for current user. See $mmCourses#getUserNavigationOptions. + * @param {any} [admOptions] Course admin options for current user. See $mmCourses#getUserAdministrationOptions. + * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. + */ + isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { + if (courseId) { + const cacheKey = courseId + '.' + user.id; + + // Link on a user course profile. + if (typeof this.participantsNavEnabledCache[cacheKey] != 'undefined') { + return this.participantsNavEnabledCache[cacheKey]; + } + + return this.competencyProvider.getCourseCompetencies(courseId, user.id).then((response) => { + const enabled = response.competencies.length > 0; + this.participantsNavEnabledCache[cacheKey] = enabled; + + return enabled; + }).catch((message) => { + this.participantsNavEnabledCache[cacheKey] = false; + + return false; + }); + } else { + // Link on a user site profile. + if (typeof this.usersNavEnabledCache[user.id] != 'undefined') { + return this.usersNavEnabledCache[user.id]; + } + + return this.competencyProvider.getLearningPlans(user.id).then((plans) => { + // Check the user has at least one learn plan available. + const enabled = plans.length > 0; + this.usersNavEnabledCache[user.id] = enabled; + + return enabled; + }); + } + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreUserProfileHandlerData} Data needed to render the handler. + */ + getDisplayData(user: any, courseId: number): CoreUserProfileHandlerData { + if (courseId) { + return { + icon: 'ribbon', + title: 'addon.competency.competencies', + class: 'addon-competency-handler', + action: ($event, navCtrl, user, courseId): void => { + event.preventDefault(); + event.stopPropagation(); + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonCompetencyCourseCompetenciesPage', {courseId, userId: user.id}); + } + }; + } else { + return { + icon: 'map', + title: 'addon.competency.learningplans', + class: 'addon-competency-handler', + action: ($event, navCtrl, user, courseId): void => { + event.preventDefault(); + event.stopPropagation(); + // Always use redirect to make it the new history root (to avoid "loops" in history). + this.linkHelper.goInSite(navCtrl, 'AddonCompetencyPlanListPage', {userId: user.id}); + } + }; + } + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 406450a66..3e79d8f56 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -70,6 +70,7 @@ import { CoreCompileModule } from '@core/compile/compile.module'; // Addon modules. import { AddonCalendarModule } from '@addon/calendar/calendar.module'; +import { AddonCompetencyModule } from '../addon/competency/competency.module'; import { AddonUserProfileFieldModule } from '@addon/userprofilefield/userprofilefield.module'; import { AddonFilesModule } from '@addon/files/files.module'; import { AddonModBookModule } from '@addon/mod/book/book.module'; @@ -147,6 +148,7 @@ export const CORE_PROVIDERS: any[] = [ CoreSitePluginsModule, CoreCompileModule, AddonCalendarModule, + AddonCompetencyModule, AddonUserProfileFieldModule, AddonFilesModule, AddonModBookModule,