diff --git a/src/addon/mod/url/components/components.module.ts b/src/addon/mod/url/components/components.module.ts new file mode 100644 index 000000000..e1d413330 --- /dev/null +++ b/src/addon/mod/url/components/components.module.ts @@ -0,0 +1,45 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { CommonModule } from '@angular/common'; +import { IonicModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreComponentsModule } from '@components/components.module'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { CoreCourseComponentsModule } from '@core/course/components/components.module'; +import { AddonModUrlIndexComponent } from './index/index'; + +@NgModule({ + declarations: [ + AddonModUrlIndexComponent + ], + imports: [ + CommonModule, + IonicModule, + TranslateModule.forChild(), + CoreComponentsModule, + CoreDirectivesModule, + CoreCourseComponentsModule + ], + providers: [ + ], + exports: [ + AddonModUrlIndexComponent + ], + entryComponents: [ + AddonModUrlIndexComponent + ] +}) +export class AddonModUrlComponentsModule {} diff --git a/src/addon/mod/url/components/index/index.html b/src/addon/mod/url/components/index/index.html new file mode 100644 index 000000000..7f4f8dd28 --- /dev/null +++ b/src/addon/mod/url/components/index/index.html @@ -0,0 +1,25 @@ + + + + + + + + + + + + + + + +

{{ 'addon.mod_url.pointingtourl' | translate }}

+

{{ url }}

