forked from CIT/Vmeda.Online
		
	MOBILE-3652 resource: Add resource activity module
This commit is contained in:
		
							parent
							
								
									072d6e4699
								
							
						
					
					
						commit
						da1749ef32
					
				@ -22,6 +22,7 @@ import { AddonModLabelModule } from './label/label.module';
 | 
			
		||||
import { AddonModLessonModule } from './lesson/lesson.module';
 | 
			
		||||
import { AddonModPageModule } from './page/page.module';
 | 
			
		||||
import { AddonModQuizModule } from './quiz/quiz.module';
 | 
			
		||||
import { AddonModResourceModule } from './resource/resource.module';
 | 
			
		||||
import { AddonModUrlModule } from './url/url.module';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
@ -35,6 +36,7 @@ import { AddonModUrlModule } from './url/url.module';
 | 
			
		||||
        AddonModQuizModule,
 | 
			
		||||
        AddonModUrlModule,
 | 
			
		||||
        AddonModLabelModule,
 | 
			
		||||
        AddonModResourceModule,
 | 
			
		||||
        AddonModFolderModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [],
 | 
			
		||||
 | 
			
		||||
							
								
								
									
										36
									
								
								src/addons/mod/resource/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										36
									
								
								src/addons/mod/resource/components/components.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,36 @@
 | 
			
		||||
// (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 { NgModule } from '@angular/core';
 | 
			
		||||
 | 
			
		||||
import { CoreSharedModule } from '@/core/shared.module';
 | 
			
		||||
import { CoreCourseComponentsModule } from '@features/course/components/components.module';
 | 
			
		||||
 | 
			
		||||
