MOBILE-3652 resource: Add resource activity module
parent
072d6e4699
commit
da1749ef32
|
@ -22,6 +22,7 @@ import { AddonModLabelModule } from './label/label.module';
|
||||||
import { AddonModLessonModule } from './lesson/lesson.module';
|
import { AddonModLessonModule } from './lesson/lesson.module';
|
||||||
import { AddonModPageModule } from './page/page.module';
|
import { AddonModPageModule } from './page/page.module';
|
||||||
import { AddonModQuizModule } from './quiz/quiz.module';
|
import { AddonModQuizModule } from './quiz/quiz.module';
|
||||||
|
import { AddonModResourceModule } from './resource/resource.module';
|
||||||
import { AddonModUrlModule } from './url/url.module';
|
import { AddonModUrlModule } from './url/url.module';
|
||||||
|
|
||||||
@NgModule({
|
@NgModule({
|
||||||
|
@ -35,6 +36,7 @@ import { AddonModUrlModule } from './url/url.module';
|
||||||
AddonModQuizModule,
|
AddonModQuizModule,
|
||||||
AddonModUrlModule,
|
AddonModUrlModule,
|
||||||
AddonModLabelModule,
|
AddonModLabelModule,
|
||||||
|
AddonModResourceModule,
|
||||||
AddonModFolderModule,
|
AddonModFolderModule,
|
||||||
],
|
],
|
||||||
providers: [],
|
providers: [],
|
||||||
|
|
|
@ -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>
|
|
@ -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!);
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
|
@ -0,0 +1,7 @@
|
||||||
|
{
|
||||||
|
"errorwhileloadingthecontent": "Error while loading the content.",
|
||||||
|
"modifieddate": "Modified {{$a}}",
|
||||||
|
"modulenameplural": "Files",
|
||||||
|
"openthefile": "Open the file",
|
||||||
|
"uploadeddate": "Uploaded {{$a}}"
|
||||||
|
}
|
|
@ -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>
|
|
@ -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;
|
||||||
|
|
||||||
|
}
|
|
@ -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 {}
|
|
@ -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 {}
|
|
@ -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);
|
|
@ -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);
|
|
@ -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;
|
||||||
|
}
|
||||||
|
;
|
|
@ -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);
|
|
@ -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);
|
|
@ -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);
|
|
@ -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 { CoreFilepool } from '@services/filepool';
|
||||||
import { CoreSites } from '@services/sites';
|
import { CoreSites } from '@services/sites';
|
||||||
import { CoreWSExternalFile } from '@services/ws';
|
import { CoreWSExternalFile } from '@services/ws';
|
||||||
import { CoreCourse, CoreCourseWSModule } from '../services/course';
|
import { CoreCourse, CoreCourseAnyModuleData, CoreCourseWSModule } from '../services/course';
|
||||||
import { CoreCourseModulePrefetchHandlerBase } from './module-prefetch-handler';
|
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).
|
* @param ignoreCache True if it should ignore cached data (it will always fail in offline or server down).
|
||||||
* @return Promise resolved when loaded.
|
* @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);
|
return CoreCourse.loadModuleContents(module, courseId, undefined, false, ignoreCache);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1439,18 +1439,20 @@ export type CoreCourseModuleWSCompletionData = {
|
||||||
};
|
};
|
||||||
|
|
||||||
export type CoreCourseModuleContentFile = {
|
export type CoreCourseModuleContentFile = {
|
||||||
type: string; // A file or a folder or external link.
|
// Common properties with CoreWSExternalFile.
|
||||||
filename: string; // Filename.
|
filename: string; // Filename.
|
||||||
filepath: string; // Filepath.
|
filepath: string; // Filepath.
|
||||||
filesize: number; // Filesize.
|
filesize: number; // Filesize.
|
||||||
fileurl: string; // Downloadable file url.
|
fileurl: string; // Downloadable file url.
|
||||||
content?: string; // Raw content, will be used when type is content.
|
|
||||||
timecreated: number; // Time created.
|
|
||||||
timemodified: number; // Time modified.
|
timemodified: number; // Time modified.
|
||||||
sortorder: number; // Content sort order.
|
|
||||||
mimetype?: string; // File mime type.
|
mimetype?: string; // File mime type.
|
||||||
isexternalfile?: number; // Whether is an external file.
|
isexternalfile?: number; // Whether is an external file.
|
||||||
repositorytype?: string; // The repository type for external files.
|
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.
|
userid: number; // User who added this content to moodle.
|
||||||
author: string; // Content owner.
|
author: string; // Content owner.
|
||||||
license: string; // Content license.
|
license: string; // Content license.
|
||||||
|
|
|
@ -65,7 +65,7 @@ export class CoreMimetypeUtilsProvider {
|
||||||
* @param extension Extension.
|
* @param extension Extension.
|
||||||
* @return Whether it can be embedded.
|
* @return Whether it can be embedded.
|
||||||
*/
|
*/
|
||||||
canBeEmbedded(extension: string): boolean {
|
canBeEmbedded(extension?: string): boolean {
|
||||||
return this.isExtensionInGroup(extension, ['web_image', 'web_video', 'web_audio']);
|
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).
|
* @param singleLine True if new lines should be removed (all the text in a single line).
|
||||||
* @return Clean text.
|
* @return Clean text.
|
||||||
*/
|
*/
|
||||||
cleanTags(text: string, singleLine?: boolean): string {
|
cleanTags(text: string | undefined, singleLine?: boolean): string {
|
||||||
if (typeof text != 'string') {
|
|
||||||
return text;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!text) {
|
if (!text) {
|
||||||
return '';
|
return '';
|
||||||
}
|
}
|
||||||
|
|
|
@ -1077,46 +1077,14 @@ export type CoreWarningsWSResponse = {
|
||||||
* Structure of files returned by WS.
|
* Structure of files returned by WS.
|
||||||
*/
|
*/
|
||||||
export type CoreWSExternalFile = {
|
export type CoreWSExternalFile = {
|
||||||
/**
|
fileurl: string; // Downloadable file url.
|
||||||
* Downloadable file url.
|
filename?: string; // File name.
|
||||||
*/
|
filepath?: string; // File path.
|
||||||
fileurl: string;
|
filesize?: number; // File size.
|
||||||
|
timemodified?: number; // Time modified.
|
||||||
/**
|
mimetype?: string; // File mime type.
|
||||||
* File name.
|
isexternalfile?: number; // Whether is an external file.
|
||||||
*/
|
repositorytype?: string; // The repository type for external files.
|
||||||
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;
|
|
||||||
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
Loading…
Reference in New Issue