+
+
+ + + {{ 'addon.mod_url.accessurl' | translate }} + +
+
diff --git a/src/addon/mod/url/components/index/index.ts b/src/addon/mod/url/components/index/index.ts new file mode 100644 index 000000000..df2cca224 --- /dev/null +++ b/src/addon/mod/url/components/index/index.ts @@ -0,0 +1,112 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 } from '@angular/core'; +import { TranslateService } from '@ngx-translate/core'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreTextUtilsProvider } from '@providers/utils/text'; +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 { CoreCourseHelperProvider } from '@core/course/providers/helper'; + +/** + * Component that displays a url. + */ +@Component({ + selector: 'addon-mod-url-index', + templateUrl: 'index.html', +}) +export class AddonModUrlIndexComponent extends CoreCourseModuleMainResourceComponent { + component = AddonModUrlProvider.COMPONENT; + + canGetUrl: boolean; + url: string; + + constructor(private urlProvider: AddonModUrlProvider, private courseProvider: CoreCourseProvider, + protected domUtils: CoreDomUtilsProvider, protected textUtils: CoreTextUtilsProvider, + protected translate: TranslateService, private urlHelper: AddonModUrlHelperProvider, + protected courseHelper: CoreCourseHelperProvider) { + super(textUtils, courseHelper, translate, domUtils); + } + + /** + * Component being initialized. + */ + ngOnInit(): void { + super.ngOnInit(); + + this.canGetUrl = this.urlProvider.isGetUrlWSAvailable(); + + this.loadContent(); + } + + /** + * Perform the invalidate content function. + * + * @return {Promise} Resolved when done. + */ + protected invalidateContent(): Promise { + return this.urlProvider.invalidateContent(this.module.id, this.courseId); + } + + /** + * Download url contents. + * + * @param {boolean} [refresh] Whether we're refreshing data. + * @return {Promise} Promise resolved when done. + */ + protected fetchContent(refresh?: boolean): Promise { + let canGetUrl = this.canGetUrl; + + // Fetch the module data. + let promise; + if (canGetUrl) { + promise = this.urlProvider.getUrl(this.courseId, this.module.id); + } else { + promise = Promise.reject(null); + } + + return promise.catch(() => { + canGetUrl = false; + + // Fallback in case is not prefetch or not avalaible. + return this.courseProvider.getModule(this.module.id, this.courseId); + }).then((url) => { + if (!canGetUrl) { + if (!url.contents.length) { + // If the data was cached maybe we don't have contents. Reject. + return Promise.reject(null); + } + } + + this.description = url.intro || url.description; + this.dataRetrieved.emit(url); + + this.url = canGetUrl ? url.externalurl : + ((url.contents[0] && url.contents[0].fileurl) ? url.contents[0].fileurl : undefined); + }); + } + + /** + * Opens a file. + */ + go(): void { + this.urlProvider.logView(this.module.instance).then(() => { + this.courseProvider.checkModuleCompletion(this.courseId, this.module.completionstatus); + }); + this.urlHelper.open(this.url); + } +} diff --git a/src/addon/mod/url/lang/en.json b/src/addon/mod/url/lang/en.json new file mode 100644 index 000000000..7d905f0cf --- /dev/null +++ b/src/addon/mod/url/lang/en.json @@ -0,0 +1,4 @@ +{ + "accessurl": "Access the URL", + "pointingtourl": "URL that the resource points to." +} \ No newline at end of file diff --git a/src/addon/mod/url/pages/index/index.html b/src/addon/mod/url/pages/index/index.html new file mode 100644 index 000000000..2c3b52af2 --- /dev/null +++ b/src/addon/mod/url/pages/index/index.html @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/addon/mod/url/pages/index/index.module.ts b/src/addon/mod/url/pages/index/index.module.ts new file mode 100644 index 000000000..0bad6f9ae --- /dev/null +++ b/src/addon/mod/url/pages/index/index.module.ts @@ -0,0 +1,33 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicPageModule } from 'ionic-angular'; +import { TranslateModule } from '@ngx-translate/core'; +import { CoreDirectivesModule } from '@directives/directives.module'; +import { AddonModUrlComponentsModule } from '../../components/components.module'; +import { AddonModUrlIndexPage } from './index'; + +@NgModule({ + declarations: [ + AddonModUrlIndexPage, + ], + imports: [ + CoreDirectivesModule, + AddonModUrlComponentsModule, + IonicPageModule.forChild(AddonModUrlIndexPage), + TranslateModule.forChild() + ], +}) +export class AddonModUrlIndexPageModule {} diff --git a/src/addon/mod/url/pages/index/index.ts b/src/addon/mod/url/pages/index/index.ts new file mode 100644 index 000000000..1e0ec28c6 --- /dev/null +++ b/src/addon/mod/url/pages/index/index.ts @@ -0,0 +1,48 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { IonicPage, NavParams } from 'ionic-angular'; +import { AddonModUrlIndexComponent } from '../../components/index/index'; + +/** + * Page that displays a url. + */ +@IonicPage({ segment: 'addon-mod-url-index' }) +@Component({ + selector: 'page-addon-mod-url-index', + templateUrl: 'index.html', +}) +export class AddonModUrlIndexPage { + @ViewChild(AddonModUrlIndexComponent) urlComponent: AddonModUrlIndexComponent; + + title: string; + module: any; + courseId: number; + + constructor(navParams: NavParams) { + this.module = navParams.get('module') || {}; + this.courseId = navParams.get('courseId'); + this.title = this.module.name; + } + + /** + * Update some data based on the url instance. + * + * @param {any} url Url instance. + */ + updateData(url: any): void { + this.title = url.name || this.title; + } +} diff --git a/src/addon/mod/url/providers/helper.ts b/src/addon/mod/url/providers/helper.ts new file mode 100644 index 000000000..5a2c0dec9 --- /dev/null +++ b/src/addon/mod/url/providers/helper.ts @@ -0,0 +1,44 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { CoreSitesProvider } from '@providers/sites'; +import { CoreDomUtilsProvider } from '@providers/utils/dom'; +import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper'; + +/** + * Service that provides helper functions for urls. + */ +@Injectable() +export class AddonModUrlHelperProvider { + + constructor(private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, + private contentLinksHelper: CoreContentLinksHelperProvider) { } + + /** + * Opens a URL. + * + * @param {string} url The URL to go to. + */ + open(url: string): void { + const modal = this.domUtils.showModalLoading(); + this.contentLinksHelper.handleLink(url).then((treated) => { + if (!treated) { + return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(url); + } + }).finally(() => { + modal.dismiss(); + }); + } +} diff --git a/src/addon/mod/url/providers/link-handler.ts b/src/addon/mod/url/providers/link-handler.ts new file mode 100644 index 000000000..c9ad1c24b --- /dev/null +++ b/src/addon/mod/url/providers/link-handler.ts @@ -0,0 +1,30 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 '@core/contentlinks/classes/module-index-handler'; +import { CoreCourseHelperProvider } from '@core/course/providers/helper'; +import { AddonModUrlProvider } from './url'; + +/** + * Handler to treat links to url. + */ +@Injectable() +export class AddonModUrlLinkHandler extends CoreContentLinksModuleIndexHandler { + name = 'AddonModUrlLinkHandler'; + + constructor(courseHelper: CoreCourseHelperProvider) { + super(courseHelper, AddonModUrlProvider.COMPONENT, 'url'); + } +} diff --git a/src/addon/mod/url/providers/module-handler.ts b/src/addon/mod/url/providers/module-handler.ts new file mode 100644 index 000000000..7bc5e1166 --- /dev/null +++ b/src/addon/mod/url/providers/module-handler.ts @@ -0,0 +1,107 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { NavController, NavOptions } from 'ionic-angular'; +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'; + +/** + * Handler to support url modules. + */ +@Injectable() +export class AddonModUrlModuleHandler implements CoreCourseModuleHandler { + name = 'url'; + + constructor(private courseProvider: CoreCourseProvider, private urlProvider: AddonModUrlProvider, + private urlHelper: AddonModUrlHelperProvider) { } + + /** + * Check if the handler is enabled on a site level. + * + * @return {boolean} Whether or not the handler is enabled on a site level. + */ + isEnabled(): boolean { + return true; + } + + /** + * Get the data required to display the module in the course contents view. + * + * @param {any} module The module object. + * @param {number} courseId The course ID. + * @param {number} sectionId The section ID. + * @return {CoreCourseModuleHandlerData} Data to render the module. + */ + getData(module: any, courseId: number, sectionId: number): CoreCourseModuleHandlerData { + const handlerData = { + icon: this.courseProvider.getModuleIconSrc('url'), + 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); + }, + buttons: [ { + hidden: !(module.contents && module.contents[0] && module.contents[0].fileurl), + 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); + } + }); + } + } ] + }; + + this.hideLinkButton(module, courseId).then((hideButton) => { + handlerData.buttons[0].hidden = hideButton; + }); + + return handlerData; + } + + /** + * Returns if contents are loaded to show link button. + * + * @param {any} module The module object. + * @param {number} courseId The course ID. + * @return {Promise} Resolved when done. + */ + protected hideLinkButton(module: any, courseId: number): Promise { + return this.courseProvider.loadModuleContents(module, courseId).then(() => { + return !(module.contents && module.contents[0] && module.contents[0].fileurl); + }); + } + + /** + * Get the component to render the module. This is needed to support singleactivity course format. + * The component returned must implement CoreCourseModuleMainComponent. + * + * @param {any} course The course object. + * @param {any} module The module object. + * @return {any} The component to use, undefined if not found. + */ + getMainComponent(course: any, module: any): any { + return AddonModUrlIndexComponent; + } +} diff --git a/src/addon/mod/url/providers/url.ts b/src/addon/mod/url/providers/url.ts new file mode 100644 index 000000000..2472dccae --- /dev/null +++ b/src/addon/mod/url/providers/url.ts @@ -0,0 +1,146 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { CoreLoggerProvider } from '@providers/logger'; +import { CoreSitesProvider } from '@providers/sites'; +import { CoreUtilsProvider } from '@providers/utils/utils'; +import { CoreCourseProvider } from '@core/course/providers/course'; + +/** + * Service that provides some features for urls. + */ +@Injectable() +export class AddonModUrlProvider { + static COMPONENT = 'mmaModUrl'; + + protected ROOT_CACHE_KEY = 'mmaModUrl:'; + protected logger; + + constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private courseProvider: CoreCourseProvider, + private utils: CoreUtilsProvider) { + this.logger = logger.getInstance('AddonModUrlProvider'); + } + + /** + * Get cache key for url data WS calls. + * + * @param {number} courseId Course ID. + * @return {string} Cache key. + */ + protected getUrlCacheKey(courseId: number): string { + return this.ROOT_CACHE_KEY + 'url:' + courseId; + } + + /** + * Get a url data. + * + * @param {number} courseId Course ID. + * @param {string} key Name of the property to check. + * @param {any} value Value to search. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the url is retrieved. + */ + protected getUrlDataByKey(courseId: number, key: string, value: any, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + const params = { + courseids: [courseId] + }, + preSets = { + cacheKey: this.getUrlCacheKey(courseId) + }; + + return site.read('mod_url_get_urls_by_courses', params, preSets).then((response) => { + if (response && response.urls) { + const currentUrl = response.urls.find((url) => { + return url[key] == value; + }); + if (currentUrl) { + return currentUrl; + } + } + + return Promise.reject(null); + }); + }); + } + + /** + * Get a url by course module ID. + * + * @param {number} courseId Course ID. + * @param {number} cmId Course module ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the url is retrieved. + */ + getUrl(courseId: number, cmId: number, siteId?: string): Promise { + return this.getUrlDataByKey(courseId, 'coursemodule', cmId, siteId); + } + + /** + * Invalidate the prefetched content. + * + * @param {number} moduleId The module ID. + * @param {number} courseId Course ID of the module. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateContent(moduleId: number, courseId: number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); + + const promises = []; + + promises.push(this.invalidateUrlData(courseId, siteId)); + promises.push(this.courseProvider.invalidateModule(moduleId, siteId)); + + return this.utils.allPromises(promises); + } + + /** + * Invalidates url data. + * + * @param {number} courseid Course ID. + * @param {string} [siteId] Site ID. If not defined, current site. + * @return {Promise} Promise resolved when the data is invalidated. + */ + invalidateUrlData(courseId: number, siteId?: string): Promise { + return this.sitesProvider.getSite(siteId).then((site) => { + return site.invalidateWsCacheForKey(this.getUrlCacheKey(courseId)); + }); + } + + /** + * Returns whether or not getUrl WS available or not. + * + * @return {boolean} If WS is abalaible. + * @since 3.3 + */ + isGetUrlWSAvailable(): boolean { + return this.sitesProvider.wsAvailableInCurrentSite('mod_url_get_urls_by_courses'); + } + + /** + * Report the url as being viewed. + * + * @param {number} id Module ID. + * @return {Promise} Promise resolved when the WS call is successful. + */ + logView(id: number): Promise { + const params = { + urlid: id + }; + + return this.sitesProvider.getCurrentSite().write('mod_url_view_url', params); + } +} diff --git a/src/addon/mod/url/url.module.ts b/src/addon/mod/url/url.module.ts new file mode 100644 index 000000000..3bdbc5912 --- /dev/null +++ b/src/addon/mod/url/url.module.ts @@ -0,0 +1,43 @@ +// (C) Copyright 2015 Martin Dougiamas +// +// 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 { AddonModUrlComponentsModule } from './components/components.module'; +import { AddonModUrlModuleHandler } from './providers/module-handler'; +import { AddonModUrlProvider } from './providers/url'; +import { AddonModUrlLinkHandler } from './providers/link-handler'; +import { AddonModUrlHelperProvider } from './providers/helper'; +import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'; +import { CoreCourseModuleDelegate } from '@core/course/providers/module-delegate'; + +@NgModule({ + declarations: [ + ], + imports: [ + AddonModUrlComponentsModule + ], + providers: [ + AddonModUrlProvider, + AddonModUrlModuleHandler, + AddonModUrlHelperProvider, + AddonModUrlLinkHandler + ] +}) +export class AddonModUrlModule { + constructor(moduleDelegate: CoreCourseModuleDelegate, moduleHandler: AddonModUrlModuleHandler, + contentLinksDelegate: CoreContentLinksDelegate, linkHandler: AddonModUrlLinkHandler) { + moduleDelegate.registerHandler(moduleHandler); + contentLinksDelegate.registerHandler(linkHandler); + } +} diff --git a/src/app/app.module.ts b/src/app/app.module.ts index f5e776ae6..4ac415d9d 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -79,6 +79,7 @@ import { AddonModLabelModule } from '@addon/mod/label/label.module'; import { AddonModResourceModule } from '@addon/mod/resource/resource.module'; import { AddonModFolderModule } from '@addon/mod/folder/folder.module'; import { AddonModPageModule } from '@addon/mod/page/page.module'; +import { AddonModUrlModule } from '@addon/mod/url/url.module'; import { AddonMessagesModule } from '@addon/messages/messages.module'; import { AddonPushNotificationsModule } from '@addon/pushnotifications/pushnotifications.module'; import { AddonRemoteThemesModule } from '@addon/remotethemes/remotethemes.module'; @@ -162,6 +163,7 @@ export const CORE_PROVIDERS: any[] = [ AddonModResourceModule, AddonModFolderModule, AddonModPageModule, + AddonModUrlModule, AddonMessagesModule, AddonPushNotificationsModule, AddonRemoteThemesModule