commit
37d6bb0464
|
@ -20,6 +20,7 @@ import { AddonFilterModule } from './filter/filter.module';
|
||||||
import { AddonUserProfileFieldModule } from './userprofilefield/userprofilefield.module';
|
import { AddonUserProfileFieldModule } from './userprofilefield/userprofilefield.module';
|
||||||
import { AddonBadgesModule } from './badges/badges.module';
|
import { AddonBadgesModule } from './badges/badges.module';
|
||||||
import { AddonCalendarModule } from './calendar/calendar.module';
|
import { AddonCalendarModule } from './calendar/calendar.module';
|
||||||
|
import { AddonCourseCompletionModule } from './coursecompletion/coursecompletion.module';
|
||||||
import { AddonNotificationsModule } from './notifications/notifications.module';
|
import { AddonNotificationsModule } from './notifications/notifications.module';
|
||||||
import { AddonMessageOutputModule } from './messageoutput/messageoutput.module';
|
import { AddonMessageOutputModule } from './messageoutput/messageoutput.module';
|
||||||
import { AddonMessagesModule } from './messages/messages.module';
|
import { AddonMessagesModule } from './messages/messages.module';
|
||||||
|
@ -35,6 +36,7 @@ import { AddonRemoteThemesModule } from './remotethemes/remotethemes.module';
|
||||||
AddonBadgesModule,
|
AddonBadgesModule,
|
||||||
AddonBlogModule,
|
AddonBlogModule,
|
||||||
AddonCalendarModule,
|
AddonCalendarModule,
|
||||||
|
AddonCourseCompletionModule,
|
||||||
AddonMessagesModule,
|
AddonMessagesModule,
|
||||||
AddonPrivateFilesModule,
|
AddonPrivateFilesModule,
|
||||||
AddonFilterModule,
|
AddonFilterModule,
|
||||||
|
|
|
@ -19,8 +19,8 @@ import { conditionalRoutes } from '@/app/app-routing.module';
|
||||||
import { CoreScreen } from '@services/screen';
|
import { CoreScreen } from '@services/screen';
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
|
||||||
import { AddonBadgesIssuedBadgePage } from './pages/issued-badge/issued-badge';
|
import { AddonBadgesIssuedBadgePage } from './pages/issued-badge/issued-badge.page';
|
||||||
import { AddonBadgesUserBadgesPage } from './pages/user-badges/user-badges';
|
import { AddonBadgesUserBadgesPage } from './pages/user-badges/user-badges.page';
|
||||||
|
|
||||||
const mobileRoutes: Routes = [
|
const mobileRoutes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -20,8 +20,9 @@ import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
import { CorePageItemsListManager } from '@classes/page-items-list-manager';
|
||||||
import { ActivatedRoute, ActivatedRouteSnapshot, Params } from '@angular/router';
|
import { ActivatedRouteSnapshot, Params } from '@angular/router';
|
||||||
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
import { CoreSplitViewComponent } from '@components/split-view/split-view';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays the list of calendar events.
|
* Page that displays the list of calendar events.
|
||||||
|
@ -37,9 +38,9 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
|
||||||
constructor(route: ActivatedRoute) {
|
constructor() {
|
||||||
const courseId = parseInt(route.snapshot.queryParams.courseId ?? 0); // Use 0 for site badges.
|
const courseId = CoreNavigator.getRouteNumberParam('courseId') ?? 0; // Use 0 for site badges.
|
||||||
const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.getCurrentSiteUserId());
|
const userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId();
|
||||||
|
|
||||||
this.badges = new AddonBadgesUserBadgesManager(AddonBadgesUserBadgesPage, courseId, userId);
|
this.badges = new AddonBadgesUserBadgesManager(AddonBadgesUserBadgesPage, courseId, userId);
|
||||||
}
|
}
|
|
@ -14,7 +14,6 @@
|
||||||
|
|
||||||
import { Injectable } from '@angular/core';
|
import { Injectable } from '@angular/core';
|
||||||
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
import { CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
||||||
import { CoreUserProfile } from '@features/user/services/user';
|
|
||||||
import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
|
import { CoreUserDelegateService, CoreUserProfileHandler, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
|
@ -42,13 +41,11 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler {
|
||||||
/**
|
/**
|
||||||
* Check if handler is enabled for this user in this context.
|
* Check if handler is enabled for this user in this context.
|
||||||
*
|
*
|
||||||
* @param user User to check.
|
|
||||||
* @param courseId Course ID.
|
* @param courseId Course ID.
|
||||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||||
* @return True if enabled, false otherwise.
|
* @return True if enabled, false otherwise.
|
||||||
*/
|
*/
|
||||||
async isEnabledForUser(
|
async isEnabledForCourse(
|
||||||
user: CoreUserProfile,
|
|
||||||
courseId: number,
|
courseId: number,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
|
|
|
@ -131,9 +131,8 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i
|
||||||
* @param entry Selected entry.
|
* @param entry Selected entry.
|
||||||
*/
|
*/
|
||||||
gotoCoureListModType(entry: AddonBlockActivityModuleEntry): void {
|
gotoCoureListModType(entry: AddonBlockActivityModuleEntry): void {
|
||||||
CoreNavigator.navigateToSitePath('course/list-mod-type', {
|
CoreNavigator.navigateToSitePath('course/' + this.getCourseId() + '/list-mod-type', {
|
||||||
params: {
|
params: {
|
||||||
courseId: this.getCourseId(),
|
|
||||||
modName: entry.modName,
|
modName: entry.modName,
|
||||||
title: entry.name,
|
title: entry.name,
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,21 +29,14 @@ export class AddonBlockCompletionStatusHandlerService extends CoreBlockBaseHandl
|
||||||
blockName = 'completionstatus';
|
blockName = 'completionstatus';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the block.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param block The block to render.
|
|
||||||
* @param contextLevel The context where the block will be used.
|
|
||||||
* @param instanceId The instance ID associated with the context level.
|
|
||||||
* @return Data or promise resolved with the data.
|
|
||||||
*/
|
*/
|
||||||
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
|
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
|
||||||
// @todo
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: 'addon.block_completionstatus.pluginname',
|
title: 'addon.block_completionstatus.pluginname',
|
||||||
class: 'addon-block-completion-status',
|
class: 'addon-block-completion-status',
|
||||||
component: CoreBlockOnlyTitleComponent,
|
component: CoreBlockOnlyTitleComponent,
|
||||||
link: 'AddonCourseCompletionReportPage',
|
link: 'coursecompletion',
|
||||||
linkParams: {
|
linkParams: {
|
||||||
courseId: instanceId,
|
courseId: instanceId,
|
||||||
},
|
},
|
||||||
|
|
|
@ -29,22 +29,17 @@ export class AddonBlockSelfCompletionHandlerService extends CoreBlockBaseHandler
|
||||||
blockName = 'selfcompletion';
|
blockName = 'selfcompletion';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the block.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param block The block to render.
|
|
||||||
* @param contextLevel The context where the block will be used.
|
|
||||||
* @param instanceId The instance ID associated with the context level.
|
|
||||||
* @return Data or promise resolved with the data.
|
|
||||||
*/
|
*/
|
||||||
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
|
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
|
||||||
// @todo
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
title: 'addon.block_selfcompletion.pluginname',
|
title: 'addon.block_selfcompletion.pluginname',
|
||||||
class: 'addon-block-self-completion',
|
class: 'addon-block-self-completion',
|
||||||
component: CoreBlockOnlyTitleComponent,
|
component: CoreBlockOnlyTitleComponent,
|
||||||
link: 'AddonCourseCompletionReportPage',
|
link: 'coursecompletion',
|
||||||
linkParams: { courseId: instanceId },
|
linkParams: {
|
||||||
|
courseId: instanceId,
|
||||||
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -35,13 +35,6 @@ export class AddonBlogUserHandlerService implements CoreUserProfileHandler {
|
||||||
return AddonBlog.isPluginEnabled();
|
return AddonBlog.isPluginEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @inheritdoc
|
|
||||||
*/
|
|
||||||
async isEnabledForUser(): Promise<boolean> {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -0,0 +1,41 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
import { NgModule } from '@angular/core';
|
||||||
|
import { Routes, RouterModule } from '@angular/router';
|
||||||
|
import { CoreCommentsComponentsModule } from '@features/comments/components/components.module';
|
||||||
|
import { CoreTagComponentsModule } from '@features/tag/components/components.module';
|
||||||
|
import { AddonCourseCompletionReportPage } from './pages/report/report';
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: '',
|
||||||
|
component: AddonCourseCompletionReportPage,
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
@NgModule({
|
||||||
|
imports: [
|
||||||
|
RouterModule.forChild(routes),
|
||||||
|
CoreSharedModule,
|
||||||
|
CoreCommentsComponentsModule,
|
||||||
|
CoreTagComponentsModule,
|
||||||
|
],
|
||||||
|
exports: [RouterModule],
|
||||||
|
declarations: [
|
||||||
|
AddonCourseCompletionReportPage,
|
||||||
|
],
|
||||||
|
})
|
||||||
|
export class AddonCourseCompletionLazyModule {}
|
|
@ -12,33 +12,42 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { NgModule } from '@angular/core';
|
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
||||||
// @todo import { AddonCourseCompletionCourseOptionHandler } from './services/course-option-handler';
|
import { Routes } from '@angular/router';
|
||||||
// @todo import { AddonCourseCompletionUserHandler } from './services/user-handler';
|
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
|
||||||
// @todo import { AddonCourseCompletionComponentsModule } from './components/components.module';
|
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||||
// @todo import { CoreCourseOptionsDelegate } from '@features/course/services/options-delegate';
|
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||||
// @todo import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||||
|
import { AddonCourseCompletionProvider } from './services/coursecompletion';
|
||||||
|
import { AddonCourseCompletionCourseOptionHandler } from './services/handlers/course-option';
|
||||||
|
import { AddonCourseCompletionUserHandler } from './services/handlers/user';
|
||||||
|
|
||||||
|
export const ADDON_COURSECOMPLETION_SERVICES: Type<unknown>[] = [
|
||||||
|
AddonCourseCompletionProvider,
|
||||||
|
];
|
||||||
|
|
||||||
|
const routes: Routes = [
|
||||||
|
{
|
||||||
|
path: 'coursecompletion',
|
||||||
|
loadChildren: () => import('./coursecompletion-lazy.module').then(m => m.AddonCourseCompletionLazyModule),
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
imports: [
|
imports: [
|
||||||
// AddonCourseCompletionComponentsModule,
|
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||||
|
CoreCourseIndexRoutingModule.forChild({ children: routes }),
|
||||||
],
|
],
|
||||||
providers: [
|
providers: [
|
||||||
// AddonCourseCompletionCourseOptionHandler,
|
{
|
||||||
// AddonCourseCompletionUserHandler,
|
provide: APP_INITIALIZER,
|
||||||
|
multi: true,
|
||||||
|
deps: [],
|
||||||
|
useFactory: () => async () => {
|
||||||
|
CoreUserDelegate.registerHandler(AddonCourseCompletionUserHandler.instance);
|
||||||
|
CoreCourseOptionsDelegate.registerHandler(AddonCourseCompletionCourseOptionHandler.instance);
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
})
|
})
|
||||||
export class AddonCourseCompletionModule {
|
export class AddonCourseCompletionModule {}
|
||||||
|
|
||||||
/* @todo constructor(
|
|
||||||
courseOptionsDelegate: CoreCourseOptionsDelegate,
|
|
||||||
courseOptionHandler: AddonCourseCompletionCourseOptionHandler,
|
|
||||||
userDelegate: CoreUserDelegate,
|
|
||||||
userHandler: AddonCourseCompletionUserHandler,
|
|
||||||
) {
|
|
||||||
// Register handlers.
|
|
||||||
courseOptionsDelegate.registerHandler(courseOptionHandler);
|
|
||||||
userDelegate.registerHandler(userHandler);
|
|
||||||
}*/
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
|
@ -0,0 +1,92 @@
|
||||||
|
<ion-header>
|
||||||
|
<ion-toolbar>
|
||||||
|
<ion-buttons slot="start">
|
||||||
|
<ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
|
||||||
|
</ion-buttons>
|
||||||
|
<ion-title>{{ 'addon.coursecompletion.coursecompletion' | translate }}</ion-title>
|
||||||
|
</ion-toolbar>
|
||||||
|
</ion-header>
|
||||||
|
<ion-content>
|
||||||
|
<ion-refresher slot="fixed" [disabled]="!completionLoaded" (ionRefresh)="refreshCompletion($event.target)">
|
||||||
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
|
</ion-refresher>
|
||||||
|
<core-loading [hideUntil]="completionLoaded">
|
||||||
|
<ion-card *ngIf="completion && tracked">
|
||||||
|
<ion-item class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'addon.coursecompletion.status' | translate }}</h2>
|
||||||
|
<p>{{ statusText! | translate }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<h2>{{ 'addon.coursecompletion.required' | translate }}</h2>
|
||||||
|
<p *ngIf="completion.aggregation === 1">{{ 'addon.coursecompletion.criteriarequiredall' | translate }}</p>
|
||||||
|
<p *ngIf="completion.aggregation === 2">{{ 'addon.coursecompletion.criteriarequiredany' | translate }}</p>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
|
<ion-card *ngIf="completion && tracked">
|
||||||
|
<ion-item-divider>
|
||||||
|
<ion-label>{{ 'addon.coursecompletion.requiredcriteria' | translate }}</ion-label>
|
||||||
|
</ion-item-divider>
|
||||||
|
<ion-item class="ion-hide-md-up ion-text-wrap" *ngFor="let criteria of completion.completions">
|
||||||
|
<ion-label>
|
||||||
|
<h2><core-format-text clean="true" [text]="criteria.details.criteria" [filter]="false"></core-format-text></h2>
|
||||||
|
<p><core-format-text clean="true" [text]="criteria.details.requirement" [filter]="false"></core-format-text></p>
|
||||||
|
</ion-label>
|
||||||
|
<strong slot="end">{{ criteria.status }}</strong>
|
||||||
|
</ion-item>
|
||||||
|
<ion-item class="ion-hide-md-down ion-text-wrap">
|
||||||
|
<ion-label>
|
||||||
|
<ion-row>
|
||||||
|
<ion-col><strong>{{ 'addon.coursecompletion.criteriagroup' | translate }}</strong></ion-col>
|
||||||
|
<ion-col><strong>{{ 'addon.coursecompletion.criteria' | translate }}</strong></ion-col>
|
||||||
|
<ion-col><strong>{{ 'addon.coursecompletion.requirement' | translate }}</strong></ion-col>
|
||||||
|
<ion-col><strong>{{ 'addon.coursecompletion.status' | translate }}</strong></ion-col>
|
||||||
|
<ion-col><strong>{{ 'addon.coursecompletion.complete' | translate }}</strong></ion-col>
|
||||||
|
<ion-col><strong>{{ 'addon.coursecompletion.completiondate' | translate }}</strong></ion-col>
|
||||||
|
</ion-row>
|
||||||
|
<ion-row *ngFor="let criteria of completion.completions">
|
||||||
|
<ion-col>
|
||||||
|
<core-format-text clean="true" [text]="criteria.title" [filter]="false"></core-format-text>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<core-format-text clean="true" [text]="criteria.details.criteria" [filter]="false"></core-format-text>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<core-format-text clean="true" [text]="criteria.details.requirement" [filter]="false"></core-format-text>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>
|
||||||
|
<core-format-text [text]="criteria.details.status" [filter]="false"></core-format-text>
|
||||||
|
</ion-col>
|
||||||
|
<ion-col>{{ criteria.status }}</ion-col>
|
||||||
|
<ion-col *ngIf="criteria.timecompleted">
|
||||||
|
{{ criteria.timecompleted * 1000 | coreFormatDate :'strftimedatetimeshort' }}
|
||||||
|
</ion-col>
|
||||||
|
<ion-col *ngIf="!criteria.timecompleted"></ion-col>
|
||||||
|
</ion-row>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
|
<ion-card *ngIf="showSelfComplete && tracked">
|
||||||
|
<ion-item-divider>
|
||||||
|
<ion-label>{{ 'addon.coursecompletion.manualselfcompletion' | translate }}</ion-label>
|
||||||
|
</ion-item-divider>
|
||||||
|
<ion-item>
|
||||||
|
<ion-label>
|
||||||
|
<ion-button expand="block" (click)="completeCourse()">
|
||||||
|
{{ 'addon.coursecompletion.completecourse' | translate }}
|
||||||
|
</ion-button>
|
||||||
|
</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
|
|
||||||
|
<ion-card class="core-warning-card" *ngIf="!tracked">
|
||||||
|
<ion-item>
|
||||||
|
<ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
|
||||||
|
<ion-label>{{ 'addon.coursecompletion.nottracked' | translate }}</ion-label>
|
||||||
|
</ion-item>
|
||||||
|
</ion-card>
|
||||||
|
</core-loading>
|
||||||
|
</ion-content>
|
|
@ -0,0 +1,112 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import {
|
||||||
|
AddonCourseCompletion,
|
||||||
|
AddonCourseCompletionCourseCompletionStatus,
|
||||||
|
} from '@addons/coursecompletion/services/coursecompletion';
|
||||||
|
import { Component, OnInit } from '@angular/core';
|
||||||
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { CoreSites } from '@services/sites';
|
||||||
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Page that displays the course completion report.
|
||||||
|
*/
|
||||||
|
@Component({
|
||||||
|
selector: 'page-addon-course-completion-report',
|
||||||
|
templateUrl: 'report.html',
|
||||||
|
})
|
||||||
|
export class AddonCourseCompletionReportPage implements OnInit {
|
||||||
|
|
||||||
|
protected courseId!: number;
|
||||||
|
protected userId!: number;
|
||||||
|
|
||||||
|
completionLoaded = false;
|
||||||
|
completion?: AddonCourseCompletionCourseCompletionStatus;
|
||||||
|
showSelfComplete = false;
|
||||||
|
tracked = true; // Whether completion is tracked.
|
||||||
|
statusText?: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
ngOnInit(): void {
|
||||||
|
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||||
|
this.userId = CoreNavigator.getRouteNumberParam('userId') || CoreSites.getCurrentSiteUserId();
|
||||||
|
|
||||||
|
if (!this.userId) {
|
||||||
|
this.userId = CoreSites.getCurrentSiteUserId();
|
||||||
|
}
|
||||||
|
|
||||||
|
this.fetchCompletion().finally(() => {
|
||||||
|
this.completionLoaded = true;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Fetch compleiton data.
|
||||||
|
*
|
||||||
|
* @return Promise resolved when done.
|
||||||
|
*/
|
||||||
|
protected async fetchCompletion(): Promise<void> {
|
||||||
|
try {
|
||||||
|
this.completion = await AddonCourseCompletion.getCompletion(this.courseId, this.userId);
|
||||||
|
|
||||||
|
this.statusText = AddonCourseCompletion.getCompletedStatusText(this.completion);
|
||||||
|
this.showSelfComplete = AddonCourseCompletion.canMarkSelfCompleted(this.userId, this.completion);
|
||||||
|
|
||||||
|
this.tracked = true;
|
||||||
|
} catch (error) {
|
||||||
|
if (error && error.errorcode == 'notenroled') {
|
||||||
|
// Not enrolled error, probably a teacher.
|
||||||
|
this.tracked = false;
|
||||||
|
} else {
|
||||||
|
CoreDomUtils.showErrorModalDefault(error, 'addon.coursecompletion.couldnotloadreport', true);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh completion data on PTR.
|
||||||
|
*
|
||||||
|
* @param refresher Refresher instance.
|
||||||
|
*/
|
||||||
|
async refreshCompletion(refresher?: IonRefresher): Promise<void> {
|
||||||
|
await AddonCourseCompletion.invalidateCourseCompletion(this.courseId, this.userId).finally(() => {
|
||||||
|
this.fetchCompletion().finally(() => {
|
||||||
|
refresher?.complete();
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mark course as completed.
|
||||||
|
*/
|
||||||
|
async completeCourse(): Promise<void> {
|
||||||
|
const modal = await CoreDomUtils.showModalLoading('core.sending', true);
|
||||||
|
|
||||||
|
try {
|
||||||
|
await AddonCourseCompletion.markCourseAsSelfCompleted(this.courseId);
|
||||||
|
|
||||||
|
await this.refreshCompletion();
|
||||||
|
} catch (error) {
|
||||||
|
CoreDomUtils.showErrorModal(error);
|
||||||
|
} finally {
|
||||||
|
modal.dismiss();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -164,9 +164,9 @@ export class AddonCourseCompletionProvider {
|
||||||
* @param preferCache True if shouldn't call WS if data is cached, false otherwise.
|
* @param preferCache True if shouldn't call WS if data is cached, false otherwise.
|
||||||
* @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
* @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
||||||
*/
|
*/
|
||||||
async isPluginViewEnabledForCourse(courseId: number, preferCache: boolean = true): Promise<boolean> {
|
async isPluginViewEnabledForCourse(courseId?: number, preferCache: boolean = true): Promise<boolean> {
|
||||||
if (!courseId) {
|
if (!courseId) {
|
||||||
throw new CoreError('No courseId provided');
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const course = await CoreCourses.getUserCourse(courseId, preferCache);
|
const course = await CoreCourses.getUserCourse(courseId, preferCache);
|
||||||
|
|
|
@ -0,0 +1,96 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreCourseProvider } from '@features/course/services/course';
|
||||||
|
import {
|
||||||
|
CoreCourseAccess,
|
||||||
|
CoreCourseOptionsHandler,
|
||||||
|
CoreCourseOptionsHandlerData,
|
||||||
|
} from '@features/course/services/course-options-delegate';
|
||||||
|
import { CoreEnrolledCourseDataWithExtraInfoAndOptions } from '@features/courses/services/courses-helper';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonCourseCompletion } from '../coursecompletion';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handler to inject an option into the course main menu.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonCourseCompletionCourseOptionHandlerService implements CoreCourseOptionsHandler {
|
||||||
|
|
||||||
|
name = 'AddonCourseCompletion';
|
||||||
|
priority = 200;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return AddonCourseCompletion.isPluginViewEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabledForCourse(courseId: number, accessData: CoreCourseAccess): Promise<boolean> {
|
||||||
|
if (accessData && accessData.type == CoreCourseProvider.ACCESS_GUEST) {
|
||||||
|
return false; // Not enabled for guests.
|
||||||
|
}
|
||||||
|
|
||||||
|
const courseEnabled = await AddonCourseCompletion.isPluginViewEnabledForCourse(courseId);
|
||||||
|
// If is not enabled in the course, is not enabled for the user.
|
||||||
|
if (!courseEnabled) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return AddonCourseCompletion.isPluginViewEnabledForUser(courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getDisplayData(): CoreCourseOptionsHandlerData | Promise<CoreCourseOptionsHandlerData> {
|
||||||
|
return {
|
||||||
|
title: 'addon.coursecompletion.completionmenuitem',
|
||||||
|
class: 'addon-coursecompletion-course-handler',
|
||||||
|
page: 'coursecompletion',
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async invalidateEnabledForCourse(courseId: number): Promise<void> {
|
||||||
|
await AddonCourseCompletion.invalidateCourseCompletion(courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async prefetch(course: CoreEnrolledCourseDataWithExtraInfoAndOptions): Promise<void> {
|
||||||
|
try {
|
||||||
|
await AddonCourseCompletion.getCompletion(course.id, undefined, {
|
||||||
|
getFromCache: false,
|
||||||
|
emergencyCache: false,
|
||||||
|
});
|
||||||
|
} catch (error) {
|
||||||
|
if (error && error.errorcode == 'notenroled') {
|
||||||
|
// Not enrolled error, probably a teacher. Ignore error.
|
||||||
|
} else {
|
||||||
|
throw error;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonCourseCompletionCourseOptionHandler = makeSingleton(AddonCourseCompletionCourseOptionHandlerService);
|
|
@ -0,0 +1,73 @@
|
||||||
|
// (C) Copyright 2015 Moodle Pty Ltd.
|
||||||
|
//
|
||||||
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
// you may not use this file except in compliance with the License.
|
||||||
|
// You may obtain a copy of the License at
|
||||||
|
//
|
||||||
|
// http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
//
|
||||||
|
// Unless required by applicable law or agreed to in writing, software
|
||||||
|
// distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
// See the License for the specific language governing permissions and
|
||||||
|
// limitations under the License.
|
||||||
|
|
||||||
|
import { Injectable } from '@angular/core';
|
||||||
|
import { CoreUserProfile } from '@features/user/services/user';
|
||||||
|
import { CoreUserProfileHandler, CoreUserDelegateService, CoreUserProfileHandlerData } from '@features/user/services/user-delegate';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
import { makeSingleton } from '@singletons';
|
||||||
|
import { AddonCourseCompletion } from '../coursecompletion';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Profile course completion handler.
|
||||||
|
*/
|
||||||
|
@Injectable({ providedIn: 'root' })
|
||||||
|
export class AddonCourseCompletionUserHandlerService implements CoreUserProfileHandler {
|
||||||
|
|
||||||
|
name = 'AddonCourseCompletion';
|
||||||
|
type = CoreUserDelegateService.TYPE_NEW_PAGE;
|
||||||
|
priority = 200;
|
||||||
|
cacheEnabled = true;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabled(): Promise<boolean> {
|
||||||
|
return AddonCourseCompletion.isPluginViewEnabled();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabledForCourse(courseId?: number): Promise<boolean> {
|
||||||
|
return AddonCourseCompletion.isPluginViewEnabledForCourse(courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
|
||||||
|
return await AddonCourseCompletion.isPluginViewEnabledForUser(courseId!, user.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
getDisplayData(): CoreUserProfileHandlerData {
|
||||||
|
return {
|
||||||
|
icon: 'fas-tasks',
|
||||||
|
title: 'addon.coursecompletion.coursecompletion',
|
||||||
|
class: 'addon-coursecompletion-handler',
|
||||||
|
action: (event, user, courseId): void => {
|
||||||
|
event.preventDefault();
|
||||||
|
event.stopPropagation();
|
||||||
|
CoreNavigator.navigateToSitePath('/coursecompletion', {
|
||||||
|
params: { courseId, userId: user.id },
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
export const AddonCourseCompletionUserHandler = makeSingleton(AddonCourseCompletionUserHandlerService);
|
|
@ -164,9 +164,9 @@ export class AddonMessagesDiscussionPage implements OnInit, OnDestroy, AfterView
|
||||||
|
|
||||||
this.route.queryParams.subscribe(async (params) => {
|
this.route.queryParams.subscribe(async (params) => {
|
||||||
this.loaded = false;
|
this.loaded = false;
|
||||||
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', params) || undefined;
|
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', { params }) || undefined;
|
||||||
this.userId = CoreNavigator.getRouteNumberParam('userId', params) || undefined;
|
this.userId = CoreNavigator.getRouteNumberParam('userId', { params }) || undefined;
|
||||||
this.showKeyboard = CoreNavigator.getRouteBooleanParam('showKeyboard', params) || false;
|
this.showKeyboard = CoreNavigator.getRouteBooleanParam('showKeyboard', { params }) || false;
|
||||||
|
|
||||||
await this.fetchData();
|
await this.fetchData();
|
||||||
|
|
||||||
|
|
|
@ -138,8 +138,8 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
||||||
*/
|
*/
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.queryParams.subscribe(async (params) => {
|
this.route.queryParams.subscribe(async (params) => {
|
||||||
const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', params) ||
|
const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', { params }) ||
|
||||||
CoreNavigator.getRouteNumberParam('userId', params) || undefined;
|
CoreNavigator.getRouteNumberParam('userId', { params }) || undefined;
|
||||||
|
|
||||||
if (this.loaded && this.discussionUserId == discussionUserId) {
|
if (this.loaded && this.discussionUserId == discussionUserId) {
|
||||||
return;
|
return;
|
||||||
|
|
|
@ -273,9 +273,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
||||||
ngOnInit(): void {
|
ngOnInit(): void {
|
||||||
this.route.queryParams.subscribe(async (params) => {
|
this.route.queryParams.subscribe(async (params) => {
|
||||||
// Conversation to load.
|
// Conversation to load.
|
||||||
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', params) || undefined;
|
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', { params }) || undefined;
|
||||||
if (!this.conversationId) {
|
if (!this.conversationId) {
|
||||||
this.discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', params) || undefined;
|
this.discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', { params }) || undefined;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (this.conversationId || this.discussionUserId) {
|
if (this.conversationId || this.discussionUserId) {
|
||||||
|
|
|
@ -40,6 +40,13 @@ export class AddonMessagesSendMessageUserHandlerService implements CoreUserProfi
|
||||||
return AddonMessages.isPluginEnabled();
|
return AddonMessages.isPluginEnabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabledForCourse(): Promise<boolean> {
|
||||||
|
return !!CoreSites.getCurrentSite();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if handler is enabled for this user in this context.
|
* Check if handler is enabled for this user in this context.
|
||||||
*
|
*
|
||||||
|
@ -47,14 +54,10 @@ export class AddonMessagesSendMessageUserHandlerService implements CoreUserProfi
|
||||||
* @return Promise resolved with true if enabled, resolved with false otherwise.
|
* @return Promise resolved with true if enabled, resolved with false otherwise.
|
||||||
*/
|
*/
|
||||||
async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
|
async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
|
||||||
const currentSite = CoreSites.getCurrentSite();
|
const currentSite = CoreSites.getCurrentSite()!;
|
||||||
|
|
||||||
if (!currentSite) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// From 3.7 you can send messages to yourself.
|
// From 3.7 you can send messages to yourself.
|
||||||
return user.id != currentSite.getUserId() || currentSite.isVersionGreaterEqualThan('3.7');
|
return user.id != CoreSites.getCurrentSiteUserId() || currentSite.isVersionGreaterEqualThan('3.7');
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -58,7 +58,7 @@ export class AddonModAssignSubmissionReviewPage implements OnInit, CanLeave {
|
||||||
this.moduleId = CoreNavigator.getRouteNumberParam('cmId')!;
|
this.moduleId = CoreNavigator.getRouteNumberParam('cmId')!;
|
||||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||||
this.submitId = CoreNavigator.getRouteNumberParam('submitId') || 0;
|
this.submitId = CoreNavigator.getRouteNumberParam('submitId') || 0;
|
||||||
this.blindId = CoreNavigator.getRouteNumberParam('blindId', params);
|
this.blindId = CoreNavigator.getRouteNumberParam('blindId', { params });
|
||||||
|
|
||||||
this.fetchSubmission().finally(() => {
|
this.fetchSubmission().finally(() => {
|
||||||
this.loaded = true;
|
this.loaded = true;
|
||||||
|
|
|
@ -117,6 +117,7 @@ import { CoreSitePluginsAssignSubmissionComponent } from '@features/siteplugins/
|
||||||
// Import addon providers. Do not import database module because it causes circular dependencies.
|
// Import addon providers. Do not import database module because it causes circular dependencies.
|
||||||
import { ADDON_BADGES_SERVICES } from '@addons/badges/badges.module';
|
import { ADDON_BADGES_SERVICES } from '@addons/badges/badges.module';
|
||||||
import { ADDON_CALENDAR_SERVICES } from '@addons/calendar/calendar.module';
|
import { ADDON_CALENDAR_SERVICES } from '@addons/calendar/calendar.module';
|
||||||
|
import { ADDON_COURSECOMPLETION_SERVICES } from '@addons/coursecompletion/coursecompletion.module';
|
||||||
// @todo import { ADDON_COMPETENCY_SERVICES } from '@addons/competency/competency.module';
|
// @todo import { ADDON_COMPETENCY_SERVICES } from '@addons/competency/competency.module';
|
||||||
import { ADDON_MESSAGEOUTPUT_SERVICES } from '@addons/messageoutput/messageoutput.module';
|
import { ADDON_MESSAGEOUTPUT_SERVICES } from '@addons/messageoutput/messageoutput.module';
|
||||||
import { ADDON_MESSAGES_SERVICES } from '@addons/messages/messages.module';
|
import { ADDON_MESSAGES_SERVICES } from '@addons/messages/messages.module';
|
||||||
|
@ -281,6 +282,7 @@ export class CoreCompileProvider {
|
||||||
...extraProviders,
|
...extraProviders,
|
||||||
...ADDON_BADGES_SERVICES,
|
...ADDON_BADGES_SERVICES,
|
||||||
...ADDON_CALENDAR_SERVICES,
|
...ADDON_CALENDAR_SERVICES,
|
||||||
|
...ADDON_COURSECOMPLETION_SERVICES,
|
||||||
// @todo ...ADDON_COMPETENCY_SERVICES,
|
// @todo ...ADDON_COMPETENCY_SERVICES,
|
||||||
...ADDON_MESSAGEOUTPUT_SERVICES,
|
...ADDON_MESSAGEOUTPUT_SERVICES,
|
||||||
...ADDON_MESSAGES_SERVICES,
|
...ADDON_MESSAGES_SERVICES,
|
||||||
|
|
|
@ -61,9 +61,8 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa
|
||||||
|
|
||||||
return [{
|
return [{
|
||||||
action: (siteId): void => {
|
action: (siteId): void => {
|
||||||
CoreNavigator.navigateToSitePath('course/list-mod-type', {
|
CoreNavigator.navigateToSitePath('course/' + params.id + '/list-mod-type', {
|
||||||
params: {
|
params: {
|
||||||
courseId: params.id,
|
|
||||||
modName: this.modName,
|
modName: this.modName,
|
||||||
title: this.title || Translate.instant('addon.mod_' + this.modName + '.modulenameplural'),
|
title: this.title || Translate.instant('addon.mod_' + this.modName + '.modulenameplural'),
|
||||||
},
|
},
|
||||||
|
|
|
@ -22,18 +22,23 @@ const routes: Routes = [
|
||||||
pathMatch: 'full',
|
pathMatch: 'full',
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'index',
|
path: ':courseId',
|
||||||
loadChildren: () => import('./pages/index/index.module').then( m => m.CoreCourseIndexPageModule),
|
loadChildren: () => import('./pages/index/index.module').then( m => m.CoreCourseIndexPageModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'unsupported-module',
|
path: ':courseId/unsupported-module',
|
||||||
loadChildren: () => import('./pages/unsupported-module/unsupported-module.module')
|
loadChildren: () => import('./pages/unsupported-module/unsupported-module.module')
|
||||||
.then( m => m.CoreCourseUnsupportedModulePageModule),
|
.then( m => m.CoreCourseUnsupportedModulePageModule),
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
path: 'list-mod-type',
|
path: ':courseId/list-mod-type',
|
||||||
loadChildren: () => import('./pages/list-mod-type/list-mod-type.module').then(m => m.CoreCourseListModTypePageModule),
|
loadChildren: () => import('./pages/list-mod-type/list-mod-type.module').then(m => m.CoreCourseListModTypePageModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: ':courseId/preview',
|
||||||
|
loadChildren: () =>
|
||||||
|
import('./pages/preview/preview.module').then(m => m.CoreCoursePreviewPageModule),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -460,7 +460,10 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
||||||
* Open the course summary
|
* Open the course summary
|
||||||
*/
|
*/
|
||||||
openCourseSummary(): void {
|
openCourseSummary(): void {
|
||||||
CoreNavigator.navigateToSitePath('/courses/preview', { params: { course: this.course, avoidOpenCourse: true } });
|
CoreNavigator.navigateToSitePath(
|
||||||
|
'/course/' + this.course.id + '/preview',
|
||||||
|
{ params: { course: this.course, avoidOpenCourse: true } },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -17,7 +17,7 @@ import { RouterModule, ROUTES, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { resolveModuleRoutes } from '@/app/app-routing.module';
|
import { resolveModuleRoutes } from '@/app/app-routing.module';
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreCourseIndexPage } from './index';
|
import { CoreCourseIndexPage } from './index.page';
|
||||||
import { COURSE_INDEX_ROUTES } from './index-routing.module';
|
import { COURSE_INDEX_ROUTES } from './index-routing.module';
|
||||||
|
|
||||||
function buildRoutes(injector: Injector): Routes {
|
function buildRoutes(injector: Injector): Routes {
|
||||||
|
|
|
@ -123,7 +123,6 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
||||||
handlers.forEach((handler, index) => {
|
handlers.forEach((handler, index) => {
|
||||||
handler.data.page = CoreTextUtils.concatenatePaths(this.currentPagePath, handler.data.page);
|
handler.data.page = CoreTextUtils.concatenatePaths(this.currentPagePath, handler.data.page);
|
||||||
handler.data.pageParams = handler.data.pageParams || {};
|
handler.data.pageParams = handler.data.pageParams || {};
|
||||||
handler.data.pageParams.courseId = this.course!.id;
|
|
||||||
|
|
||||||
// Check if this handler should be the first selected tab.
|
// Check if this handler should be the first selected tab.
|
||||||
if (this.firstTabName && handler.name == this.firstTabName) {
|
if (this.firstTabName && handler.name == this.firstTabName) {
|
|
@ -16,7 +16,7 @@ import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreCourseListModTypePage } from './list-mod-type';
|
import { CoreCourseListModTypePage } from './list-mod-type.page';
|
||||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
|
|
|
@ -13,8 +13,8 @@
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="dataLoaded">
|
<core-loading [hideUntil]="dataLoaded">
|
||||||
<div class="core-course-thumb-parallax">
|
<div *ngIf="courseImageUrl" class="core-course-thumb-parallax">
|
||||||
<div *ngIf="courseImageUrl" (click)="openCourse()" class="core-course-thumb">
|
<div (click)="openCourse()" class="core-course-thumb">
|
||||||
<img [src]="courseImageUrl" core-external-content alt=""/>
|
<img [src]="courseImageUrl" core-external-content alt=""/>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
|
@ -16,13 +16,12 @@ import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreCoursesCoursePreviewPage } from './course-preview';
|
import { CoreCoursePreviewPage } from './preview.page';
|
||||||
import { CoreCoursesComponentsModule } from '../../components/components.module';
|
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
path: '',
|
path: '',
|
||||||
component: CoreCoursesCoursePreviewPage,
|
component: CoreCoursePreviewPage,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
|
@ -30,11 +29,10 @@ const routes: Routes = [
|
||||||
imports: [
|
imports: [
|
||||||
RouterModule.forChild(routes),
|
RouterModule.forChild(routes),
|
||||||
CoreSharedModule,
|
CoreSharedModule,
|
||||||
CoreCoursesComponentsModule,
|
|
||||||
],
|
],
|
||||||
declarations: [
|
declarations: [
|
||||||
CoreCoursesCoursePreviewPage,
|
CoreCoursePreviewPage,
|
||||||
],
|
],
|
||||||
exports: [RouterModule],
|
exports: [RouterModule],
|
||||||
})
|
})
|
||||||
export class CoreCoursesCoursePreviewPageModule { }
|
export class CoreCoursePreviewPageModule { }
|
|
@ -15,7 +15,7 @@
|
||||||
import { Component, OnDestroy, NgZone, OnInit } from '@angular/core';
|
import { Component, OnDestroy, NgZone, OnInit } from '@angular/core';
|
||||||
import { ModalController, IonRefresher } from '@ionic/angular';
|
import { ModalController, IonRefresher } from '@ionic/angular';
|
||||||
import { CoreApp } from '@services/app';
|
import { CoreApp } from '@services/app';
|
||||||
import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events';
|
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreDomUtils } from '@services/utils/dom';
|
import { CoreDomUtils } from '@services/utils/dom';
|
||||||
import { CoreTextUtils } from '@services/utils/text';
|
import { CoreTextUtils } from '@services/utils/text';
|
||||||
|
@ -32,18 +32,18 @@ import { CoreCourse, CoreCourseProvider } from '@features/course/services/course
|
||||||
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
|
import { CoreCourseHelper, CorePrefetchStatusInfo } from '@features/course/services/course-helper';
|
||||||
import { Translate } from '@singletons';
|
import { Translate } from '@singletons';
|
||||||
import { CoreConstants } from '@/core/constants';
|
import { CoreConstants } from '@/core/constants';
|
||||||
import { CoreCoursesSelfEnrolPasswordComponent } from '../../components/self-enrol-password/self-enrol-password';
|
import { CoreCoursesSelfEnrolPasswordComponent } from '../../../courses/components/self-enrol-password/self-enrol-password';
|
||||||
import { CoreNavigator } from '@services/navigator';
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
||||||
*/
|
*/
|
||||||
@Component({
|
@Component({
|
||||||
selector: 'page-core-courses-course-preview',
|
selector: 'page-core-course-preview',
|
||||||
templateUrl: 'course-preview.html',
|
templateUrl: 'preview.html',
|
||||||
styleUrls: ['course-preview.scss'],
|
styleUrls: ['preview.scss'],
|
||||||
})
|
})
|
||||||
export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy {
|
export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
course?: CoreCourseSearchedData;
|
course?: CoreCourseSearchedData;
|
||||||
isEnrolled = false;
|
isEnrolled = false;
|
||||||
|
@ -84,7 +84,7 @@ export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy {
|
||||||
|
|
||||||
if (this.downloadCourseEnabled) {
|
if (this.downloadCourseEnabled) {
|
||||||
// Listen for status change in course.
|
// Listen for status change in course.
|
||||||
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data: CoreEventCourseStatusChanged) => {
|
this.courseStatusObserver = CoreEvents.on(CoreEvents.COURSE_STATUS_CHANGED, (data) => {
|
||||||
if (data.courseId == this.course!.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
if (data.courseId == this.course!.id || data.courseId == CoreCourseProvider.ALL_COURSES_CLEARED) {
|
||||||
this.updateCourseStatus(data.status);
|
this.updateCourseStatus(data.status);
|
||||||
}
|
}
|
|
@ -16,7 +16,7 @@ import { NgModule } from '@angular/core';
|
||||||
import { RouterModule, Routes } from '@angular/router';
|
import { RouterModule, Routes } from '@angular/router';
|
||||||
|
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreCourseUnsupportedModulePage } from './unsupported-module';
|
import { CoreCourseUnsupportedModulePage } from './unsupported-module.page';
|
||||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
|
|
|
@ -1848,7 +1848,7 @@ export class CoreCourseHelperProvider {
|
||||||
params = params || {};
|
params = params || {};
|
||||||
Object.assign(params, { course: course });
|
Object.assign(params, { course: course });
|
||||||
|
|
||||||
await CoreNavigator.navigateToSitePath('course', { siteId, params });
|
await CoreNavigator.navigateToSitePath('course/' + course.id, { siteId, params });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -177,7 +177,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler {
|
||||||
Object.assign(params, { course: course });
|
Object.assign(params, { course: course });
|
||||||
|
|
||||||
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
// Don't return the .push promise, we don't want to display a loading modal during the page transition.
|
||||||
CoreNavigator.navigateToSitePath('course', { params });
|
CoreNavigator.navigateToSitePath('course/' + course.id, { params });
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -64,9 +64,9 @@ export class CoreCourseModuleDefaultHandler implements CoreCourseModuleHandler {
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
|
|
||||||
options = options || {};
|
options = options || {};
|
||||||
options.params = { module, courseId };
|
options.params = { module };
|
||||||
|
|
||||||
CoreNavigator.navigateToSitePath('course/unsupported-module', options);
|
CoreNavigator.navigateToSitePath('course/' + courseId + '/unsupported-module', options);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -94,7 +94,10 @@ export class CoreCoursesCourseListItemComponent implements OnInit {
|
||||||
if (this.isEnrolled) {
|
if (this.isEnrolled) {
|
||||||
CoreCourseHelper.openCourse(this.course);
|
CoreCourseHelper.openCourse(this.course);
|
||||||
} else {
|
} else {
|
||||||
CoreNavigator.navigate('courses/preview', { params: { course: this.course } });
|
CoreNavigator.navigate(
|
||||||
|
'/course/' + this.course.id + '/preview',
|
||||||
|
{ params: { course: this.course } },
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -50,12 +50,6 @@ const routes: Routes = [
|
||||||
import('./pages/my-courses/my-courses.module')
|
import('./pages/my-courses/my-courses.module')
|
||||||
.then(m => m.CoreCoursesMyCoursesPageModule),
|
.then(m => m.CoreCoursesMyCoursesPageModule),
|
||||||
},
|
},
|
||||||
{
|
|
||||||
path: 'preview',
|
|
||||||
loadChildren: () =>
|
|
||||||
import('./pages/course-preview/course-preview.module')
|
|
||||||
.then(m => m.CoreCoursesCoursePreviewPageModule),
|
|
||||||
},
|
|
||||||
];
|
];
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
|
|
@ -59,18 +59,14 @@ export class CoreCoursesEnrolPushClickHandlerService implements CorePushNotifica
|
||||||
const params: Params = {
|
const params: Params = {
|
||||||
course: result.course,
|
course: result.course,
|
||||||
};
|
};
|
||||||
let page: string;
|
let page = 'course/' + courseId;
|
||||||
|
|
||||||
if (notification.contexturl?.indexOf('user/index.php') != -1) {
|
if (notification.contexturl?.indexOf('user/index.php') != -1) {
|
||||||
// Open the participants tab.
|
// Open the participants tab.
|
||||||
page = 'course';
|
params.selectedTab = 'participants'; // @todo: Set this when participants is done.
|
||||||
params.selectedTab = 'user_participants'; // @todo: Set this when participants is done.
|
} else if (!result.enrolled) {
|
||||||
} else if (result.enrolled) {
|
|
||||||
// User is still enrolled, open the course.
|
|
||||||
page = 'course';
|
|
||||||
} else {
|
|
||||||
// User not enrolled anymore, open the preview page.
|
// User not enrolled anymore, open the preview page.
|
||||||
page = 'courses/preview';;
|
page += '/preview';
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site });
|
await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site });
|
||||||
|
|
|
@ -75,14 +75,11 @@ export class CoreCoursesRequestPushClickHandlerService implements CorePushNotifi
|
||||||
const params: Params = {
|
const params: Params = {
|
||||||
course: result.course,
|
course: result.course,
|
||||||
};
|
};
|
||||||
let page: string;
|
let page = 'course/' + courseId;
|
||||||
|
|
||||||
if (result.enrolled) {
|
if (!result.enrolled) {
|
||||||
// User is still enrolled, open the course.
|
|
||||||
page = 'course';
|
|
||||||
} else {
|
|
||||||
// User not enrolled (shouldn't happen), open the preview page.
|
// User not enrolled (shouldn't happen), open the preview page.
|
||||||
page = 'courses/preview';
|
page += '/preview';
|
||||||
}
|
}
|
||||||
|
|
||||||
await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site });
|
await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site });
|
||||||
|
|
|
@ -21,8 +21,8 @@ import { CoreSharedModule } from '@/core/shared.module';
|
||||||
|
|
||||||
import { CoreGradesCoursePage } from './pages/course/course.page';
|
import { CoreGradesCoursePage } from './pages/course/course.page';
|
||||||
import { CoreGradesCoursePageModule } from './pages/course/course.module';
|
import { CoreGradesCoursePageModule } from './pages/course/course.module';
|
||||||
import { CoreGradesCoursesPage } from './pages/courses/courses';
|
import { CoreGradesCoursesPage } from './pages/courses/courses.page';
|
||||||
import { CoreGradesGradePage } from './pages/grade/grade';
|
import { CoreGradesGradePage } from './pages/grade/grade.page';
|
||||||
|
|
||||||
const mobileRoutes: Routes = [
|
const mobileRoutes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -39,6 +39,10 @@ const routes: Routes = [
|
||||||
path: CoreGradesMainMenuHandlerService.PAGE_NAME,
|
path: CoreGradesMainMenuHandlerService.PAGE_NAME,
|
||||||
loadChildren: () => import('@features/grades/grades-lazy.module').then(m => m.CoreGradesLazyModule),
|
loadChildren: () => import('@features/grades/grades-lazy.module').then(m => m.CoreGradesLazyModule),
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
path: 'user-grades/:courseId',
|
||||||
|
loadChildren: () => import('@features/grades/grades-course-lazy.module').then(m => m.CoreGradesCourseLazyModule),
|
||||||
|
},
|
||||||
];
|
];
|
||||||
|
|
||||||
const courseIndexRoutes: Routes = [
|
const courseIndexRoutes: Routes = [
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="grades.loaded" class="safe-area-page">
|
<core-loading [hideUntil]="grades.loaded" class="safe-area-page">
|
||||||
<core-empty-box *ngIf="grades.empty" icon="stats" [message]="'core.grades.nogradesreturned' | translate">
|
<core-empty-box *ngIf="grades.empty" icon="fas-chart-bar" [message]="'core.grades.nogradesreturned' | translate">
|
||||||
</core-empty-box>
|
</core-empty-box>
|
||||||
<div *ngIf="!grades.empty" class="core-grades-container">
|
<div *ngIf="!grades.empty" class="core-grades-container">
|
||||||
<table cellspacing="0" cellpadding="0" class="core-grades-table">
|
<table cellspacing="0" cellpadding="0" class="core-grades-table">
|
||||||
|
|
|
@ -45,9 +45,9 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
||||||
|
|
||||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
|
||||||
constructor(route: ActivatedRoute) {
|
constructor(protected route: ActivatedRoute) {
|
||||||
const courseId = parseInt(route.snapshot.params.courseId ?? route.snapshot.queryParams.courseId);
|
const courseId = CoreNavigator.getRouteNumberParam('courseId', { route })!;
|
||||||
const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.getCurrentSiteUserId());
|
const userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId();
|
||||||
const useSplitView = route.snapshot.data.useSplitView ?? true;
|
const useSplitView = route.snapshot.data.useSplitView ?? true;
|
||||||
const outsideGradesTab = route.snapshot.data.outsideGradesTab ?? false;
|
const outsideGradesTab = route.snapshot.data.outsideGradesTab ?? false;
|
||||||
|
|
||||||
|
|
|
@ -14,7 +14,7 @@
|
||||||
<core-loading [hideUntil]="courses.loaded">
|
<core-loading [hideUntil]="courses.loaded">
|
||||||
<core-empty-box
|
<core-empty-box
|
||||||
*ngIf="courses.empty"
|
*ngIf="courses.empty"
|
||||||
icon="stats"
|
icon="fas-chart-bar"
|
||||||
[message]="'core.grades.nogradesreturned' | translate"
|
[message]="'core.grades.nogradesreturned' | translate"
|
||||||
></core-empty-box>
|
></core-empty-box>
|
||||||
|
|
||||||
|
|
|
@ -11,7 +11,7 @@
|
||||||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||||
</ion-refresher>
|
</ion-refresher>
|
||||||
<core-loading [hideUntil]="gradeLoaded">
|
<core-loading [hideUntil]="gradeLoaded">
|
||||||
<core-empty-box *ngIf="!grade" icon="stats" [message]="'core.grades.nogradesreturned' | translate"></core-empty-box>
|
<core-empty-box *ngIf="!grade" icon="fas-chart-bar" [message]="'core.grades.nogradesreturned' | translate"></core-empty-box>
|
||||||
|
|
||||||
<ion-list *ngIf="grade">
|
<ion-list *ngIf="grade">
|
||||||
<ion-item *ngIf="grade.itemname && grade.link" class="ion-text-wrap" detail="true" [href]="grade.link" core-link
|
<ion-item *ngIf="grade.itemname && grade.link" class="ion-text-wrap" detail="true" [href]="grade.link" core-link
|
||||||
|
|
|
@ -12,7 +12,6 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { ActivatedRoute } from '@angular/router';
|
|
||||||
import { Component, OnInit } from '@angular/core';
|
import { Component, OnInit } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
|
@ -21,6 +20,7 @@ import { CoreGrades } from '@features/grades/services/grades';
|
||||||
import { CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper';
|
import { CoreGradesFormattedRow, CoreGradesHelper } from '@features/grades/services/grades-helper';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
|
import { CoreNavigator } from '@services/navigator';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Page that displays activity grade.
|
* Page that displays activity grade.
|
||||||
|
@ -37,10 +37,10 @@ export class CoreGradesGradePage implements OnInit {
|
||||||
grade?: CoreGradesFormattedRow | null;
|
grade?: CoreGradesFormattedRow | null;
|
||||||
gradeLoaded = false;
|
gradeLoaded = false;
|
||||||
|
|
||||||
constructor(route: ActivatedRoute) {
|
constructor() {
|
||||||
this.courseId = parseInt(route.snapshot.params.courseId ?? route.snapshot.parent?.params.courseId);
|
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||||
this.gradeId = parseInt(route.snapshot.params.gradeId);
|
this.gradeId = CoreNavigator.getRouteNumberParam('gradeId')!;
|
||||||
this.userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.getCurrentSiteUserId());
|
this.userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
|
@ -19,6 +19,8 @@ import { CorePushNotifications } from '@features/pushnotifications/services/push
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreLogger } from '@singletons/logger';
|
import { CoreLogger } from '@singletons/logger';
|
||||||
import { CoreWSExternalWarning } from '@services/ws';
|
import { CoreWSExternalWarning } from '@services/ws';
|
||||||
|
import { CoreSiteWSPreSets } from '@classes/site';
|
||||||
|
import { CoreError } from '@classes/errors/error';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Service to provide grade functionalities.
|
* Service to provide grade functionalities.
|
||||||
|
@ -114,8 +116,8 @@ export class CoreGradesProvider {
|
||||||
const items = await this.getCourseGradesItems(courseId, userId, groupId, siteId, ignoreCache);
|
const items = await this.getCourseGradesItems(courseId, userId, groupId, siteId, ignoreCache);
|
||||||
|
|
||||||
return items;
|
return items;
|
||||||
} catch (error) {
|
} catch {
|
||||||
// Ignore while solving MDL-57255
|
// Ignore while solving MDL-57255 (fixed on 3.2.1)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -151,13 +153,13 @@ export class CoreGradesProvider {
|
||||||
userid: userId,
|
userid: userId,
|
||||||
groupid: groupId,
|
groupid: groupId,
|
||||||
};
|
};
|
||||||
const preSets = {
|
const preSets: CoreSiteWSPreSets = {
|
||||||
cacheKey: this.getCourseGradesItemsCacheKey(courseId, userId, groupId),
|
cacheKey: this.getCourseGradesItemsCacheKey(courseId, userId, groupId),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ignoreCache) {
|
if (ignoreCache) {
|
||||||
preSets['getFromCache'] = 0;
|
preSets.getFromCache = false;
|
||||||
preSets['emergencyCache'] = 0;
|
preSets.emergencyCache = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const grades = await site.read<CoreGradesGetUserGradeItemsWSResponse>(
|
const grades = await site.read<CoreGradesGetUserGradeItemsWSResponse>(
|
||||||
|
@ -167,7 +169,7 @@ export class CoreGradesProvider {
|
||||||
);
|
);
|
||||||
|
|
||||||
if (!grades?.usergrades?.[0]) {
|
if (!grades?.usergrades?.[0]) {
|
||||||
throw new Error('Couldn\'t get course grades items');
|
throw new CoreError('Couldn\'t get course grades items');
|
||||||
}
|
}
|
||||||
|
|
||||||
return grades.usergrades[0].gradeitems;
|
return grades.usergrades[0].gradeitems;
|
||||||
|
@ -198,19 +200,19 @@ export class CoreGradesProvider {
|
||||||
courseid: courseId,
|
courseid: courseId,
|
||||||
userid: userId,
|
userid: userId,
|
||||||
};
|
};
|
||||||
const preSets = {
|
const preSets: CoreSiteWSPreSets = {
|
||||||
cacheKey: this.getCourseGradesCacheKey(courseId, userId),
|
cacheKey: this.getCourseGradesCacheKey(courseId, userId),
|
||||||
};
|
};
|
||||||
|
|
||||||
if (ignoreCache) {
|
if (ignoreCache) {
|
||||||
preSets['getFromCache'] = 0;
|
preSets.getFromCache = false;
|
||||||
preSets['emergencyCache'] = 0;
|
preSets.emergencyCache = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
const table = await site.read<CoreGradesGetUserGradesTableWSResponse>('gradereport_user_get_grades_table', params, preSets);
|
const table = await site.read<CoreGradesGetUserGradesTableWSResponse>('gradereport_user_get_grades_table', params, preSets);
|
||||||
|
|
||||||
if (!table?.tables?.[0]) {
|
if (!table?.tables?.[0]) {
|
||||||
throw new Error('Coudln\'t get course grades table');
|
throw new CoreError('Coudln\'t get course grades table');
|
||||||
}
|
}
|
||||||
|
|
||||||
return table.tables[0];
|
return table.tables[0];
|
||||||
|
@ -228,7 +230,7 @@ export class CoreGradesProvider {
|
||||||
this.logger.debug('Get course grades');
|
this.logger.debug('Get course grades');
|
||||||
|
|
||||||
const params: CoreGradesGetOverviewCourseGradesWSParams = {};
|
const params: CoreGradesGetOverviewCourseGradesWSParams = {};
|
||||||
const preSets = {
|
const preSets: CoreSiteWSPreSets = {
|
||||||
cacheKey: this.getCoursesGradesCacheKey(),
|
cacheKey: this.getCoursesGradesCacheKey(),
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@ -252,9 +254,10 @@ export class CoreGradesProvider {
|
||||||
* @param siteId Site ID (empty for current site).
|
* @param siteId Site ID (empty for current site).
|
||||||
* @return Promise resolved when the data is invalidated.
|
* @return Promise resolved when the data is invalidated.
|
||||||
*/
|
*/
|
||||||
invalidateAllCourseGradesData(courseId: number, siteId?: string): Promise<void> {
|
async invalidateAllCourseGradesData(courseId: number, siteId?: string): Promise<void> {
|
||||||
return CoreSites.getSite(siteId)
|
const site = await CoreSites.getSite(siteId);
|
||||||
.then((site) => site.invalidateWsCacheForKeyStartingWith(this.getCourseGradesPrefixCacheKey(courseId)));
|
|
||||||
|
await site.invalidateWsCacheForKeyStartingWith(this.getCourseGradesPrefixCacheKey(courseId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -265,12 +268,11 @@ export class CoreGradesProvider {
|
||||||
* @param siteId Site id (empty for current site).
|
* @param siteId Site id (empty for current site).
|
||||||
* @return Promise resolved when the data is invalidated.
|
* @return Promise resolved when the data is invalidated.
|
||||||
*/
|
*/
|
||||||
invalidateCourseGradesData(courseId: number, userId?: number, siteId?: string): Promise<void> {
|
async invalidateCourseGradesData(courseId: number, userId?: number, siteId?: string): Promise<void> {
|
||||||
return CoreSites.getSite(siteId).then((site) => {
|
const site = await CoreSites.getSite(siteId);
|
||||||
userId = userId || site.getUserId();
|
userId = userId || site.getUserId();
|
||||||
|
|
||||||
return site.invalidateWsCacheForKey(this.getCourseGradesCacheKey(courseId, userId));
|
await site.invalidateWsCacheForKey(this.getCourseGradesCacheKey(courseId, userId));
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -279,8 +281,10 @@ export class CoreGradesProvider {
|
||||||
* @param siteId Site id (empty for current site).
|
* @param siteId Site id (empty for current site).
|
||||||
* @return Promise resolved when the data is invalidated.
|
* @return Promise resolved when the data is invalidated.
|
||||||
*/
|
*/
|
||||||
invalidateCoursesGradesData(siteId?: string): Promise<void> {
|
async invalidateCoursesGradesData(siteId?: string): Promise<void> {
|
||||||
return CoreSites.getSite(siteId).then((site) => site.invalidateWsCacheForKey(this.getCoursesGradesCacheKey()));
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
|
await site.invalidateWsCacheForKey(this.getCoursesGradesCacheKey());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -292,9 +296,10 @@ export class CoreGradesProvider {
|
||||||
* @param siteId Site id (empty for current site).
|
* @param siteId Site id (empty for current site).
|
||||||
* @return Promise resolved when the data is invalidated.
|
* @return Promise resolved when the data is invalidated.
|
||||||
*/
|
*/
|
||||||
invalidateCourseGradesItemsData(courseId: number, userId: number, groupId?: number, siteId?: string): Promise<void> {
|
async invalidateCourseGradesItemsData(courseId: number, userId: number, groupId?: number, siteId?: string): Promise<void> {
|
||||||
return CoreSites.getSite(siteId)
|
const site = await CoreSites.getSite(siteId);
|
||||||
.then((site) => site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId)));
|
|
||||||
|
await site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -304,16 +309,17 @@ export class CoreGradesProvider {
|
||||||
* @return Resolve with true if plugin is enabled, false otherwise.
|
* @return Resolve with true if plugin is enabled, false otherwise.
|
||||||
* @since Moodle 3.2
|
* @since Moodle 3.2
|
||||||
*/
|
*/
|
||||||
isCourseGradesEnabled(siteId?: string): Promise<boolean> {
|
async isCourseGradesEnabled(siteId?: string): Promise<boolean> {
|
||||||
return CoreSites.getSite(siteId).then((site) => {
|
const site = await CoreSites.getSite(siteId);
|
||||||
|
|
||||||
if (!site.wsAvailable('gradereport_overview_get_course_grades')) {
|
if (!site.wsAvailable('gradereport_overview_get_course_grades')) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Now check that the configurable mygradesurl is pointing to the gradereport_overview plugin.
|
// Now check that the configurable mygradesurl is pointing to the gradereport_overview plugin.
|
||||||
const url = site.getStoredConfig('mygradesurl') || '';
|
const url = site.getStoredConfig('mygradesurl') || '';
|
||||||
|
|
||||||
return url.indexOf('/grade/report/overview/') !== -1;
|
return url.indexOf('/grade/report/overview/') !== -1;
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -323,13 +329,14 @@ export class CoreGradesProvider {
|
||||||
* @param siteId Site ID. If not defined, current site.
|
* @param siteId Site ID. If not defined, current site.
|
||||||
* @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
* @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
|
||||||
*/
|
*/
|
||||||
isPluginEnabledForCourse(courseId: number, siteId?: string): Promise<boolean> {
|
async isPluginEnabledForCourse(courseId?: number, siteId?: string): Promise<boolean> {
|
||||||
if (!courseId) {
|
if (!courseId) {
|
||||||
return Promise.reject(null);
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
return CoreCourses.getUserCourse(courseId, true, siteId)
|
const course = await CoreCourses.getUserCourse(courseId, true, siteId);
|
||||||
.then((course) => !(course && typeof course.showgrades != 'undefined' && !course.showgrades));
|
|
||||||
|
return !(course && typeof course.showgrades != 'undefined' && !course.showgrades);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -373,9 +380,11 @@ export class CoreGradesProvider {
|
||||||
CorePushNotifications.logViewEvent(courseId, name, 'grades', wsName, { userid: userId });
|
CorePushNotifications.logViewEvent(courseId, name, 'grades', wsName, { userid: userId });
|
||||||
}
|
}
|
||||||
|
|
||||||
const site = await CoreSites.getCurrentSite();
|
const site = CoreSites.getCurrentSite();
|
||||||
|
|
||||||
await site?.write(wsName, { courseid: courseId, userid: userId });
|
const params: CoreGradesGradereportViewGradeReportWSParams = { courseid: courseId, userid: userId };
|
||||||
|
|
||||||
|
await site?.write(wsName, params);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -389,13 +398,13 @@ export class CoreGradesProvider {
|
||||||
courseId = CoreSites.getCurrentSiteHomeId();
|
courseId = CoreSites.getCurrentSiteHomeId();
|
||||||
}
|
}
|
||||||
|
|
||||||
const params = {
|
const params: CoreGradesGradereportViewGradeReportWSParams = {
|
||||||
courseid: courseId,
|
courseid: courseId,
|
||||||
};
|
};
|
||||||
|
|
||||||
CorePushNotifications.logViewListEvent('grades', 'gradereport_overview_view_grade_report', params);
|
CorePushNotifications.logViewListEvent('grades', 'gradereport_overview_view_grade_report', params);
|
||||||
|
|
||||||
const site = await CoreSites.getCurrentSite();
|
const site = CoreSites.getCurrentSite();
|
||||||
|
|
||||||
await site?.write('gradereport_overview_view_grade_report', params);
|
await site?.write('gradereport_overview_view_grade_report', params);
|
||||||
}
|
}
|
||||||
|
@ -404,6 +413,14 @@ export class CoreGradesProvider {
|
||||||
|
|
||||||
export const CoreGrades = makeSingleton(CoreGradesProvider);
|
export const CoreGrades = makeSingleton(CoreGradesProvider);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Params of gradereport_user_view_grade_report and gradereport_overview_view_grade_report WS.
|
||||||
|
*/
|
||||||
|
type CoreGradesGradereportViewGradeReportWSParams = {
|
||||||
|
courseid: number; // Id of the course.
|
||||||
|
userid?: number; // Id of the user, 0 means current user.
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Params of gradereport_user_get_grade_items WS.
|
* Params of gradereport_user_get_grade_items WS.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -54,8 +54,8 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa
|
||||||
*
|
*
|
||||||
* @return Whether or not the handler is enabled on a site level.
|
* @return Whether or not the handler is enabled on a site level.
|
||||||
*/
|
*/
|
||||||
isEnabled(): Promise<boolean> {
|
async isEnabled(): Promise<boolean> {
|
||||||
return Promise.resolve(true);
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -44,7 +44,7 @@ export class CoreGradesMainMenuHandlerService implements CoreMainMenuHandler {
|
||||||
*/
|
*/
|
||||||
getDisplayData(): CoreMainMenuHandlerData {
|
getDisplayData(): CoreMainMenuHandlerData {
|
||||||
return {
|
return {
|
||||||
icon: 'stats-chart',
|
icon: 'fas-chart-bar',
|
||||||
title: 'core.grades.grades',
|
title: 'core.grades.grades',
|
||||||
page: CoreGradesMainMenuHandlerService.PAGE_NAME,
|
page: CoreGradesMainMenuHandlerService.PAGE_NAME,
|
||||||
class: 'core-grades-coursesgrades-handler',
|
class: 'core-grades-coursesgrades-handler',
|
||||||
|
|
|
@ -34,79 +34,41 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
|
||||||
name = 'CoreGrades:viewGrades';
|
name = 'CoreGrades:viewGrades';
|
||||||
priority = 400;
|
priority = 400;
|
||||||
type = CoreUserDelegateService.TYPE_NEW_PAGE;
|
type = CoreUserDelegateService.TYPE_NEW_PAGE;
|
||||||
viewGradesEnabledCache = {};
|
cacheEnabled = true;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Clear view grades cache.
|
* @inheritdoc
|
||||||
* If a courseId and userId are specified, it will only delete the entry for that user and course.
|
|
||||||
*
|
|
||||||
* @param courseId Course ID.
|
|
||||||
* @param userId User ID.
|
|
||||||
*/
|
*/
|
||||||
clearViewGradesCache(courseId?: number, userId?: number): void {
|
async isEnabled(): Promise<boolean> {
|
||||||
if (courseId && userId) {
|
return true;
|
||||||
delete this.viewGradesEnabledCache[this.getCacheKey(courseId, userId)];
|
|
||||||
} else {
|
|
||||||
this.viewGradesEnabledCache = {};
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get a cache key to identify a course and a user.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param courseId Course ID.
|
|
||||||
* @param userId User ID.
|
|
||||||
* @return Cache key.
|
|
||||||
*/
|
*/
|
||||||
protected getCacheKey(courseId: number, userId: number): string {
|
async isEnabledForCourse(courseId?: number): Promise<boolean> {
|
||||||
return courseId + '#' + userId;
|
return CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if handler is enabled.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @return Always enabled.
|
|
||||||
*/
|
*/
|
||||||
isEnabled(): Promise<boolean> {
|
async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
|
||||||
return Promise.resolve(true);
|
return CoreUtils.promiseWorks(CoreGrades.getCourseGradesTable(courseId!, user.id));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if handler is enabled for this user in this context.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param user User to check.
|
|
||||||
* @param courseId Course ID.
|
|
||||||
* @return Promise resolved with true if enabled, resolved with false otherwise.
|
|
||||||
*/
|
|
||||||
async isEnabledForUser(user: CoreUserProfile, courseId: number): Promise<boolean> {
|
|
||||||
const cacheKey = this.getCacheKey(courseId, user.id);
|
|
||||||
const cache = this.viewGradesEnabledCache[cacheKey];
|
|
||||||
|
|
||||||
if (typeof cache != 'undefined') {
|
|
||||||
return cache;
|
|
||||||
}
|
|
||||||
|
|
||||||
const enabled = await CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false);
|
|
||||||
|
|
||||||
this.viewGradesEnabledCache[cacheKey] = enabled;
|
|
||||||
|
|
||||||
return enabled;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the data needed to render the handler.
|
|
||||||
*
|
|
||||||
* @return Data needed to render the handler.
|
|
||||||
*/
|
*/
|
||||||
getDisplayData(): CoreUserProfileHandlerData {
|
getDisplayData(): CoreUserProfileHandlerData {
|
||||||
return {
|
return {
|
||||||
icon: 'stats-chart',
|
icon: 'fas-chart-bar',
|
||||||
title: 'core.grades.grades',
|
title: 'core.grades.grades',
|
||||||
class: 'core-grades-user-handler',
|
class: 'core-grades-user-handler',
|
||||||
action: (event, user, courseId): void => {
|
action: (event, user, courseId): void => {
|
||||||
event.preventDefault();
|
event.preventDefault();
|
||||||
event.stopPropagation();
|
event.stopPropagation();
|
||||||
CoreNavigator.navigateToSitePath(`/grades/${courseId}`, {
|
CoreNavigator.navigateToSitePath(`/user-grades/${courseId}`, {
|
||||||
params: { userId: user.id },
|
params: { userId: user.id },
|
||||||
});
|
});
|
||||||
},
|
},
|
||||||
|
|
|
@ -55,24 +55,12 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
async isEnabledForUser(
|
async isEnabledForCourse(
|
||||||
user: CoreUserProfile,
|
|
||||||
courseId?: number,
|
courseId?: number,
|
||||||
): Promise<boolean> {
|
): Promise<boolean> {
|
||||||
// First check if it's enabled for the user.
|
|
||||||
const enabledForUser = CoreSitePlugins.isHandlerEnabledForUser(
|
|
||||||
user.id,
|
|
||||||
this.handlerSchema.restricttocurrentuser,
|
|
||||||
this.initResult?.restrict,
|
|
||||||
);
|
|
||||||
|
|
||||||
if (!enabledForUser) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
courseId = courseId || CoreSites.getCurrentSiteHomeId();
|
courseId = courseId || CoreSites.getCurrentSiteHomeId();
|
||||||
|
|
||||||
// Enabled for user, check if it's enabled for the course.
|
// Check if it's enabled for the course.
|
||||||
return CoreSitePlugins.isHandlerEnabledForCourse(
|
return CoreSitePlugins.isHandlerEnabledForCourse(
|
||||||
courseId,
|
courseId,
|
||||||
this.handlerSchema.restricttoenrolledcourses,
|
this.handlerSchema.restricttoenrolledcourses,
|
||||||
|
@ -80,6 +68,19 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @inheritdoc
|
||||||
|
*/
|
||||||
|
async isEnabledForUser(
|
||||||
|
user: CoreUserProfile,
|
||||||
|
): Promise<boolean> {
|
||||||
|
return CoreSitePlugins.isHandlerEnabledForUser(
|
||||||
|
user.id,
|
||||||
|
this.handlerSchema.restricttocurrentuser,
|
||||||
|
this.initResult?.restrict,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @inheritdoc
|
* @inheritdoc
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -12,7 +12,7 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
|
|
||||||
import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
|
import { ActivatedRouteSnapshot } from '@angular/router';
|
||||||
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
import { AfterViewInit, Component, OnDestroy, OnInit, ViewChild } from '@angular/core';
|
||||||
import { IonRefresher } from '@ionic/angular';
|
import { IonRefresher } from '@ionic/angular';
|
||||||
|
|
||||||
|
@ -43,8 +43,8 @@ export class CoreUserParticipantsPage implements OnInit, AfterViewInit, OnDestro
|
||||||
|
|
||||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||||
|
|
||||||
constructor(route: ActivatedRoute) {
|
constructor() {
|
||||||
const courseId = parseInt(route.snapshot.queryParams.courseId);
|
const courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||||
|
|
||||||
this.participants = new CoreUserParticipantsManager(CoreUserParticipantsPage, courseId);
|
this.participants = new CoreUserParticipantsManager(CoreUserParticipantsPage, courseId);
|
||||||
}
|
}
|
|
@ -31,35 +31,23 @@ export class CoreUserProfileMailHandlerService implements CoreUserProfileHandler
|
||||||
type = CoreUserDelegateService.TYPE_COMMUNICATION;
|
type = CoreUserDelegateService.TYPE_COMMUNICATION;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if handler is enabled.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @return Always enabled.
|
|
||||||
*/
|
*/
|
||||||
async isEnabled(): Promise<boolean> {
|
async isEnabled(): Promise<boolean> {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Check if handler is enabled for this user in this context.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @param user User to check.
|
|
||||||
* @param courseId Course ID.
|
|
||||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
|
||||||
* @param admOptions Course admin options for current user. See CoreCoursesProvider.getUserAdministrationOptions.
|
|
||||||
* @return Promise resolved with true if enabled, resolved with false otherwise.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
|
||||||
async isEnabledForUser(user: CoreUserProfile, courseId: number, navOptions?: unknown, admOptions?: unknown): Promise<boolean> {
|
|
||||||
return user.id != CoreSites.getCurrentSiteUserId() && !!user.email;
|
return user.id != CoreSites.getCurrentSiteUserId() && !!user.email;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the handler.
|
* @inheritdoc
|
||||||
*
|
|
||||||
* @return Data needed to render the handler.
|
|
||||||
*/
|
*/
|
||||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
getDisplayData(): CoreUserProfileHandlerData {
|
||||||
getDisplayData(user: CoreUserProfile, courseId: number): CoreUserProfileHandlerData {
|
|
||||||
return {
|
return {
|
||||||
icon: 'mail',
|
icon: 'mail',
|
||||||
title: 'core.user.sendemail',
|
title: 'core.user.sendemail',
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { Subject, BehaviorSubject } from 'rxjs';
|
||||||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||||
import { CoreUtils } from '@services/utils/utils';
|
import { CoreUtils } from '@services/utils/utils';
|
||||||
import { CoreEvents } from '@singletons/events';
|
import { CoreEvents } from '@singletons/events';
|
||||||
import { CoreUserProfile } from './user';
|
import { CoreUserProfile, CoreUserProvider } from './user';
|
||||||
import { makeSingleton } from '@singletons';
|
import { makeSingleton } from '@singletons';
|
||||||
import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
|
@ -42,21 +42,36 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler {
|
||||||
type: string;
|
type: string;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether or not the handler is enabled for a user.
|
* If isEnabledForUser Cache should be enabled.
|
||||||
|
*/
|
||||||
|
cacheEnabled?: boolean;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled for a course.
|
||||||
*
|
*
|
||||||
* @param user User object.
|
|
||||||
* @param courseId Course ID where to show.
|
* @param courseId Course ID where to show.
|
||||||
* @param navOptions Navigation options for the course.
|
* @param navOptions Navigation options for the course.
|
||||||
* @param admOptions Admin options for the course.
|
* @param admOptions Admin options for the course.
|
||||||
* @return Whether or not the handler is enabled for a user.
|
* @return Whether or not the handler is enabled for a user.
|
||||||
*/
|
*/
|
||||||
isEnabledForUser(
|
isEnabledForCourse?(
|
||||||
user: CoreUserProfile,
|
|
||||||
courseId?: number,
|
courseId?: number,
|
||||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
): Promise<boolean>;
|
): Promise<boolean>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether or not the handler is enabled for a user.
|
||||||
|
*
|
||||||
|
* @param user User object.
|
||||||
|
* @param courseId Course ID where to show.
|
||||||
|
* @return Whether or not the handler is enabled for a user.
|
||||||
|
*/
|
||||||
|
isEnabledForUser?(
|
||||||
|
user: CoreUserProfile,
|
||||||
|
courseId?: number,
|
||||||
|
): Promise<boolean>;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the data needed to render the handler.
|
* Returns the data needed to render the handler.
|
||||||
*
|
*
|
||||||
|
@ -156,6 +171,11 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
|
||||||
*/
|
*/
|
||||||
static readonly UPDATE_HANDLER_EVENT = 'CoreUserDelegate_update_handler_event';
|
static readonly UPDATE_HANDLER_EVENT = 'CoreUserDelegate_update_handler_event';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Cache object that checks enabled for use.
|
||||||
|
*/
|
||||||
|
protected enabledForUserCache: Record<string, Record<string, boolean>> = {};
|
||||||
|
|
||||||
protected featurePrefix = 'CoreUserDelegate_';
|
protected featurePrefix = 'CoreUserDelegate_';
|
||||||
|
|
||||||
// Hold the handlers and the observable to notify them for each user.
|
// Hold the handlers and the observable to notify them for each user.
|
||||||
|
@ -186,6 +206,14 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
|
||||||
Object.assign(handler.data, data.data);
|
Object.assign(handler.data, data.data);
|
||||||
this.userHandlers[data.userId].observable.next(this.userHandlers[data.userId].handlers);
|
this.userHandlers[data.userId].observable.next(this.userHandlers[data.userId].handlers);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
CoreEvents.on(CoreEvents.LOGOUT, () => {
|
||||||
|
this.clearHandlerCache();
|
||||||
|
});
|
||||||
|
|
||||||
|
CoreEvents.on(CoreUserProvider.PROFILE_REFRESHED, (data) => {
|
||||||
|
this.clearHandlerCache(data.courseId, data.userId);
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -267,7 +295,7 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
|
||||||
const handler = this.handlers[name];
|
const handler = this.handlers[name];
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const enabled = await handler.isEnabledForUser(user, courseId, navOptions, admOptions);
|
const enabled = await this.getAndCacheEnabledForUserFromHandler(handler, user, courseId, navOptions, admOptions);
|
||||||
|
|
||||||
if (enabled) {
|
if (enabled) {
|
||||||
userData.handlers.push({
|
userData.handlers.push({
|
||||||
|
@ -288,6 +316,91 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
|
||||||
userData.observable.next(userData.handlers);
|
userData.observable.next(userData.handlers);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Helper funtion to get enabled for user from the handler.
|
||||||
|
*
|
||||||
|
* @param handler Handler object.
|
||||||
|
* @param user User object.
|
||||||
|
* @param courseId Course ID where to show.
|
||||||
|
* @param navOptions Navigation options for the course.
|
||||||
|
* @param admOptions Admin options for the course.
|
||||||
|
* @return Whether or not the handler is enabled for a user.
|
||||||
|
*/
|
||||||
|
protected async getAndCacheEnabledForUserFromHandler(
|
||||||
|
handler: CoreUserProfileHandler,
|
||||||
|
user: CoreUserProfile,
|
||||||
|
courseId?: number,
|
||||||
|
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||||
|
): Promise<boolean> {
|
||||||
|
if (handler.isEnabledForCourse) {
|
||||||
|
const enabledOnCourse = await handler.isEnabledForCourse(courseId, navOptions, admOptions);
|
||||||
|
|
||||||
|
if (!enabledOnCourse) {
|
||||||
|
// If is not enabled in the course, is not enabled for the user.
|
||||||
|
// Do not cache if this is false.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!handler.cacheEnabled) {
|
||||||
|
if (!handler.isEnabledForUser) {
|
||||||
|
// True by default.
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return handler.isEnabledForUser(user, courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof this.enabledForUserCache[handler.name] == 'undefined') {
|
||||||
|
this.enabledForUserCache[handler.name] = {};
|
||||||
|
}
|
||||||
|
|
||||||
|
const cacheKey = this.getCacheKey(courseId, user.id);
|
||||||
|
const cache = this.enabledForUserCache[handler.name][cacheKey];
|
||||||
|
|
||||||
|
if (typeof cache != 'undefined') {
|
||||||
|
return cache;
|
||||||
|
}
|
||||||
|
|
||||||
|
let enabled = true; // Default value.
|
||||||
|
if (handler.isEnabledForUser) {
|
||||||
|
enabled = await handler.isEnabledForUser(user, courseId);
|
||||||
|
}
|
||||||
|
|
||||||
|
this.enabledForUserCache[handler.name][cacheKey] = enabled;
|
||||||
|
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear handler enabled for user cache.
|
||||||
|
* If a courseId and userId are specified, it will only delete the entry for that user and course.
|
||||||
|
*
|
||||||
|
* @param courseId Course ID.
|
||||||
|
* @param userId User ID.
|
||||||
|
*/
|
||||||
|
protected clearHandlerCache(courseId?: number, userId?: number): void {
|
||||||
|
if (courseId && userId) {
|
||||||
|
Object.keys(this.enabledHandlers).forEach((name) => {
|
||||||
|
delete this.enabledForUserCache[name][this.getCacheKey(courseId, userId)];
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
this.enabledForUserCache = {};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Get a cache key to identify a course and a user.
|
||||||
|
*
|
||||||
|
* @param courseId Course ID.
|
||||||
|
* @param userId User ID.
|
||||||
|
* @return Cache key.
|
||||||
|
*/
|
||||||
|
protected getCacheKey(courseId = 0, userId = 0): string {
|
||||||
|
return courseId + '#' + userId;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreUserDelegate = makeSingleton(CoreUserDelegateService);
|
export const CoreUserDelegate = makeSingleton(CoreUserDelegateService);
|
||||||
|
|
|
@ -820,9 +820,22 @@ export class CoreUserProvider {
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export const CoreUser = makeSingleton(CoreUserProvider);
|
export const CoreUser = makeSingleton(CoreUserProvider);
|
||||||
|
|
||||||
|
declare module '@singletons/events' {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Augment CoreEventsData interface with events specific to this service.
|
||||||
|
*
|
||||||
|
* @see https://www.typescriptlang.org/docs/handbook/declaration-merging.html#module-augmentation
|
||||||
|
*/
|
||||||
|
export interface CoreEventsData {
|
||||||
|
[CoreUserProvider.PROFILE_REFRESHED]: CoreUserProfileRefreshedData;
|
||||||
|
[CoreUserProvider.PROFILE_PICTURE_UPDATED]: CoreUserProfilePictureUpdatedData;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Data passed to PROFILE_REFRESHED event.
|
* Data passed to PROFILE_REFRESHED event.
|
||||||
*/
|
*/
|
||||||
|
|
|
@ -18,7 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
|
||||||
import { CoreSharedModule } from '@/core/shared.module';
|
import { CoreSharedModule } from '@/core/shared.module';
|
||||||
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
import { CoreSearchComponentsModule } from '@features/search/components/components.module';
|
||||||
|
|
||||||
import { CoreUserParticipantsPage } from './pages/participants/participants';
|
import { CoreUserParticipantsPage } from './pages/participants/participants.page';
|
||||||
|
|
||||||
const routes: Routes = [
|
const routes: Routes = [
|
||||||
{
|
{
|
||||||
|
|
|
@ -52,10 +52,11 @@ export type CoreNavigationOptions = {
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Options for CoreNavigatorService#getCurrentRoute method.
|
* Route options to get route or params values.
|
||||||
*/
|
*/
|
||||||
type GetCurrentRouteOptions = Partial<{
|
export type CoreNavigatorCurrentRouteOptions = Partial<{
|
||||||
parentRoute: ActivatedRoute;
|
params: Params; // Params to get the value from.
|
||||||
|
route: ActivatedRoute; // Current Route.
|
||||||
pageComponent: unknown;
|
pageComponent: unknown;
|
||||||
}>;
|
}>;
|
||||||
|
|
||||||
|
@ -246,8 +247,8 @@ export class CoreNavigatorService {
|
||||||
/**
|
/**
|
||||||
* Iterately get the params checking parent routes.
|
* Iterately get the params checking parent routes.
|
||||||
*
|
*
|
||||||
* @param route Current route.
|
|
||||||
* @param name Name of the parameter.
|
* @param name Name of the parameter.
|
||||||
|
* @param route Current route.
|
||||||
* @return Value of the parameter, undefined if not found.
|
* @return Value of the parameter, undefined if not found.
|
||||||
*/
|
*/
|
||||||
protected getRouteSnapshotParam<T = unknown>(name: string, route?: ActivatedRoute): T | undefined {
|
protected getRouteSnapshotParam<T = unknown>(name: string, route?: ActivatedRoute): T | undefined {
|
||||||
|
@ -270,18 +271,21 @@ export class CoreNavigatorService {
|
||||||
* unless there's a new navigation to the page.
|
* unless there's a new navigation to the page.
|
||||||
*
|
*
|
||||||
* @param name Name of the parameter.
|
* @param name Name of the parameter.
|
||||||
* @param params Optional params to get the value from. If missing, it will autodetect.
|
* @param routeOptions Optional routeOptions to get the params or route value from. If missing, it will autodetect.
|
||||||
* @return Value of the parameter, undefined if not found.
|
* @return Value of the parameter, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getRouteParam<T = unknown>(name: string, params?: Params): T | undefined {
|
getRouteParam<T = unknown>(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined {
|
||||||
let value: any;
|
let value: any;
|
||||||
|
|
||||||
if (!params) {
|
if (!routeOptions.params) {
|
||||||
const route = this.getCurrentRoute();
|
let route = this.getCurrentRoute();
|
||||||
|
if (!route?.snapshot && routeOptions.route) {
|
||||||
|
route = routeOptions.route;
|
||||||
|
}
|
||||||
|
|
||||||
value = this.getRouteSnapshotParam(name, route);
|
value = this.getRouteSnapshotParam(name, route);
|
||||||
} else {
|
} else {
|
||||||
value = params[name];
|
value = routeOptions.params[name];
|
||||||
}
|
}
|
||||||
|
|
||||||
if (typeof value == 'undefined') {
|
if (typeof value == 'undefined') {
|
||||||
|
@ -309,11 +313,11 @@ export class CoreNavigatorService {
|
||||||
* Angular router automatically converts numbers to string, this function automatically converts it back to number.
|
* Angular router automatically converts numbers to string, this function automatically converts it back to number.
|
||||||
*
|
*
|
||||||
* @param name Name of the parameter.
|
* @param name Name of the parameter.
|
||||||
* @param params Optional params to get the value from. If missing, it will autodetect.
|
* @param routeOptions Optional routeOptions to get the params or route value from. If missing, it will autodetect.
|
||||||
* @return Value of the parameter, undefined if not found.
|
* @return Value of the parameter, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getRouteNumberParam(name: string, params?: Params): number | undefined {
|
getRouteNumberParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): number | undefined {
|
||||||
const value = this.getRouteParam<string>(name, params);
|
const value = this.getRouteParam<string>(name, routeOptions);
|
||||||
|
|
||||||
return value !== undefined ? Number(value) : value;
|
return value !== undefined ? Number(value) : value;
|
||||||
}
|
}
|
||||||
|
@ -323,13 +327,25 @@ export class CoreNavigatorService {
|
||||||
* Angular router automatically converts booleans to string, this function automatically converts it back to boolean.
|
* Angular router automatically converts booleans to string, this function automatically converts it back to boolean.
|
||||||
*
|
*
|
||||||
* @param name Name of the parameter.
|
* @param name Name of the parameter.
|
||||||
* @param params Optional params to get the value from. If missing, it will autodetect.
|
* @param routeOptions Optional routeOptions to get the params or route value from. If missing, it will autodetect.
|
||||||
* @return Value of the parameter, undefined if not found.
|
* @return Value of the parameter, undefined if not found.
|
||||||
*/
|
*/
|
||||||
getRouteBooleanParam(name: string, params?: Params): boolean | undefined {
|
getRouteBooleanParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): boolean | undefined {
|
||||||
const value = this.getRouteParam<string>(name, params);
|
const value = this.getRouteParam<string>(name, routeOptions);
|
||||||
|
|
||||||
return value !== undefined ? Boolean(value) : value;
|
if (typeof value == 'undefined') {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CoreUtils.isTrueOrOne(value)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (CoreUtils.isFalseOrZero(value)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return Boolean(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -345,25 +361,25 @@ export class CoreNavigatorService {
|
||||||
* Get current activated route.
|
* Get current activated route.
|
||||||
*
|
*
|
||||||
* @param options
|
* @param options
|
||||||
* - parent: Parent route, if this isn't provided the current active route will be used.
|
* - route: Parent route, if this isn't provided the current active route will be used.
|
||||||
* - pageComponent: Page component of the route to find, if this isn't provided the deepest route in the hierarchy
|
* - pageComponent: Page component of the route to find, if this isn't provided the deepest route in the hierarchy
|
||||||
* will be returned.
|
* will be returned.
|
||||||
* @return Current activated route.
|
* @return Current activated route.
|
||||||
*/
|
*/
|
||||||
getCurrentRoute(): ActivatedRoute;
|
getCurrentRoute(): ActivatedRoute;
|
||||||
getCurrentRoute(options: GetCurrentRouteOptions): ActivatedRoute | null;
|
getCurrentRoute(options: CoreNavigatorCurrentRouteOptions): ActivatedRoute | null;
|
||||||
getCurrentRoute({ parentRoute, pageComponent }: GetCurrentRouteOptions = {}): ActivatedRoute | null {
|
getCurrentRoute({ route, pageComponent }: CoreNavigatorCurrentRouteOptions = {}): ActivatedRoute | null {
|
||||||
parentRoute = parentRoute ?? Router.routerState.root;
|
route = route ?? Router.routerState.root;
|
||||||
|
|
||||||
if (pageComponent && parentRoute.component === pageComponent) {
|
if (pageComponent && route.component === pageComponent) {
|
||||||
return parentRoute;
|
return route;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (parentRoute.firstChild) {
|
if (route.firstChild) {
|
||||||
return this.getCurrentRoute({ parentRoute: parentRoute.firstChild, pageComponent });
|
return this.getCurrentRoute({ route: route.firstChild, pageComponent });
|
||||||
}
|
}
|
||||||
|
|
||||||
return pageComponent ? null : parentRoute;
|
return pageComponent ? null : route;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -309,7 +309,8 @@ img[alt] {
|
||||||
// Activity modules
|
// Activity modules
|
||||||
.core-module-icon {
|
.core-module-icon {
|
||||||
--size: 24px;
|
--size: 24px;
|
||||||
width: auto;
|
width: var(--size);
|
||||||
|
height: var(--size);
|
||||||
max-width: var(--size);
|
max-width: var(--size);
|
||||||
max-height: var(--size);
|
max-height: var(--size);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue