commit
37d6bb0464
|
@ -20,6 +20,7 @@ import { AddonFilterModule } from './filter/filter.module';
|
|||
import { AddonUserProfileFieldModule } from './userprofilefield/userprofilefield.module';
|
||||
import { AddonBadgesModule } from './badges/badges.module';
|
||||
import { AddonCalendarModule } from './calendar/calendar.module';
|
||||
import { AddonCourseCompletionModule } from './coursecompletion/coursecompletion.module';
|
||||
import { AddonNotificationsModule } from './notifications/notifications.module';
|
||||
import { AddonMessageOutputModule } from './messageoutput/messageoutput.module';
|
||||
import { AddonMessagesModule } from './messages/messages.module';
|
||||
|
@ -35,6 +36,7 @@ import { AddonRemoteThemesModule } from './remotethemes/remotethemes.module';
|
|||
AddonBadgesModule,
|
||||
AddonBlogModule,
|
||||
AddonCalendarModule,
|
||||
AddonCourseCompletionModule,
|
||||
AddonMessagesModule,
|
||||
AddonPrivateFilesModule,
|
||||
AddonFilterModule,
|
||||
|
|
|
@ -19,8 +19,8 @@ import { conditionalRoutes } from '@/app/app-routing.module';
|
|||
import { CoreScreen } from '@services/screen';
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
|
||||
import { AddonBadgesIssuedBadgePage } from './pages/issued-badge/issued-badge';
|
||||
import { AddonBadgesUserBadgesPage } from './pages/user-badges/user-badges';
|
||||
import { AddonBadgesIssuedBadgePage } from './pages/issued-badge/issued-badge.page';
|
||||
import { AddonBadgesUserBadgesPage } from './pages/user-badges/user-badges.page';
|
||||
|
||||
const mobileRoutes: Routes = [
|
||||
{
|
||||
|
|
|
@ -20,8 +20,9 @@ import { CoreDomUtils } from '@services/utils/dom';
|
|||
import { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
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 { CoreNavigator } from '@services/navigator';
|
||||
|
||||
/**
|
||||
* Page that displays the list of calendar events.
|
||||
|
@ -37,9 +38,9 @@ export class AddonBadgesUserBadgesPage implements AfterViewInit, OnDestroy {
|
|||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
constructor(route: ActivatedRoute) {
|
||||
const courseId = parseInt(route.snapshot.queryParams.courseId ?? 0); // Use 0 for site badges.
|
||||
const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.getCurrentSiteUserId());
|
||||
constructor() {
|
||||
const courseId = CoreNavigator.getRouteNumberParam('courseId') ?? 0; // Use 0 for site badges.
|
||||
const userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId();
|
||||
|
||||
this.badges = new AddonBadgesUserBadgesManager(AddonBadgesUserBadgesPage, courseId, userId);
|
||||
}
|
|
@ -14,7 +14,6 @@
|
|||
|
||||
import { Injectable } from '@angular/core';
|
||||
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 { CoreNavigator } from '@services/navigator';
|
||||
import { makeSingleton } from '@singletons';
|
||||
|
@ -42,13 +41,11 @@ export class AddonBadgesUserHandlerService implements CoreUserProfileHandler {
|
|||
/**
|
||||
* Check if handler is enabled for this user in this context.
|
||||
*
|
||||
* @param user User to check.
|
||||
* @param courseId Course ID.
|
||||
* @param navOptions Course navigation options for current user. See CoreCoursesProvider.getUserNavigationOptions.
|
||||
* @return True if enabled, false otherwise.
|
||||
*/
|
||||
async isEnabledForUser(
|
||||
user: CoreUserProfile,
|
||||
async isEnabledForCourse(
|
||||
courseId: number,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): Promise<boolean> {
|
||||
|
|
|
@ -131,9 +131,8 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i
|
|||
* @param entry Selected entry.
|
||||
*/
|
||||
gotoCoureListModType(entry: AddonBlockActivityModuleEntry): void {
|
||||
CoreNavigator.navigateToSitePath('course/list-mod-type', {
|
||||
CoreNavigator.navigateToSitePath('course/' + this.getCourseId() + '/list-mod-type', {
|
||||
params: {
|
||||
courseId: this.getCourseId(),
|
||||
modName: entry.modName,
|
||||
title: entry.name,
|
||||
},
|
||||
|
|
|
@ -29,21 +29,14 @@ export class AddonBlockCompletionStatusHandlerService extends CoreBlockBaseHandl
|
|||
blockName = 'completionstatus';
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the block.
|
||||
*
|
||||
* @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.
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
|
||||
// @todo
|
||||
|
||||
return {
|
||||
title: 'addon.block_completionstatus.pluginname',
|
||||
class: 'addon-block-completion-status',
|
||||
component: CoreBlockOnlyTitleComponent,
|
||||
link: 'AddonCourseCompletionReportPage',
|
||||
link: 'coursecompletion',
|
||||
linkParams: {
|
||||
courseId: instanceId,
|
||||
},
|
||||
|
|
|
@ -29,22 +29,17 @@ export class AddonBlockSelfCompletionHandlerService extends CoreBlockBaseHandler
|
|||
blockName = 'selfcompletion';
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the block.
|
||||
*
|
||||
* @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.
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(block: CoreCourseBlock, contextLevel: string, instanceId: number): CoreBlockHandlerData {
|
||||
// @todo
|
||||
|
||||
return {
|
||||
title: 'addon.block_selfcompletion.pluginname',
|
||||
class: 'addon-block-self-completion',
|
||||
component: CoreBlockOnlyTitleComponent,
|
||||
link: 'AddonCourseCompletionReportPage',
|
||||
linkParams: { courseId: instanceId },
|
||||
link: 'coursecompletion',
|
||||
linkParams: {
|
||||
courseId: instanceId,
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -35,13 +35,6 @@ export class AddonBlogUserHandlerService implements CoreUserProfileHandler {
|
|||
return AddonBlog.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabledForUser(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* @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
|
||||
// limitations under the License.
|
||||
|
||||
import { NgModule } from '@angular/core';
|
||||
// @todo import { AddonCourseCompletionCourseOptionHandler } from './services/course-option-handler';
|
||||
// @todo import { AddonCourseCompletionUserHandler } from './services/user-handler';
|
||||
// @todo import { AddonCourseCompletionComponentsModule } from './components/components.module';
|
||||
// @todo import { CoreCourseOptionsDelegate } from '@features/course/services/options-delegate';
|
||||
// @todo import { CoreUserDelegate } from '@features/user/services/user-delegate';
|
||||
import { APP_INITIALIZER, NgModule, Type } from '@angular/core';
|
||||
import { Routes } from '@angular/router';
|
||||
import { CoreCourseIndexRoutingModule } from '@features/course/pages/index/index-routing.module';
|
||||
import { CoreCourseOptionsDelegate } from '@features/course/services/course-options-delegate';
|
||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
|
||||
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({
|
||||
imports: [
|
||||
// AddonCourseCompletionComponentsModule,
|
||||
CoreMainMenuTabRoutingModule.forChild(routes),
|
||||
CoreCourseIndexRoutingModule.forChild({ children: routes }),
|
||||
],
|
||||
providers: [
|
||||
// AddonCourseCompletionCourseOptionHandler,
|
||||
// AddonCourseCompletionUserHandler,
|
||||
{
|
||||
provide: APP_INITIALIZER,
|
||||
multi: true,
|
||||
deps: [],
|
||||
useFactory: () => async () => {
|
||||
CoreUserDelegate.registerHandler(AddonCourseCompletionUserHandler.instance);
|
||||
CoreCourseOptionsDelegate.registerHandler(AddonCourseCompletionCourseOptionHandler.instance);
|
||||
},
|
||||
},
|
||||
],
|
||||
})
|
||||
export class AddonCourseCompletionModule {
|
||||
|
||||
/* @todo constructor(
|
||||
courseOptionsDelegate: CoreCourseOptionsDelegate,
|
||||
courseOptionHandler: AddonCourseCompletionCourseOptionHandler,
|
||||
userDelegate: CoreUserDelegate,
|
||||
userHandler: AddonCourseCompletionUserHandler,
|
||||
) {
|
||||
// Register handlers.
|
||||
courseOptionsDelegate.registerHandler(courseOptionHandler);
|
||||
userDelegate.registerHandler(userHandler);
|
||||
}*/
|
||||
|
||||
}
|
||||
export class AddonCourseCompletionModule {}
|
||||
|
|
|
@ -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.
|
||||
* @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) {
|
||||
throw new CoreError('No courseId provided');
|
||||
return false;
|
||||
}
|
||||
|
||||
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.loaded = false;
|
||||
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', params) || undefined;
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId', params) || undefined;
|
||||
this.showKeyboard = CoreNavigator.getRouteBooleanParam('showKeyboard', params) || false;
|
||||
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', { params }) || undefined;
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId', { params }) || undefined;
|
||||
this.showKeyboard = CoreNavigator.getRouteBooleanParam('showKeyboard', { params }) || false;
|
||||
|
||||
await this.fetchData();
|
||||
|
||||
|
|
|
@ -138,8 +138,8 @@ export class AddonMessagesDiscussions35Page implements OnInit, OnDestroy {
|
|||
*/
|
||||
ngOnInit(): void {
|
||||
this.route.queryParams.subscribe(async (params) => {
|
||||
const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', params) ||
|
||||
CoreNavigator.getRouteNumberParam('userId', params) || undefined;
|
||||
const discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', { params }) ||
|
||||
CoreNavigator.getRouteNumberParam('userId', { params }) || undefined;
|
||||
|
||||
if (this.loaded && this.discussionUserId == discussionUserId) {
|
||||
return;
|
||||
|
|
|
@ -273,9 +273,9 @@ export class AddonMessagesGroupConversationsPage implements OnInit, OnDestroy {
|
|||
ngOnInit(): void {
|
||||
this.route.queryParams.subscribe(async (params) => {
|
||||
// Conversation to load.
|
||||
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', params) || undefined;
|
||||
this.conversationId = CoreNavigator.getRouteNumberParam('conversationId', { params }) || undefined;
|
||||
if (!this.conversationId) {
|
||||
this.discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', params) || undefined;
|
||||
this.discussionUserId = CoreNavigator.getRouteNumberParam('discussionUserId', { params }) || undefined;
|
||||
}
|
||||
|
||||
if (this.conversationId || this.discussionUserId) {
|
||||
|
|
|
@ -40,6 +40,13 @@ export class AddonMessagesSendMessageUserHandlerService implements CoreUserProfi
|
|||
return AddonMessages.isPluginEnabled();
|
||||
}
|
||||
|
||||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabledForCourse(): Promise<boolean> {
|
||||
return !!CoreSites.getCurrentSite();
|
||||
}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
|
||||
const currentSite = CoreSites.getCurrentSite();
|
||||
|
||||
if (!currentSite) {
|
||||
return false;
|
||||
}
|
||||
const currentSite = CoreSites.getCurrentSite()!;
|
||||
|
||||
// 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.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
this.submitId = CoreNavigator.getRouteNumberParam('submitId') || 0;
|
||||
this.blindId = CoreNavigator.getRouteNumberParam('blindId', params);
|
||||
this.blindId = CoreNavigator.getRouteNumberParam('blindId', { params });
|
||||
|
||||
this.fetchSubmission().finally(() => {
|
||||
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_BADGES_SERVICES } from '@addons/badges/badges.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';
|
||||
import { ADDON_MESSAGEOUTPUT_SERVICES } from '@addons/messageoutput/messageoutput.module';
|
||||
import { ADDON_MESSAGES_SERVICES } from '@addons/messages/messages.module';
|
||||
|
@ -281,6 +282,7 @@ export class CoreCompileProvider {
|
|||
...extraProviders,
|
||||
...ADDON_BADGES_SERVICES,
|
||||
...ADDON_CALENDAR_SERVICES,
|
||||
...ADDON_COURSECOMPLETION_SERVICES,
|
||||
// @todo ...ADDON_COMPETENCY_SERVICES,
|
||||
...ADDON_MESSAGEOUTPUT_SERVICES,
|
||||
...ADDON_MESSAGES_SERVICES,
|
||||
|
|
|
@ -61,9 +61,8 @@ export class CoreContentLinksModuleListHandler extends CoreContentLinksHandlerBa
|
|||
|
||||
return [{
|
||||
action: (siteId): void => {
|
||||
CoreNavigator.navigateToSitePath('course/list-mod-type', {
|
||||
CoreNavigator.navigateToSitePath('course/' + params.id + '/list-mod-type', {
|
||||
params: {
|
||||
courseId: params.id,
|
||||
modName: this.modName,
|
||||
title: this.title || Translate.instant('addon.mod_' + this.modName + '.modulenameplural'),
|
||||
},
|
||||
|
|
|
@ -22,18 +22,23 @@ const routes: Routes = [
|
|||
pathMatch: 'full',
|
||||
},
|
||||
{
|
||||
path: 'index',
|
||||
path: ':courseId',
|
||||
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')
|
||||
.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),
|
||||
},
|
||||
{
|
||||
path: ':courseId/preview',
|
||||
loadChildren: () =>
|
||||
import('./pages/preview/preview.module').then(m => m.CoreCoursePreviewPageModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -460,7 +460,10 @@ export class CoreCourseContentsPage implements OnInit, OnDestroy {
|
|||
* Open the course summary
|
||||
*/
|
||||
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 { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreCourseIndexPage } from './index';
|
||||
import { CoreCourseIndexPage } from './index.page';
|
||||
import { COURSE_INDEX_ROUTES } from './index-routing.module';
|
||||
|
||||
function buildRoutes(injector: Injector): Routes {
|
||||
|
|
|
@ -123,7 +123,6 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy {
|
|||
handlers.forEach((handler, index) => {
|
||||
handler.data.page = CoreTextUtils.concatenatePaths(this.currentPagePath, handler.data.page);
|
||||
handler.data.pageParams = handler.data.pageParams || {};
|
||||
handler.data.pageParams.courseId = this.course!.id;
|
||||
|
||||
// Check if this handler should be the first selected tab.
|
||||
if (this.firstTabName && handler.name == this.firstTabName) {
|
|
@ -16,7 +16,7 @@ import { NgModule } from '@angular/core';
|
|||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
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';
|
||||
|
||||
const routes: Routes = [
|
||||
|
|
|
@ -13,8 +13,8 @@
|
|||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<core-loading [hideUntil]="dataLoaded">
|
||||
<div class="core-course-thumb-parallax">
|
||||
<div *ngIf="courseImageUrl" (click)="openCourse()" class="core-course-thumb">
|
||||
<div *ngIf="courseImageUrl" class="core-course-thumb-parallax">
|
||||
<div (click)="openCourse()" class="core-course-thumb">
|
||||
<img [src]="courseImageUrl" core-external-content alt=""/>
|
||||
</div>
|
||||
</div>
|
|
@ -16,13 +16,12 @@ import { NgModule } from '@angular/core';
|
|||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
import { CoreSharedModule } from '@/core/shared.module';
|
||||
import { CoreCoursesCoursePreviewPage } from './course-preview';
|
||||
import { CoreCoursesComponentsModule } from '../../components/components.module';
|
||||
import { CoreCoursePreviewPage } from './preview.page';
|
||||
|
||||
const routes: Routes = [
|
||||
{
|
||||
path: '',
|
||||
component: CoreCoursesCoursePreviewPage,
|
||||
component: CoreCoursePreviewPage,
|
||||
},
|
||||
];
|
||||
|
||||
|
@ -30,11 +29,10 @@ const routes: Routes = [
|
|||
imports: [
|
||||
RouterModule.forChild(routes),
|
||||
CoreSharedModule,
|
||||
CoreCoursesComponentsModule,
|
||||
],
|
||||
declarations: [
|
||||
CoreCoursesCoursePreviewPage,
|
||||
CoreCoursePreviewPage,
|
||||
],
|
||||
exports: [RouterModule],
|
||||
})
|
||||
export class CoreCoursesCoursePreviewPageModule { }
|
||||
export class CoreCoursePreviewPageModule { }
|
|
@ -15,7 +15,7 @@
|
|||
import { Component, OnDestroy, NgZone, OnInit } from '@angular/core';
|
||||
import { ModalController, IonRefresher } from '@ionic/angular';
|
||||
import { CoreApp } from '@services/app';
|
||||
import { CoreEventCourseStatusChanged, CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreEventObserver, CoreEvents } from '@singletons/events';
|
||||
import { CoreSites } from '@services/sites';
|
||||
import { CoreDomUtils } from '@services/utils/dom';
|
||||
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 { Translate } from '@singletons';
|
||||
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';
|
||||
|
||||
/**
|
||||
* Page that allows "previewing" a course and enrolling in it if enabled and not enrolled.
|
||||
*/
|
||||
@Component({
|
||||
selector: 'page-core-courses-course-preview',
|
||||
templateUrl: 'course-preview.html',
|
||||
styleUrls: ['course-preview.scss'],
|
||||
selector: 'page-core-course-preview',
|
||||
templateUrl: 'preview.html',
|
||||
styleUrls: ['preview.scss'],
|
||||
})
|
||||
export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy {
|
||||
export class CoreCoursePreviewPage implements OnInit, OnDestroy {
|
||||
|
||||
course?: CoreCourseSearchedData;
|
||||
isEnrolled = false;
|
||||
|
@ -84,7 +84,7 @@ export class CoreCoursesCoursePreviewPage implements OnInit, OnDestroy {
|
|||
|
||||
if (this.downloadCourseEnabled) {
|
||||
// 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) {
|
||||
this.updateCourseStatus(data.status);
|
||||
}
|
|
@ -16,7 +16,7 @@ import { NgModule } from '@angular/core';
|
|||
import { RouterModule, Routes } from '@angular/router';
|
||||
|
||||
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';
|
||||
|
||||
const routes: Routes = [
|
||||
|
|
|
@ -1848,7 +1848,7 @@ export class CoreCourseHelperProvider {
|
|||
params = params || {};
|
||||
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 });
|
||||
|
||||
// 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();
|
||||
|
||||
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) {
|
||||
CoreCourseHelper.openCourse(this.course);
|
||||
} 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')
|
||||
.then(m => m.CoreCoursesMyCoursesPageModule),
|
||||
},
|
||||
{
|
||||
path: 'preview',
|
||||
loadChildren: () =>
|
||||
import('./pages/course-preview/course-preview.module')
|
||||
.then(m => m.CoreCoursesCoursePreviewPageModule),
|
||||
},
|
||||
];
|
||||
|
||||
@NgModule({
|
||||
|
|
|
@ -59,18 +59,14 @@ export class CoreCoursesEnrolPushClickHandlerService implements CorePushNotifica
|
|||
const params: Params = {
|
||||
course: result.course,
|
||||
};
|
||||
let page: string;
|
||||
let page = 'course/' + courseId;
|
||||
|
||||
if (notification.contexturl?.indexOf('user/index.php') != -1) {
|
||||
// Open the participants tab.
|
||||
page = 'course';
|
||||
params.selectedTab = 'user_participants'; // @todo: Set this when participants is done.
|
||||
} else if (result.enrolled) {
|
||||
// User is still enrolled, open the course.
|
||||
page = 'course';
|
||||
} else {
|
||||
params.selectedTab = 'participants'; // @todo: Set this when participants is done.
|
||||
} else if (!result.enrolled) {
|
||||
// User not enrolled anymore, open the preview page.
|
||||
page = 'courses/preview';;
|
||||
page += '/preview';
|
||||
}
|
||||
|
||||
await CoreNavigator.navigateToSitePath(page, { params, siteId: notification.site });
|
||||
|
|
|
@ -75,14 +75,11 @@ export class CoreCoursesRequestPushClickHandlerService implements CorePushNotifi
|
|||
const params: Params = {
|
||||
course: result.course,
|
||||
};
|
||||
let page: string;
|
||||
let page = 'course/' + courseId;
|
||||
|
||||
if (result.enrolled) {
|
||||
// User is still enrolled, open the course.
|
||||
page = 'course';
|
||||
} else {
|
||||
if (!result.enrolled) {
|
||||
// User not enrolled (shouldn't happen), open the preview page.
|
||||
page = 'courses/preview';
|
||||
page += '/preview';
|
||||
}
|
||||
|
||||
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 { CoreGradesCoursePageModule } from './pages/course/course.module';
|
||||
import { CoreGradesCoursesPage } from './pages/courses/courses';
|
||||
import { CoreGradesGradePage } from './pages/grade/grade';
|
||||
import { CoreGradesCoursesPage } from './pages/courses/courses.page';
|
||||
import { CoreGradesGradePage } from './pages/grade/grade.page';
|
||||
|
||||
const mobileRoutes: Routes = [
|
||||
{
|
||||
|
|
|
@ -39,6 +39,10 @@ const routes: Routes = [
|
|||
path: CoreGradesMainMenuHandlerService.PAGE_NAME,
|
||||
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 = [
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<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>
|
||||
<div *ngIf="!grades.empty" class="core-grades-container">
|
||||
<table cellspacing="0" cellpadding="0" class="core-grades-table">
|
||||
|
|
|
@ -45,9 +45,9 @@ export class CoreGradesCoursePage implements AfterViewInit, OnDestroy {
|
|||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
constructor(route: ActivatedRoute) {
|
||||
const courseId = parseInt(route.snapshot.params.courseId ?? route.snapshot.queryParams.courseId);
|
||||
const userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.getCurrentSiteUserId());
|
||||
constructor(protected route: ActivatedRoute) {
|
||||
const courseId = CoreNavigator.getRouteNumberParam('courseId', { route })!;
|
||||
const userId = CoreNavigator.getRouteNumberParam('userId', { route }) ?? CoreSites.getCurrentSiteUserId();
|
||||
const useSplitView = route.snapshot.data.useSplitView ?? true;
|
||||
const outsideGradesTab = route.snapshot.data.outsideGradesTab ?? false;
|
||||
|
||||
|
|
|
@ -14,7 +14,7 @@
|
|||
<core-loading [hideUntil]="courses.loaded">
|
||||
<core-empty-box
|
||||
*ngIf="courses.empty"
|
||||
icon="stats"
|
||||
icon="fas-chart-bar"
|
||||
[message]="'core.grades.nogradesreturned' | translate"
|
||||
></core-empty-box>
|
||||
|
||||
|
|
|
@ -11,7 +11,7 @@
|
|||
<ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
|
||||
</ion-refresher>
|
||||
<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-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
|
||||
// limitations under the License.
|
||||
|
||||
import { ActivatedRoute } from '@angular/router';
|
||||
import { Component, OnInit } from '@angular/core';
|
||||
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 { CoreSites } from '@services/sites';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreNavigator } from '@services/navigator';
|
||||
|
||||
/**
|
||||
* Page that displays activity grade.
|
||||
|
@ -37,10 +37,10 @@ export class CoreGradesGradePage implements OnInit {
|
|||
grade?: CoreGradesFormattedRow | null;
|
||||
gradeLoaded = false;
|
||||
|
||||
constructor(route: ActivatedRoute) {
|
||||
this.courseId = parseInt(route.snapshot.params.courseId ?? route.snapshot.parent?.params.courseId);
|
||||
this.gradeId = parseInt(route.snapshot.params.gradeId);
|
||||
this.userId = parseInt(route.snapshot.queryParams.userId ?? CoreSites.getCurrentSiteUserId());
|
||||
constructor() {
|
||||
this.courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
this.gradeId = CoreNavigator.getRouteNumberParam('gradeId')!;
|
||||
this.userId = CoreNavigator.getRouteNumberParam('userId') ?? CoreSites.getCurrentSiteUserId();
|
||||
}
|
||||
|
||||
/**
|
|
@ -19,6 +19,8 @@ import { CorePushNotifications } from '@features/pushnotifications/services/push
|
|||
import { makeSingleton } from '@singletons';
|
||||
import { CoreLogger } from '@singletons/logger';
|
||||
import { CoreWSExternalWarning } from '@services/ws';
|
||||
import { CoreSiteWSPreSets } from '@classes/site';
|
||||
import { CoreError } from '@classes/errors/error';
|
||||
|
||||
/**
|
||||
* Service to provide grade functionalities.
|
||||
|
@ -114,8 +116,8 @@ export class CoreGradesProvider {
|
|||
const items = await this.getCourseGradesItems(courseId, userId, groupId, siteId, ignoreCache);
|
||||
|
||||
return items;
|
||||
} catch (error) {
|
||||
// Ignore while solving MDL-57255
|
||||
} catch {
|
||||
// Ignore while solving MDL-57255 (fixed on 3.2.1)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -151,13 +153,13 @@ export class CoreGradesProvider {
|
|||
userid: userId,
|
||||
groupid: groupId,
|
||||
};
|
||||
const preSets = {
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getCourseGradesItemsCacheKey(courseId, userId, groupId),
|
||||
};
|
||||
|
||||
if (ignoreCache) {
|
||||
preSets['getFromCache'] = 0;
|
||||
preSets['emergencyCache'] = 0;
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
const grades = await site.read<CoreGradesGetUserGradeItemsWSResponse>(
|
||||
|
@ -167,7 +169,7 @@ export class CoreGradesProvider {
|
|||
);
|
||||
|
||||
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;
|
||||
|
@ -198,19 +200,19 @@ export class CoreGradesProvider {
|
|||
courseid: courseId,
|
||||
userid: userId,
|
||||
};
|
||||
const preSets = {
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getCourseGradesCacheKey(courseId, userId),
|
||||
};
|
||||
|
||||
if (ignoreCache) {
|
||||
preSets['getFromCache'] = 0;
|
||||
preSets['emergencyCache'] = 0;
|
||||
preSets.getFromCache = false;
|
||||
preSets.emergencyCache = false;
|
||||
}
|
||||
|
||||
const table = await site.read<CoreGradesGetUserGradesTableWSResponse>('gradereport_user_get_grades_table', params, preSets);
|
||||
|
||||
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];
|
||||
|
@ -228,7 +230,7 @@ export class CoreGradesProvider {
|
|||
this.logger.debug('Get course grades');
|
||||
|
||||
const params: CoreGradesGetOverviewCourseGradesWSParams = {};
|
||||
const preSets = {
|
||||
const preSets: CoreSiteWSPreSets = {
|
||||
cacheKey: this.getCoursesGradesCacheKey(),
|
||||
};
|
||||
|
||||
|
@ -252,9 +254,10 @@ export class CoreGradesProvider {
|
|||
* @param siteId Site ID (empty for current site).
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateAllCourseGradesData(courseId: number, siteId?: string): Promise<void> {
|
||||
return CoreSites.getSite(siteId)
|
||||
.then((site) => site.invalidateWsCacheForKeyStartingWith(this.getCourseGradesPrefixCacheKey(courseId)));
|
||||
async invalidateAllCourseGradesData(courseId: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
await site.invalidateWsCacheForKeyStartingWith(this.getCourseGradesPrefixCacheKey(courseId));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -265,12 +268,11 @@ export class CoreGradesProvider {
|
|||
* @param siteId Site id (empty for current site).
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateCourseGradesData(courseId: number, userId?: number, siteId?: string): Promise<void> {
|
||||
return CoreSites.getSite(siteId).then((site) => {
|
||||
userId = userId || site.getUserId();
|
||||
async invalidateCourseGradesData(courseId: number, userId?: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
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).
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateCoursesGradesData(siteId?: string): Promise<void> {
|
||||
return CoreSites.getSite(siteId).then((site) => site.invalidateWsCacheForKey(this.getCoursesGradesCacheKey()));
|
||||
async invalidateCoursesGradesData(siteId?: string): Promise<void> {
|
||||
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).
|
||||
* @return Promise resolved when the data is invalidated.
|
||||
*/
|
||||
invalidateCourseGradesItemsData(courseId: number, userId: number, groupId?: number, siteId?: string): Promise<void> {
|
||||
return CoreSites.getSite(siteId)
|
||||
.then((site) => site.invalidateWsCacheForKey(this.getCourseGradesItemsCacheKey(courseId, userId, groupId)));
|
||||
async invalidateCourseGradesItemsData(courseId: number, userId: number, groupId?: number, siteId?: string): Promise<void> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
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.
|
||||
* @since Moodle 3.2
|
||||
*/
|
||||
isCourseGradesEnabled(siteId?: string): Promise<boolean> {
|
||||
return CoreSites.getSite(siteId).then((site) => {
|
||||
if (!site.wsAvailable('gradereport_overview_get_course_grades')) {
|
||||
return false;
|
||||
}
|
||||
// Now check that the configurable mygradesurl is pointing to the gradereport_overview plugin.
|
||||
const url = site.getStoredConfig('mygradesurl') || '';
|
||||
async isCourseGradesEnabled(siteId?: string): Promise<boolean> {
|
||||
const site = await CoreSites.getSite(siteId);
|
||||
|
||||
return url.indexOf('/grade/report/overview/') !== -1;
|
||||
});
|
||||
if (!site.wsAvailable('gradereport_overview_get_course_grades')) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Now check that the configurable mygradesurl is pointing to the gradereport_overview plugin.
|
||||
const url = site.getStoredConfig('mygradesurl') || '';
|
||||
|
||||
return url.indexOf('/grade/report/overview/') !== -1;
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -323,13 +329,14 @@ export class CoreGradesProvider {
|
|||
* @param siteId Site ID. If not defined, current site.
|
||||
* @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) {
|
||||
return Promise.reject(null);
|
||||
return false;
|
||||
}
|
||||
|
||||
return CoreCourses.getUserCourse(courseId, true, siteId)
|
||||
.then((course) => !(course && typeof course.showgrades != 'undefined' && !course.showgrades));
|
||||
const course = await CoreCourses.getUserCourse(courseId, true, siteId);
|
||||
|
||||
return !(course && typeof course.showgrades != 'undefined' && !course.showgrades);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -373,9 +380,11 @@ export class CoreGradesProvider {
|
|||
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();
|
||||
}
|
||||
|
||||
const params = {
|
||||
const params: CoreGradesGradereportViewGradeReportWSParams = {
|
||||
courseid: courseId,
|
||||
};
|
||||
|
||||
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);
|
||||
}
|
||||
|
@ -404,6 +413,14 @@ export class 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.
|
||||
*/
|
||||
|
|
|
@ -54,8 +54,8 @@ export class CoreGradesCourseOptionHandlerService implements CoreCourseOptionsHa
|
|||
*
|
||||
* @return Whether or not the handler is enabled on a site level.
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -44,7 +44,7 @@ export class CoreGradesMainMenuHandlerService implements CoreMainMenuHandler {
|
|||
*/
|
||||
getDisplayData(): CoreMainMenuHandlerData {
|
||||
return {
|
||||
icon: 'stats-chart',
|
||||
icon: 'fas-chart-bar',
|
||||
title: 'core.grades.grades',
|
||||
page: CoreGradesMainMenuHandlerService.PAGE_NAME,
|
||||
class: 'core-grades-coursesgrades-handler',
|
||||
|
|
|
@ -34,79 +34,41 @@ export class CoreGradesUserHandlerService implements CoreUserProfileHandler {
|
|||
name = 'CoreGrades:viewGrades';
|
||||
priority = 400;
|
||||
type = CoreUserDelegateService.TYPE_NEW_PAGE;
|
||||
viewGradesEnabledCache = {};
|
||||
cacheEnabled = true;
|
||||
|
||||
/**
|
||||
* Clear view grades 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.
|
||||
* @inheritdoc
|
||||
*/
|
||||
clearViewGradesCache(courseId?: number, userId?: number): void {
|
||||
if (courseId && userId) {
|
||||
delete this.viewGradesEnabledCache[this.getCacheKey(courseId, userId)];
|
||||
} else {
|
||||
this.viewGradesEnabledCache = {};
|
||||
}
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get a cache key to identify a course and a user.
|
||||
*
|
||||
* @param courseId Course ID.
|
||||
* @param userId User ID.
|
||||
* @return Cache key.
|
||||
* @inheritdoc
|
||||
*/
|
||||
protected getCacheKey(courseId: number, userId: number): string {
|
||||
return courseId + '#' + userId;
|
||||
async isEnabledForCourse(courseId?: number): Promise<boolean> {
|
||||
return CoreUtils.ignoreErrors(CoreGrades.isPluginEnabledForCourse(courseId), false);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled.
|
||||
*
|
||||
* @return Always enabled.
|
||||
* @inheritdoc
|
||||
*/
|
||||
isEnabled(): Promise<boolean> {
|
||||
return Promise.resolve(true);
|
||||
async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
|
||||
return CoreUtils.promiseWorks(CoreGrades.getCourseGradesTable(courseId!, user.id));
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled for this user in this context.
|
||||
*
|
||||
* @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.
|
||||
* @inheritdoc
|
||||
*/
|
||||
getDisplayData(): CoreUserProfileHandlerData {
|
||||
return {
|
||||
icon: 'stats-chart',
|
||||
icon: 'fas-chart-bar',
|
||||
title: 'core.grades.grades',
|
||||
class: 'core-grades-user-handler',
|
||||
action: (event, user, courseId): void => {
|
||||
event.preventDefault();
|
||||
event.stopPropagation();
|
||||
CoreNavigator.navigateToSitePath(`/grades/${courseId}`, {
|
||||
CoreNavigator.navigateToSitePath(`/user-grades/${courseId}`, {
|
||||
params: { userId: user.id },
|
||||
});
|
||||
},
|
||||
|
|
|
@ -55,24 +55,12 @@ export class CoreSitePluginsUserProfileHandler extends CoreSitePluginsBaseHandle
|
|||
/**
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabledForUser(
|
||||
user: CoreUserProfile,
|
||||
async isEnabledForCourse(
|
||||
courseId?: number,
|
||||
): 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();
|
||||
|
||||
// Enabled for user, check if it's enabled for the course.
|
||||
// Check if it's enabled for the course.
|
||||
return CoreSitePlugins.isHandlerEnabledForCourse(
|
||||
courseId,
|
||||
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
|
||||
*/
|
||||
|
|
|
@ -12,7 +12,7 @@
|
|||
// See the License for the specific language governing permissions and
|
||||
// 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 { IonRefresher } from '@ionic/angular';
|
||||
|
||||
|
@ -43,8 +43,8 @@ export class CoreUserParticipantsPage implements OnInit, AfterViewInit, OnDestro
|
|||
|
||||
@ViewChild(CoreSplitViewComponent) splitView!: CoreSplitViewComponent;
|
||||
|
||||
constructor(route: ActivatedRoute) {
|
||||
const courseId = parseInt(route.snapshot.queryParams.courseId);
|
||||
constructor() {
|
||||
const courseId = CoreNavigator.getRouteNumberParam('courseId')!;
|
||||
|
||||
this.participants = new CoreUserParticipantsManager(CoreUserParticipantsPage, courseId);
|
||||
}
|
|
@ -31,35 +31,23 @@ export class CoreUserProfileMailHandlerService implements CoreUserProfileHandler
|
|||
type = CoreUserDelegateService.TYPE_COMMUNICATION;
|
||||
|
||||
/**
|
||||
* Check if handler is enabled.
|
||||
*
|
||||
* @return Always enabled.
|
||||
* @inheritdoc
|
||||
*/
|
||||
async isEnabled(): Promise<boolean> {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if handler is enabled for this user in this context.
|
||||
*
|
||||
* @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.
|
||||
* @inheritdoc
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
async isEnabledForUser(user: CoreUserProfile, courseId: number, navOptions?: unknown, admOptions?: unknown): Promise<boolean> {
|
||||
async isEnabledForUser(user: CoreUserProfile): Promise<boolean> {
|
||||
return user.id != CoreSites.getCurrentSiteUserId() && !!user.email;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the data needed to render the handler.
|
||||
*
|
||||
* @return Data needed to render the handler.
|
||||
* @inheritdoc
|
||||
*/
|
||||
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||
getDisplayData(user: CoreUserProfile, courseId: number): CoreUserProfileHandlerData {
|
||||
getDisplayData(): CoreUserProfileHandlerData {
|
||||
return {
|
||||
icon: 'mail',
|
||||
title: 'core.user.sendemail',
|
||||
|
|
|
@ -18,7 +18,7 @@ import { Subject, BehaviorSubject } from 'rxjs';
|
|||
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
|
||||
import { CoreUtils } from '@services/utils/utils';
|
||||
import { CoreEvents } from '@singletons/events';
|
||||
import { CoreUserProfile } from './user';
|
||||
import { CoreUserProfile, CoreUserProvider } from './user';
|
||||
import { makeSingleton } from '@singletons';
|
||||
import { CoreCourses, CoreCourseUserAdminOrNavOptionIndexed } from '@features/courses/services/courses';
|
||||
import { CoreSites } from '@services/sites';
|
||||
|
@ -42,21 +42,36 @@ export interface CoreUserProfileHandler extends CoreDelegateHandler {
|
|||
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 navOptions Navigation options for the course.
|
||||
* @param admOptions Admin options for the course.
|
||||
* @return Whether or not the handler is enabled for a user.
|
||||
*/
|
||||
isEnabledForUser(
|
||||
user: CoreUserProfile,
|
||||
isEnabledForCourse?(
|
||||
courseId?: number,
|
||||
navOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
admOptions?: CoreCourseUserAdminOrNavOptionIndexed,
|
||||
): 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.
|
||||
*
|
||||
|
@ -156,6 +171,11 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
|
|||
*/
|
||||
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_';
|
||||
|
||||
// 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);
|
||||
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];
|
||||
|
||||
try {
|
||||
const enabled = await handler.isEnabledForUser(user, courseId, navOptions, admOptions);
|
||||
const enabled = await this.getAndCacheEnabledForUserFromHandler(handler, user, courseId, navOptions, admOptions);
|
||||
|
||||
if (enabled) {
|
||||
userData.handlers.push({
|
||||
|
@ -288,6 +316,91 @@ export class CoreUserDelegateService extends CoreDelegate<CoreUserProfileHandler
|
|||
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);
|
||||
|
|
|
@ -820,9 +820,22 @@ export class 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.
|
||||
*/
|
||||
|
|
|
@ -18,7 +18,7 @@ import { RouterModule, Routes } from '@angular/router';
|
|||
import { CoreSharedModule } from '@/core/shared.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 = [
|
||||
{
|
||||
|
|
|
@ -52,10 +52,11 @@ export type CoreNavigationOptions = {
|
|||
};
|
||||
|
||||
/**
|
||||
* Options for CoreNavigatorService#getCurrentRoute method.
|
||||
* Route options to get route or params values.
|
||||
*/
|
||||
type GetCurrentRouteOptions = Partial<{
|
||||
parentRoute: ActivatedRoute;
|
||||
export type CoreNavigatorCurrentRouteOptions = Partial<{
|
||||
params: Params; // Params to get the value from.
|
||||
route: ActivatedRoute; // Current Route.
|
||||
pageComponent: unknown;
|
||||
}>;
|
||||
|
||||
|
@ -246,8 +247,8 @@ export class CoreNavigatorService {
|
|||
/**
|
||||
* Iterately get the params checking parent routes.
|
||||
*
|
||||
* @param route Current route.
|
||||
* @param name Name of the parameter.
|
||||
* @param route Current route.
|
||||
* @return Value of the parameter, undefined if not found.
|
||||
*/
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
getRouteParam<T = unknown>(name: string, params?: Params): T | undefined {
|
||||
getRouteParam<T = unknown>(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): T | undefined {
|
||||
let value: any;
|
||||
|
||||
if (!params) {
|
||||
const route = this.getCurrentRoute();
|
||||
if (!routeOptions.params) {
|
||||
let route = this.getCurrentRoute();
|
||||
if (!route?.snapshot && routeOptions.route) {
|
||||
route = routeOptions.route;
|
||||
}
|
||||
|
||||
value = this.getRouteSnapshotParam(name, route);
|
||||
} else {
|
||||
value = params[name];
|
||||
value = routeOptions.params[name];
|
||||
}
|
||||
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
getRouteNumberParam(name: string, params?: Params): number | undefined {
|
||||
const value = this.getRouteParam<string>(name, params);
|
||||
getRouteNumberParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): number | undefined {
|
||||
const value = this.getRouteParam<string>(name, routeOptions);
|
||||
|
||||
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.
|
||||
*
|
||||
* @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.
|
||||
*/
|
||||
getRouteBooleanParam(name: string, params?: Params): boolean | undefined {
|
||||
const value = this.getRouteParam<string>(name, params);
|
||||
getRouteBooleanParam(name: string, routeOptions: CoreNavigatorCurrentRouteOptions = {}): boolean | undefined {
|
||||
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.
|
||||
*
|
||||
* @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
|
||||
* will be returned.
|
||||
* @return Current activated route.
|
||||
*/
|
||||
getCurrentRoute(): ActivatedRoute;
|
||||
getCurrentRoute(options: GetCurrentRouteOptions): ActivatedRoute | null;
|
||||
getCurrentRoute({ parentRoute, pageComponent }: GetCurrentRouteOptions = {}): ActivatedRoute | null {
|
||||
parentRoute = parentRoute ?? Router.routerState.root;
|
||||
getCurrentRoute(options: CoreNavigatorCurrentRouteOptions): ActivatedRoute | null;
|
||||
getCurrentRoute({ route, pageComponent }: CoreNavigatorCurrentRouteOptions = {}): ActivatedRoute | null {
|
||||
route = route ?? Router.routerState.root;
|
||||
|
||||
if (pageComponent && parentRoute.component === pageComponent) {
|
||||
return parentRoute;
|
||||
if (pageComponent && route.component === pageComponent) {
|
||||
return route;
|
||||
}
|
||||
|
||||
if (parentRoute.firstChild) {
|
||||
return this.getCurrentRoute({ parentRoute: parentRoute.firstChild, pageComponent });
|
||||
if (route.firstChild) {
|
||||
return this.getCurrentRoute({ route: route.firstChild, pageComponent });
|
||||
}
|
||||
|
||||
return pageComponent ? null : parentRoute;
|
||||
return pageComponent ? null : route;
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -309,7 +309,8 @@ img[alt] {
|
|||
// Activity modules
|
||||
.core-module-icon {
|
||||
--size: 24px;
|
||||
width: auto;
|
||||
width: var(--size);
|
||||
height: var(--size);
|
||||
max-width: var(--size);
|
||||
max-height: var(--size);
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue