diff --git a/src/addon/mod/url/components/index/addon-mod-url-index.html b/src/addon/mod/url/components/index/addon-mod-url-index.html
index 7f4f8dd28..00e14a049 100644
--- a/src/addon/mod/url/components/index/addon-mod-url-index.html
+++ b/src/addon/mod/url/components/index/addon-mod-url-index.html
@@ -12,14 +12,29 @@
-
- {{ 'addon.mod_url.pointingtourl' | translate }}
- {{ url }}
-
-
-
-
- {{ 'addon.mod_url.accessurl' | translate }}
-
+
+
+
+
+
+
+
+
+
+
+ {{ 'addon.mod_url.pointingtourl' | translate }}
+ {{ url }}
+
+
+
diff --git a/src/addon/mod/url/components/index/index.scss b/src/addon/mod/url/components/index/index.scss
new file mode 100644
index 000000000..a9d107124
--- /dev/null
+++ b/src/addon/mod/url/components/index/index.scss
@@ -0,0 +1,6 @@
+ion-app.app-root addon-mod-url-index {
+
+ .addon-mod_url-embedded-url {
+ height: 100%;
+ }
+}
diff --git a/src/addon/mod/url/components/index/index.ts b/src/addon/mod/url/components/index/index.ts
index d1602299f..16c7942a8 100644
--- a/src/addon/mod/url/components/index/index.ts
+++ b/src/addon/mod/url/components/index/index.ts
@@ -13,10 +13,13 @@
// limitations under the License.
import { Component, Injector } from '@angular/core';
+import { CoreSitesProvider } from '@providers/sites';
+import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
import { CoreCourseProvider } from '@core/course/providers/course';
import { CoreCourseModuleMainResourceComponent } from '@core/course/classes/main-resource-component';
import { AddonModUrlProvider } from '../../providers/url';
import { AddonModUrlHelperProvider } from '../../providers/helper';
+import { CoreConstants } from '@core/constants';
/**
* Component that displays a url.
@@ -30,9 +33,17 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
canGetUrl: boolean;
url: string;
+ name: string;
+ shouldEmbed = false;
+ shouldIframe = false;
+ isImage = false;
+ isAudio = false;
+ isVideo = false;
+ mimetype: string;
constructor(injector: Injector, private urlProvider: AddonModUrlProvider, private courseProvider: CoreCourseProvider,
- private urlHelper: AddonModUrlHelperProvider) {
+ private urlHelper: AddonModUrlHelperProvider, private mimeUtils: CoreMimetypeUtilsProvider,
+ private sitesProvider: CoreSitesProvider) {
super(injector);
}
@@ -65,6 +76,7 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
protected fetchContent(refresh?: boolean): Promise
{
let canGetUrl = this.canGetUrl,
mod,
+ url,
promise;
// Fetch the module data.
@@ -79,7 +91,10 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
// Fallback in case is not prefetched or not available.
return this.courseProvider.getModule(this.module.id, this.courseId, undefined, false, false, undefined, 'url');
- }).then((url) => {
+ }).then((urlData) => {
+ url = urlData;
+
+ this.name = url.name || this.module.name;
this.description = url.intro || url.description;
this.dataRetrieved.emit(url);
@@ -101,9 +116,49 @@ export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceCompo
}).then(() => {
// Always use the URL from the module because it already includes the parameters.
this.url = mod.contents && mod.contents[0] && mod.contents[0].fileurl ? mod.contents[0].fileurl : undefined;
+
+ if (canGetUrl) {
+ return this.calculateDisplayOptions(url);
+ }
});
}
+ /**
+ * Calculate the display options to determine how the URL should be rendered.
+ *
+ * @param {any} url Object with the URL data.
+ * @return {Promise} Promise resolved when done.
+ */
+ protected calculateDisplayOptions(url: any): Promise {
+ const displayType = this.urlProvider.getFinalDisplayType(url);
+
+ this.shouldEmbed = displayType == CoreConstants.RESOURCELIB_DISPLAY_EMBED;
+ this.shouldIframe = displayType == CoreConstants.RESOURCELIB_DISPLAY_FRAME;
+
+ if (this.shouldEmbed) {
+ const extension = this.mimeUtils.guessExtensionFromUrl(url.externalurl);
+
+ this.mimetype = this.mimeUtils.getMimeType(extension);
+ this.isImage = this.mimeUtils.isExtensionInGroup(extension, ['web_image']);
+ this.isAudio = this.mimeUtils.isExtensionInGroup(extension, ['web_audio']);
+ this.isVideo = this.mimeUtils.isExtensionInGroup(extension, ['web_video']);
+ }
+
+ if (this.shouldIframe || (this.shouldEmbed && !this.isImage && !this.isAudio && !this.isVideo)) {
+ // Will be displayed in an iframe. Check if we need to auto-login.
+ const currentSite = this.sitesProvider.getCurrentSite();
+
+ if (currentSite && currentSite.containsUrl(this.url)) {
+ // Format the URL to add auto-login.
+ return currentSite.getAutoLoginUrl(this.url, false).then((url) => {
+ this.url = url;
+ });
+ }
+ }
+
+ return Promise.resolve();
+ }
+
/**
* Opens a file.
*/
diff --git a/src/addon/mod/url/providers/module-handler.ts b/src/addon/mod/url/providers/module-handler.ts
index 57480c0d5..54ccb6ead 100644
--- a/src/addon/mod/url/providers/module-handler.ts
+++ b/src/addon/mod/url/providers/module-handler.ts
@@ -14,11 +14,13 @@
import { Injectable } from '@angular/core';
import { NavController, NavOptions } from 'ionic-angular';
+import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { AddonModUrlIndexComponent } from '../components/index/index';
import { CoreCourseModuleHandler, CoreCourseModuleHandlerData } from '@core/course/providers/module-delegate';
import { CoreCourseProvider } from '@core/course/providers/course';
import { AddonModUrlProvider } from './url';
import { AddonModUrlHelperProvider } from './helper';
+import { CoreConstants } from '@core/constants';
/**
* Handler to support url modules.
@@ -29,7 +31,7 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
modName = 'url';
constructor(private courseProvider: CoreCourseProvider, private urlProvider: AddonModUrlProvider,
- private urlHelper: AddonModUrlHelperProvider) { }
+ private urlHelper: AddonModUrlHelperProvider, private domUtils: CoreDomUtilsProvider) { }
/**
* Check if the handler is enabled on a site level.
@@ -49,33 +51,60 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
* @return {CoreCourseModuleHandlerData} Data to render the module.
*/
getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData {
+ // tslint:disable: no-this-assignment
+ const handler = this;
const handlerData = {
icon: this.courseProvider.getModuleIconSrc(this.modName),
title: module.name,
class: 'addon-mod_url-handler',
showDownloadButton: false,
action(event: Event, navCtrl: NavController, module: any, courseId: number, options: NavOptions): void {
- navCtrl.push('AddonModUrlIndexPage', {module: module, courseId: courseId}, options);
+ // Check if we need to open the URL directly.
+ let promise;
+
+ if (handler.urlProvider.isGetUrlWSAvailable()) {
+ const modal = handler.domUtils.showModalLoading();
+
+ promise = handler.urlProvider.getUrl(courseId, module.id).catch(() => {
+ // Ignore errors.
+ }).then((url) => {
+ modal.dismiss();
+
+ const displayType = handler.urlProvider.getFinalDisplayType(url);
+
+ return displayType == CoreConstants.RESOURCELIB_DISPLAY_OPEN ||
+ displayType == CoreConstants.RESOURCELIB_DISPLAY_POPUP;
+ });
+ } else {
+ promise = Promise.resolve(false);
+ }
+
+ return promise.then((shouldOpen) => {
+ if (shouldOpen) {
+ handler.openUrl(module, courseId);
+ } else {
+ navCtrl.push('AddonModUrlIndexPage', {module: module, courseId: courseId}, options);
+ }
+ });
},
buttons: [ {
- hidden: !(module.contents && module.contents[0] && module.contents[0].fileurl),
+ hidden: true, // Hide it until we calculate if it should be displayed or not.
icon: 'link',
label: 'core.openinbrowser',
action: (event: Event, navCtrl: NavController, module: any, courseId: number): void => {
- this.hideLinkButton(module, courseId).then((hide) => {
- if (!hide) {
- this.urlProvider.logView(module.instance).then(() => {
- this.courseProvider.checkModuleCompletion(courseId, module.completionstatus);
- });
- this.urlHelper.open(module.contents[0].fileurl);
- }
- });
+ handler.openUrl(module, courseId);
}
} ]
};
this.hideLinkButton(module, courseId).then((hideButton) => {
handlerData.buttons[0].hidden = hideButton;
+
+ if (module.contents && module.contents[0]) {
+ // Calculate the icon to use.
+ handlerData.icon = this.urlProvider.guessIcon(module.contents[0].fileurl) ||
+ this.courseProvider.getModuleIconSrc(this.modName);
+ }
});
return handlerData;
@@ -91,7 +120,24 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
protected hideLinkButton(module: any, courseId: number): Promise {
return this.courseProvider.loadModuleContents(module, courseId, undefined, false, false, undefined, this.modName)
.then(() => {
- return !(module.contents && module.contents[0] && module.contents[0].fileurl);
+
+ if (!module.contents || !module.contents[0] || !module.contents[0].fileurl) {
+ // No module contents, hide the button.
+ return true;
+ }
+
+ if (!this.urlProvider.isGetUrlWSAvailable()) {
+ return false;
+ }
+
+ // Get the URL data.
+ return this.urlProvider.getUrl(courseId, module.id).then((url) => {
+ const displayType = this.urlProvider.getFinalDisplayType(url);
+
+ // Don't display the button if the URL should be embedded.
+ return displayType == CoreConstants.RESOURCELIB_DISPLAY_EMBED ||
+ displayType == CoreConstants.RESOURCELIB_DISPLAY_FRAME;
+ });
});
}
@@ -106,4 +152,17 @@ export class AddonModUrlModuleHandler implements CoreCourseModuleHandler {
getMainComponent(course: any, module: any): any {
return AddonModUrlIndexComponent;
}
+
+ /**
+ * Open the URL.
+ *
+ * @param {any} module The module object.
+ * @param {number} courseId The course ID.
+ */
+ protected openUrl(module: any, courseId: number): void {
+ this.urlProvider.logView(module.instance).then(() => {
+ this.courseProvider.checkModuleCompletion(courseId, module.completionstatus);
+ });
+ this.urlHelper.open(module.contents[0].fileurl);
+ }
}
diff --git a/src/addon/mod/url/providers/url.ts b/src/addon/mod/url/providers/url.ts
index 19750f8c7..c1642b6ca 100644
--- a/src/addon/mod/url/providers/url.ts
+++ b/src/addon/mod/url/providers/url.ts
@@ -15,8 +15,10 @@
import { Injectable } from '@angular/core';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreSitesProvider } from '@providers/sites';
+import { CoreMimetypeUtilsProvider } from '@providers/utils/mimetype';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreCourseProvider } from '@core/course/providers/course';
+import { CoreConstants } from '@core/constants';
/**
* Service that provides some features for urls.
@@ -29,10 +31,62 @@ export class AddonModUrlProvider {
protected logger;
constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider,
- private utils: CoreUtilsProvider) {
+ private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider) {
this.logger = logger.getInstance('AddonModUrlProvider');
}
+ /**
+ * Get the final display type for a certain URL. Based on Moodle's url_get_final_display_type.
+ *
+ * @param {any} url URL data.
+ * @return {number} Final display type.
+ */
+ getFinalDisplayType(url: any): number {
+ if (!url) {
+ return -1;
+ }
+
+ const extension = this.mimeUtils.guessExtensionFromUrl(url.externalurl);
+
+ // PDFs can be embedded in web, but not in the Mobile app.
+ if (url.display == CoreConstants.RESOURCELIB_DISPLAY_EMBED && extension == 'pdf') {
+ return CoreConstants.RESOURCELIB_DISPLAY_DOWNLOAD;
+ }
+
+ if (url.display != CoreConstants.RESOURCELIB_DISPLAY_AUTO) {
+ return url.display;
+ }
+
+ // Detect links to local moodle pages.
+ const currentSite = this.sitesProvider.getCurrentSite();
+ if (currentSite && currentSite.containsUrl(url.externalurl)) {
+ if (url.externalurl.indexOf('file.php') == -1 && url.externalurl.indexOf('.php') != -1) {
+ // Most probably our moodle page with navigation.
+ return CoreConstants.RESOURCELIB_DISPLAY_OPEN;
+ }
+ }
+
+ const download = ['application/zip', 'application/x-tar', 'application/g-zip', 'application/pdf', 'text/html'];
+ let mimetype = this.mimeUtils.getMimeType(extension);
+
+ if (url.externalurl.indexOf('.php') != -1 || url.externalurl.substr(-1) === '/' ||
+ (url.externalurl.indexOf('//') != -1 && url.externalurl.match(/\//g).length == 2)) {
+ // Seems to be a web, use HTML mimetype.
+ mimetype = 'text/html';
+ }
+
+ if (download.indexOf(mimetype) != -1) {
+ return CoreConstants.RESOURCELIB_DISPLAY_DOWNLOAD;
+ }
+
+ if (this.mimeUtils.canBeEmbedded(extension)) {
+ return CoreConstants.RESOURCELIB_DISPLAY_EMBED;
+ }
+
+ // Let the browser deal with it somehow.
+ return CoreConstants.RESOURCELIB_DISPLAY_OPEN;
+ }
+
/**
* Get cache key for url data WS calls.
*
@@ -88,6 +142,33 @@ export class AddonModUrlProvider {
return this.getUrlDataByKey(courseId, 'coursemodule', cmId, siteId);
}
+ /**
+ * Guess the icon for a certain URL. Based on Moodle's url_guess_icon.
+ *
+ * @param {string} url URL to check.
+ * @return {string} Icon, empty if it should use the default icon.
+ */
+ guessIcon(url: string): string {
+ url = url || '';
+
+ const matches = url.match(/\//g),
+ extension = this.mimeUtils.getFileExtension(url);
+
+ if (!matches || matches.length < 3 || url.substr(-1) === '/' || extension == 'php') {
+ // Use default icon.
+ return '';
+ }
+
+ const icon = this.mimeUtils.getFileIcon(url);
+
+ // We do not want to return those icon types, the module icon is more appropriate.
+ if (icon === this.mimeUtils.getFileIconForType('unknown') || icon === this.mimeUtils.getFileIconForType('html')) {
+ return '';
+ }
+
+ return icon;
+ }
+
/**
* Invalidate the prefetched content.
*
diff --git a/src/core/constants.ts b/src/core/constants.ts
index ac25f07b6..f93bb41d5 100644
--- a/src/core/constants.ts
+++ b/src/core/constants.ts
@@ -47,4 +47,13 @@ export class CoreConstants {
static NOT_DOWNLOADED = 'notdownloaded';
static OUTDATED = 'outdated';
static NOT_DOWNLOADABLE = 'notdownloadable';
+
+ // Constants from Moodle's resourcelib.
+ static RESOURCELIB_DISPLAY_AUTO = 0; // Try the best way.
+ static RESOURCELIB_DISPLAY_EMBED = 1; // Display using object tag.
+ static RESOURCELIB_DISPLAY_FRAME = 2; // Display inside frame.
+ static RESOURCELIB_DISPLAY_NEW = 3; // Display normal link in new window.
+ static RESOURCELIB_DISPLAY_DOWNLOAD = 4; // Force download of file instead of display.
+ static RESOURCELIB_DISPLAY_OPEN = 5; // Open directly.
+ static RESOURCELIB_DISPLAY_POPUP = 6; // Open in "emulated" pop-up without navigation.
}
diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts
index a1bce44ce..9df797d94 100644
--- a/src/providers/utils/mimetype.ts
+++ b/src/providers/utils/mimetype.ts
@@ -186,7 +186,7 @@ export class CoreMimetypeUtilsProvider {
}
}
- return 'assets/img/files/' + icon + '-64.png';
+ return this.getFileIconForType(icon);
}
/**
@@ -198,6 +198,16 @@ export class CoreMimetypeUtilsProvider {
return 'assets/img/files/folder-64.png';
}
+ /**
+ * Given a type (audio, video, html, ...), return its file icon path.
+ *
+ * @param {string} type The type to get the icon.
+ * @return {string} The icon path.
+ */
+ getFileIconForType(type: string): string {
+ return 'assets/img/files/' + type + '-64.png';
+ }
+
/**
* Guess the extension of a file from its URL.
* This is very weak and unreliable.