forked from CIT/Vmeda.Online
		
	MOBILE-3629 coursecompletion: Implement coursecompletion feature
This commit is contained in:
		
							parent
							
								
									211e15d59c
								
							
						
					
					
						commit
						70809d79ec
					
				@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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,
 | 
			
		||||
            },
 | 
			
		||||
        };
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										41
									
								
								src/addons/coursecompletion/coursecompletion-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										41
									
								
								src/addons/coursecompletion/coursecompletion-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -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 {}
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										92
									
								
								src/addons/coursecompletion/pages/report/report.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										92
									
								
								src/addons/coursecompletion/pages/report/report.html
									
									
									
									
									
										Normal file
									
								
							@ -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>
 | 
			
		||||
							
								
								
									
										112
									
								
								src/addons/coursecompletion/pages/report/report.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										112
									
								
								src/addons/coursecompletion/pages/report/report.ts
									
									
									
									
									
										Normal file
									
								
							@ -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();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
@ -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);
 | 
			
		||||
							
								
								
									
										98
									
								
								src/addons/coursecompletion/services/handlers/user.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										98
									
								
								src/addons/coursecompletion/services/handlers/user.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,98 @@
 | 
			
		||||
// (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, CoreUserProvider } 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 { CoreEvents } from '@singletons/events';
 | 
			
		||||
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;
 | 
			
		||||
 | 
			
		||||
    protected enabledCache = {};
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        CoreEvents.on(CoreEvents.LOGOUT, () => {
 | 
			
		||||
            this.enabledCache = {};
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        CoreEvents.on(CoreUserProvider.PROFILE_REFRESHED, (data) => {
 | 
			
		||||
            const cacheKey = data.userId + '-' + data.courseId;
 | 
			
		||||
 | 
			
		||||
            delete this.enabledCache[cacheKey];
 | 
			
		||||
        });
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonCourseCompletion.isPluginViewEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabledForUser(user: CoreUserProfile, courseId?: number): Promise<boolean> {
 | 
			
		||||
        if (!courseId) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const courseEnabled = await AddonCourseCompletion.isPluginViewEnabledForCourse(courseId);
 | 
			
		||||
        // If is not enabled in the course, is not enabled for the user.
 | 
			
		||||
        if (!courseEnabled) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const cacheKey = user.id + '-' + courseId;
 | 
			
		||||
        if (typeof this.enabledCache[cacheKey] !== 'undefined') {
 | 
			
		||||
            return this.enabledCache[cacheKey];
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const enabled = await AddonCourseCompletion.isPluginViewEnabledForUser(courseId, user.id);
 | 
			
		||||
        this.enabledCache[cacheKey] = enabled;
 | 
			
		||||
 | 
			
		||||
        return enabled;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @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);
 | 
			
		||||
@ -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,
 | 
			
		||||
 | 
			
		||||
@ -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.
 | 
			
		||||
 */
 | 
			
		||||
 | 
			
		||||
@ -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…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user