diff --git a/src/app/app.module.ts b/src/app/app.module.ts index 5da362ffc..919648b2d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -61,6 +61,7 @@ import { CoreCourseModule } from '../core/course/course.module'; import { CoreSiteHomeModule } from '../core/sitehome/sitehome.module'; import { CoreContentLinksModule } from '../core/contentlinks/contentlinks.module'; import { CoreUserModule } from '../core/user/user.module'; +import { CoreGradesModule } from '../core/grades/grades.module'; // Addon modules. import { AddonCalendarModule } from '../addon/calendar/calendar.module'; @@ -102,6 +103,7 @@ export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { CoreSiteHomeModule, CoreContentLinksModule, CoreUserModule, + CoreGradesModule, AddonCalendarModule, AddonUserProfileFieldModule, AddonFilesModule, diff --git a/src/components/split-view/split-view.ts b/src/components/split-view/split-view.ts index 9ddf22670..0ab53cca2 100644 --- a/src/components/split-view/split-view.ts +++ b/src/components/split-view/split-view.ts @@ -143,7 +143,7 @@ export class CoreSplitViewComponent implements OnInit { activateSplitView(): void { const currentView = this.masterNav.getActive(), currentPageName = currentView.component.name; - if (this.masterNav.getPrevious().component.name == this.masterPageName) { + if (this.masterNav.getPrevious() && this.masterNav.getPrevious().component.name == this.masterPageName) { if (currentPageName != this.masterPageName) { // CurrentView is a 'Detail' page remove it from the 'master' nav stack. this.masterNav.pop(); diff --git a/src/core/grades/grades.module.ts b/src/core/grades/grades.module.ts new file mode 100644 index 000000000..5f3b4d752 --- /dev/null +++ b/src/core/grades/grades.module.ts @@ -0,0 +1,36 @@ +// (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 { CoreGradesProvider } from './providers/grades'; +import { CoreGradesHelperProvider } from './providers/helper'; +import { CoreMainMenuDelegate } from '../mainmenu/providers/delegate'; +import { CoreGradesMainMenuHandler } from './providers/mainmenu-handler'; + +@NgModule({ + declarations: [ + ], + imports: [ + ], + providers: [ + CoreGradesProvider, + CoreGradesHelperProvider, + CoreGradesMainMenuHandler + ] +}) +export class CoreGradesModule { + constructor(mainMenuDelegate: CoreMainMenuDelegate, gradesMenuHandler: CoreGradesMainMenuHandler) { + mainMenuDelegate.registerHandler(gradesMenuHandler); + } +} diff --git a/src/core/grades/lang/en.json b/src/core/grades/lang/en.json new file mode 100644 index 000000000..256d5b636 --- /dev/null +++ b/src/core/grades/lang/en.json @@ -0,0 +1,4 @@ +{ + "grades": "Grades", + "nogradesreturned": "No grades returned" +} \ No newline at end of file diff --git a/src/core/grades/pages/courses/courses.html b/src/core/grades/pages/courses/courses.html new file mode 100644 index 000000000..9541c0f75 --- /dev/null +++ b/src/core/grades/pages/courses/courses.html @@ -0,0 +1,23 @@ + + + {{ 'core.grades.grades' | translate }} + + + + + + + + + + + + + +