import { AddonModResourceIndexComponent } from './index/index';
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModResourceIndexComponent,
 | 
			
		||||
    ],
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        CoreCourseComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
    ],
 | 
			
		||||
    exports: [
 | 
			
		||||
        AddonModResourceIndexComponent,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModResourceComponentsModule {}
 | 
			
		||||
@ -0,0 +1,50 @@
 | 
			
		||||
<!-- Buttons to add to the header. -->
 | 
			
		||||
<core-navbar-buttons slot="end">
 | 
			
		||||
    <core-context-menu>
 | 
			
		||||
        <core-context-menu-item *ngIf="externalUrl" [priority]="900" [content]="'core.openinbrowser' | translate"
 | 
			
		||||
            [href]="externalUrl" iconAction="fas-external-link-alt"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="description" [priority]="800" [content]="'core.moduleintro' | translate"
 | 
			
		||||
            (action)="expandDescription()" iconAction="fas-arrow-right"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="blog" [priority]="750" content="{{'addon.blog.blog' | translate}}"
 | 
			
		||||
            iconAction="far-newspaper" (action)="gotoBlog()"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item [priority]="700" [content]="'core.refresh' | translate" (action)="doRefresh(null, $event)"
 | 
			
		||||
            [iconAction]="refreshIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="prefetchStatusIcon" [priority]="600" [content]="prefetchText" (action)="prefetch($event)"
 | 
			
		||||
            [iconAction]="prefetchStatusIcon" [closeOnClick]="false"></core-context-menu-item>
 | 
			
		||||
        <core-context-menu-item *ngIf="size" [priority]="500" [content]="'core.clearstoreddata' | translate:{$a: size}"
 | 
			
		||||
            iconDescription="fas-archive" (action)="removeFiles($event)" iconAction="fas-trash" [closeOnClick]="false">
 | 
			
		||||
        </core-context-menu-item>
 | 
			
		||||
    </core-context-menu>
 | 
			
		||||
</core-navbar-buttons>
 | 
			
		||||
 | 
			
		||||
<!-- Content. -->
 | 
			
		||||
<core-loading [hideUntil]="loaded" class="core-loading-center safe-area-page">
 | 
			
		||||
 | 
			
		||||
    <core-course-module-description *ngIf="mode != 'iframe' && (mode != 'embedded' || displayDescription)"
 | 
			
		||||
        [description]="description" [component]="component" [componentId]="componentId" contextLevel="module"
 | 
			
		||||
        [contextInstanceId]="module!.id" [courseId]="courseId">
 | 
			
		||||
    </core-course-module-description>
 | 
			
		||||
 | 
			
		||||
    <ion-card class="core-warning-card" *ngIf="warning">
 | 
			
		||||
        <ion-item>
 | 
			
		||||
            <ion-icon name="fas-exclamation-triangle" slot="start"></ion-icon>
 | 
			
		||||
            <ion-label><span [innerHTML]="warning"></span></ion-label>
 | 
			
		||||
        </ion-item>
 | 
			
		||||
    </ion-card>
 | 
			
		||||
 | 
			
		||||
    <ng-container *ngIf="mode == 'iframe'">
 | 
			
		||||
        <core-iframe [src]="src"></core-iframe>
 | 
			
		||||
    </ng-container>
 | 
			
		||||
 | 
			
		||||
    <div *ngIf="mode == 'embedded'">
 | 
			
		||||
        <core-format-text [text]="contentText" [filter]="false"></core-format-text>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
    <div class="ion-padding" *ngIf="mode == 'external'">
 | 
			
		||||
        <ion-button expand="block" (click)="open()">
 | 
			
		||||
            <ion-icon name="far-file" slot="start"></ion-icon>
 | 
			
		||||
            {{ 'addon.mod_resource.openthefile' | translate }}
 | 
			
		||||
        </ion-button>
 | 
			
		||||
    </div>
 | 
			
		||||
 | 
			
		||||
</core-loading>
 | 
			
		||||
							
								
								
									
										177
									
								
								src/addons/mod/resource/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										177
									
								
								src/addons/mod/resource/components/index/index.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,177 @@
 | 
			
		||||
// (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 { Component, OnInit, Optional } from '@angular/core';
 | 
			
		||||
import { CoreError } from '@classes/errors/error';
 | 
			
		||||
import {
 | 
			
		||||
    CoreCourseModuleMainResourceComponent,
 | 
			
		||||
} from '@features/course/classes/main-resource-component';
 | 
			
		||||
import { CoreCourseContentsPage } from '@features/course/pages/contents/contents';
 | 
			
		||||
import { CoreCourse, CoreCourseWSModule } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { Translate } from '@singletons';
 | 
			
		||||
import {
 | 
			
		||||
    AddonModResource,
 | 
			
		||||
    AddonModResourceCustomData,
 | 
			
		||||
    AddonModResourceProvider,
 | 
			
		||||
    AddonModResourceResource,
 | 
			
		||||
} from '../../services/resource';
 | 
			
		||||
import { AddonModResourceHelper } from '../../services/resource-helper';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Component that displays a resource.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'addon-mod-resource-index',
 | 
			
		||||
    templateUrl: 'addon-mod-resource-index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModResourceIndexComponent extends CoreCourseModuleMainResourceComponent implements OnInit {
 | 
			
		||||
 | 
			
		||||
    component = AddonModResourceProvider.COMPONENT;
 | 
			
		||||
 | 
			
		||||
    canGetResource = false;
 | 
			
		||||
    mode = '';
 | 
			
		||||
    src = '';
 | 
			
		||||
    contentText = '';
 | 
			
		||||
    displayDescription = true;
 | 
			
		||||
    warning = '';
 | 
			
		||||
 | 
			
		||||
    constructor(@Optional() courseContentsPage?: CoreCourseContentsPage) {
 | 
			
		||||
        super('AddonModResourceIndexComponent', courseContentsPage);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Component being initialized.
 | 
			
		||||
     */
 | 
			
		||||
    async ngOnInit(): Promise<void> {
 | 
			
		||||
        super.ngOnInit();
 | 
			
		||||
 | 
			
		||||
        this.canGetResource = AddonModResource.isGetResourceWSAvailable();
 | 
			
		||||
 | 
			
		||||
        await this.loadContent();
 | 
			
		||||
        try {
 | 
			
		||||
            await AddonModResource.logView(this.module!.instance!, this.module!.name);
 | 
			
		||||
            CoreCourse.checkModuleCompletion(this.courseId!, this.module!.completiondata);
 | 
			
		||||
        } catch {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Perform the invalidate content function.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async invalidateContent(): Promise<void> {
 | 
			
		||||
        return AddonModResource.invalidateContent(this.module!.id, this.courseId!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download resource contents.
 | 
			
		||||
     *
 | 
			
		||||
     * @param refresh Whether we're refreshing data.
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async fetchContent(refresh?: boolean): Promise<void> {
 | 
			
		||||
        // Load module contents if needed. Passing refresh is needed to force reloading contents.
 | 
			
		||||
        await CoreCourse.loadModuleContents(this.module!, this.courseId, undefined, false, refresh);
 | 
			
		||||
 | 
			
		||||
        if (!this.module!.contents || !this.module!.contents.length) {
 | 
			
		||||
            throw new CoreError(Translate.instant('core.filenotfound'));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let resource: AddonModResourceResource | CoreCourseWSModule | undefined;
 | 
			
		||||
        let options: AddonModResourceCustomData = {};
 | 
			
		||||
 | 
			
		||||
        // Get the resource instance to get the latest name/description and to know if it's embedded.
 | 
			
		||||
        if (this.canGetResource) {
 | 
			
		||||
            resource = await CoreUtils.ignoreErrors(AddonModResource.getResourceData(this.courseId!, this.module!.id));
 | 
			
		||||
            this.description = resource?.intro || '';
 | 
			
		||||
            options = resource?.displayoptions ? CoreTextUtils.unserialize(resource.displayoptions) : {};
 | 
			
		||||
        } else {
 | 
			
		||||
            resource = await CoreUtils.ignoreErrors(CoreCourse.getModule(this.module!.id, this.courseId));
 | 
			
		||||
            this.description = resource?.description || '';
 | 
			
		||||
            options = resource?.customdata ? CoreTextUtils.unserialize(CoreTextUtils.parseJSON(resource.customdata)) : {};
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            if (resource) {
 | 
			
		||||
                this.displayDescription = typeof options.printintro == 'undefined' || !!options.printintro;
 | 
			
		||||
                this.dataRetrieved.emit(resource);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (AddonModResourceHelper.isDisplayedInIframe(this.module!)) {
 | 
			
		||||
                const downloadResult = await this.downloadResourceIfNeeded(refresh, true);
 | 
			
		||||
                const src = await AddonModResourceHelper.getIframeSrc(this.module!);
 | 
			
		||||
                this.mode = 'iframe';
 | 
			
		||||
 | 
			
		||||
                if (this.src && src.toString() == this.src.toString()) {
 | 
			
		||||
                    // Re-loading same page.
 | 
			
		||||
                    // Set it to empty and then re-set the src in the next digest so it detects it has changed.
 | 
			
		||||
                    this.src = '';
 | 
			
		||||
                    setTimeout(() => {
 | 
			
		||||
                        this.src = src;
 | 
			
		||||
                    });
 | 
			
		||||
                } else {
 | 
			
		||||
                    this.src = src;
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                this.warning = downloadResult.failed
 | 
			
		||||
                    ? this.getErrorDownloadingSomeFilesMessage(downloadResult.error!)
 | 
			
		||||
                    : '';
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (resource && 'display' in resource && AddonModResourceHelper.isDisplayedEmbedded(this.module!, resource.display)) {
 | 
			
		||||
                this.mode = 'embedded';
 | 
			
		||||
                this.warning = '';
 | 
			
		||||
 | 
			
		||||
                this.contentText = await AddonModResourceHelper.getEmbeddedHtml(this.module!, this.courseId!);
 | 
			
		||||
                this.mode = this.contentText.length > 0 ? 'embedded' : 'external';
 | 
			
		||||
            } else {
 | 
			
		||||
                this.mode = 'external';
 | 
			
		||||
                this.warning = '';
 | 
			
		||||
            }
 | 
			
		||||
        } finally {
 | 
			
		||||
            this.fillContextMenu(refresh);
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Opens a file.
 | 
			
		||||
     *
 | 
			
		||||
     * @return Promise resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async open(): Promise<void> {
 | 
			
		||||
        let downloadable = await CoreCourseModulePrefetchDelegate.isModuleDownloadable(this.module!, this.courseId!);
 | 
			
		||||
 | 
			
		||||
        if (downloadable) {
 | 
			
		||||
            // Check if the main file is downloadle.
 | 
			
		||||
            // This isn't done in "isDownloadable" to prevent extra WS calls in the course page.
 | 
			
		||||
            downloadable = await AddonModResourceHelper.isMainFileDownloadable(this.module!);
 | 
			
		||||
 | 
			
		||||
            if (downloadable) {
 | 
			
		||||
                return AddonModResourceHelper.openModuleFile(this.module!, this.courseId!);
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // The resource cannot be downloaded, open the activity in browser.
 | 
			
		||||
        await CoreSites.getCurrentSite()?.openInBrowserWithAutoLoginIfSameSite(this.module!.url!);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										7
									
								
								src/addons/mod/resource/lang.json
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								src/addons/mod/resource/lang.json
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,7 @@
 | 
			
		||||
{
 | 
			
		||||
    "errorwhileloadingthecontent": "Error while loading the content.",
 | 
			
		||||
    "modifieddate": "Modified {{$a}}",
 | 
			
		||||
    "modulenameplural": "Files",
 | 
			
		||||
    "openthefile": "Open the file",
 | 
			
		||||
    "uploadeddate": "Uploaded {{$a}}"
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										25
									
								
								src/addons/mod/resource/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										25
									
								
								src/addons/mod/resource/pages/index/index.html
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,25 @@
 | 
			
		||||
<ion-header>
 | 
			
		||||
    <ion-toolbar>
 | 
			
		||||
        <ion-buttons slot="start">
 | 
			
		||||
            <ion-back-button [attr.aria-label]="'core.back' | translate"></ion-back-button>
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
        <ion-title>
 | 
			
		||||
            <core-format-text [text]="title" contextLevel="module" [contextInstanceId]="module?.id" [courseId]="courseId">
 | 
			
		||||
            </core-format-text>
 | 
			
		||||
        </ion-title>
 | 
			
		||||
 | 
			
		||||
        <ion-buttons slot="end">
 | 
			
		||||
            <!-- The buttons defined by the component will be added in here. -->
 | 
			
		||||
        </ion-buttons>
 | 
			
		||||
    </ion-toolbar>
 | 
			
		||||
</ion-header>
 | 
			
		||||
<ion-content>
 | 
			
		||||
    <ion-refresher slot="fixed"
 | 
			
		||||
        [disabled]="!activityComponent?.loaded || activityComponent?.mode != 'external'"
 | 
			
		||||
        (ionRefresh)="activityComponent?.doRefresh($event)">
 | 
			
		||||
        <ion-refresher-content pullingText="{{ 'core.pulltorefresh' | translate }}"></ion-refresher-content>
 | 
			
		||||
    </ion-refresher>
 | 
			
		||||
 | 
			
		||||
    <addon-mod-resource-index [module]="module" [courseId]="courseId" (dataRetrieved)="updateData($event)">
 | 
			
		||||
    </addon-mod-resource-index>
 | 
			
		||||
</ion-content>
 | 
			
		||||
							
								
								
									
										30
									
								
								src/addons/mod/resource/pages/index/index.page.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										30
									
								
								src/addons/mod/resource/pages/index/index.page.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,30 @@
 | 
			
		||||
// (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 { Component, ViewChild } from '@angular/core';
 | 
			
		||||
import { CoreCourseModuleMainActivityPage } from '@features/course/classes/main-activity-page';
 | 
			
		||||
import { AddonModResourceIndexComponent } from '../../components/index/index';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Page that displays a resource.
 | 
			
		||||
 */
 | 
			
		||||
@Component({
 | 
			
		||||
    selector: 'page-addon-mod-resource-index',
 | 
			
		||||
    templateUrl: 'index.html',
 | 
			
		||||
})
 | 
			
		||||
export class AddonModResourceIndexPage extends CoreCourseModuleMainActivityPage<AddonModResourceIndexComponent> {
 | 
			
		||||
 | 
			
		||||
    @ViewChild(AddonModResourceIndexComponent) activityComponent?: AddonModResourceIndexComponent;
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
							
								
								
									
										38
									
								
								src/addons/mod/resource/resource-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										38
									
								
								src/addons/mod/resource/resource-lazy.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,38 @@
 | 
			
		||||
// (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 { RouterModule, Routes } from '@angular/router';
 | 
			
		||||
import { AddonModResourceComponentsModule } from './components/components.module';
 | 
			
		||||
import { AddonModResourceIndexPage } from './pages/index/index.page';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: ':courseId/:cmId',
 | 
			
		||||
        component: AddonModResourceIndexPage,
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        RouterModule.forChild(routes),
 | 
			
		||||
        CoreSharedModule,
 | 
			
		||||
        AddonModResourceComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    declarations: [
 | 
			
		||||
        AddonModResourceIndexPage,
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModResourceLazyModule {}
 | 
			
		||||
							
								
								
									
										56
									
								
								src/addons/mod/resource/resource.module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								src/addons/mod/resource/resource.module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,56 @@
 | 
			
		||||
// (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 { APP_INITIALIZER, NgModule } from '@angular/core';
 | 
			
		||||
import { Routes } from '@angular/router';
 | 
			
		||||
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
 | 
			
		||||
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreMainMenuTabRoutingModule } from '@features/mainmenu/mainmenu-tab-routing.module';
 | 
			
		||||
import { CorePluginFileDelegate } from '@services/plugin-file-delegate';
 | 
			
		||||
import { AddonModResourceComponentsModule } from './components/components.module';
 | 
			
		||||
import { AddonModResourceIndexLinkHandler } from './services/handlers/index-link';
 | 
			
		||||
import { AddonModResourceListLinkHandler } from './services/handlers/list-link';
 | 
			
		||||
import { AddonModResourceModuleHandlerService, AddonModResourceModuleHandler } from './services/handlers/module';
 | 
			
		||||
import { AddonModResourcePluginFileHandler } from './services/handlers/pluginfile';
 | 
			
		||||
import { AddonModResourcePrefetchHandler } from './services/handlers/prefetch';
 | 
			
		||||
 | 
			
		||||
const routes: Routes = [
 | 
			
		||||
    {
 | 
			
		||||
        path: AddonModResourceModuleHandlerService.PAGE_NAME,
 | 
			
		||||
        loadChildren: () => import('./resource-lazy.module').then(m => m.AddonModResourceLazyModule),
 | 
			
		||||
    },
 | 
			
		||||
];
 | 
			
		||||
 | 
			
		||||
@NgModule({
 | 
			
		||||
    imports: [
 | 
			
		||||
        CoreMainMenuTabRoutingModule.forChild(routes),
 | 
			
		||||
        AddonModResourceComponentsModule,
 | 
			
		||||
    ],
 | 
			
		||||
    providers: [
 | 
			
		||||
        {
 | 
			
		||||
            provide: APP_INITIALIZER,
 | 
			
		||||
            multi: true,
 | 
			
		||||
            deps: [],
 | 
			
		||||
            useFactory: () => () => {
 | 
			
		||||
                CoreCourseModuleDelegate.registerHandler(AddonModResourceModuleHandler.instance);
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonModResourceIndexLinkHandler.instance);
 | 
			
		||||
                CoreContentLinksDelegate.registerHandler(AddonModResourceListLinkHandler.instance);
 | 
			
		||||
                CoreCourseModulePrefetchDelegate.registerHandler(AddonModResourcePrefetchHandler.instance);
 | 
			
		||||
                CorePluginFileDelegate.registerHandler(AddonModResourcePluginFileHandler.instance);
 | 
			
		||||
            },
 | 
			
		||||
        },
 | 
			
		||||
    ],
 | 
			
		||||
})
 | 
			
		||||
export class AddonModResourceModule {}
 | 
			
		||||
							
								
								
									
										40
									
								
								src/addons/mod/resource/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/addons/mod/resource/services/handlers/index-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
// (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 { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/classes/module-index-handler';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModResource } from '../resource';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to resource.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModResourceIndexLinkHandlerService extends CoreContentLinksModuleIndexHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModResourceLinkHandler';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModResource', 'resource', 'r');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(siteId: string): Promise<boolean> {
 | 
			
		||||
        return AddonModResource.isPluginEnabled(siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModResourceIndexLinkHandler = makeSingleton(AddonModResourceIndexLinkHandlerService);
 | 
			
		||||
							
								
								
									
										40
									
								
								src/addons/mod/resource/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										40
									
								
								src/addons/mod/resource/services/handlers/list-link.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,40 @@
 | 
			
		||||
// (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 { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModResource } from '../resource';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to resource list page.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModResourceListLinkHandlerService extends CoreContentLinksModuleListHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModResourceListLinkHandler';
 | 
			
		||||
 | 
			
		||||
    constructor() {
 | 
			
		||||
        super('AddonModResource', 'resource');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(siteId: string): Promise<boolean> {
 | 
			
		||||
        return AddonModResource.isPluginEnabled(siteId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModResourceListLinkHandler = makeSingleton(AddonModResourceListLinkHandlerService);
 | 
			
		||||
							
								
								
									
										259
									
								
								src/addons/mod/resource/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										259
									
								
								src/addons/mod/resource/services/handlers/module.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,259 @@
 | 
			
		||||
// (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 { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Injectable, Type } from '@angular/core';
 | 
			
		||||
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseModuleContentFile } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseModule } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@features/course/services/module-delegate';
 | 
			
		||||
import { CoreCourseModulePrefetchDelegate } from '@features/course/services/module-prefetch-delegate';
 | 
			
		||||
import { CoreNavigationOptions, CoreNavigator } from '@services/navigator';
 | 
			
		||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { CoreTimeUtils } from '@services/utils/time';
 | 
			
		||||
import { CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
import { makeSingleton, Translate } from '@singletons';
 | 
			
		||||
import { AddonModResourceIndexComponent } from '../../components/index';
 | 
			
		||||
import { AddonModResource, AddonModResourceCustomData } from '../resource';
 | 
			
		||||
import { AddonModResourceHelper } from '../resource-helper';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to support resource modules.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModResourceModuleHandlerService implements CoreCourseModuleHandler {
 | 
			
		||||
 | 
			
		||||
    static readonly PAGE_NAME = 'mod_resource';
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModResource';
 | 
			
		||||
    modName = 'resource';
 | 
			
		||||
 | 
			
		||||
    supportedFeatures = {
 | 
			
		||||
        [CoreConstants.FEATURE_MOD_ARCHETYPE]: CoreConstants.MOD_ARCHETYPE_RESOURCE,
 | 
			
		||||
        [CoreConstants.FEATURE_GROUPS]: false,
 | 
			
		||||
        [CoreConstants.FEATURE_GROUPINGS]: false,
 | 
			
		||||
        [CoreConstants.FEATURE_MOD_INTRO]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_COMPLETION_TRACKS_VIEWS]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_GRADE_HAS_GRADE]: false,
 | 
			
		||||
        [CoreConstants.FEATURE_GRADE_OUTCOMES]: false,
 | 
			
		||||
        [CoreConstants.FEATURE_BACKUP_MOODLE2]: true,
 | 
			
		||||
        [CoreConstants.FEATURE_SHOW_DESCRIPTION]: true,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonModResource.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the data required to display the module in the course contents view.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module object.
 | 
			
		||||
     * @param courseId The course ID.
 | 
			
		||||
     * @param sectionId The section ID.
 | 
			
		||||
     * @return Data to render the module.
 | 
			
		||||
     */
 | 
			
		||||
    getData(module: CoreCourseAnyModuleData, courseId: number): CoreCourseModuleHandlerData {
 | 
			
		||||
        const updateStatus = (status: string): void => {
 | 
			
		||||
            handlerData.buttons![0].hidden = status !== CoreConstants.DOWNLOADED ||
 | 
			
		||||
                AddonModResourceHelper.isDisplayedInIframe(module);
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const handlerData: CoreCourseModuleHandlerData = {
 | 
			
		||||
            icon: CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined),
 | 
			
		||||
            title: module.name,
 | 
			
		||||
            class: 'addon-mod_resource-handler',
 | 
			
		||||
            showDownloadButton: true,
 | 
			
		||||
            action(event: Event, module: CoreCourseModule, courseId: number, options?: CoreNavigationOptions): void {
 | 
			
		||||
                options = options || {};
 | 
			
		||||
                options.params = options.params || {};
 | 
			
		||||
                Object.assign(options.params, { module });
 | 
			
		||||
                const routeParams = '/' + courseId + '/' + module.id;
 | 
			
		||||
 | 
			
		||||
                CoreNavigator.navigateToSitePath(AddonModResourceModuleHandlerService.PAGE_NAME + routeParams, options);
 | 
			
		||||
            },
 | 
			
		||||
            updateStatus: updateStatus.bind(this),
 | 
			
		||||
            buttons: [{
 | 
			
		||||
                hidden: true,
 | 
			
		||||
                icon: 'document',
 | 
			
		||||
                label: 'addon.mod_resource.openthefile',
 | 
			
		||||
                action: async (event: Event, module: CoreCourseModule, courseId: number): Promise<void> => {
 | 
			
		||||
                    const hide = await this.hideOpenButton(module, courseId);
 | 
			
		||||
                    if (!hide) {
 | 
			
		||||
                        AddonModResourceHelper.openModuleFile(module, courseId);
 | 
			
		||||
                    }
 | 
			
		||||
                },
 | 
			
		||||
            }],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        this.getResourceData(module, courseId, handlerData).then((data) => {
 | 
			
		||||
            handlerData.icon = data.icon;
 | 
			
		||||
            handlerData.extraBadge = data.extra;
 | 
			
		||||
            handlerData.extraBadgeColor = 'light';
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }).catch(() => {
 | 
			
		||||
            // Ignore errors.
 | 
			
		||||
        });
 | 
			
		||||
 | 
			
		||||
        return handlerData;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns if contents are loaded to show open button.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module object.
 | 
			
		||||
     * @param courseId The course ID.
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    protected async hideOpenButton(module: CoreCourseAnyModuleData, courseId: number): Promise<boolean> {
 | 
			
		||||
        if (!('contentsinfo' in module) || !module.contentsinfo) {
 | 
			
		||||
            await CoreCourse.loadModuleContents(module, courseId, undefined, false, false, undefined, this.modName);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const status = await CoreCourseModulePrefetchDelegate.getModuleStatus(module, courseId);
 | 
			
		||||
 | 
			
		||||
        return status !== CoreConstants.DOWNLOADED || AddonModResourceHelper.isDisplayedInIframe(module);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns the activity icon and data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module object.
 | 
			
		||||
     * @param courseId The course ID.
 | 
			
		||||
     * @return Resource data.
 | 
			
		||||
     */
 | 
			
		||||
    protected async getResourceData(
 | 
			
		||||
        module: CoreCourseAnyModuleData,
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        handlerData: CoreCourseModuleHandlerData,
 | 
			
		||||
    ): Promise<AddonResourceHandlerData> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
        let infoFiles: CoreWSExternalFile[] = [];
 | 
			
		||||
        let options: AddonModResourceCustomData = {};
 | 
			
		||||
 | 
			
		||||
        // Check if the button needs to be shown or not.
 | 
			
		||||
        promises.push(this.hideOpenButton(module, courseId).then((hideOpenButton) => {
 | 
			
		||||
            handlerData.buttons![0].hidden = hideOpenButton;
 | 
			
		||||
 | 
			
		||||
            return;
 | 
			
		||||
        }));
 | 
			
		||||
 | 
			
		||||
        if ('customdata' in module && typeof module.customdata != 'undefined') {
 | 
			
		||||
            options = CoreTextUtils.unserialize(CoreTextUtils.parseJSON(module.customdata));
 | 
			
		||||
        } else if (AddonModResource.isGetResourceWSAvailable()) {
 | 
			
		||||
            // Get the resource data.
 | 
			
		||||
            promises.push(AddonModResource.getResourceData(courseId, module.id).then((info) => {
 | 
			
		||||
                infoFiles = info.contentfiles;
 | 
			
		||||
                options = CoreTextUtils.unserialize(info.displayoptions);
 | 
			
		||||
 | 
			
		||||
                return;
 | 
			
		||||
            }));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
 | 
			
		||||
        const files: (CoreCourseModuleContentFile | CoreWSExternalFile)[] = module.contents && module.contents.length
 | 
			
		||||
            ? module.contents
 | 
			
		||||
            : infoFiles;
 | 
			
		||||
 | 
			
		||||
        const resourceData: AddonResourceHandlerData = {
 | 
			
		||||
            icon: '',
 | 
			
		||||
            extra: '',
 | 
			
		||||
        };
 | 
			
		||||
        const extra: string[] = [];
 | 
			
		||||
 | 
			
		||||
        if ('contentsinfo' in module && module.contentsinfo) {
 | 
			
		||||
            // No need to use the list of files.
 | 
			
		||||
            const mimetype = module.contentsinfo.mimetypes[0];
 | 
			
		||||
            if (mimetype) {
 | 
			
		||||
                resourceData.icon = CoreMimetypeUtils.getMimetypeIcon(mimetype);
 | 
			
		||||
            }
 | 
			
		||||
            resourceData.extra = CoreTextUtils.cleanTags(module.afterlink);
 | 
			
		||||
 | 
			
		||||
        } else if (files && files.length) {
 | 
			
		||||
            const file = files[0];
 | 
			
		||||
 | 
			
		||||
            resourceData.icon = CoreMimetypeUtils.getFileIcon(file.filename || '');
 | 
			
		||||
 | 
			
		||||
            if (options.showsize) {
 | 
			
		||||
                const size = options.filedetails
 | 
			
		||||
                    ? options.filedetails.size
 | 
			
		||||
                    : files.reduce((result, file) => result + (file.filesize || 0), 0);
 | 
			
		||||
 | 
			
		||||
                extra.push(CoreTextUtils.bytesToSize(size, 1));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (options.showtype) {
 | 
			
		||||
                // We should take it from options.filedetails.size if avalaible but it's already translated.
 | 
			
		||||
                extra.push(CoreMimetypeUtils.getMimetypeDescription(file));
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            if (options.showdate) {
 | 
			
		||||
                const timecreated = 'timecreated' in file ? file.timecreated : 0;
 | 
			
		||||
 | 
			
		||||
                if (options.filedetails && options.filedetails.modifieddate) {
 | 
			
		||||
                    extra.push(Translate.instant(
 | 
			
		||||
                        'addon.mod_resource.modifieddate',
 | 
			
		||||
                        { $a: CoreTimeUtils.userDate(options.filedetails.modifieddate * 1000, 'core.strftimedatetimeshort') },
 | 
			
		||||
                    ));
 | 
			
		||||
                } else if (options.filedetails && options.filedetails.uploadeddate) {
 | 
			
		||||
                    extra.push(Translate.instant(
 | 
			
		||||
                        'addon.mod_resource.uploadeddate',
 | 
			
		||||
                        { $a: CoreTimeUtils.userDate(options.filedetails.uploadeddate * 1000, 'core.strftimedatetimeshort') },
 | 
			
		||||
                    ));
 | 
			
		||||
                } else if ((file.timemodified || 0) > timecreated + CoreConstants.SECONDS_MINUTE * 5) {
 | 
			
		||||
                    /* Modified date may be up to several minutes later than uploaded date just because
 | 
			
		||||
                        teacher did not submit the form promptly. Give teacher up to 5 minutes to do it. */
 | 
			
		||||
                    extra.push(Translate.instant(
 | 
			
		||||
                        'addon.mod_resource.modifieddate',
 | 
			
		||||
                        { $a: CoreTimeUtils.userDate((file.timemodified || 0) * 1000, 'core.strftimedatetimeshort') },
 | 
			
		||||
                    ));
 | 
			
		||||
                } else {
 | 
			
		||||
                    extra.push(Translate.instant(
 | 
			
		||||
                        'addon.mod_resource.uploadeddate',
 | 
			
		||||
                        { $a: CoreTimeUtils.userDate(timecreated * 1000, 'core.strftimedatetimeshort') },
 | 
			
		||||
                    ));
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            resourceData.extra += extra.join(' ');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // No previously set, just set the icon.
 | 
			
		||||
        if (resourceData.icon == '') {
 | 
			
		||||
            resourceData.icon = CoreCourse.getModuleIconSrc(this.modName, 'modicon' in module ? module.modicon : undefined);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return resourceData;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async getMainComponent(): Promise<Type<unknown> | undefined> {
 | 
			
		||||
        return AddonModResourceIndexComponent;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModResourceModuleHandler = makeSingleton(AddonModResourceModuleHandlerService);
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
type AddonResourceHandlerData = {
 | 
			
		||||
    icon: string;
 | 
			
		||||
    extra: string;
 | 
			
		||||
}
 | 
			
		||||
;
 | 
			
		||||
							
								
								
									
										55
									
								
								src/addons/mod/resource/services/handlers/pluginfile.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										55
									
								
								src/addons/mod/resource/services/handlers/pluginfile.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,55 @@
 | 
			
		||||
// (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 { CorePluginFileHandler } from '@services/plugin-file-delegate';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to treat links to resource.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModResourcePluginFileHandlerService implements CorePluginFileHandler {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModResourcePluginFileHandler';
 | 
			
		||||
    component = 'mod_resource';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getComponentRevisionRegExp(args: string[]): RegExp | undefined {
 | 
			
		||||
        // Check filearea.
 | 
			
		||||
        if (args[2] == 'content') {
 | 
			
		||||
            // Component + Filearea + Revision
 | 
			
		||||
            return new RegExp('/mod_resource/content/([0-9]+)/');
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    getComponentRevisionReplace(): string {
 | 
			
		||||
        // Component + Filearea + Revision
 | 
			
		||||
        return '/mod_resource/content/0/';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return true;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModResourcePluginFileHandler = makeSingleton(AddonModResourcePluginFileHandlerService);
 | 
			
		||||
							
								
								
									
										120
									
								
								src/addons/mod/resource/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								src/addons/mod/resource/services/handlers/prefetch.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,120 @@
 | 
			
		||||
// (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 { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreCourseResourcePrefetchHandlerBase } from '@features/course/classes/resource-prefetch-handler';
 | 
			
		||||
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModResource, AddonModResourceProvider } from '../resource';
 | 
			
		||||
import { AddonModResourceHelper } from '../resource-helper';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Handler to prefetch resources.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModResourcePrefetchHandlerService extends CoreCourseResourcePrefetchHandlerBase {
 | 
			
		||||
 | 
			
		||||
    name = 'AddonModResource';
 | 
			
		||||
    modName = 'resource';
 | 
			
		||||
    component = AddonModResourceProvider.COMPONENT;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    determineStatus(module: CoreCourseAnyModuleData, status: string): string {
 | 
			
		||||
        if (status == CoreConstants.DOWNLOADED && module) {
 | 
			
		||||
            // If the main file is an external file, always display the module as outdated.
 | 
			
		||||
            if ('contentsinfo' in module && module.contentsinfo) {
 | 
			
		||||
                if (module.contentsinfo.repositorytype) {
 | 
			
		||||
                    // It's an external file.
 | 
			
		||||
                    return CoreConstants.OUTDATED;
 | 
			
		||||
                }
 | 
			
		||||
            } else if (module.contents) {
 | 
			
		||||
                const mainFile = module.contents[0];
 | 
			
		||||
                if (mainFile && mainFile.isexternalfile) {
 | 
			
		||||
                    return CoreConstants.OUTDATED;
 | 
			
		||||
                }
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return status;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async downloadOrPrefetch(module: CoreCourseWSModule, courseId: number, prefetch?: boolean): Promise<void> {
 | 
			
		||||
        let dirPath: string | undefined;
 | 
			
		||||
 | 
			
		||||
        if (AddonModResourceHelper.isDisplayedInIframe(module)) {
 | 
			
		||||
            dirPath = await CoreFilepool.getPackageDirPathByUrl(CoreSites.getCurrentSiteId(), module.url!);
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<any>[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(super.downloadOrPrefetch(module, courseId, prefetch, dirPath));
 | 
			
		||||
 | 
			
		||||
        if (AddonModResource.isGetResourceWSAvailable()) {
 | 
			
		||||
            promises.push(AddonModResource.getResourceData(courseId, module.id));
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateContent(moduleId: number, courseId: number): Promise<void> {
 | 
			
		||||
        await AddonModResource.invalidateContent(moduleId, courseId);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateModule(module: CoreCourseAnyModuleData, courseId: number): Promise<void> {
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(AddonModResource.invalidateResourceData(courseId));
 | 
			
		||||
        promises.push(CoreCourse.invalidateModule(module.id, undefined, this.modName));
 | 
			
		||||
 | 
			
		||||
        await Promise.all(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isDownloadable(module: CoreCourseAnyModuleData, courseId: number): Promise<boolean> {
 | 
			
		||||
        if (CoreSites.getCurrentSite()?.isVersionGreaterEqualThan('3.7')) {
 | 
			
		||||
            // Nextcloud files are downloadable from 3.7 onwards.
 | 
			
		||||
            return true;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        // Don't allow downloading Nextcloud files in older sites.
 | 
			
		||||
        await this.loadContents(module, courseId, false);
 | 
			
		||||
 | 
			
		||||
        return !AddonModResourceHelper.isNextcloudFile(module);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * @inheritdoc
 | 
			
		||||
     */
 | 
			
		||||
    async isEnabled(): Promise<boolean> {
 | 
			
		||||
        return AddonModResource.isPluginEnabled();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModResourcePrefetchHandler = makeSingleton(AddonModResourcePrefetchHandlerService);
 | 
			
		||||
							
								
								
									
										203
									
								
								src/addons/mod/resource/services/resource-helper.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										203
									
								
								src/addons/mod/resource/services/resource-helper.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,203 @@
 | 
			
		||||
// (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 { CoreConstants } from '@/core/constants';
 | 
			
		||||
import { Injectable } from '@angular/core';
 | 
			
		||||
import { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSModule } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseHelper } from '@features/course/services/course-helper';
 | 
			
		||||
import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreFile } from '@services/file';
 | 
			
		||||
import { CoreFileHelper } from '@services/file-helper';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreDomUtils } from '@services/utils/dom';
 | 
			
		||||
import { CoreMimetypeUtils } from '@services/utils/mimetype';
 | 
			
		||||
import { CoreTextUtils } from '@services/utils/text';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
import { AddonModResource, AddonModResourceProvider } from './resource';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service that provides helper functions for resources.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModResourceHelperProvider {
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get the HTML to display an embedded resource.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module object.
 | 
			
		||||
     * @param courseId The course ID.
 | 
			
		||||
     * @return Promise resolved with the HTML.
 | 
			
		||||
     */
 | 
			
		||||
    async getEmbeddedHtml(module: CoreCourseWSModule, courseId: number): Promise<string> {
 | 
			
		||||
        const result = await CoreCourseHelper.downloadModuleWithMainFileIfNeeded(
 | 
			
		||||
            module,
 | 
			
		||||
            courseId,
 | 
			
		||||
            AddonModResourceProvider.COMPONENT,
 | 
			
		||||
            module.id,
 | 
			
		||||
            module.contents,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        return CoreMimetypeUtils.getEmbeddedHtml(module.contents[0], result.path);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Download all the files needed and returns the src of the iframe.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module object.
 | 
			
		||||
     * @return Promise resolved with the iframe src.
 | 
			
		||||
     */
 | 
			
		||||
    async getIframeSrc(module: CoreCourseWSModule): Promise<string> {
 | 
			
		||||
        if (!module.contents.length) {
 | 
			
		||||
            throw new CoreError('No contents available in module');
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const mainFile = module.contents[0];
 | 
			
		||||
        let mainFilePath = mainFile.filename;
 | 
			
		||||
 | 
			
		||||
        if (mainFile.filepath !== '/') {
 | 
			
		||||
            mainFilePath = mainFile.filepath.substr(1) + mainFilePath;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url!);
 | 
			
		||||
 | 
			
		||||
            // This URL is going to be injected in an iframe, we need trustAsResourceUrl to make it work in a browser.
 | 
			
		||||
            return CoreTextUtils.concatenatePaths(dirPath, mainFilePath);
 | 
			
		||||
        } catch (e) {
 | 
			
		||||
            // Error getting directory, there was an error downloading or we're in browser. Return online URL.
 | 
			
		||||
            if (CoreApp.isOnline() && mainFile.fileurl) {
 | 
			
		||||
                // This URL is going to be injected in an iframe, we need this to make it work.
 | 
			
		||||
                return CoreSites.getCurrentSite()!.checkAndFixPluginfileURL(mainFile.fileurl);
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            throw e;
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the resource has to be displayed embedded.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module object.
 | 
			
		||||
     * @param display The display mode (if available).
 | 
			
		||||
     * @return Whether the resource should be displayed embeded.
 | 
			
		||||
     */
 | 
			
		||||
    isDisplayedEmbedded(module: CoreCourseWSModule, display: number): boolean {
 | 
			
		||||
        const currentSite = CoreSites.getCurrentSite();
 | 
			
		||||
 | 
			
		||||
        if ((!module.contents.length && !module.contentsinfo) ||
 | 
			
		||||
            !CoreFile.isAvailable() ||
 | 
			
		||||
            (currentSite && !currentSite.isVersionGreaterEqualThan('3.7') && this.isNextcloudFile(module))) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        const ext = module.contentsinfo
 | 
			
		||||
            ? CoreMimetypeUtils.getExtension(module.contentsinfo.mimetypes[0])
 | 
			
		||||
            : CoreMimetypeUtils.getFileExtension(module.contents[0].filename);
 | 
			
		||||
 | 
			
		||||
        return (display == CoreConstants.RESOURCELIB_DISPLAY_EMBED || display == CoreConstants.RESOURCELIB_DISPLAY_AUTO) &&
 | 
			
		||||
            CoreMimetypeUtils.canBeEmbedded(ext);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether the resource has to be displayed in an iframe.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module The module object.
 | 
			
		||||
     * @return Whether the resource should be displayed in an iframe.
 | 
			
		||||
     */
 | 
			
		||||
    isDisplayedInIframe(module: CoreCourseAnyModuleData): boolean {
 | 
			
		||||
        if (!CoreFile.isAvailable()) {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        let mimetype: string | undefined;
 | 
			
		||||
 | 
			
		||||
        if ('contentsinfo' in module && module.contentsinfo) {
 | 
			
		||||
            mimetype = module.contentsinfo.mimetypes[0];
 | 
			
		||||
        } else if (module.contents) {
 | 
			
		||||
            const ext = CoreMimetypeUtils.getFileExtension(module.contents[0].filename);
 | 
			
		||||
            mimetype = CoreMimetypeUtils.getMimeType(ext);
 | 
			
		||||
        } else {
 | 
			
		||||
            return false;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return mimetype == 'text/html';
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if main file of resource is downloadable.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module Module instance.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with boolean: whether main file is downloadable.
 | 
			
		||||
     */
 | 
			
		||||
    isMainFileDownloadable(module: CoreCourseWSModule, siteId?: string): Promise<boolean> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const mainFile = module.contents[0];
 | 
			
		||||
        const timemodified = CoreFileHelper.getFileTimemodified(mainFile);
 | 
			
		||||
 | 
			
		||||
        return CoreFilepool.isFileDownloadable(siteId, mainFile.fileurl, timemodified);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Check if the resource is a Nextcloud file.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module Module to check.
 | 
			
		||||
     * @return Whether it's a Nextcloud file.
 | 
			
		||||
     */
 | 
			
		||||
    isNextcloudFile(module: CoreCourseAnyModuleData): boolean {
 | 
			
		||||
        if ('contentsinfo' in module && module.contentsinfo) {
 | 
			
		||||
            return module.contentsinfo.repositorytype == 'nextcloud';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return !!(module.contents && module.contents[0] && module.contents[0].repositorytype == 'nextcloud');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Opens a file of the resource activity.
 | 
			
		||||
     *
 | 
			
		||||
     * @param module Module where to get the contents.
 | 
			
		||||
     * @param courseId Course Id, used for completion purposes.
 | 
			
		||||
     * @return Resolved when done.
 | 
			
		||||
     */
 | 
			
		||||
    async openModuleFile(module: CoreCourseWSModule, courseId: number): Promise<void> {
 | 
			
		||||
        const modal = await CoreDomUtils.showModalLoading();
 | 
			
		||||
 | 
			
		||||
        try {
 | 
			
		||||
            // Download and open the file from the resource contents.
 | 
			
		||||
            await CoreCourseHelper.downloadModuleAndOpenFile(
 | 
			
		||||
                module,
 | 
			
		||||
                courseId,
 | 
			
		||||
                AddonModResourceProvider.COMPONENT,
 | 
			
		||||
                module.id,
 | 
			
		||||
                module.contents,
 | 
			
		||||
            );
 | 
			
		||||
 | 
			
		||||
            try {
 | 
			
		||||
                await AddonModResource.logView(module.instance!, module.name);
 | 
			
		||||
                CoreCourse.checkModuleCompletion(courseId, module.completiondata);
 | 
			
		||||
            } catch {
 | 
			
		||||
                // Ignore errors.
 | 
			
		||||
            }
 | 
			
		||||
        } catch (error) {
 | 
			
		||||
            CoreDomUtils.showErrorModalDefault(error, 'addon.mod_resource.errorwhileloadingthecontent', true);
 | 
			
		||||
        } finally {
 | 
			
		||||
            modal.dismiss();
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModResourceHelper = makeSingleton(AddonModResourceHelperProvider);
 | 
			
		||||
							
								
								
									
										241
									
								
								src/addons/mod/resource/services/resource.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										241
									
								
								src/addons/mod/resource/services/resource.ts
									
									
									
									
									
										Normal file
									
								
							@ -0,0 +1,241 @@
 | 
			
		||||
// (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 { CoreError } from '@classes/errors/error';
 | 
			
		||||
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
 | 
			
		||||
import { CoreCourse } from '@features/course/services/course';
 | 
			
		||||
import { CoreCourseLogHelper } from '@features/course/services/log-helper';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
 | 
			
		||||
import { CoreUtils } from '@services/utils/utils';
 | 
			
		||||
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
 | 
			
		||||
import { makeSingleton } from '@singletons';
 | 
			
		||||
 | 
			
		||||
const ROOT_CACHE_KEY = 'mmaModResource:';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Service that provides some features for resources.
 | 
			
		||||
 */
 | 
			
		||||
@Injectable({ providedIn: 'root' })
 | 
			
		||||
export class AddonModResourceProvider {
 | 
			
		||||
 | 
			
		||||
    static readonly COMPONENT = 'mmaModResource';
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get cache key for resource data WS calls.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @return Cache key.
 | 
			
		||||
     */
 | 
			
		||||
    protected getResourceCacheKey(courseId: number): string {
 | 
			
		||||
        return ROOT_CACHE_KEY + 'resource:' + courseId;
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a resource data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param key Name of the property to check.
 | 
			
		||||
     * @param value Value to search.
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @return Promise resolved when the resource is retrieved.
 | 
			
		||||
     */
 | 
			
		||||
    protected async getResourceDataByKey(
 | 
			
		||||
        courseId: number,
 | 
			
		||||
        key: string,
 | 
			
		||||
        value: number,
 | 
			
		||||
        options: CoreSitesCommonWSOptions = {},
 | 
			
		||||
    ): Promise<AddonModResourceResource> {
 | 
			
		||||
        const site = await CoreSites.getSite(options.siteId);
 | 
			
		||||
 | 
			
		||||
        const params: AddonModResourceGetResourcesByCoursesWSParams = {
 | 
			
		||||
            courseids: [courseId],
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const preSets: CoreSiteWSPreSets = {
 | 
			
		||||
            cacheKey: this.getResourceCacheKey(courseId),
 | 
			
		||||
            updateFrequency: CoreSite.FREQUENCY_RARELY,
 | 
			
		||||
            component: AddonModResourceProvider.COMPONENT,
 | 
			
		||||
            ...CoreSites.getReadingStrategyPreSets(options.readingStrategy),
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        const response = await site.read<AddonModResourceGetResourcesByCoursesWSResponse>(
 | 
			
		||||
            'mod_resource_get_resources_by_courses',
 | 
			
		||||
            params,
 | 
			
		||||
            preSets,
 | 
			
		||||
        );
 | 
			
		||||
 | 
			
		||||
        const currentResource = response.resources.find((resource) => resource[key] == value);
 | 
			
		||||
        if (currentResource) {
 | 
			
		||||
            return currentResource;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        throw new CoreError('Resource not found');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Get a resource by course module ID.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseId Course ID.
 | 
			
		||||
     * @param cmId Course module ID.
 | 
			
		||||
     * @param options Other options.
 | 
			
		||||
     * @return Promise resolved when the resource is retrieved.
 | 
			
		||||
     */
 | 
			
		||||
    getResourceData(courseId: number, cmId: number, options: CoreSitesCommonWSOptions = {}): Promise<AddonModResourceResource> {
 | 
			
		||||
        return this.getResourceDataByKey(courseId, 'coursemodule', cmId, options);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidate the prefetched content.
 | 
			
		||||
     *
 | 
			
		||||
     * @param moduleId The module ID.
 | 
			
		||||
     * @param courseId Course ID of the module.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        siteId = siteId || CoreSites.getCurrentSiteId();
 | 
			
		||||
 | 
			
		||||
        const promises: Promise<void>[] = [];
 | 
			
		||||
 | 
			
		||||
        promises.push(this.invalidateResourceData(courseId, siteId));
 | 
			
		||||
        promises.push(CoreFilepool.invalidateFilesByComponent(siteId, AddonModResourceProvider.COMPONENT, moduleId));
 | 
			
		||||
        promises.push(CoreCourse.invalidateModule(moduleId, siteId, 'resource'));
 | 
			
		||||
 | 
			
		||||
        await CoreUtils.allPromises(promises);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Invalidates resource data.
 | 
			
		||||
     *
 | 
			
		||||
     * @param courseid Course ID.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the data is invalidated.
 | 
			
		||||
     */
 | 
			
		||||
    async invalidateResourceData(courseId: number, siteId?: string): Promise<void> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        await site.invalidateWsCacheForKey(this.getResourceCacheKey(courseId));
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Returns whether or not getResource WS available or not.
 | 
			
		||||
     *
 | 
			
		||||
     * @return If WS is abalaible.
 | 
			
		||||
     * @since 3.3
 | 
			
		||||
     */
 | 
			
		||||
    isGetResourceWSAvailable(): boolean {
 | 
			
		||||
        return CoreSites.wsAvailableInCurrentSite('mod_resource_get_resources_by_courses');
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Return whether or not the plugin is enabled.
 | 
			
		||||
     *
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved with true if plugin is enabled, rejected or resolved with false otherwise.
 | 
			
		||||
     */
 | 
			
		||||
    async isPluginEnabled(siteId?: string): Promise<boolean> {
 | 
			
		||||
        const site = await CoreSites.getSite(siteId);
 | 
			
		||||
 | 
			
		||||
        return site.canDownloadFiles();
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Report the resource as being viewed.
 | 
			
		||||
     *
 | 
			
		||||
     * @param id Module ID.
 | 
			
		||||
     * @param name Name of the resource.
 | 
			
		||||
     * @param siteId Site ID. If not defined, current site.
 | 
			
		||||
     * @return Promise resolved when the WS call is successful.
 | 
			
		||||
     */
 | 
			
		||||
    async logView(id: number, name?: string, siteId?: string): Promise<void> {
 | 
			
		||||
        const params: AddonModResourceViewResourceWSParams = {
 | 
			
		||||
            resourceid: id,
 | 
			
		||||
        };
 | 
			
		||||
 | 
			
		||||
        await CoreCourseLogHelper.logSingle(
 | 
			
		||||
            'mod_resource_view_resource',
 | 
			
		||||
            params,
 | 
			
		||||
            AddonModResourceProvider.COMPONENT,
 | 
			
		||||
            id,
 | 
			
		||||
            name,
 | 
			
		||||
            'resource',
 | 
			
		||||
            {},
 | 
			
		||||
            siteId,
 | 
			
		||||
        );
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
}
 | 
			
		||||
export const AddonModResource = makeSingleton(AddonModResourceProvider);
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_resource_view_resource WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonModResourceViewResourceWSParams = {
 | 
			
		||||
    resourceid: number; // Resource instance id.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Resource returned by mod_resource_get_resources_by_courses.
 | 
			
		||||
 */
 | 
			
		||||
export type AddonModResourceResource = {
 | 
			
		||||
    id: number; // Module id.
 | 
			
		||||
    coursemodule: number; // Course module id.
 | 
			
		||||
    course: number; // Course id.
 | 
			
		||||
    name: string; // Page name.
 | 
			
		||||
    intro: string; // Summary.
 | 
			
		||||
    introformat: number; // Intro format (1 = HTML, 0 = MOODLE, 2 = PLAIN or 4 = MARKDOWN).
 | 
			
		||||
    introfiles: CoreWSExternalFile[];
 | 
			
		||||
    contentfiles: CoreWSExternalFile[];
 | 
			
		||||
    tobemigrated: number; // Whether this resource was migrated.
 | 
			
		||||
    legacyfiles: number; // Legacy files flag.
 | 
			
		||||
    legacyfileslast: number; // Legacy files last control flag.
 | 
			
		||||
    display: number; // How to display the resource.
 | 
			
		||||
    displayoptions: string; // Display options (width, height).
 | 
			
		||||
    filterfiles: number; // If filters should be applied to the resource content.
 | 
			
		||||
    revision: number; // Incremented when after each file changes, to avoid cache.
 | 
			
		||||
    timemodified: number; // Last time the resource was modified.
 | 
			
		||||
    section: number; // Course section id.
 | 
			
		||||
    visible: number; // Module visibility.
 | 
			
		||||
    groupmode: number; // Group mode.
 | 
			
		||||
    groupingid: number; // Grouping id.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type AddonModResourceCustomData = {
 | 
			
		||||
    showsize?: boolean;
 | 
			
		||||
    filedetails?: {
 | 
			
		||||
        size: number;
 | 
			
		||||
        modifieddate: number;
 | 
			
		||||
        uploadeddate: number;
 | 
			
		||||
    };
 | 
			
		||||
    showtype?: boolean;
 | 
			
		||||
    showdate?: boolean;
 | 
			
		||||
    printintro?: boolean;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Params of mod_resource_get_resources_by_courses WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonModResourceGetResourcesByCoursesWSParams = {
 | 
			
		||||
    courseids?: number[]; // Array of course ids.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Data returned by mod_resource_get_resources_by_courses WS.
 | 
			
		||||
 */
 | 
			
		||||
type AddonModResourceGetResourcesByCoursesWSResponse = {
 | 
			
		||||
    resources: AddonModResourceResource[];
 | 
			
		||||
    warnings?: CoreWSExternalWarning[];
 | 
			
		||||
};
 | 
			
		||||
@ -19,7 +19,7 @@ import { CoreApp } from '@services/app';
 | 
			
		||||
import { CoreFilepool } from '@services/filepool';
 | 
			
		||||
import { CoreSites } from '@services/sites';
 | 
			
		||||
import { CoreWSExternalFile } from '@services/ws';
 | 
			
		||||
import { CoreCourse, CoreCourseWSModule } from '../services/course';
 | 
			
		||||
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSModule } from '../services/course';
 | 
			
		||||
import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@ -178,7 +178,7 @@ export class CoreCourseResourcePrefetchHandlerBase extends CoreCourseModulePrefe
 | 
			
		||||
     * @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
 | 
			
		||||
     * @return Promise resolved when loaded.
 | 
			
		||||
     */
 | 
			
		||||
    loadContents(module: CoreCourseWSModule, courseId: number, ignoreCache?: boolean): Promise<void> {
 | 
			
		||||
    loadContents(module: CoreCourseAnyModuleData, courseId: number, ignoreCache?: boolean): Promise<void> {
 | 
			
		||||
        return CoreCourse.loadModuleContents(module, courseId, undefined, false, ignoreCache);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -1439,18 +1439,20 @@ export type CoreCourseModuleWSCompletionData = {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export type CoreCourseModuleContentFile = {
 | 
			
		||||
    type: string; // A file or a folder or external link.
 | 
			
		||||
    // Common properties with CoreWSExternalFile.
 | 
			
		||||
    filename: string; // Filename.
 | 
			
		||||
    filepath: string; // Filepath.
 | 
			
		||||
    filesize: number; // Filesize.
 | 
			
		||||
    fileurl: string; // Downloadable file url.
 | 
			
		||||
    content?: string; // Raw content, will be used when type is content.
 | 
			
		||||
    timecreated: number; // Time created.
 | 
			
		||||
    timemodified: number; // Time modified.
 | 
			
		||||
    sortorder: number; // Content sort order.
 | 
			
		||||
    mimetype?: string; // File mime type.
 | 
			
		||||
    isexternalfile?: number; // Whether is an external file.
 | 
			
		||||
    repositorytype?: string; // The repository type for external files.
 | 
			
		||||
 | 
			
		||||
    type: string; // A file or a folder or external link.
 | 
			
		||||
    content?: string; // Raw content, will be used when type is content.
 | 
			
		||||
    timecreated: number; // Time created.
 | 
			
		||||
    sortorder: number; // Content sort order.
 | 
			
		||||
    userid: number; // User who added this content to moodle.
 | 
			
		||||
    author: string; // Content owner.
 | 
			
		||||
    license: string; // Content license.
 | 
			
		||||
 | 
			
		||||
@ -65,7 +65,7 @@ export class CoreMimetypeUtilsProvider {
 | 
			
		||||
     * @param extension Extension.
 | 
			
		||||
     * @return Whether it can be embedded.
 | 
			
		||||
     */
 | 
			
		||||
    canBeEmbedded(extension: string): boolean {
 | 
			
		||||
    canBeEmbedded(extension?: string): boolean {
 | 
			
		||||
        return this.isExtensionInGroup(extension, ['web_image', 'web_video', 'web_audio']);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@ -238,11 +238,7 @@ export class CoreTextUtilsProvider {
 | 
			
		||||
     * @param singleLine True if new lines should be removed (all the text in a single line).
 | 
			
		||||
     * @return Clean text.
 | 
			
		||||
     */
 | 
			
		||||
    cleanTags(text: string, singleLine?: boolean): string {
 | 
			
		||||
        if (typeof text != 'string') {
 | 
			
		||||
            return text;
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
    cleanTags(text: string | undefined, singleLine?: boolean): string {
 | 
			
		||||
        if (!text) {
 | 
			
		||||
            return '';
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
@ -1077,46 +1077,14 @@ export type CoreWarningsWSResponse = {
 | 
			
		||||
 * Structure of files returned by WS.
 | 
			
		||||
 */
 | 
			
		||||
export type CoreWSExternalFile = {
 | 
			
		||||
    /**
 | 
			
		||||
     * Downloadable file url.
 | 
			
		||||
     */
 | 
			
		||||
    fileurl: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * File name.
 | 
			
		||||
     */
 | 
			
		||||
    filename?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * File path.
 | 
			
		||||
     */
 | 
			
		||||
    filepath?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * File size.
 | 
			
		||||
     */
 | 
			
		||||
    filesize?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Time modified.
 | 
			
		||||
     */
 | 
			
		||||
    timemodified?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * File mime type.
 | 
			
		||||
     */
 | 
			
		||||
    mimetype?: string;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * Whether is an external file.
 | 
			
		||||
     */
 | 
			
		||||
    isexternalfile?: number;
 | 
			
		||||
 | 
			
		||||
    /**
 | 
			
		||||
     * The repository type for external files.
 | 
			
		||||
     */
 | 
			
		||||
    repositorytype?: string;
 | 
			
		||||
 | 
			
		||||
    fileurl: string; // Downloadable file url.
 | 
			
		||||
    filename?: string; // File name.
 | 
			
		||||
    filepath?: string; // File path.
 | 
			
		||||
    filesize?: number; // File size.
 | 
			
		||||
    timemodified?: number; // Time modified.
 | 
			
		||||
    mimetype?: string; // File mime type.
 | 
			
		||||
    isexternalfile?: number; // Whether is an external file.
 | 
			
		||||
    repositorytype?: string; // The repository type for external files.
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 | 
			
		||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user