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 }}
+
+
+
+
+
+
+
+
+
+
+
+ 0">
+
+
+ {{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'
+ };
+ }
+}