+ {{grade.grade}} +
+
+
+
+
\ No newline at end of file diff --git a/src/core/grades/pages/courses/courses.module.ts b/src/core/grades/pages/courses/courses.module.ts new file mode 100644 index 000000000..b4c8b834d --- /dev/null +++ b/src/core/grades/pages/courses/courses.module.ts @@ -0,0 +1,33 @@ +// (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 { CoreGradesCoursesPage } from './courses'; +import { CoreComponentsModule } from '../../../../components/components.module'; +import { CoreDirectivesModule } from '../../../../directives/directives.module'; + +@NgModule({ + declarations: [ + CoreGradesCoursesPage + ], + imports: [ + CoreComponentsModule, + CoreDirectivesModule, + IonicPageModule.forChild(CoreGradesCoursesPage), + TranslateModule.forChild() + ], +}) +export class CoreGradesCoursesPageModule {} diff --git a/src/core/grades/pages/courses/courses.scss b/src/core/grades/pages/courses/courses.scss new file mode 100644 index 000000000..e69de29bb diff --git a/src/core/grades/pages/courses/courses.ts b/src/core/grades/pages/courses/courses.ts new file mode 100644 index 000000000..9c9081b4b --- /dev/null +++ b/src/core/grades/pages/courses/courses.ts @@ -0,0 +1,92 @@ +// (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, Content } from 'ionic-angular'; +import { CoreGradesProvider } from '../../providers/grades'; +import { CoreDomUtilsProvider } from '../../../../providers/utils/dom'; +import { CoreSplitViewComponent } from '../../../../components/split-view/split-view'; +import { CoreGradesHelperProvider } from '../../providers/helper'; + +/** + * Page that displays courses grades (main menu option). + */ +@IonicPage({ segment: 'core-grades-courses' }) +@Component({ + selector: 'page-core-grades-courses', + templateUrl: 'courses.html', +}) +export class CoreGradesCoursesPage { + @ViewChild(Content) content: Content; + @ViewChild(CoreSplitViewComponent) splitviewCtrl: CoreSplitViewComponent; + + grades = []; + courseId: number; + userId: number; + gradesLoaded = false; + + constructor(private gradesProvider: CoreGradesProvider, private domUtils: CoreDomUtilsProvider, + private courseHelper: CoreGradesHelperProvider) { + } + + /** + * View loaded. + */ + ionViewDidLoad(): void { + // Get first participants. + this.fetchData().then(() => { + // Add log in Moodle. + return this.gradesProvider.logCoursesGradesView(); + }).finally(() => { + this.gradesLoaded = true; + }); + } + + /** + * Fetch all the data required for the view. + * + * @return {Promise} Resolved when done. + */ + fetchData(): Promise { + return this.gradesProvider.getCoursesGrades().then((grades) => { + return this.courseHelper.getGradesCourseData(grades).then((grades) => { + this.grades = grades; + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'Error loading grades'); + }); + } + + /** + * Refresh data. + * + * @param {any} refresher Refresher. + */ + refreshGrades(refresher: any): void { + this.gradesProvider.invalidateCoursesGradesData().finally(() => { + this.fetchData().finally(() => { + refresher.complete(); + }); + }); + } + + /** + * Navigate to the grades of the selected course. + * @param {number} courseId Course Id where to navigate. + */ + gotoCourseGrades(courseId: number): void { + this.courseId = courseId; + this.splitviewCtrl.push('CoreGradesCoursePage', {courseId: courseId, userId: this.userId, forcephoneview: 1}); + } +} diff --git a/src/core/grades/providers/grades.ts b/src/core/grades/providers/grades.ts new file mode 100644 index 000000000..66b5a79f6 --- /dev/null +++ b/src/core/grades/providers/grades.ts @@ -0,0 +1,113 @@ +// (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 { CoreSite } from '../../../classes/site'; +import { CoreSitesProvider } from '../../../providers/sites'; +import { CoreUtilsProvider } from '../../../providers/utils/utils'; + +/** + * Service to provide grade functionalities. + */ +@Injectable() +export class CoreGradesProvider { + protected ROOT_CACHE_KEY = 'mmGrades:'; + + protected logger; + + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider) { + this.logger = logger.getInstance('CoreGradesProvider'); + } + + /** + * Get cache key for courses grade WS calls. + * + * @return {string} Cache key. + */ + protected getCoursesGradesCacheKey(): string { + return this.ROOT_CACHE_KEY + 'coursesgrades'; + } + + /** + * Get the grades for a certain course. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise to be resolved when the grades are retrieved. + */ + getCoursesGrades(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + this.logger.debug('Get course grades'); + + const preSets = { + cacheKey: this.getCoursesGradesCacheKey() + }; + + return site.read('gradereport_overview_get_course_grades', undefined, preSets).then((data) => { + if (data && data.grades) { + return data.grades; + } + + return Promise.reject(null); + }); + }); + } + + /** + * Invalidates courses grade data WS calls. + * + * @param {string} [siteId] Site id (empty for current site). + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateCoursesGradesData(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getCoursesGradesCacheKey()); + }); + } + + /** + * Returns whether or not the plugin is enabled for a certain site. + * + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Resolve with true if plugin is enabled, false otherwise. + * @since Moodle 3.2 + */ + isCourseGradesEnabled(siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + if (!site.wsAvailable('gradereport_overview_get_course_grades')) { + return false; + } + // Now check that the configurable mygradesurl is pointing to the gradereport_overview plugin. + const url = site.getStoredConfig('mygradesurl') || ''; + + return url.indexOf('/grade/report/overview/') !== -1; + }); + } + + /** + * Log Courses grades view in Moodle. + * + * @param {number} courseId Course ID. + * @return {Promise} Promise resolved when done. + */ + logCoursesGradesView(courseId?: number): Promise { + if (!courseId) { + courseId = this.sitesProvider.getCurrentSiteHomeId(); + } + + return this.sitesProvider.getCurrentSite().write('gradereport_overview_view_grade_report', { + courseid: courseId + }); + } +} diff --git a/src/core/grades/providers/helper.ts b/src/core/grades/providers/helper.ts new file mode 100644 index 000000000..b5cd905c0 --- /dev/null +++ b/src/core/grades/providers/helper.ts @@ -0,0 +1,55 @@ +// (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 { TranslateService } from '@ngx-translate/core'; +import { CoreCoursesProvider } from '../../courses/providers/courses'; + +/** + * Service that provides some features regarding users information. + */ +@Injectable() +export class CoreGradesHelperProvider { + protected logger; + + constructor(logger: CoreLoggerProvider, private coursesProvider: CoreCoursesProvider) { + this.logger = logger.getInstance('CoreGradesHelperProvider'); + } + + /** + * Get course data for grades since they only have courseid. + * + * @param {Object[]} grades Grades to get the data for. + * @return {Promise} Promise always resolved. Resolve param is the formatted grades. + */ + getGradesCourseData(grades: any): Promise { + // We ommit to use $mmCourses.getUserCourse for performance reasons. + return this.coursesProvider.getUserCourses(true).then((courses) => { + const indexedCourses = {}; + courses.forEach((course) => { + indexedCourses[course.id] = course; + }); + + grades.forEach((grade) => { + if (typeof indexedCourses[grade.courseid] != 'undefined') { + grade.coursefullname = indexedCourses[grade.courseid].fullname; + } + }); + + return grades; + }); + } + +} diff --git a/src/core/grades/providers/mainmenu-handler.ts b/src/core/grades/providers/mainmenu-handler.ts new file mode 100644 index 000000000..ff9ee2ad7 --- /dev/null +++ b/src/core/grades/providers/mainmenu-handler.ts @@ -0,0 +1,51 @@ +// (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 { CoreGradesProvider } from './grades'; +import { CoreMainMenuHandler, CoreMainMenuHandlerData } from '../../mainmenu/providers/delegate'; + +/** + * Handler to inject an option into main menu. + */ +@Injectable() +export class CoreGradesMainMenuHandler implements CoreMainMenuHandler { + name = 'CoreGrades'; + priority = 950; + + constructor(private gradesProvider: CoreGradesProvider) { } + + /** + * Check if 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.gradesProvider.isCourseGradesEnabled(); + } + + /** + * Returns the data needed to render the handler. + * + * @return {CoreMainMenuHandlerData} Data needed to render the handler. + */ + getDisplayData(): CoreMainMenuHandlerData { + return { + icon: 'stats', + title: 'core.grades.grades', + page: 'CoreGradesCoursesPage', + class: 'core-grades-coursesgrades-handler' + }; + } +}