diff --git a/config.xml b/config.xml
index 6f949ffa9..1282a7a65 100644
--- a/config.xml
+++ b/config.xml
@@ -8,6 +8,7 @@
+
@@ -38,6 +39,8 @@
+
+
@@ -171,6 +174,7 @@
+
@@ -178,6 +182,8 @@
+
+
diff --git a/config/webpack.config.js b/config/webpack.config.js
index 3f622bcd3..1c6ae7ca5 100644
--- a/config/webpack.config.js
+++ b/config/webpack.config.js
@@ -12,7 +12,8 @@ const customConfig = {
'@providers': resolve('./src/providers'),
'@components': resolve('./src/components'),
'@directives': resolve('./src/directives'),
- '@pipes': resolve('./src/pipes')
+ '@pipes': resolve('./src/pipes'),
+ '@singletons': resolve('./src/singletons'),
}
},
externals: [
diff --git a/package-lock.json b/package-lock.json
index 88471ab97..15e6dbdf1 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,6 +1,6 @@
{
"name": "moodlemobile",
- "version": "3.8.1",
+ "version": "3.8.2",
"lockfileVersion": 1,
"requires": true,
"dependencies": {
@@ -147,6 +147,11 @@
"resolved": "https://registry.npmjs.org/@ionic-native/globalization/-/globalization-4.20.0.tgz",
"integrity": "sha512-zyxaW+vZb1OHeDgGbrZHQe3hy30K4YeKjGr8KNGcwq+k2ZHkfqo/H6XIwf2m/UlFTgacvdR9XZtfP+6N0suybg=="
},
+ "@ionic-native/http": {
+ "version": "4.20.0",
+ "resolved": "https://registry.npmjs.org/@ionic-native/http/-/http-4.20.0.tgz",
+ "integrity": "sha512-DF+Y1oYoHTv9Y22a2jLgniOmj9Twba+9j8rzHA4xboVT2HpB6bsBSWOktdAXDVjoajXiLsA/u7fh6YD8//NVGg=="
+ },
"@ionic-native/in-app-browser": {
"version": "4.20.0",
"resolved": "https://registry.npmjs.org/@ionic-native/in-app-browser/-/in-app-browser-4.20.0.tgz",
@@ -1366,7 +1371,8 @@
},
"kind-of": {
"version": "6.0.2",
- "resolved": ""
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
}
}
},
@@ -2620,6 +2626,11 @@
}
}
},
+ "cordova-plugin-advanced-http": {
+ "version": "2.4.1",
+ "resolved": "https://registry.npmjs.org/cordova-plugin-advanced-http/-/cordova-plugin-advanced-http-2.4.1.tgz",
+ "integrity": "sha512-6G8MTy/d02jE6n3Y9CVyCtD5hZGiBb+/dR2AIzhKN1RGGz38g1D2C8yE4MqHRvnmry6k/KHQWT1MsHNXrjouXQ=="
+ },
"cordova-plugin-badge": {
"version": "0.8.8",
"resolved": "https://registry.npmjs.org/cordova-plugin-badge/-/cordova-plugin-badge-0.8.8.tgz",
@@ -2675,6 +2686,11 @@
"resolved": "https://registry.npmjs.org/cordova-plugin-ionic-keyboard/-/cordova-plugin-ionic-keyboard-2.1.3.tgz",
"integrity": "sha512-6ucQ6FdlLdBm8kJfFnzozmBTjru/0xekHP/dAhjoCZggkGRlgs8TsUJFkxa/bV+qi7Dlo50JjmpE4UMWAO+aOQ=="
},
+ "cordova-plugin-ionic-webview": {
+ "version": "4.1.3",
+ "resolved": "https://registry.npmjs.org/cordova-plugin-ionic-webview/-/cordova-plugin-ionic-webview-4.1.3.tgz",
+ "integrity": "sha512-hlrUF0kLjjEkZmpYlLJO0NnXmVjMmQ3MOZVXm1ytDihLPKHklYCOpCvjA5Wz3hJrPD1shFEsqi/SPnp873AsdQ=="
+ },
"cordova-plugin-local-notification": {
"version": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#0bb96b757fb484553ceabf35a59802f7983a2836",
"from": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle"
@@ -2709,6 +2725,10 @@
"resolved": "https://registry.npmjs.org/cordova-plugin-whitelist/-/cordova-plugin-whitelist-1.3.4.tgz",
"integrity": "sha512-EYC5eQFVkoYXq39l7tYKE6lEjHJ04mvTmKXxGL7quHLdFPfJMNzru/UYpn92AOfpl3PQaZmou78C7EgmFOwFQQ=="
},
+ "cordova-plugin-wkwebview-cookies": {
+ "version": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git#0f20979803ffd9b13250215975692703342d759c",
+ "from": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git"
+ },
"cordova-plugin-zip": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/cordova-plugin-zip/-/cordova-plugin-zip-3.1.0.tgz",
@@ -6672,7 +6692,8 @@
},
"kind-of": {
"version": "6.0.2",
- "resolved": "",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
},
"micromatch": {
@@ -11323,7 +11344,8 @@
},
"kind-of": {
"version": "6.0.2",
- "resolved": ""
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA=="
}
}
},
@@ -13484,7 +13506,8 @@
},
"kind-of": {
"version": "6.0.2",
- "resolved": "",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.2.tgz",
+ "integrity": "sha512-s5kLOcnH0XqDO+FvuaLX8DDjZ18CGFk7VygH40QoKPUQhW4e2rvM0rwUq0t8IQDOwYSeLK01U90OjzBTme2QqA==",
"dev": true
},
"micromatch": {
diff --git a/package.json b/package.json
index bd1de5e56..941127fae 100644
--- a/package.json
+++ b/package.json
@@ -59,6 +59,7 @@
"@ionic-native/file-transfer": "4.20.0",
"@ionic-native/geolocation": "4.20.0",
"@ionic-native/globalization": "4.20.0",
+ "@ionic-native/http": "^4.20.0",
"@ionic-native/in-app-browser": "4.20.0",
"@ionic-native/keyboard": "4.20.0",
"@ionic-native/local-notifications": "4.20.0",
@@ -81,6 +82,7 @@
"cordova-android-support-gradle-release": "3.0.1",
"cordova-clipboard": "1.3.0",
"cordova-ios": "5.1.1",
+ "cordova-plugin-advanced-http": "2.4.1",
"cordova-plugin-badge": "0.8.8",
"cordova-plugin-camera": "4.1.0",
"cordova-plugin-customurlscheme": "5.0.0",
@@ -92,6 +94,7 @@
"cordova-plugin-globalization": "1.11.0",
"cordova-plugin-inappbrowser": "3.2.0",
"cordova-plugin-ionic-keyboard": "2.1.3",
+ "cordova-plugin-ionic-webview": "4.1.3",
"cordova-plugin-local-notification": "git+https://github.com/moodlemobile/cordova-plugin-local-notification.git#moodle",
"cordova-plugin-media-capture": "3.0.3",
"cordova-plugin-network-information": "2.0.2",
@@ -99,6 +102,7 @@
"cordova-plugin-splashscreen": "5.0.3",
"cordova-plugin-statusbar": "2.4.3",
"cordova-plugin-whitelist": "1.3.4",
+ "cordova-plugin-wkwebview-cookies": "git+https://github.com/moodlemobile/cordova-plugin-wkwebview-cookies.git",
"cordova-plugin-zip": "3.1.0",
"cordova-sqlite-storage": "4.0.0",
"cordova-support-google-services": "1.3.2",
@@ -185,7 +189,12 @@
},
"cordova-plugin-geolocation": {
"GEOLOCATION_USAGE_DESCRIPTION": "To locate you"
- }
+ },
+ "cordova-plugin-ionic-webview": {},
+ "cordova-plugin-advanced-http": {
+ "OKHTTP_VERSION": "3.10.0"
+ },
+ "cordova-plugin-wkwebview-cookies": {}
}
},
"main": "desktop/electron.js",
diff --git a/src/app/app.module.ts b/src/app/app.module.ts
index 81fdb84a7..c36f76078 100644
--- a/src/app/app.module.ts
+++ b/src/app/app.module.ts
@@ -14,7 +14,7 @@
import { BrowserModule } from '@angular/platform-browser';
import { BrowserAnimationsModule } from '@angular/platform-browser/animations';
-import { NgModule, COMPILER_OPTIONS } from '@angular/core';
+import { NgModule, COMPILER_OPTIONS, Injector } from '@angular/core';
import { IonicApp, IonicModule, Platform, Content, ScrollEvent, Config, Refresher } from 'ionic-angular';
import { assert } from 'ionic-angular/util/util';
import { HttpClient, HttpClientModule, HTTP_INTERCEPTORS } from '@angular/common/http';
@@ -155,6 +155,8 @@ import { AddonQtypeModule } from '@addon/qtype/qtype.module';
import { AddonStorageManagerModule } from '@addon/storagemanager/storagemanager.module';
import { AddonFilterModule } from '@addon/filter/filter.module';
+import { setSingletonsInjector } from '@singletons/core.singletons';
+
// For translate loader. AoT requires an exported function for factories.
export function createTranslateLoader(http: HttpClient): TranslateHttpLoader {
return new TranslateHttpLoader(http, './assets/lang/', '.json');
@@ -357,6 +359,7 @@ export class AppModule {
private eventsProvider: CoreEventsProvider,
cronDelegate: CoreCronDelegate,
siteInfoCronHandler: CoreSiteInfoCronHandler,
+ injector: Injector,
) {
// Register a handler for platform ready.
initDelegate.registerProcess({
@@ -391,6 +394,9 @@ export class AppModule {
// Register handlers.
cronDelegate.register(siteInfoCronHandler);
+ // Set the injector.
+ setSingletonsInjector(injector);
+
// Set transition animation.
config.setTransition('core-page-transition', CorePageTransition);
config.setTransition('core-modal-lateral-transition', CoreModalLateralTransition);
diff --git a/src/classes/native-to-angular-http.ts b/src/classes/native-to-angular-http.ts
new file mode 100644
index 000000000..e21e7eb7e
--- /dev/null
+++ b/src/classes/native-to-angular-http.ts
@@ -0,0 +1,98 @@
+// (C) Copyright 2015 Moodle Pty Ltd.
+//
+// Licensed under the Apache License, Version 2.0 (the "License");
+// you may not use this file except in compliance with the License.
+// You may obtain a copy of the License at
+//
+// http://www.apache.org/licenses/LICENSE-2.0
+//
+// Unless required by applicable law or agreed to in writing, software
+// distributed under the License is distributed on an "AS IS" BASIS,
+// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+// See the License for the specific language governing permissions and
+// limitations under the License.
+
+import { HttpResponse as AngularHttpResponse, HttpHeaders } from '@angular/common/http';
+import { HTTPResponse as NativeHttpResponse } from '@ionic-native/http';
+
+const HTTP_STATUS_MESSAGES = {
+ 100: 'Continue',
+ 101: 'Switching Protocol',
+ 102: 'Processing',
+ 103: 'Early Hints',
+ 200: 'OK',
+ 201: 'Created',
+ 202: 'Accepted',
+ 203: 'Non-Authoritative Information',
+ 204: 'No Content',
+ 205: 'Reset Content',
+ 206: 'Partial Content',
+ 207: 'Multi-Status',
+ 208: 'Already Reported',
+ 226: 'IM Used',
+ 300: 'Multiple Choice',
+ 301: 'Moved Permanently',
+ 302: 'Found',
+ 303: 'See Other',
+ 304: 'Not Modified',
+ 305: 'Use Proxy',
+ 306: 'unused',
+ 307: 'Temporary Redirect',
+ 308: 'Permanent Redirect',
+ 400: 'Bad Request',
+ 401: 'Unauthorized',
+ 402: 'Payment Required',
+ 403: 'Forbidden',
+ 404: 'Not Found',
+ 405: 'Method Not Allowed',
+ 406: 'Not Acceptable',
+ 407: 'Proxy Authentication Required',
+ 408: 'Request Timeout',
+ 409: 'Conflict',
+ 410: 'Gone',
+ 411: 'Length Required',
+ 412: 'Precondition Failed',
+ 413: 'Payload Too Large',
+ 414: 'URI Too Long',
+ 415: 'Unsupported Media Type',
+ 416: 'Range Not Satisfiable',
+ 417: 'Expectation Failed',
+ 418: 'I\'m a teapot',
+ 421: 'Misdirected Request',
+ 422: 'Unprocessable Entity',
+ 423: 'Locked',
+ 424: 'Failed Dependency',
+ 425: 'Too Early',
+ 426: 'Upgrade Required',
+ 428: 'Precondition Required',
+ 429: 'Too Many Requests',
+ 431: 'Request Header Fields Too Large',
+ 451: 'Unavailable For Legal Reasons',
+ 500: 'Internal Server Error',
+ 501: 'Not Implemented',
+ 502: 'Bad Gateway',
+ 503: 'Service Unavailable',
+ 504: 'Gateway Timeout',
+ 505: 'HTTP Version Not Supported',
+ 506: 'Variant Also Negotiates',
+ 507: 'Insufficient Storage',
+ 508: 'Loop Detected',
+ 510: 'Not Extended',
+ 511: 'Network Authentication Required',
+};
+
+/**
+ * Class that adapts a Cordova plugin http response to an Angular http response.
+ */
+export class CoreNativeToAngularHttpResponse extends AngularHttpResponse {
+
+ constructor(protected nativeResponse: NativeHttpResponse) {
+ super({
+ body: nativeResponse.data,
+ headers: new HttpHeaders(nativeResponse.headers),
+ status: nativeResponse.status,
+ statusText: HTTP_STATUS_MESSAGES[nativeResponse.status] || '',
+ url: nativeResponse.url || ''
+ });
+ }
+}
diff --git a/src/classes/singletons-factory.ts b/src/classes/singletons-factory.ts
new file mode 100644
index 000000000..791b192f0
--- /dev/null
+++ b/src/classes/singletons-factory.ts
@@ -0,0 +1,76 @@
+// (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 { Injector, Type } from '@angular/core';
+
+/**
+ * Stub class used to type anonymous classes created in CoreSingletonsFactory#makeSingleton method.
+ */
+class CoreSingleton {}
+
+/**
+ * Token that can be used to resolve instances from the injector.
+ */
+export type CoreInjectionToken = Type | Type | string;
+
+/**
+ * Singleton class created using the factory.
+ */
+export type CoreSingletonClass = typeof CoreSingleton & { instance: Service };
+
+/**
+ * Factory used to create CoreSingleton classes that get instances from an injector.
+ */
+export class CoreSingletonsFactory {
+
+ /**
+ * Angular injector used to resolve singleton instances.
+ */
+ private injector: Injector;
+
+ /**
+ * Set the injector that will be used to resolve instances in the singletons created with this factory.
+ *
+ * @param injector Injector.
+ */
+ setInjector(injector: Injector): void {
+ this.injector = injector;
+ }
+
+ /**
+ * Make a singleton that will hold an instance resolved from the factory injector.
+ *
+ * @param injectionToken Injection token used to resolve the singleton instance. This is usually the service class if the
+ * provider was defined using a class or the string used in the `provide` key if it was defined using an object.
+ */
+ makeSingleton(injectionToken: CoreInjectionToken): CoreSingletonClass {
+ // tslint:disable: no-this-assignment
+ const factory = this;
+
+ return class {
+
+ private static _instance: Service;
+
+ static get instance(): Service {
+ // Initialize instances lazily.
+ if (!this._instance) {
+ this._instance = factory.injector.get(injectionToken);
+ }
+
+ return this._instance;
+ }
+
+ };
+ }
+}
diff --git a/src/classes/site.ts b/src/classes/site.ts
index 54b2dd294..befacd29e 100644
--- a/src/classes/site.ts
+++ b/src/classes/site.ts
@@ -14,7 +14,6 @@
import { Injector } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
-import { HttpClient } from '@angular/common/http';
import { SQLiteDB } from './sqlitedb';
import { CoreAppProvider } from '@providers/app';
import { CoreDbProvider } from '@providers/db';
@@ -190,7 +189,6 @@ export class CoreSite {
protected domUtils: CoreDomUtilsProvider;
protected eventsProvider: CoreEventsProvider;
protected fileProvider: CoreFileProvider;
- protected http: HttpClient;
protected textUtils: CoreTextUtilsProvider;
protected timeUtils: CoreTimeUtilsProvider;
protected translate: TranslateService;
@@ -256,7 +254,6 @@ export class CoreSite {
this.domUtils = injector.get(CoreDomUtilsProvider);
this.eventsProvider = injector.get(CoreEventsProvider);
this.fileProvider = injector.get(CoreFileProvider);
- this.http = injector.get(HttpClient);
this.textUtils = injector.get(CoreTextUtilsProvider);
this.timeUtils = injector.get(CoreTimeUtilsProvider);
this.translate = injector.get(TranslateService);
@@ -1357,55 +1354,62 @@ export class CoreSite {
* @param retrying True if we're retrying the check.
* @return Promise resolved when the check is done.
*/
- checkLocalMobilePlugin(retrying?: boolean): Promise {
+ async checkLocalMobilePlugin(retrying?: boolean): Promise {
const checkUrl = this.siteUrl + '/local/mobile/check.php',
service = CoreConfigConstants.wsextservice;
if (!service) {
// External service not defined.
- return Promise.resolve({ code: 0 });
+ return { code: 0 };
}
- const promise = this.http.post(checkUrl, { service: service }).timeout(this.wsProvider.getRequestTimeout()).toPromise();
+ let data;
- return promise.then((data: any) => {
- if (typeof data != 'undefined' && data.errorcode === 'requirecorrectaccess') {
- if (!retrying) {
- this.siteUrl = this.urlUtils.addOrRemoveWWW(this.siteUrl);
+ try {
+ const response = await this.wsProvider.sendHTTPRequest(checkUrl, {
+ method: 'post',
+ data: { service: service },
+ });
- return this.checkLocalMobilePlugin(true);
- } else {
- return Promise.reject(data.error);
- }
- } else if (typeof data == 'undefined' || typeof data.code == 'undefined') {
- // The local_mobile returned something we didn't expect. Let's assume it's not installed.
- return { code: 0, warning: 'core.login.localmobileunexpectedresponse' };
- }
-
- const code = parseInt(data.code, 10);
- if (data.error) {
- switch (code) {
- case 1:
- // Site in maintenance mode.
- return Promise.reject(this.translate.instant('core.login.siteinmaintenance'));
- case 2:
- // Web services not enabled.
- return Promise.reject(this.translate.instant('core.login.webservicesnotenabled'));
- case 3:
- // Extended service not enabled, but the official is enabled.
- return { code: 0 };
- case 4:
- // Neither extended or official services enabled.
- return Promise.reject(this.translate.instant('core.login.mobileservicesnotenabled'));
- default:
- return Promise.reject(this.translate.instant('core.unexpectederror'));
- }
- } else {
- return { code: code, service: service, coreSupported: !!data.coresupported };
- }
- }, () => {
+ data = response.body;
+ } catch (ex) {
return { code: 0 };
- });
+ }
+
+ if (typeof data != 'undefined' && data.errorcode === 'requirecorrectaccess') {
+ if (!retrying) {
+ this.siteUrl = this.urlUtils.addOrRemoveWWW(this.siteUrl);
+
+ return this.checkLocalMobilePlugin(true);
+ } else {
+ throw data.error;
+ }
+ } else if (typeof data == 'undefined' || typeof data.code == 'undefined') {
+ // The local_mobile returned something we didn't expect. Let's assume it's not installed.
+ return { code: 0, warning: 'core.login.localmobileunexpectedresponse' };
+ }
+
+ const code = parseInt(data.code, 10);
+ if (data.error) {
+ switch (code) {
+ case 1:
+ // Site in maintenance mode.
+ throw this.translate.instant('core.login.siteinmaintenance');
+ case 2:
+ // Web services not enabled.
+ throw this.translate.instant('core.login.webservicesnotenabled');
+ case 3:
+ // Extended service not enabled, but the official is enabled.
+ return { code: 0 };
+ case 4:
+ // Neither extended or official services enabled.
+ throw this.translate.instant('core.login.mobileservicesnotenabled');
+ default:
+ throw this.translate.instant('core.unexpectederror');
+ }
+ } else {
+ return { code: code, service: service, coreSupported: !!data.coresupported };
+ }
}
/**
@@ -1970,7 +1974,7 @@ export class CoreSite {
url = this.fixPluginfileURL(url);
this.tokenPluginFileWorksPromise = this.wsProvider.performHead(url).then((result) => {
- return result.ok;
+ return result.status >= 200 && result.status < 300;
}).catch((error) => {
// Error performing head request.
return false;
diff --git a/src/components/iframe/iframe.ts b/src/components/iframe/iframe.ts
index 516012a8f..8f7ce3b49 100644
--- a/src/components/iframe/iframe.ts
+++ b/src/components/iframe/iframe.ts
@@ -16,13 +16,16 @@ import {
Component, Input, Output, OnInit, ViewChild, ElementRef, EventEmitter, OnChanges, SimpleChange, Optional
} from '@angular/core';
import { DomSanitizer, SafeResourceUrl } from '@angular/platform-browser';
-import { NavController } from 'ionic-angular';
+import { NavController, Platform } from 'ionic-angular';
+import { CoreFile } from '@providers/file';
import { CoreLoggerProvider } from '@providers/logger';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreIframeUtilsProvider } from '@providers/utils/iframe';
import { CoreUtilsProvider } from '@providers/utils/utils';
import { CoreSplitViewComponent } from '@components/split-view/split-view';
+import { CoreUrl } from '@singletons/url';
+import { WKWebViewCookiesWindow } from 'cordova-plugin-wkwebview-cookies';
@Component({
selector: 'core-iframe',
@@ -49,7 +52,8 @@ export class CoreIframeComponent implements OnInit, OnChanges {
protected navCtrl: NavController,
protected urlUtils: CoreUrlUtilsProvider,
protected utils: CoreUtilsProvider,
- @Optional() protected svComponent: CoreSplitViewComponent) {
+ @Optional() protected svComponent: CoreSplitViewComponent,
+ protected platform: Platform) {
this.logger = logger.getInstance('CoreIframe');
this.loaded = new EventEmitter();
@@ -91,10 +95,28 @@ export class CoreIframeComponent implements OnInit, OnChanges {
/**
* Detect changes on input properties.
*/
- ngOnChanges(changes: {[name: string]: SimpleChange }): void {
+ async ngOnChanges(changes: {[name: string]: SimpleChange }): Promise {
if (changes.src) {
- const youtubeUrl = this.urlUtils.getYoutubeEmbedUrl(changes.src.currentValue);
- this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(youtubeUrl || changes.src.currentValue);
+ const url = this.urlUtils.getYoutubeEmbedUrl(changes.src.currentValue) || changes.src.currentValue;
+
+ if (this.platform.is('ios')) {
+ // Save a "fake" cookie for the iframe's domain to fix a bug in WKWebView.
+ try {
+ const win = window;
+ const urlParts = CoreUrl.parse(url);
+
+ await win.WKWebViewCookies.setCookie({
+ name: 'MoodleAppCookieForWKWebView',
+ value: '1',
+ domain: urlParts.domain,
+ });
+ } catch (err) {
+ // Ignore errors.
+ this.logger.error('Error setting cookie', err);
+ }
+ }
+
+ this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(CoreFile.instance.convertFileSrc(url));
}
}
}
diff --git a/src/config.json b/src/config.json
index 50897768a..a81136800 100644
--- a/src/config.json
+++ b/src/config.json
@@ -93,5 +93,6 @@
"statusbarbgremotetheme": "#000000",
"statusbarlighttextremotetheme": true,
"enableanalytics": false,
- "forceColorScheme": ""
+ "forceColorScheme": "",
+ "webviewscheme": "moodleappfs"
}
\ No newline at end of file
diff --git a/src/core/compile/providers/compile.ts b/src/core/compile/providers/compile.ts
index 51cbc8afd..fc8268f61 100644
--- a/src/core/compile/providers/compile.ts
+++ b/src/core/compile/providers/compile.ts
@@ -56,7 +56,7 @@ import { Md5 } from 'ts-md5/dist/md5';
// Import core classes that can be useful for site plugins.
import { CoreSyncBaseProvider } from '@classes/base-sync';
-import { CoreUrl } from '@classes/utils/url';
+import { CoreUrl } from '@singletons/url';
import { CoreCache } from '@classes/cache';
import { CoreDelegate } from '@classes/delegate';
import { CoreContentLinksHandlerBase } from '@core/contentlinks/classes/base-handler';
diff --git a/src/core/h5p/providers/h5p.ts b/src/core/h5p/providers/h5p.ts
index 75fe4b2b9..821509cda 100644
--- a/src/core/h5p/providers/h5p.ts
+++ b/src/core/h5p/providers/h5p.ts
@@ -27,6 +27,7 @@ import { CoreH5PUtilsProvider } from './utils';
import { CoreH5PContentValidator } from '../classes/content-validator';
import { TranslateService } from '@ngx-translate/core';
import { FileEntry } from '@ionic-native/file';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Service to provide H5P functionalities.
@@ -410,7 +411,7 @@ export class CoreH5PProvider {
* @return Promise resolved with all of the files content in one string.
*/
protected concatenateFiles(assets: CoreH5PDependencyAsset[], type: string): Promise {
- const basePath = this.fileProvider.getBasePathInstant();
+ const basePath = this.fileProvider.convertFileSrc(this.fileProvider.getBasePathInstant());
let content = '',
promise = Promise.resolve(); // Use a chain of promises so the order is kept.
@@ -495,7 +496,8 @@ export class CoreH5PProvider {
const contentId = this.getContentId(id),
basePath = this.fileProvider.getBasePathInstant(),
- contentUrl = this.textUtils.concatenatePaths(basePath, this.getContentFolderPath(content.folderName, site.getId()));
+ contentUrl = this.fileProvider.convertFileSrc(this.textUtils.concatenatePaths(
+ basePath, this.getContentFolderPath(content.folderName, site.getId())));
// Create the settings needed for the content.
const contentSettings = {
@@ -560,6 +562,40 @@ export class CoreH5PProvider {
});
}
+ /**
+ * Delete all content indexes of all sites from filesystem.
+ *
+ * @return Promise resolved when done.
+ */
+ async deleteAllContentIndexes(): Promise {
+ const siteIds = await this.sitesProvider.getSitesIds();
+
+ await Promise.all(siteIds.map((siteId) => this.deleteAllContentIndexesForSite(siteId)));
+ }
+
+ /**
+ * Delete all content indexes for a certain site from filesystem.
+ *
+ * @param siteId Site ID. If not defined, current site.
+ * @return Promise resolved when done.
+ */
+ async deleteAllContentIndexesForSite(siteId?: string): Promise {
+
+ const site = await this.sitesProvider.getSite(siteId);
+
+ const records = await site.getDb().getAllRecords(this.CONTENT_TABLE);
+
+ const promises = records.map(async (record) => {
+ try {
+ await this.fileProvider.removeFile(this.getContentIndexPath(record.foldername, site.getId()));
+ } catch (err) {
+ // Ignore errors, maybe the file doesn't exist.
+ }
+ });
+
+ await Promise.all(promises);
+ }
+
/**
* Delete cached assets from DB and filesystem.
*
@@ -1033,12 +1069,11 @@ export class CoreH5PProvider {
settings.loadedJs = [];
settings.loadedCss = [];
- const libUrl = this.getCoreH5PPath(),
- relPath = this.urlUtils.removeProtocolAndWWW(libUrl);
+ const libUrl = this.getCoreH5PPath();
// Add core stylesheets.
CoreH5PProvider.STYLES.forEach((style) => {
- settings.core.styles.push(relPath + style);
+ settings.core.styles.push(libUrl + style);
cssRequires.push(libUrl + style);
});
@@ -1279,8 +1314,10 @@ export class CoreH5PProvider {
return {
baseUrl: this.fileProvider.getWWWPath(),
- url: this.textUtils.concatenatePaths(basePath, this.getExternalH5PFolderPath(site.getId())),
- urlLibraries: this.textUtils.concatenatePaths(basePath, this.getLibrariesFolderPath(site.getId())),
+ url: this.fileProvider.convertFileSrc(this.textUtils.concatenatePaths(
+ basePath, this.getExternalH5PFolderPath(site.getId()))),
+ urlLibraries: this.fileProvider.convertFileSrc(this.textUtils.concatenatePaths(
+ basePath, this.getLibrariesFolderPath(site.getId()))),
postUserStatistics: false,
ajax: ajaxPaths,
saveFreq: false,
@@ -2623,6 +2660,8 @@ export class CoreH5PProvider {
}
}
+export class CoreH5P extends makeSingleton(CoreH5PProvider) {}
+
/**
* Display options behaviour constants.
*/
diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts
index ebf6cfcaf..400491069 100644
--- a/src/core/login/pages/site/site.ts
+++ b/src/core/login/pages/site/site.ts
@@ -22,7 +22,7 @@ import { CoreUrlUtilsProvider } from '@providers/utils/url';
import { CoreConfigConstants } from '../../../../configconstants';
import { CoreLoginHelperProvider } from '../../providers/helper';
import { FormBuilder, FormGroup, Validators } from '@angular/forms';
-import { CoreUrl } from '@classes/utils/url';
+import { CoreUrl } from '@singletons/url';
import { TranslateService } from '@ngx-translate/core';
/**
diff --git a/src/directives/external-content.ts b/src/directives/external-content.ts
index 1879772c7..47b4c776e 100644
--- a/src/directives/external-content.ts
+++ b/src/directives/external-content.ts
@@ -16,6 +16,7 @@ import { Directive, Input, AfterViewInit, ElementRef, OnChanges, SimpleChange, O
import { Platform } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreLoggerProvider } from '@providers/logger';
+import { CoreFile } from '@providers/file';
import { CoreFilepoolProvider } from '@providers/filepool';
import { CoreSitesProvider } from '@providers/sites';
import { CoreDomUtilsProvider } from '@providers/utils/dom';
@@ -52,9 +53,15 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges {
invalid = false;
- constructor(element: ElementRef, logger: CoreLoggerProvider, private filepoolProvider: CoreFilepoolProvider,
- private platform: Platform, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider,
- private urlUtils: CoreUrlUtilsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider) {
+ constructor(element: ElementRef,
+ logger: CoreLoggerProvider,
+ protected filepoolProvider: CoreFilepoolProvider,
+ protected platform: Platform,
+ protected sitesProvider: CoreSitesProvider,
+ protected domUtils: CoreDomUtilsProvider,
+ protected urlUtils: CoreUrlUtilsProvider,
+ protected appProvider: CoreAppProvider,
+ protected utils: CoreUtilsProvider) {
// This directive can be added dynamically. In that case, the first param is the HTMLElement.
this.element = element.nativeElement || element;
this.logger = logger.getInstance('CoreExternalContentDirective');
@@ -179,7 +186,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges {
* @param siteId Site ID.
* @return Promise resolved if the element is successfully treated.
*/
- protected handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise {
+ protected async handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise {
const tagName = this.element.tagName;
@@ -214,72 +221,70 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges {
this.addSource(url);
}
- return Promise.reject(null);
+ throw 'Non-downloadable URL';
}
- // Get the webservice pluginfile URL, we ignore failures here.
- return this.sitesProvider.getSite(siteId).then((site) => {
- if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) {
- this.element.parentElement.removeChild(this.element); // Remove element since it'll be broken.
+ const site = await this.sitesProvider.getSite(siteId);
- return Promise.reject(null);
+ if (!site.canDownloadFiles() && this.urlUtils.isPluginFileUrl(url)) {
+ this.element.parentElement.removeChild(this.element); // Remove element since it'll be broken.
+
+ throw 'Site doesn\'t allow downloading files.';
+ }
+
+ // Download images, tracks and posters if size is unknown.
+ const dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster';
+ let finalUrl: string;
+
+ if (targetAttr === 'src' && tagName !== 'SOURCE' && tagName !== 'TRACK' && tagName !== 'VIDEO' && tagName !== 'AUDIO') {
+ finalUrl = await this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
+ } else {
+ finalUrl = await this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
+
+ finalUrl = CoreFile.instance.convertFileSrc(finalUrl);
+ }
+
+ if (finalUrl.match(/^https?:\/\//i)) {
+ /* In iOS, if we use the same URL in embedded file and background download then the download only
+ downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */
+ finalUrl = finalUrl + '#moodlemobile-embedded';
+ }
+
+ this.logger.debug('Using URL ' + finalUrl + ' for ' + url);
+ if (tagName === 'SOURCE') {
+ // The browser does not catch changes in SRC, we need to add a new source.
+ this.addSource(finalUrl);
+ } else {
+ if (tagName === 'IMG') {
+ this.loaded = false;
+ this.waitForLoad();
+ }
+ this.element.setAttribute(targetAttr, finalUrl);
+ this.element.setAttribute('data-original-' + targetAttr, url);
+ }
+
+ // Set events to download big files (not downloaded automatically).
+ if (finalUrl.indexOf('http') === 0 && targetAttr != 'poster' &&
+ (tagName == 'VIDEO' || tagName == 'AUDIO' || tagName == 'A' || tagName == 'SOURCE')) {
+ const eventName = tagName == 'A' ? 'click' : 'play';
+ let clickableEl = this.element;
+
+ if (tagName == 'SOURCE') {
+ clickableEl = this.domUtils.closest(this.element, 'video,audio');
+ if (!clickableEl) {
+ return;
+ }
}
- // Download images, tracks and posters if size is unknown.
- const dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster';
- let promise;
-
- if (targetAttr === 'src' && tagName !== 'SOURCE' && tagName !== 'TRACK' && tagName !== 'VIDEO' &&
- tagName !== 'AUDIO') {
- promise = this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
- } else {
- promise = this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown);
- }
-
- return promise.then((finalUrl) => {
- if (finalUrl.match(/^https?:\/\//i)) {
- /* In iOS, if we use the same URL in embedded file and background download then the download only
- downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */
- finalUrl = finalUrl + '#moodlemobile-embedded';
- }
-
- this.logger.debug('Using URL ' + finalUrl + ' for ' + url);
- if (tagName === 'SOURCE') {
- // The browser does not catch changes in SRC, we need to add a new source.
- this.addSource(finalUrl);
- } else {
- if (tagName === 'IMG') {
- this.loaded = false;
- this.waitForLoad();
- }
- this.element.setAttribute(targetAttr, finalUrl);
- this.element.setAttribute('data-original-' + targetAttr, url);
- }
-
- // Set events to download big files (not downloaded automatically).
- if (finalUrl.indexOf('http') === 0 && targetAttr != 'poster' &&
- (tagName == 'VIDEO' || tagName == 'AUDIO' || tagName == 'A' || tagName == 'SOURCE')) {
- const eventName = tagName == 'A' ? 'click' : 'play';
- let clickableEl = this.element;
-
- if (tagName == 'SOURCE') {
- clickableEl = this.domUtils.closest(this.element, 'video,audio');
- if (!clickableEl) {
- return;
- }
- }
-
- clickableEl.addEventListener(eventName, () => {
- // User played media or opened a downloadable link.
- // Download the file if in wifi and it hasn't been downloaded already (for big files).
- if (this.appProvider.isWifi()) {
- // We aren't using the result, so it doesn't matter which of the 2 functions we call.
- this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, false);
- }
- });
+ clickableEl.addEventListener(eventName, () => {
+ // User played media or opened a downloadable link.
+ // Download the file if in wifi and it hasn't been downloaded already (for big files).
+ if (this.appProvider.isWifi()) {
+ // We aren't using the result, so it doesn't matter which of the 2 functions we call.
+ this.filepoolProvider.getUrlByUrl(siteId, url, this.component, this.componentId, 0, false);
}
});
- });
+ }
}
/**
diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts
index 7057b269f..9dfe8a746 100644
--- a/src/directives/format-text.ts
+++ b/src/directives/format-text.ts
@@ -74,26 +74,27 @@ export class CoreFormatTextDirective implements OnChanges {
protected loadingChangedListener;
constructor(element: ElementRef,
- private sitesProvider: CoreSitesProvider,
- private domUtils: CoreDomUtilsProvider,
- private textUtils: CoreTextUtilsProvider,
- private translate: TranslateService,
- private platform: Platform,
- private utils: CoreUtilsProvider,
- private urlUtils: CoreUrlUtilsProvider,
- private loggerProvider: CoreLoggerProvider,
- private filepoolProvider: CoreFilepoolProvider,
- private appProvider: CoreAppProvider,
- private contentLinksHelper: CoreContentLinksHelperProvider,
- @Optional() private navCtrl: NavController,
- @Optional() private content: Content, @Optional()
- private svComponent: CoreSplitViewComponent,
- private iframeUtils: CoreIframeUtilsProvider,
- private eventsProvider: CoreEventsProvider,
- private filterProvider: CoreFilterProvider,
- private filterHelper: CoreFilterHelperProvider,
- private filterDelegate: CoreFilterDelegate,
- private viewContainerRef: ViewContainerRef) {
+ protected sitesProvider: CoreSitesProvider,
+ protected domUtils: CoreDomUtilsProvider,
+ protected textUtils: CoreTextUtilsProvider,
+ protected translate: TranslateService,
+ protected platform: Platform,
+ protected utils: CoreUtilsProvider,
+ protected urlUtils: CoreUrlUtilsProvider,
+ protected loggerProvider: CoreLoggerProvider,
+ protected filepoolProvider: CoreFilepoolProvider,
+ protected appProvider: CoreAppProvider,
+ protected contentLinksHelper: CoreContentLinksHelperProvider,
+ @Optional() protected navCtrl: NavController,
+ @Optional() protected content: Content, @Optional()
+ protected svComponent: CoreSplitViewComponent,
+ protected iframeUtils: CoreIframeUtilsProvider,
+ protected eventsProvider: CoreEventsProvider,
+ protected filterProvider: CoreFilterProvider,
+ protected filterHelper: CoreFilterHelperProvider,
+ protected filterDelegate: CoreFilterDelegate,
+ protected viewContainerRef: ViewContainerRef,
+ ) {
this.element = element.nativeElement;
this.element.classList.add('opacity-hide'); // Hide contents until they're treated.
diff --git a/src/directives/link.ts b/src/directives/link.ts
index 5ccfe3c64..5d85e2199 100644
--- a/src/directives/link.ts
+++ b/src/directives/link.ts
@@ -92,7 +92,7 @@ export class CoreLinkDirective implements OnInit {
protected navigate(href: string): void {
const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link=';
- if (href.indexOf('cdvfile://') === 0 || href.indexOf('file://') === 0 || href.indexOf('filesystem:') === 0) {
+ if (this.urlUtils.isLocalFileUrl(href)) {
// We have a local file.
this.utils.openFile(href).catch((error) => {
this.domUtils.showErrorModal(error);
diff --git a/src/index.html b/src/index.html
index fa07d2622..78f94ce36 100644
--- a/src/index.html
+++ b/src/index.html
@@ -4,7 +4,7 @@
Moodle Desktop
-
+
diff --git a/src/providers/app.ts b/src/providers/app.ts
index 7a50ff553..4d29185e5 100644
--- a/src/providers/app.ts
+++ b/src/providers/app.ts
@@ -23,6 +23,7 @@ import { CoreLoggerProvider } from './logger';
import { CoreEventsProvider } from './events';
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { CoreConfigConstants } from '../configconstants';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Data stored for a redirect to another page/site.
@@ -703,3 +704,5 @@ export class CoreAppProvider {
this.forceOffline = !!value;
}
}
+
+export class CoreApp extends makeSingleton(CoreAppProvider) {}
diff --git a/src/providers/config.ts b/src/providers/config.ts
index 709b5ce8e..cf62a9012 100644
--- a/src/providers/config.ts
+++ b/src/providers/config.ts
@@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreAppProvider, CoreAppSchema } from './app';
import { SQLiteDB } from '@classes/sqlitedb';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Factory to provide access to dynamic and permanent config and settings.
@@ -102,3 +103,5 @@ export class CoreConfigProvider {
return this.appDB.insertRecord(this.TABLE_NAME, { name: name, value: value });
}
}
+
+export class CoreConfig extends makeSingleton(CoreConfigProvider) {}
diff --git a/src/providers/cron.ts b/src/providers/cron.ts
index f0b130e9f..0cdf5e7e2 100644
--- a/src/providers/cron.ts
+++ b/src/providers/cron.ts
@@ -20,6 +20,7 @@ import { CoreLoggerProvider } from './logger';
import { CoreUtilsProvider } from './utils/utils';
import { CoreConstants } from '@core/constants';
import { SQLiteDB } from '@classes/sqlitedb';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Interface that all cron handlers must implement.
@@ -554,3 +555,5 @@ export class CoreCronDelegate {
delete this.handlers[name].timeout;
}
}
+
+export class CoreCron extends makeSingleton(CoreCronDelegate) {}
diff --git a/src/providers/db.ts b/src/providers/db.ts
index 6805c82e1..8e6c03299 100644
--- a/src/providers/db.ts
+++ b/src/providers/db.ts
@@ -17,6 +17,7 @@ import { SQLite } from '@ionic-native/sqlite';
import { Platform } from 'ionic-angular';
import { SQLiteDB } from '@classes/sqlitedb';
import { SQLiteDBMock } from '@core/emulator/classes/sqlitedb';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* This service allows interacting with the local database to store and retrieve data.
@@ -81,3 +82,5 @@ export class CoreDbProvider {
});
}
}
+
+export class CoreDB extends makeSingleton(CoreDbProvider) {}
diff --git a/src/providers/events.ts b/src/providers/events.ts
index 4da2900c0..a5e6e3bef 100644
--- a/src/providers/events.ts
+++ b/src/providers/events.ts
@@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { Subject } from 'rxjs';
import { CoreLoggerProvider } from '@providers/logger';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Observer instance to stop listening to an event.
@@ -174,3 +175,5 @@ export class CoreEventsProvider {
}
}
}
+
+export class CoreEvents extends makeSingleton(CoreEventsProvider) {}
diff --git a/src/providers/file-helper.ts b/src/providers/file-helper.ts
index 73daa59b3..00b4b4150 100644
--- a/src/providers/file-helper.ts
+++ b/src/providers/file-helper.ts
@@ -21,6 +21,7 @@ import { CoreSitesProvider } from './sites';
import { CoreWSProvider } from './ws';
import { CoreUtilsProvider } from './utils/utils';
import { CoreConstants } from '@core/constants';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Provider to provide some helper functions regarding files and packages.
@@ -333,5 +334,6 @@ export class CoreFileHelperProvider {
throw new Error('Couldn\'t determine file size: ' + file.fileurl);
}
-
}
+
+export class CoreFileHelper extends makeSingleton(CoreFileHelperProvider) {}
diff --git a/src/providers/file-session.ts b/src/providers/file-session.ts
index 79798ae0f..552284a86 100644
--- a/src/providers/file-session.ts
+++ b/src/providers/file-session.ts
@@ -14,6 +14,7 @@
import { Injectable } from '@angular/core';
import { CoreSitesProvider } from './sites';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Helper to store some temporary data for file submission.
@@ -146,3 +147,5 @@ export class CoreFileSessionProvider {
this.files[siteId][component][id] = newFiles;
}
}
+
+export class CoreFileSession extends makeSingleton(CoreFileSessionProvider) {}
diff --git a/src/providers/file.ts b/src/providers/file.ts
index 07d1628f5..1bee27880 100644
--- a/src/providers/file.ts
+++ b/src/providers/file.ts
@@ -20,7 +20,9 @@ import { CoreAppProvider } from './app';
import { CoreLoggerProvider } from './logger';
import { CoreMimetypeUtilsProvider } from './utils/mimetype';
import { CoreTextUtilsProvider } from './utils/text';
+import { CoreConfigConstants } from '../configconstants';
import { Zip } from '@ionic-native/zip';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Progress event used when writing a file data into a file.
@@ -945,6 +947,7 @@ export class CoreFileProvider {
/**
* Get the internal URL of a file.
+ * Please notice that with WKWebView these URLs no longer work in mobile. Use fileEntry.toURL() along with convertFileSrc.
*
* @param fileEntry File Entry.
* @return Internal URL.
@@ -1269,4 +1272,31 @@ export class CoreFileProvider {
return window.location.href;
}
+
+ /**
+ * Helper function to call Ionic WebView convertFileSrc only in the needed platforms.
+ * This is needed to make files work with the Ionic WebView plugin.
+ *
+ * @param src Source to convert.
+ * @return Converted src.
+ */
+ convertFileSrc(src: string): string {
+ return this.appProvider.isMobile() ? ( window).Ionic.WebView.convertFileSrc(src) : src;
+ }
+
+ /**
+ * Undo the conversion of convertFileSrc.
+ *
+ * @param src Source to unconvert.
+ * @return Unconverted src.
+ */
+ unconvertFileSrc(src: string): string {
+ if (!this.appProvider.isMobile()) {
+ return src;
+ }
+
+ return src.replace(CoreConfigConstants.webviewscheme + '://localhost/_app_file_', 'file://');
+ }
}
+
+export class CoreFile extends makeSingleton(CoreFileProvider) {}
diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts
index a3889773d..0f939b65d 100644
--- a/src/providers/filepool.ts
+++ b/src/providers/filepool.ts
@@ -31,6 +31,7 @@ import { CoreUtilsProvider } from './utils/utils';
import { SQLiteDB } from '@classes/sqlitedb';
import { CoreConstants } from '@core/constants';
import { Md5 } from 'ts-md5/dist/md5';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Entry from filepool.
@@ -471,11 +472,21 @@ export class CoreFilepoolProvider {
protected packagesPromises = {};
protected filePromises: { [s: string]: { [s: string]: Promise } } = {};
- constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider,
- private sitesProvider: CoreSitesProvider, private wsProvider: CoreWSProvider, private textUtils: CoreTextUtilsProvider,
- private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private urlUtils: CoreUrlUtilsProvider,
- private timeUtils: CoreTimeUtilsProvider, private eventsProvider: CoreEventsProvider, initDelegate: CoreInitDelegate,
- network: Network, private pluginFileDelegate: CorePluginFileDelegate, private domUtils: CoreDomUtilsProvider,
+ constructor(logger: CoreLoggerProvider,
+ protected appProvider: CoreAppProvider,
+ protected fileProvider: CoreFileProvider,
+ protected sitesProvider: CoreSitesProvider,
+ protected wsProvider: CoreWSProvider,
+ protected textUtils: CoreTextUtilsProvider,
+ protected utils: CoreUtilsProvider,
+ protected mimeUtils: CoreMimetypeUtilsProvider,
+ protected urlUtils: CoreUrlUtilsProvider,
+ protected timeUtils: CoreTimeUtilsProvider,
+ protected eventsProvider: CoreEventsProvider,
+ initDelegate: CoreInitDelegate,
+ network: Network,
+ protected pluginFileDelegate: CorePluginFileDelegate,
+ protected domUtils: CoreDomUtilsProvider,
zone: NgZone) {
this.logger = logger.getInstance('CoreFilepoolProvider');
@@ -1901,8 +1912,7 @@ export class CoreFilepoolProvider {
if (this.fileProvider.isAvailable()) {
return Promise.resolve(this.getFilePath(siteId, fileId)).then((path) => {
return this.fileProvider.getFile(path).then((fileEntry) => {
- // We use toInternalURL so images are loaded in iOS8 using img HTML tags.
- return this.fileProvider.getInternalURL(fileEntry);
+ return this.fileProvider.convertFileSrc(fileEntry.toURL());
});
});
}
@@ -3250,3 +3260,5 @@ export class CoreFilepoolProvider {
});
}
}
+
+export class CoreFilepool extends makeSingleton(CoreFilepoolProvider) {}
diff --git a/src/providers/groups.ts b/src/providers/groups.ts
index d0c96e8d2..e05335501 100644
--- a/src/providers/groups.ts
+++ b/src/providers/groups.ts
@@ -17,6 +17,7 @@ import { TranslateService } from '@ngx-translate/core';
import { CoreSitesProvider } from './sites';
import { CoreCoursesProvider } from '@core/courses/providers/courses';
import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Group info for an activity.
@@ -449,3 +450,5 @@ export class CoreGroupsProvider {
return groupInfo.defaultGroupId;
}
}
+
+export class CoreGroups extends makeSingleton(CoreGroupsProvider) {}
diff --git a/src/providers/init.ts b/src/providers/init.ts
index f44e6f79e..3ac620352 100644
--- a/src/providers/init.ts
+++ b/src/providers/init.ts
@@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
import { Platform } from 'ionic-angular';
import { CoreLoggerProvider } from './logger';
import { CoreUtilsProvider } from './utils/utils';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Interface that all init handlers must implement.
@@ -175,3 +176,5 @@ export class CoreInitDelegate {
this.initProcesses[handler.name] = handler;
}
}
+
+export class CoreInit extends makeSingleton(CoreInitDelegate) {}
diff --git a/src/providers/lang.ts b/src/providers/lang.ts
index df16004e3..b598be1b7 100644
--- a/src/providers/lang.ts
+++ b/src/providers/lang.ts
@@ -20,6 +20,7 @@ import { Platform, Config } from 'ionic-angular';
import { CoreAppProvider } from '@providers/app';
import { CoreConfigProvider } from './config';
import { CoreConfigConstants } from '../configconstants';
+import { makeSingleton } from '@singletons/core.singletons';
/*
* Service to handle language features, like changing the current language.
@@ -453,3 +454,5 @@ export class CoreLangProvider {
}
}
}
+
+export class CoreLang extends makeSingleton(CoreLangProvider) {}
diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts
index 78f2ccb08..2ac4dbdfb 100644
--- a/src/providers/local-notifications.ts
+++ b/src/providers/local-notifications.ts
@@ -27,6 +27,7 @@ import { SQLiteDB } from '@classes/sqlitedb';
import { CoreConstants } from '@core/constants';
import { CoreConfigConstants } from '../configconstants';
import { Subject, Subscription } from 'rxjs';
+import { makeSingleton } from '@singletons/core.singletons';
/*
* Generated class for the LocalNotificationsProvider provider.
@@ -754,3 +755,5 @@ export class CoreLocalNotificationsProvider {
return this.appDB.updateRecords(this.COMPONENTS_TABLE, {id: newId}, {id: oldId});
}
}
+
+export class CoreLocalNotifications extends makeSingleton(CoreLocalNotificationsProvider) {}
diff --git a/src/providers/logger.ts b/src/providers/logger.ts
index 1453f8cbf..12ad8a79f 100644
--- a/src/providers/logger.ts
+++ b/src/providers/logger.ts
@@ -14,6 +14,7 @@
import { Injectable } from '@angular/core';
import * as moment from 'moment';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Helper service to display messages in the console.
@@ -72,3 +73,5 @@ export class CoreLoggerProvider {
};
}
}
+
+export class CoreLogger extends makeSingleton(CoreLoggerProvider) {}
diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts
index 42ee781a7..b08989dc2 100644
--- a/src/providers/plugin-file-delegate.ts
+++ b/src/providers/plugin-file-delegate.ts
@@ -19,6 +19,7 @@ import { CoreSitesProvider } from './sites';
import { CoreWSExternalFile } from '@providers/ws';
import { FileEntry } from '@ionic-native/file';
import { CoreDelegate, CoreDelegateHandler } from '@classes/delegate';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Interface that all plugin file handlers must implement.
@@ -371,3 +372,5 @@ export class CorePluginFileDelegate extends CoreDelegate {
return Promise.resolve();
}
}
+
+export class CorePluginFile extends makeSingleton(CorePluginFileDelegate) {}
diff --git a/src/providers/sites-factory.ts b/src/providers/sites-factory.ts
index f97d334cb..2ce61382b 100644
--- a/src/providers/sites-factory.ts
+++ b/src/providers/sites-factory.ts
@@ -14,6 +14,7 @@
import { Injectable, Injector } from '@angular/core';
import { CoreSite } from '@classes/site';
+import { makeSingleton } from '@singletons/core.singletons';
/*
* Provider to create sites instances.
@@ -56,3 +57,5 @@ export class CoreSitesFactoryProvider {
return methods;
}
}
+
+export class CoreSitesFactory extends makeSingleton(CoreSitesFactoryProvider) {}
diff --git a/src/providers/sites.ts b/src/providers/sites.ts
index dcf33d0c4..f0f85df99 100644
--- a/src/providers/sites.ts
+++ b/src/providers/sites.ts
@@ -30,6 +30,7 @@ import { CoreSite, CoreSiteWSPreSets } from '@classes/site';
import { SQLiteDB, SQLiteDBTableSchema } from '@classes/sqlitedb';
import { Md5 } from 'ts-md5/dist/md5';
import { WP_PROVIDER } from '@app/app.module';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Response of checking if a site exists and its configuration.
@@ -1925,5 +1926,6 @@ export class CoreSitesProvider {
return {};
}
}
-
}
+
+export class CoreSites extends makeSingleton(CoreSitesProvider) {}
diff --git a/src/providers/sync.ts b/src/providers/sync.ts
index 739d1b59f..c68514165 100644
--- a/src/providers/sync.ts
+++ b/src/providers/sync.ts
@@ -15,6 +15,7 @@
import { Injectable } from '@angular/core';
import { CoreEventsProvider } from './events';
import { CoreSitesProvider, CoreSiteSchema } from './sites';
+import { makeSingleton } from '@singletons/core.singletons';
/*
* Service that provides some features regarding synchronization.
@@ -206,3 +207,5 @@ export class CoreSyncProvider {
}
}
}
+
+export class CoreSync extends makeSingleton(CoreSyncProvider) {}
diff --git a/src/providers/update-manager.ts b/src/providers/update-manager.ts
index b32ef3499..bf76dec22 100644
--- a/src/providers/update-manager.ts
+++ b/src/providers/update-manager.ts
@@ -17,6 +17,8 @@ import { CoreConfigProvider } from './config';
import { CoreInitHandler, CoreInitDelegate } from './init';
import { CoreLoggerProvider } from './logger';
import { CoreConfigConstants } from '../configconstants';
+import { CoreH5P } from '@core/h5p/providers/h5p';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Factory to handle app updates. This factory shouldn't be used outside of core.
@@ -33,7 +35,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
protected VERSION_APPLIED = 'version_applied';
protected logger;
- constructor(logger: CoreLoggerProvider, private configProvider: CoreConfigProvider) {
+ constructor(logger: CoreLoggerProvider,
+ protected configProvider: CoreConfigProvider) {
this.logger = logger.getInstance('CoreUpdateManagerProvider');
}
@@ -43,19 +46,24 @@ export class CoreUpdateManagerProvider implements CoreInitHandler {
*
* @return Promise resolved when the update process finishes.
*/
- load(): Promise {
- const promises = [],
- versionCode = CoreConfigConstants.versioncode;
+ async load(): Promise {
+ const promises = [];
+ const versionCode = CoreConfigConstants.versioncode;
- return this.configProvider.get(this.VERSION_APPLIED, 0).then((versionApplied: number) => {
+ const versionApplied: number = await this.configProvider.get(this.VERSION_APPLIED, 0);
- // Put here the code to treat app updates.
+ if (versionCode >= 3900 && versionApplied < 3900 && versionApplied > 0) {
+ promises.push(CoreH5P.instance.deleteAllContentIndexes());
+ }
- return Promise.all(promises).then(() => {
- return this.configProvider.set(this.VERSION_APPLIED, versionCode);
- }).catch((error) => {
- this.logger.error(`Error applying update from ${versionApplied} to ${versionCode}`, error);
- });
- });
+ try {
+ await Promise.all(promises);
+
+ await this.configProvider.set(this.VERSION_APPLIED, versionCode);
+ } catch (error) {
+ this.logger.error(`Error applying update from ${versionApplied} to ${versionCode}`, error);
+ }
}
}
+
+export class CoreUpdateManager extends makeSingleton(CoreUpdateManagerProvider) {}
diff --git a/src/providers/urlschemes.ts b/src/providers/urlschemes.ts
index b7ed851c3..5c22508d2 100644
--- a/src/providers/urlschemes.ts
+++ b/src/providers/urlschemes.ts
@@ -28,6 +28,7 @@ import { CoreContentLinksDelegate } from '@core/contentlinks/providers/delegate'
import { CoreSitePluginsProvider } from '@core/siteplugins/providers/siteplugins';
import { CoreConfigConstants } from '../configconstants';
import { CoreConstants } from '@core/constants';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* All params that can be in a custom URL scheme.
@@ -488,3 +489,5 @@ export class CoreCustomURLSchemesProvider {
return url.indexOf(CoreConfigConstants.customurlscheme + '://token=') != -1;
}
}
+
+export class CoreCustomURLSchemes extends makeSingleton(CoreCustomURLSchemesProvider) {}
diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts
index c692faf65..743a333ec 100644
--- a/src/providers/utils/dom.ts
+++ b/src/providers/utils/dom.ts
@@ -30,6 +30,7 @@ import { CoreConstants } from '@core/constants';
import { CoreBSTooltipComponent } from '@components/bs-tooltip/bs-tooltip';
import { Md5 } from 'ts-md5/dist/md5';
import { Subject } from 'rxjs';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Interface that defines an extension of the Ionic Alert class, to support multiple listeners.
@@ -1666,3 +1667,5 @@ export class CoreDomUtilsProvider {
}, siteId);
}
}
+
+export class CoreDomUtils extends makeSingleton(CoreDomUtilsProvider) {}
diff --git a/src/providers/utils/iframe.ts b/src/providers/utils/iframe.ts
index e60c19b58..69c4251f8 100644
--- a/src/providers/utils/iframe.ts
+++ b/src/providers/utils/iframe.ts
@@ -25,6 +25,7 @@ import { CoreTextUtilsProvider } from './text';
import { CoreUrlUtilsProvider } from './url';
import { CoreUtilsProvider } from './utils';
import { CoreContentLinksHelperProvider } from '@core/contentlinks/providers/helper';
+import { makeSingleton } from '@singletons/core.singletons';
/*
* "Utils" service with helper functions for iframes, embed and similar.
@@ -224,7 +225,7 @@ export class CoreIframeUtilsProvider {
} else {
element.setAttribute('src', url);
}
- } else if (url.indexOf('cdvfile://') === 0 || url.indexOf('file://') === 0) {
+ } else if (this.urlUtils.isLocalFileUrl(url)) {
// It's a local file.
this.utils.openFile(url).catch((error) => {
this.domUtils.showErrorModal(error);
@@ -352,16 +353,14 @@ export class CoreIframeUtilsProvider {
return;
}
- if (scheme && scheme != 'file' && scheme != 'filesystem') {
+ if (!this.urlUtils.isLocalFileUrlScheme(scheme)) {
// Scheme suggests it's an external resource.
event.preventDefault();
- const frameSrc = ( element).src || ( element).data,
- frameScheme = this.urlUtils.getUrlScheme(frameSrc);
+ const frameSrc = ( element).src || ( element).data;
// If the frame is not local, check the target to identify how to treat the link.
- if (frameScheme && frameScheme != 'file' && frameScheme != 'filesystem' &&
- (!link.target || link.target == '_self')) {
+ if (!this.urlUtils.isLocalFileUrl(frameSrc) && (!link.target || link.target == '_self')) {
// Load the link inside the frame itself.
if (element.tagName.toLowerCase() == 'object') {
element.setAttribute('data', link.href);
@@ -396,6 +395,8 @@ export class CoreIframeUtilsProvider {
}
}
+export class CoreIframeUtils extends makeSingleton(CoreIframeUtilsProvider) {}
+
/**
* Subtype of HTMLAnchorElement, with some calculated data.
*/
diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts
index c162f3dc7..eae580a46 100644
--- a/src/providers/utils/mimetype.ts
+++ b/src/providers/utils/mimetype.ts
@@ -14,9 +14,11 @@
import { Injectable } from '@angular/core';
import { HttpClient } from '@angular/common/http';
+import { CoreFile } from '../file';
import { CoreLoggerProvider } from '../logger';
import { TranslateService } from '@ngx-translate/core';
import { CoreTextUtilsProvider } from './text';
+import { makeSingleton } from '@singletons/core.singletons';
/*
* "Utils" service with helper functions for mimetypes and extensions.
@@ -164,7 +166,7 @@ export class CoreMimetypeUtilsProvider {
if (this.canBeEmbedded(ext)) {
file.embedType = this.getExtensionType(ext);
- path = path || file.fileurl || (file.toURL && file.toURL());
+ path = CoreFile.instance.convertFileSrc(path || file.fileurl || (file.toURL && file.toURL()));
if (file.embedType == 'image') {
return '
';
@@ -549,3 +551,5 @@ export class CoreMimetypeUtilsProvider {
return path;
}
}
+
+export class CoreMimetypeUtils extends makeSingleton(CoreMimetypeUtilsProvider) {}
diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts
index 8565b6dfc..66e617091 100644
--- a/src/providers/utils/text.ts
+++ b/src/providers/utils/text.ts
@@ -17,6 +17,7 @@ import { DomSanitizer, SafeUrl } from '@angular/platform-browser';
import { ModalController, Platform } from 'ionic-angular';
import { TranslateService } from '@ngx-translate/core';
import { CoreLangProvider } from '../lang';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Different type of errors the app can treat.
@@ -1106,3 +1107,5 @@ export class CoreTextUtilsProvider {
return _unserialize((data + ''), 0)[2];
}
}
+
+export class CoreTextUtils extends makeSingleton(CoreTextUtilsProvider) {}
diff --git a/src/providers/utils/time.ts b/src/providers/utils/time.ts
index ca0d464fe..470c30f44 100644
--- a/src/providers/utils/time.ts
+++ b/src/providers/utils/time.ts
@@ -16,6 +16,7 @@ import { Injectable } from '@angular/core';
import { TranslateService } from '@ngx-translate/core';
import * as moment from 'moment';
import { CoreConstants } from '@core/constants';
+import { makeSingleton } from '@singletons/core.singletons';
/*
* "Utils" service with helper functions for date and time.
@@ -353,3 +354,5 @@ export class CoreTimeUtilsProvider {
}
}
}
+
+export class CoreTimeUtils extends makeSingleton(CoreTimeUtilsProvider) {}
diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts
index 5c8c0e35a..f9b07929b 100644
--- a/src/providers/utils/url.ts
+++ b/src/providers/utils/url.ts
@@ -15,6 +15,8 @@
import { Injectable } from '@angular/core';
import { CoreLangProvider } from '../lang';
import { CoreTextUtilsProvider } from './text';
+import { makeSingleton } from '@singletons/core.singletons';
+import { CoreConfigConstants } from '../../configconstants';
/*
* "Utils" service with helper functions for URLs.
@@ -423,6 +425,26 @@ export class CoreUrlUtilsProvider {
return /^https?\:\/\/.+/i.test(url);
}
+ /**
+ * Check whether an URL belongs to a local file.
+ *
+ * @param url URL to check.
+ * @return Whether the URL belongs to a local file.
+ */
+ isLocalFileUrl(url: string): boolean {
+ return this.isLocalFileUrlScheme(this.getUrlScheme(url));
+ }
+
+ /**
+ * Check whether a URL scheme belongs to a local file.
+ *
+ * @param scheme Scheme to check.
+ * @return Whether the scheme belongs to a local file.
+ */
+ isLocalFileUrlScheme(scheme: string): boolean {
+ return scheme == 'cdvfile' || scheme == 'file' || scheme == 'filesystem' || scheme == CoreConfigConstants.webviewscheme;
+ }
+
/**
* Returns if a URL is a pluginfile URL.
*
@@ -498,3 +520,5 @@ export class CoreUrlUtilsProvider {
return url;
}
}
+
+export class CoreUrlUtils extends makeSingleton(CoreUrlUtilsProvider) {}
diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts
index bebc3ce1c..b3781a706 100644
--- a/src/providers/utils/utils.ts
+++ b/src/providers/utils/utils.ts
@@ -27,6 +27,8 @@ import { CoreLoggerProvider } from '../logger';
import { TranslateService } from '@ngx-translate/core';
import { CoreLangProvider } from '../lang';
import { CoreWSProvider, CoreWSError } from '../ws';
+import { CoreFile } from '../file';
+import { makeSingleton } from '@singletons/core.singletons';
/**
* Deferred promise. It's similar to the result of $q.defer() in AngularJS.
@@ -862,8 +864,11 @@ export class CoreUtilsProvider {
* @return Promise resolved when done.
*/
openFile(path: string): Promise {
- const extension = this.mimetypeUtils.getFileExtension(path),
- mimetype = this.mimetypeUtils.getMimeType(extension);
+ // Convert the path to a native path if needed.
+ path = CoreFile.instance.unconvertFileSrc(path);
+
+ const extension = this.mimetypeUtils.getFileExtension(path);
+ const mimetype = this.mimetypeUtils.getMimeType(extension);
// Path needs to be decoded, the file won't be opened if the path has %20 instead of spaces and so.
try {
@@ -1416,3 +1421,5 @@ export class CoreUtilsProvider {
return debounced;
}
}
+
+export class CoreUtils extends makeSingleton(CoreUtilsProvider) {}
diff --git a/src/providers/ws.ts b/src/providers/ws.ts
index 8440316ae..57c91985a 100644
--- a/src/providers/ws.ts
+++ b/src/providers/ws.ts
@@ -24,6 +24,9 @@ import { CoreTextUtilsProvider } from './utils/text';
import { CoreConstants } from '@core/constants';
import { Md5 } from 'ts-md5/dist/md5';
import { CoreInterceptor } from '@classes/interceptor';
+import { makeSingleton } from '@singletons/core.singletons';
+import { Observable } from 'rxjs/Observable';
+import { CoreNativeToAngularHttpResponse } from '@classes/native-to-angular-http';
/**
* PreSets accepted by the WS call.
@@ -80,6 +83,61 @@ export interface CoreWSAjaxPreSets {
useGet?: boolean;
}
+/**
+ * Options for HTTP requests.
+ */
+export type HttpRequestOptions = {
+ /**
+ * The HTTP method.
+ */
+ method: string;
+
+ /**
+ * Payload to send to the server. Only applicable on post, put or patch methods.
+ */
+ data?: any;
+
+ /**
+ * Query params to be appended to the URL (only applicable on get, head, delete, upload or download methods).
+ */
+ params?: any;
+
+ /**
+ * Response type. Defaults to json.
+ */
+ responseType?: 'json' | 'text' | 'arraybuffer' | 'blob';
+
+ /**
+ * Timeout for the request in seconds. If undefined, the default value will be used. If null, no timeout.
+ */
+ timeout?: number | null;
+
+ /**
+ * Serializer to use. Defaults to 'urlencoded'. Only for mobile environments.
+ */
+ serializer?: string;
+
+ /**
+ * Whether to follow redirects. Defaults to true. Only for mobile environments.
+ */
+ followRedirect?: boolean;
+
+ /**
+ * Headers. Only for mobile environments.
+ */
+ headers?: {[name: string]: string};
+
+ /**
+ * File paths to use for upload or download. Only for mobile environments.
+ */
+ filePath?: string;
+
+ /**
+ * Name to use during upload. Only for mobile environments.
+ */
+ name?: string;
+};
+
/**
* This service allows performing WS calls and download/upload files.
*/
@@ -376,8 +434,8 @@ export class CoreWSProvider {
return Promise.resolve(this.mimeTypeCache[url]);
}
- return this.performHead(url).then((data) => {
- let mimeType = data.headers.get('Content-Type');
+ return this.performHead(url).then((response) => {
+ let mimeType = response.headers.get('Content-Type');
if (mimeType) {
// Remove "parameters" like charset.
mimeType = mimeType.split(';')[0];
@@ -398,8 +456,8 @@ export class CoreWSProvider {
* @return Promise resolved with the size or -1 if failure.
*/
getRemoteFileSize(url: string): Promise {
- return this.performHead(url).then((data) => {
- const size = parseInt(data.headers.get('Content-Length'), 10);
+ return this.performHead(url).then((response) => {
+ const size = parseInt(response.headers.get('Content-Length'), 10);
if (size) {
return size;
@@ -462,12 +520,12 @@ export class CoreWSProvider {
preSets.responseExpected = true;
}
- const script = preSets.noLogin ? 'service-nologin.php' : 'service.php',
- ajaxData = JSON.stringify([{
- index: 0,
- methodname: method,
- args: this.convertValuesToString(data)
- }]);
+ const script = preSets.noLogin ? 'service-nologin.php' : 'service.php';
+ const ajaxData = [{
+ index: 0,
+ methodname: method,
+ args: this.convertValuesToString(data)
+ }];
// The info= parameter has no function. It is just to help with debugging.
// We call it info to match the parameter name use by Moodle's AMD ajax module.
@@ -475,13 +533,22 @@ export class CoreWSProvider {
if (preSets.noLogin && preSets.useGet) {
// Send params using GET.
- siteUrl += '&args=' + encodeURIComponent(ajaxData);
- promise = this.http.get(siteUrl).timeout(this.getRequestTimeout()).toPromise();
+ siteUrl += '&args=' + encodeURIComponent(JSON.stringify(ajaxData));
+
+ promise = this.sendHTTPRequest(siteUrl, {
+ method: 'get',
+ });
} else {
- promise = this.http.post(siteUrl, ajaxData).timeout(this.getRequestTimeout()).toPromise();
+ promise = this.sendHTTPRequest(siteUrl, {
+ method: 'post',
+ data: ajaxData,
+ serializer: 'json',
+ });
}
- return promise.then((data: any) => {
+ return promise.then((response: HttpResponse) => {
+ let data = response.body;
+
// Some moodle web services return null.
// If the responseExpected value is set then so long as no data is returned, we create a blank object.
if (!data && !preSets.responseExpected) {
@@ -535,8 +602,11 @@ export class CoreWSProvider {
let promise = this.getPromiseHttp('head', url);
if (!promise) {
- promise = this.http.head(url, {observe: 'response', responseType: 'blob'}).timeout(this.getRequestTimeout())
- .toPromise();
+ promise = this.sendHTTPRequest(url, {
+ method: 'head',
+ responseType: 'text',
+ });
+
promise = this.setPromiseHttp(promise, 'head', url);
}
@@ -570,6 +640,7 @@ export class CoreWSProvider {
const promise = this.http.post(requestUrl, ajaxData, options).timeout(this.getRequestTimeout()).toPromise();
return promise.then((data: any) => {
+
// Some moodle web services return null.
// If the responseExpected value is set to false, we create a blank object if the response is null.
if (!data && !preSets.responseExpected) {
@@ -870,15 +941,107 @@ export class CoreWSProvider {
*/
async getText(url: string): Promise {
// Fetch the URL content.
- const content = await this.http.get(url, { responseType: 'text' }).toPromise();
+ const options: HttpRequestOptions = {
+ method: 'get',
+ responseType: 'text',
+ };
+
+ const response = await this.sendHTTPRequest(url, options);
+
+ const content = response.body;
+
if (typeof content !== 'string') {
- return Promise.reject(null);
+ throw 'Error reading content';
}
return content;
}
+
+ /**
+ * Send an HTTP request. In mobile devices it will use the cordova plugin.
+ *
+ * @param url URL of the request.
+ * @param options Options for the request.
+ * @return Promise resolved with the response.
+ */
+ async sendHTTPRequest(url: string, options: HttpRequestOptions): Promise> {
+
+ // Set default values.
+ options.responseType = options.responseType || 'json';
+ options.timeout = typeof options.timeout == 'undefined' ? this.getRequestTimeout() : options.timeout;
+
+ if (this.appProvider.isMobile()) {
+ // Use the cordova plugin.
+ if (url.indexOf('file://') === 0) {
+ // We cannot load local files using the http native plugin. Use file provider instead.
+ const format = options.responseType == 'json' ? CoreFileProvider.FORMATJSON : CoreFileProvider.FORMATTEXT;
+
+ const content = await this.fileProvider.readFile(url, format);
+
+ return new HttpResponse({
+ body: content,
+ headers: null,
+ status: 200,
+ statusText: 'OK',
+ url: url
+ });
+ }
+
+ return new Promise>((resolve, reject): void => {
+ // We cannot use Ionic Native plugin because it doesn't have the sendRequest method.
+ ( cordova).plugin.http.sendRequest(url, options, (response) => {
+ resolve(new CoreNativeToAngularHttpResponse(response));
+ }, reject);
+ });
+ } else {
+ let observable: Observable;
+
+ // Use Angular's library.
+ switch (options.method) {
+ case 'get':
+ observable = this.http.get(url, {
+ headers: options.headers,
+ params: options.params,
+ observe: 'response',
+ responseType: options.responseType,
+ });
+ break;
+
+ case 'post':
+ if (options.serializer == 'json') {
+ options.data = JSON.stringify(options.data);
+ }
+
+ observable = this.http.post(url, options.data, {
+ headers: options.headers,
+ observe: 'response',
+ responseType: options.responseType,
+ });
+ break;
+
+ case 'head':
+ observable = this.http.head(url, {
+ headers: options.headers,
+ observe: 'response',
+ responseType: options.responseType
+ });
+ break;
+
+ default:
+ return Promise.reject('Method not implemented yet.');
+ }
+
+ if (options.timeout) {
+ observable = observable.timeout(options.timeout);
+ }
+
+ return observable.toPromise();
+ }
+ }
}
+export class CoreWS extends makeSingleton(CoreWSProvider) {}
+
/**
* Error returned by a WS call.
*/
diff --git a/src/singletons/core.singletons.ts b/src/singletons/core.singletons.ts
new file mode 100644
index 000000000..7eacfe6bc
--- /dev/null
+++ b/src/singletons/core.singletons.ts
@@ -0,0 +1,49 @@
+// (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 { AlertController, App } from 'ionic-angular';
+import { Injector } from '@angular/core';
+import { TranslateService } from '@ngx-translate/core';
+import { HttpClient } from '@angular/common/http';
+
+import { CoreSingletonsFactory, CoreInjectionToken, CoreSingletonClass } from '@classes/singletons-factory';
+
+const factory = new CoreSingletonsFactory();
+
+/**
+ * Set the injector that will be used to resolve instances in the singletons of this module.
+ *
+ * @param injector Module injector.
+ */
+export function setSingletonsInjector(injector: Injector): void {
+ factory.setInjector(injector);
+}
+
+/**
+ * Make a singleton for this module.
+ *
+ * @param injectionToken Injection token used to resolve the singleton instance. This is usually the service class if the
+ * provider was defined using a class or the string used in the `provide` key if it was defined using an object.
+ */
+export function makeSingleton(injectionToken: CoreInjectionToken): CoreSingletonClass {
+ return factory.makeSingleton(injectionToken);
+}
+
+export class Translate extends makeSingleton(TranslateService) {}
+
+export class Alerts extends makeSingleton(AlertController) {}
+
+export class Ionic extends makeSingleton(App) {}
+
+export class Http extends makeSingleton(HttpClient) {}
diff --git a/src/classes/utils/url.ts b/src/singletons/url.ts
similarity index 100%
rename from src/classes/utils/url.ts
rename to src/singletons/url.ts
diff --git a/tsconfig.json b/tsconfig.json
index 84a65ecd5..22454c549 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -21,7 +21,8 @@
"@providers/*": ["providers/*"],
"@components/*": ["components/*"],
"@directives/*": ["directives/*"],
- "@pipes/*": ["pipes/*"]
+ "@pipes/*": ["pipes/*"],
+ "@singletons/*": ["singletons/*"]
},
"typeRoots": [
"node_modules/@types"