From 01ddf7019c3dad3b5dd2c1b4c9581e85728cffa3 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Mon, 29 Jan 2018 10:05:20 +0100 Subject: [PATCH] MOBILE-2317 core: Fix tslint warnings --- gulpfile.js | 49 +- src/addon/calendar/calendar.module.ts | 3 +- src/addon/calendar/pages/event/event.ts | 20 +- src/addon/calendar/pages/list/list.ts | 52 +- src/addon/calendar/pages/settings/settings.ts | 19 +- src/addon/calendar/providers/calendar.ts | 131 ++--- src/addon/calendar/providers/helper.ts | 16 +- .../calendar/providers/mainmenu-handler.ts | 7 +- .../checkbox/checkbox.module.ts | 2 +- .../checkbox/component/checkbox.ts | 15 +- .../checkbox/providers/handler.ts | 13 +- .../datetime/component/datetime.ts | 20 +- .../datetime/datetime.module.ts | 2 +- .../datetime/providers/handler.ts | 16 +- .../userprofilefield/menu/component/menu.ts | 15 +- .../userprofilefield/menu/menu.module.ts | 2 +- .../menu/providers/handler.ts | 13 +- .../userprofilefield/text/component/text.ts | 15 +- .../text/providers/handler.ts | 11 +- .../userprofilefield/text/text.module.ts | 2 +- .../textarea/component/textarea.ts | 21 +- .../textarea/providers/handler.ts | 13 +- .../textarea/textarea.module.ts | 2 +- .../userprofilefield.module.ts | 3 +- src/app/app.component.ts | 15 +- src/app/app.module.ts | 3 +- src/app/main.ts | 14 + src/classes/base-sync.ts | 45 +- src/classes/cache.ts | 17 +- src/classes/delegate.ts | 25 +- src/classes/interceptor.ts | 14 +- src/classes/site.ts | 287 ++++++----- src/classes/sqlitedb.ts | 208 ++++---- src/components/chrono/chrono.ts | 16 +- .../context-menu/context-menu-item.ts | 21 +- .../context-menu/context-menu-popover.ts | 5 +- src/components/context-menu/context-menu.ts | 20 +- .../course-picker-menu-popover.ts | 3 +- src/components/empty-box/empty-box.ts | 4 +- src/components/file/file.ts | 81 +-- src/components/iframe/iframe.ts | 18 +- src/components/input-errors/input-errors.ts | 30 +- src/components/loading/loading.ts | 4 +- src/components/local-file/local-file.ts | 34 +- src/components/mark-required/mark-required.ts | 6 +- src/components/progress-bar/progress-bar.ts | 6 +- .../rich-text-editor/rich-text-editor.ts | 44 +- src/components/search-box/search-box.ts | 17 +- src/components/show-password/show-password.ts | 14 +- src/components/site-picker/site-picker.ts | 8 +- .../split-view/placeholder/placeholder.ts | 6 +- src/components/split-view/split-view.ts | 26 +- src/components/tabs/tab.ts | 18 +- src/components/tabs/tabs.ts | 43 +- src/core/constants.ts | 44 +- src/core/contentlinks/classes/base-handler.ts | 16 +- .../classes/module-grade-handler.ts | 9 +- .../classes/module-index-handler.ts | 7 +- .../pages/choose-site/choose-site.ts | 13 +- src/core/contentlinks/providers/delegate.ts | 41 +- src/core/contentlinks/providers/helper.ts | 30 +- .../course/classes/module-prefetch-handler.ts | 79 +-- src/core/course/components/format/format.ts | 65 +-- .../module-completion/module-completion.ts | 24 +- .../module-description/module-description.ts | 8 +- src/core/course/components/module/module.ts | 6 +- .../unsupported-module/unsupported-module.ts | 4 +- src/core/course/course.module.ts | 2 +- .../singleactivity/components/format.ts | 24 +- .../singleactivity/providers/handler.ts | 15 +- .../formats/topics/providers/handler.ts | 6 +- .../course/formats/weeks/providers/handler.ts | 17 +- src/core/course/pages/section/section.ts | 72 +-- .../unsupported-module/unsupported-module.ts | 4 +- src/core/course/providers/course.ts | 143 +++--- src/core/course/providers/default-format.ts | 24 +- src/core/course/providers/format-delegate.ts | 4 +- src/core/course/providers/helper.ts | 177 ++++--- src/core/course/providers/module-delegate.ts | 16 +- .../providers/module-prefetch-delegate.ts | 124 ++--- src/core/course/providers/options-delegate.ts | 37 +- .../course-list-item/course-list-item.ts | 9 +- .../course-progress/course-progress.ts | 12 +- .../overview-events/overview-events.ts | 18 +- .../available-courses/available-courses.ts | 17 +- .../courses/pages/categories/categories.ts | 19 +- .../pages/course-preview/course-preview.ts | 63 +-- .../courses/pages/my-courses/my-courses.ts | 32 +- .../courses/pages/my-overview/my-overview.ts | 64 ++- src/core/courses/pages/search/search.ts | 16 +- .../self-enrol-password.ts | 10 +- .../courses/providers/course-link-handler.ts | 43 +- .../providers/courses-index-link-handler.ts | 12 +- src/core/courses/providers/courses.ts | 177 +++---- .../courses/providers/mainmenu-handler.ts | 6 +- .../providers/my-overview-link-handler.ts | 6 +- src/core/courses/providers/my-overview.ts | 58 ++- .../emulator/classes/inappbrowserobject.ts | 47 +- src/core/emulator/classes/sqlitedb.ts | 31 +- src/core/emulator/emulator.module.ts | 23 +- .../pages/capture-media/capture-media.ts | 100 ++-- src/core/emulator/providers/camera.ts | 6 +- src/core/emulator/providers/capture-helper.ts | 39 +- src/core/emulator/providers/clipboard.ts | 8 +- src/core/emulator/providers/file-transfer.ts | 85 ++-- src/core/emulator/providers/file.ts | 108 ++-- src/core/emulator/providers/globalization.ts | 20 +- src/core/emulator/providers/helper.ts | 12 +- src/core/emulator/providers/inappbrowser.ts | 2 +- .../emulator/providers/local-notifications.ts | 133 ++--- src/core/emulator/providers/media-capture.ts | 6 +- src/core/emulator/providers/network.ts | 34 +- src/core/emulator/providers/zip.ts | 18 +- .../fileuploader/providers/album-handler.ts | 10 +- .../fileuploader/providers/audio-handler.ts | 19 +- .../fileuploader/providers/camera-handler.ts | 12 +- src/core/fileuploader/providers/delegate.ts | 10 +- .../fileuploader/providers/file-handler.ts | 18 +- .../fileuploader/providers/fileuploader.ts | 91 ++-- src/core/fileuploader/providers/helper.ts | 115 +++-- .../fileuploader/providers/video-handler.ts | 21 +- .../login/pages/credentials/credentials.ts | 45 +- .../login/pages/email-signup/email-signup.ts | 89 ++-- .../forgotten-password/forgotten-password.ts | 21 +- src/core/login/pages/init/init.ts | 12 +- src/core/login/pages/reconnect/reconnect.ts | 25 +- src/core/login/pages/site-error/site-error.ts | 6 +- src/core/login/pages/site-help/site-help.ts | 8 +- .../login/pages/site-policy/site-policy.ts | 18 +- src/core/login/pages/site/site.ts | 22 +- src/core/login/pages/sites/sites.ts | 31 +- src/core/login/providers/helper.ts | 240 ++++----- src/core/mainmenu/pages/menu/menu.ts | 17 +- src/core/mainmenu/pages/more/more.ts | 18 +- src/core/mainmenu/providers/delegate.ts | 28 +- src/core/mainmenu/providers/mainmenu.ts | 33 +- .../pages/choose-site/choose-site.ts | 14 +- src/core/sharedfiles/pages/list/list.ts | 30 +- src/core/sharedfiles/providers/helper.ts | 34 +- src/core/sharedfiles/providers/sharedfiles.ts | 46 +- .../sharedfiles/providers/upload-handler.ts | 10 +- .../enrolled-course-list.ts | 6 +- src/core/sitehome/components/index/index.ts | 12 +- src/core/sitehome/components/news/news.ts | 24 +- src/core/sitehome/pages/index/index.ts | 4 +- .../sitehome/providers/index-link-handler.ts | 8 +- .../sitehome/providers/mainmenu-handler.ts | 4 +- src/core/sitehome/providers/sitehome.ts | 13 +- .../user-profile-field/user-profile-field.ts | 17 +- src/core/user/pages/about/about.ts | 25 +- src/core/user/pages/profile/profile.ts | 50 +- src/core/user/providers/helper.ts | 19 +- src/core/user/providers/user-delegate.ts | 18 +- src/core/user/providers/user-handler.ts | 10 +- src/core/user/providers/user-link-handler.ts | 10 +- .../providers/user-profile-field-delegate.ts | 23 +- src/core/user/providers/user.ts | 58 ++- src/core/user/user.module.ts | 5 +- src/core/viewer/pages/iframe/iframe.ts | 4 +- src/core/viewer/pages/image/image.ts | 8 +- src/core/viewer/pages/text/text.module.ts | 3 + src/core/viewer/pages/text/text.ts | 8 +- src/directives/auto-focus.ts | 8 +- src/directives/external-content.ts | 45 +- src/directives/format-text.ts | 92 ++-- src/directives/keep-keyboard.ts | 39 +- src/directives/link.ts | 26 +- src/directives/user-link.ts | 7 +- src/pipes/bytes-to-size.ts | 3 +- src/pipes/create-links.ts | 2 +- src/pipes/date-day-or-time.ts | 3 +- src/pipes/duration.ts | 3 +- src/pipes/format-date.ts | 6 +- src/pipes/no-tags.ts | 2 +- src/pipes/pipes.module.ts | 2 +- src/pipes/seconds-to-hms.ts | 3 +- src/pipes/time-ago.ts | 3 +- src/pipes/to-locale-string.ts | 4 +- src/providers/app.ts | 88 ++-- src/providers/config.ts | 12 +- src/providers/cron.ts | 95 ++-- src/providers/db.ts | 11 +- src/providers/events.ts | 65 +-- src/providers/file-session.ts | 19 +- src/providers/file.ts | 210 ++++---- src/providers/filepool.ts | 470 +++++++++-------- src/providers/groups.ts | 77 +-- src/providers/init.ts | 22 +- src/providers/lang.ts | 73 +-- src/providers/local-notifications.ts | 79 +-- src/providers/logger.ts | 25 +- src/providers/plugin-file-delegate.ts | 17 +- src/providers/sites-factory.ts | 9 +- src/providers/sites.ts | 189 ++++--- src/providers/sync.ts | 16 +- src/providers/update-manager.ts | 27 +- src/providers/utils/dom.ts | 228 +++++---- src/providers/utils/mimetype.ts | 116 +++-- src/providers/utils/text.ts | 134 ++--- src/providers/utils/time.ts | 56 ++- src/providers/utils/url.ts | 60 ++- src/providers/utils/utils.ts | 476 ++++++------------ src/providers/ws.ts | 135 ++--- tslint.json | 27 +- 204 files changed, 4243 insertions(+), 3872 deletions(-) diff --git a/gulpfile.js b/gulpfile.js index 01a2deb13..d3377c5d7 100644 --- a/gulpfile.js +++ b/gulpfile.js @@ -6,7 +6,21 @@ var gulp = require('gulp'), slash = require('gulp-slash'), clipEmptyFiles = require('gulp-clip-empty-files'), gutil = require('gulp-util'), - File = gutil.File; + File = gutil.File, + license = '' + + '// (C) Copyright 2015 Martin Dougiamas\n' + + '//\n' + + '// Licensed under the Apache License, Version 2.0 (the "License");\n' + + '// you may not use this file except in compliance with the License.\n' + + '// You may obtain a copy of the License at\n' + + '//\n' + + '// http://www.apache.org/licenses/LICENSE-2.0\n' + + '//\n' + + '// Unless required by applicable law or agreed to in writing, software\n' + + '// distributed under the License is distributed on an "AS IS" BASIS,\n' + + '// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n' + + '// See the License for the specific language governing permissions and\n' + + '// limitations under the License.\n\n'; // Get the names of the JSON files inside a directory. function getFilenames(dir) { @@ -211,15 +225,40 @@ gulp.task('config', function(done) { gulp.src(paths.config) .pipe(through(function(file) { // Convert the contents of the file into a TypeScript class. + // Disable the rule variable-name in the file. var config = JSON.parse(file.contents.toString()), - contents = 'export class CoreConfigConstants {\n'; + contents = license + '// tslint:disable: variable-name\n' + 'export class CoreConfigConstants {\n'; for (var key in config) { var value = config[key]; - if (typeof value != 'number' && typeof value != 'boolean') { - value = JSON.stringify(value); + if (typeof value == 'string') { + // Wrap the string in ' . + value = "'" + value + "'"; + } else if (typeof value != 'number' && typeof value != 'boolean') { + // Stringify with 4 spaces of indentation, and then add 4 more spaces in each line. + value = JSON.stringify(value, null, 4).replace(/^(?: )/gm, ' ').replace(/^(?:})/gm, ' }'); + // Replace " by ' in values. + value = value.replace(/: "([^"]*)"/g, ": '$1'"); + + // Check if the keys have "-" in it. + var matches = value.match(/"([^"]*\-[^"]*)":/g); + if (matches) { + // Replace " by ' in keys. We cannot remove them because keys have chars like '-'. + value = value.replace(/"([^"]*)":/g, "'$1':"); + } else { + // Remove ' in keys. + value = value.replace(/"([^"]*)":/g, "$1:"); + } + + // Add type any to the key. + key = key + ': any'; } - contents += ' public static ' + key + ' = ' + value + ';\n'; + + // If key has quotation marks, remove them. + if (key[0] == '"') { + key = key.substr(1, key.length - 2); + } + contents += ' static ' + key + ' = ' + value + ';\n'; } contents += '}\n'; diff --git a/src/addon/calendar/calendar.module.ts b/src/addon/calendar/calendar.module.ts index a0feb1722..3fcfdf52b 100644 --- a/src/addon/calendar/calendar.module.ts +++ b/src/addon/calendar/calendar.module.ts @@ -42,7 +42,6 @@ export class AddonCalendarModule { calendarProvider.scheduleAllSitesEventsNotifications(); }); - localNotificationsProvider.registerClick(AddonCalendarProvider.COMPONENT, (data) => { if (data.eventid) { initDelegate.ready().then(() => { @@ -58,4 +57,4 @@ export class AddonCalendarModule { } }); } -} \ No newline at end of file +} diff --git a/src/addon/calendar/pages/event/event.ts b/src/addon/calendar/pages/event/event.ts index 4bce81ca5..85dfd003a 100644 --- a/src/addon/calendar/pages/event/event.ts +++ b/src/addon/calendar/pages/event/event.ts @@ -27,7 +27,7 @@ import * as moment from 'moment'; /** * Page that displays a single calendar event. */ -@IonicPage({segment: "addon-calendar-event"}) +@IonicPage({ segment: 'addon-calendar-event' }) @Component({ selector: 'page-addon-calendar-event', templateUrl: 'event.html', @@ -72,13 +72,13 @@ export class AddonCalendarEventPage { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.fetchEvent().finally(() => { this.eventLoaded = true; }); } - updateNotificationTime() { + updateNotificationTime(): void { if (!isNaN(this.notificationTime) && this.event && this.event.id) { this.calendarProvider.updateNotificationTime(this.event, this.notificationTime); } @@ -86,8 +86,10 @@ export class AddonCalendarEventPage { /** * Fetches the event and updates the view. + * + * @return {Promise} Promise resolved when done. */ - fetchEvent() { + fetchEvent(): Promise { return this.calendarProvider.getEvent(this.eventId).then((event) => { this.calendarHelper.formatEventData(event); this.event = event; @@ -96,14 +98,14 @@ export class AddonCalendarEventPage { let title = this.translate.instant('addon.calendar.type' + event.eventtype); if (event.moduleIcon) { // It's a module event, translate the module name to the current language. - let name = this.courseProvider.translateModuleName(event.modulename); + const name = this.courseProvider.translateModuleName(event.modulename); if (name.indexOf('core.mod_') === -1) { event.moduleName = name; } if (title == 'addon.calendar.type' + event.eventtype) { - title = this.translate.instant('core.mod_'+ event.modulename + '.' + event.eventtype); + title = this.translate.instant('core.mod_' + event.modulename + '.' + event.eventtype); - if (title == 'core.mod_'+ event.modulename + '.' + event.eventtype) { + if (title == 'core.mod_' + event.modulename + '.' + event.eventtype) { title = name; } } @@ -130,11 +132,11 @@ export class AddonCalendarEventPage { * * @param {any} refresher Refresher. */ - refreshEvent(refresher: any) { + refreshEvent(refresher: any): void { this.calendarProvider.invalidateEvent(this.eventId).finally(() => { this.fetchEvent().finally(() => { refresher.complete(); }); }); } -} \ No newline at end of file +} diff --git a/src/addon/calendar/pages/list/list.ts b/src/addon/calendar/pages/list/list.ts index fb567453c..85847215b 100644 --- a/src/addon/calendar/pages/list/list.ts +++ b/src/addon/calendar/pages/list/list.ts @@ -30,7 +30,7 @@ import { CoreSplitViewComponent } from '../../../../components/split-view/split- /** * Page that displays the list of calendar events. */ -@IonicPage({segment: "addon-calendar-list"}) +@IonicPage({ segment: 'addon-calendar-list' }) @Component({ selector: 'page-addon-calendar-list', templateUrl: 'list.html', @@ -84,7 +84,7 @@ export class AddonCalendarListPage implements OnDestroy { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { if (this.eventId) { // There is an event to load, open the event in a new state. this.gotoEvent(this.eventId); @@ -104,8 +104,9 @@ export class AddonCalendarListPage implements OnDestroy { * Fetch all the data required for the view. * * @param {boolean} [refresh] Empty events array first. + * @return {Promise} Promise resolved when done. */ - fetchData(refresh = false) { + fetchData(refresh: boolean = false): Promise { this.daysLoaded = 0; this.emptyEventsTimes = 0; @@ -114,6 +115,7 @@ export class AddonCalendarListPage implements OnDestroy { // Add "All courses". courses.unshift(this.allCourses); this.courses = courses; + return this.fetchEvents(refresh); }); } @@ -122,8 +124,9 @@ export class AddonCalendarListPage implements OnDestroy { * Fetches the events and updates the view. * * @param {boolean} [refresh] Empty events array first. + * @return {Promise} Promise resolved when done. */ - fetchEvents(refresh = false) { + fetchEvents(refresh: boolean = false): Promise { return this.calendarProvider.getEventsList(this.daysLoaded, AddonCalendarProvider.DAYS_INTERVAL).then((events) => { this.daysLoaded += AddonCalendarProvider.DAYS_INTERVAL; if (events.length === 0) { @@ -170,6 +173,7 @@ export class AddonCalendarListPage implements OnDestroy { // Success retrieving events. Get categories if needed. if (this.getCategories) { this.getCategories = false; + return this.loadCategories(); } }); @@ -177,8 +181,10 @@ export class AddonCalendarListPage implements OnDestroy { /** * Get filtered events. + * + * @return {any[]} Filtered events. */ - protected getFilteredEvents() { + protected getFilteredEvents(): any[] { if (this.filter.course.id == -1) { // No filter, display everything. return this.events; @@ -191,8 +197,9 @@ export class AddonCalendarListPage implements OnDestroy { * Check if an event should be displayed based on the filter. * * @param {any} event Event object. + * @return {boolean} Whether it should be displayed. */ - protected shouldDisplayEvent(event: any) { + protected shouldDisplayEvent(event: any): boolean { if (event.eventtype == 'user' || event.eventtype == 'site') { // User or site event, display it. return true; @@ -234,21 +241,24 @@ export class AddonCalendarListPage implements OnDestroy { * @param {any[]} events Events to parse. * @return {boolean} True if categories should be loaded. */ - protected shouldLoadCategories(events: any[]) : boolean { + protected shouldLoadCategories(events: any[]): boolean { if (this.categoriesRetrieved || this.getCategories) { // Use previous value return this.getCategories; } // Categories not loaded yet. We should get them if there's any category event. - let found = events.some(event => event.categoryid != 'undefined' && event.categoryid > 0); + const found = events.some((event) => event.categoryid != 'undefined' && event.categoryid > 0); + return found || this.getCategories; } /** * Load categories to be able to filter events. + * + * @return {Promise} Promise resolved when done. */ - protected loadCategories() { + protected loadCategories(): Promise { return this.coursesProvider.getCategories(0, true).then((cats) => { this.categoriesRetrieved = true; this.categories = {}; @@ -266,8 +276,8 @@ export class AddonCalendarListPage implements OnDestroy { * * @param {any} refresher Refresher. */ - refreshEvents(refresher: any) { - let promises = []; + refreshEvents(refresher: any): void { + const promises = []; promises.push(this.calendarProvider.invalidateEventsList(this.courses)); @@ -288,9 +298,11 @@ export class AddonCalendarListPage implements OnDestroy { * * @param {MouseEvent} event Event. */ - openCourseFilter(event: MouseEvent) : void { - let popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, {courses: this.courses, - courseId: this.filter.course.id}); + openCourseFilter(event: MouseEvent): void { + const popover = this.popoverCtrl.create(CoreCoursePickerMenuPopoverComponent, { + courses: this.courses, + courseId: this.filter.course.id + }); popover.onDidDismiss((course) => { if (course) { this.filter.course = course; @@ -306,22 +318,24 @@ export class AddonCalendarListPage implements OnDestroy { /** * Open calendar events settings. */ - openSettings() { + openSettings(): void { this.navCtrl.push('AddonCalendarSettingsPage'); } /** * Navigate to a particular event. + * + * @param {number} eventId Event to load. */ - gotoEvent(eventId) { + gotoEvent(eventId: number): void { this.eventId = eventId; - this.splitviewCtrl.push('AddonCalendarEventPage', {id: eventId}); + this.splitviewCtrl.push('AddonCalendarEventPage', { id: eventId }); } /** * Page destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.obsDefaultTimeChange && this.obsDefaultTimeChange.off(); } -} \ No newline at end of file +} diff --git a/src/addon/calendar/pages/settings/settings.ts b/src/addon/calendar/pages/settings/settings.ts index a677ea8dd..5cb59dda4 100644 --- a/src/addon/calendar/pages/settings/settings.ts +++ b/src/addon/calendar/pages/settings/settings.ts @@ -21,7 +21,7 @@ import { CoreSitesProvider } from '../../../../providers/sites'; /** * Page that displays the calendar settings. */ -@IonicPage({segment: "addon-calendar-settings"}) +@IonicPage({ segment: 'addon-calendar-settings' }) @Component({ selector: 'page-addon-calendar-settings', templateUrl: 'settings.html', @@ -31,20 +31,25 @@ export class AddonCalendarSettingsPage { defaultTime = 0; constructor(private calendarProvider: AddonCalendarProvider, private eventsProvider: CoreEventsProvider, - private sitesProvider: CoreSitesProvider) {} + private sitesProvider: CoreSitesProvider) { } /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.calendarProvider.getDefaultNotificationTime().then((time) => { this.defaultTime = time; }); } - updateDefaultTime(newTime) { + /** + * Update default time. + * + * @param {number} newTime New time. + */ + updateDefaultTime(newTime: number): void { this.calendarProvider.setDefaultNotificationTime(newTime); - this.eventsProvider.trigger(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, {time: newTime}, + this.eventsProvider.trigger(AddonCalendarProvider.DEFAULT_NOTIFICATION_TIME_CHANGED, { time: newTime }, this.sitesProvider.getCurrentSiteId()); - }; -} \ No newline at end of file + } +} diff --git a/src/addon/calendar/providers/calendar.ts b/src/addon/calendar/providers/calendar.ts index d5a9a89fc..20532f254 100644 --- a/src/addon/calendar/providers/calendar.ts +++ b/src/addon/calendar/providers/calendar.ts @@ -28,9 +28,9 @@ import { CoreConfigProvider } from '../../../providers/config'; */ @Injectable() export class AddonCalendarProvider { - public static DAYS_INTERVAL = 30; - public static COMPONENT = 'AddonCalendarEvents'; - public static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent'; + static DAYS_INTERVAL = 30; + static COMPONENT = 'AddonCalendarEvents'; + static DEFAULT_NOTIFICATION_TIME_CHANGED = 'AddonCalendarDefaultNotificationTimeChangedEvent'; protected DEFAULT_NOTIFICATION_TIME_SETTING = 'mmaCalendarDefaultNotifTime'; protected ROOT_CACHE_KEY = 'mmaCalendar:'; protected DEFAULT_NOTIFICATION_TIME = 60; @@ -118,10 +118,11 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site. If not defined, use current site. * @return {Promise} Promise resolved with the default time. */ - getDefaultNotificationTime(siteId?: string) : Promise { + getDefaultNotificationTime(siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - let key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; + const key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; + return this.configProvider.get(key, this.DEFAULT_NOTIFICATION_TIME); } @@ -133,19 +134,27 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site. If not defined, use current site. * @return {Promise} Promise resolved when the event data is retrieved. */ - getEvent(id: number, siteId?: string) : Promise { + getEvent(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let presets = { + const preSets = { cacheKey: this.getEventCacheKey(id) }, data = { - "options[userevents]": 0, - "options[siteevents]": 0, - "events[eventids][0]": id + options: { + userevents: 0, + siteevents: 0, + }, + events: { + eventids: [ + id + ] + } }; - return site.read('core_calendar_get_calendar_events', data, presets).then((response) => { + + return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => { // The WebService returns all category events. Check the response to search for the event we want. - let event = response.events.find((e) => {return e.id == id}); + const event = response.events.find((e) => { return e.id == id; }); + return event || this.getEventFromLocalDb(id); }).catch(() => { return this.getEventFromLocalDb(id); @@ -170,9 +179,9 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. * @return {Promise} Promise resolved when the event data is retrieved. */ - getEventFromLocalDb(id: number, siteId?: string) : Promise { + getEventFromLocalDb(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.EVENTS_TABLE, {id: id}); + return site.getDb().getRecord(this.EVENTS_TABLE, { id: id }); }); } @@ -183,13 +192,14 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. * @return {Promise} Event notification time in minutes. 0 if disabled. */ - getEventNotificationTime(id: number, siteId?: string) : Promise { + getEventNotificationTime(id: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.getEventNotificationTimeOption(id, siteId).then((time: number) => { if (time == -1) { return this.getDefaultNotificationTime(siteId); } + return time; }); } @@ -201,7 +211,7 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. * @return {Promise} Promise with wvent notification time in minutes. 0 if disabled, -1 if default time. */ - getEventNotificationTimeOption(id: number, siteId?: string) : Promise { + getEventNotificationTimeOption(id: number, siteId?: string): Promise { return this.getEventFromLocalDb(id, siteId).then((e) => { return e.notificationtime || -1; }).catch(() => { @@ -221,42 +231,48 @@ export class AddonCalendarProvider { * @param {string} [siteId] Site to get the events from. If not defined, use current site. * @return {Promise} Promise to be resolved when the participants are retrieved. */ - getEventsList(daysToStart = 0, daysInterval = AddonCalendarProvider.DAYS_INTERVAL, siteId?: string) : Promise { + getEventsList(daysToStart: number = 0, daysInterval: number = AddonCalendarProvider.DAYS_INTERVAL, siteId?: string) + : Promise { return this.sitesProvider.getSite(siteId).then((site) => { siteId = site.getId(); return this.coursesProvider.getUserCourses(false, siteId).then((courses) => { - courses.push({id: site.getSiteHomeId()}); // Add front page. + courses.push({ id: site.getSiteHomeId() }); // Add front page. return this.groupsProvider.getUserGroups(courses, siteId).then((groups) => { - let now = this.timeUtils.timestamp(), + const now = this.timeUtils.timestamp(), start = now + (CoreConstants.SECONDS_DAY * daysToStart), - end = start + (CoreConstants.SECONDS_DAY * daysInterval); - - // The core_calendar_get_calendar_events needs all the current user courses and groups. - let data = { - "options[userevents]": 1, - "options[siteevents]": 1, - "options[timestart]": start, - "options[timeend]": end - }; + end = start + (CoreConstants.SECONDS_DAY * daysInterval), + data = { + options: { + userevents: 1, + siteevents: 1, + timestart: start, + timeend: end + }, + events: { + courseids: [], + groupids: [] + } + }; courses.forEach((course, index) => { - data["events[courseids][" + index + "]"] = course.id; + data.events.courseids[index] = course.id; }); groups.forEach((group, index) => { - data["events[groupids][" + index + "]"] = group.id; + data.events.groupids[index] = group.id; }); // We need to retrieve cached data using cache key because we have timestamp in the params. - let preSets = { + const preSets = { cacheKey: this.getEventsListCacheKey(daysToStart, daysInterval), getCacheUsingCacheKey: true }; return site.read('core_calendar_get_calendar_events', data, preSets).then((response) => { this.storeEventsInLocalDB(response.events, siteId); + return response.events; }); }); @@ -269,7 +285,7 @@ export class AddonCalendarProvider { * * @return {string} Prefix Cache key. */ - protected getEventsListPrefixCacheKey() : string { + protected getEventsListPrefixCacheKey(): string { return this.ROOT_CACHE_KEY + 'eventslist:'; } @@ -280,7 +296,7 @@ export class AddonCalendarProvider { * @param {number} daysInterval Number of days between timestart and timeend. * @return {string} Cache key. */ - protected getEventsListCacheKey(daysToStart: number, daysInterval: number) : string { + protected getEventsListCacheKey(daysToStart: number, daysInterval: number): string { return this.getEventsListPrefixCacheKey() + daysToStart + ':' + daysInterval; } @@ -291,27 +307,28 @@ export class AddonCalendarProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the list is invalidated. */ - invalidateEventsList(courses: any[], siteId?: string) : Promise { + invalidateEventsList(courses: any[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { siteId = site.getId(); - let promises = []; + const promises = []; promises.push(this.coursesProvider.invalidateUserCourses(siteId)); promises.push(this.groupsProvider.invalidateUserGroups(courses, siteId)); promises.push(site.invalidateWsCacheForKeyStartingWith(this.getEventsListPrefixCacheKey())); + return Promise.all(promises); }); } - /** + /** * Invalidates a single event. * * @param {number} eventId List of courses or course ids. * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the list is invalidated. */ - invalidateEvent(eventId: number, siteId?: string) : Promise { + invalidateEvent(eventId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getEventCacheKey(eventId)); }); @@ -323,8 +340,9 @@ export class AddonCalendarProvider { * @param {CoreSite} [site] Site. If not defined, use current site. * @return {boolean} Whether it's disabled. */ - isCalendarDisabledInSite(site?: CoreSite) : boolean { + isCalendarDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); + return site.isFeatureDisabled('$mmSideMenuDelegate_mmaCalendar'); } @@ -334,13 +352,12 @@ export class AddonCalendarProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. */ - isDisabled(siteId?: string) : Promise { + isDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return this.isCalendarDisabledInSite(site); }); } - /** * Get the next events for all the sites and schedules their notifications. * If an event notification time is 0, cancel its scheduled notification (if any). @@ -348,10 +365,10 @@ export class AddonCalendarProvider { * * @return {Promise} Promise resolved when all the notifications have been scheduled. */ - scheduleAllSitesEventsNotifications() : Promise { + scheduleAllSitesEventsNotifications(): Promise { if (this.localNotificationsProvider.isAvailable()) { return this.sitesProvider.getSitesIds().then((siteIds) => { - let promises = []; + const promises = []; siteIds.forEach((siteId) => { // Check if calendar is disabled for the site. @@ -381,7 +398,7 @@ export class AddonCalendarProvider { * @param {string} [siteId] Site ID the event belongs to. If not defined, use current site. * @return {Promise} Promise resolved when the notification is scheduled. */ - scheduleEventNotification(event: any, time: number, siteId?: string) : Promise { + scheduleEventNotification(event: any, time: number, siteId?: string): Promise { if (this.localNotificationsProvider.isAvailable()) { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -391,16 +408,16 @@ export class AddonCalendarProvider { } // If time is -1, get event default time. - let promise = time == -1 ? this.getDefaultNotificationTime(siteId) : Promise.resolve(time); + const promise = time == -1 ? this.getDefaultNotificationTime(siteId) : Promise.resolve(time); return promise.then((time) => { - let timeEnd = (event.timestart + event.timeduration) * 1000; + const timeEnd = (event.timestart + event.timeduration) * 1000; if (timeEnd <= new Date().getTime()) { // The event has finished already, don't schedule it. return Promise.resolve(); } - let dateTriggered = new Date((event.timestart - (time * 60)) * 1000), + const dateTriggered = new Date((event.timestart - (time * 60)) * 1000), startDate = new Date(event.timestart * 1000), notification = { id: event.id, @@ -421,7 +438,6 @@ export class AddonCalendarProvider { } } - /** * Schedules the notifications for a list of events. * If an event notification time is 0, cancel its scheduled notification (if any). @@ -431,8 +447,8 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site the events belong to. If not defined, use current site. * @return {Promise} Promise resolved when all the notifications have been scheduled. */ - scheduleEventsNotifications(events: any[], siteId?: string) : Promise { - var promises = []; + scheduleEventsNotifications(events: any[], siteId?: string): Promise { + const promises = []; if (this.localNotificationsProvider.isAvailable()) { siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -453,10 +469,11 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site. If not defined, use current site. * @return {Promise} Promise resolved when stored. */ - setDefaultNotificationTime(time: number, siteId?: string) : Promise { + setDefaultNotificationTime(time: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - let key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; + const key = this.DEFAULT_NOTIFICATION_TIME_SETTING + '#' + siteId; + return this.configProvider.set(key, time); } @@ -467,11 +484,11 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. * @return {Promise} Promise resolved when the events are stored. */ - protected storeEventsInLocalDB(events: any[], siteId?: string) : Promise { + protected storeEventsInLocalDB(events: any[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { siteId = site.getId(); - let promises = [], + const promises = [], db = site.getDb(); events.forEach((event) => { @@ -480,7 +497,7 @@ export class AddonCalendarProvider { // Event not stored, return empty object. return {}; }).then((e) => { - let eventRecord = { + const eventRecord = { id: event.id, name: event.name, description: event.description, @@ -497,7 +514,7 @@ export class AddonCalendarProvider { notificationtime: e.notificationtime || -1 }; - return db.insertOrUpdateRecord(this.EVENTS_TABLE, eventRecord, {id: eventRecord.id}); + return db.insertOrUpdateRecord(this.EVENTS_TABLE, eventRecord, { id: eventRecord.id }); })); }); @@ -513,7 +530,7 @@ export class AddonCalendarProvider { * @param {string} [siteId] ID of the site the event belongs to. If not defined, use current site. * @return {Promise} Promise resolved when the notification is updated. */ - updateNotificationTime(event: any, time: number, siteId?: string) : Promise { + updateNotificationTime(event: any, time: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { if (!this.sitesProvider.isLoggedIn()) { // Not logged in, we can't get the site DB. User logged out or session expired while an operation was ongoing. @@ -522,7 +539,7 @@ export class AddonCalendarProvider { event.notificationtime = time; - return site.getDb().insertOrUpdateRecord(this.EVENTS_TABLE, event, {id: event.id}).then(() => { + return site.getDb().insertOrUpdateRecord(this.EVENTS_TABLE, event, { id: event.id }).then(() => { return this.scheduleEventNotification(event, time); }); }); diff --git a/src/addon/calendar/providers/helper.ts b/src/addon/calendar/providers/helper.ts index e0e08da98..9c2bdcf9f 100644 --- a/src/addon/calendar/providers/helper.ts +++ b/src/addon/calendar/providers/helper.ts @@ -23,12 +23,12 @@ import { CoreCourseProvider } from '../../../core/course/providers/course'; export class AddonCalendarHelperProvider { protected logger; - private EVENTICONS = { - 'course': 'ionic', - 'group': 'people', - 'site': 'globe', - 'user': 'person', - 'category': 'albums' + protected EVENTICONS = { + course: 'ionic', + group: 'people', + site: 'globe', + user: 'person', + category: 'albums' }; constructor(logger: CoreLoggerProvider, private courseProvider: CoreCourseProvider) { @@ -40,11 +40,11 @@ export class AddonCalendarHelperProvider { * * @param {any} e Event to format. */ - formatEventData(e: any) { + formatEventData(e: any): void { e.icon = this.EVENTICONS[e.eventtype] || false; if (!e.icon) { e.icon = this.courseProvider.getModuleIconSrc(e.modulename); e.moduleIcon = e.icon; } - }; + } } diff --git a/src/addon/calendar/providers/mainmenu-handler.ts b/src/addon/calendar/providers/mainmenu-handler.ts index 0b80de8cd..555e271fd 100644 --- a/src/addon/calendar/providers/mainmenu-handler.ts +++ b/src/addon/calendar/providers/mainmenu-handler.ts @@ -24,16 +24,15 @@ export class AddonCalendarMainMenuHandler implements CoreMainMenuHandler { name = 'AddonCalendar'; priority = 400; - constructor(private calendarProvider: AddonCalendarProvider) {} + constructor(private calendarProvider: AddonCalendarProvider) { } /** * 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|Promise { - let isDisabled = this.calendarProvider.isCalendarDisabledInSite(); - return !isDisabled; + isEnabled(): boolean | Promise { + return !this.calendarProvider.isCalendarDisabledInSite(); } /** diff --git a/src/addon/userprofilefield/checkbox/checkbox.module.ts b/src/addon/userprofilefield/checkbox/checkbox.module.ts index 1d85b6890..c8358a518 100644 --- a/src/addon/userprofilefield/checkbox/checkbox.module.ts +++ b/src/addon/userprofilefield/checkbox/checkbox.module.ts @@ -43,4 +43,4 @@ export class AddonUserProfileFieldCheckboxModule { constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldCheckboxHandler) { userProfileFieldDelegate.registerHandler(handler); } -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/checkbox/component/checkbox.ts b/src/addon/userprofilefield/checkbox/component/checkbox.ts index f04dabb92..839c38e88 100644 --- a/src/addon/userprofilefield/checkbox/component/checkbox.ts +++ b/src/addon/userprofilefield/checkbox/component/checkbox.ts @@ -25,23 +25,23 @@ import { CoreUtilsProvider } from '../../../../providers/utils/utils'; }) export class AddonUserProfileFieldCheckboxComponent implements OnInit { @Input() field: any; // The profile field to be rendered. - @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() edit?: false; // True if editing the field. Defaults to false. + @Input() disabled?: false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) {} + constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { } /** * Component being initialized. */ - ngOnInit() { - let field = this.field; + ngOnInit(): void { + const field = this.field; if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; // Initialize the value. - let formData = { + const formData = { value: this.utils.isTrueOrOne(field.defaultdata), disabled: this.disabled }; @@ -49,5 +49,4 @@ export class AddonUserProfileFieldCheckboxComponent implements OnInit { field.required && !field.locked ? Validators.requiredTrue : null)); } } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/checkbox/providers/handler.ts b/src/addon/userprofilefield/checkbox/providers/handler.ts index d6547bf96..a3dbb51e3 100644 --- a/src/addon/userprofilefield/checkbox/providers/handler.ts +++ b/src/addon/userprofilefield/checkbox/providers/handler.ts @@ -24,14 +24,16 @@ import { AddonUserProfileFieldCheckboxComponent } from '../component/checkbox'; export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFieldHandler { name = 'checkbox'; - constructor() {} + constructor() { + // Nothing to do. + } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -45,7 +47,7 @@ export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFiel * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { - let name = 'profile_field_' + field.shortname; + const name = 'profile_field_' + field.shortname; if (typeof formValues[name] != 'undefined') { return { @@ -61,8 +63,7 @@ export class AddonUserProfileFieldCheckboxHandler implements CoreUserProfileFiel * * @return {any} The component to use, undefined if not found. */ - getComponent() { + getComponent(): any { return AddonUserProfileFieldCheckboxComponent; } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/datetime/component/datetime.ts b/src/addon/userprofilefield/datetime/component/datetime.ts index b7408eb1b..5c797ae92 100644 --- a/src/addon/userprofilefield/datetime/component/datetime.ts +++ b/src/addon/userprofilefield/datetime/component/datetime.ts @@ -26,23 +26,24 @@ import { CoreUtilsProvider } from '../../../../providers/utils/utils'; }) export class AddonUserProfileFieldDatetimeComponent implements OnInit { @Input() field: any; // The profile field to be rendered. - @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() edit? = false; // True if editing the field. Defaults to false. + @Input() disabled? = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider, protected utils: CoreUtilsProvider) {} + constructor(private fb: FormBuilder, private timeUtils: CoreTimeUtilsProvider, protected utils: CoreUtilsProvider) { } /** * Component being initialized. */ - ngOnInit() { - let field = this.field, - year; + ngOnInit(): void { + const field = this.field; + let year; + if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; // Check if it's only date or it has time too. - let hasTime = this.utils.isTrueOrOne(field.param3); + const hasTime = this.utils.isTrueOrOne(field.param3); field.format = hasTime ? this.timeUtils.getLocalizedDateFormat('LLL') : this.timeUtils.getLocalizedDateFormat('LL'); // Check min value. @@ -61,7 +62,7 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { } } - let formData = { + const formData = { value: field.defaultdata, disabled: this.disabled }; @@ -69,5 +70,4 @@ export class AddonUserProfileFieldDatetimeComponent implements OnInit { field.required && !field.locked ? Validators.required : null)); } } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/datetime/datetime.module.ts b/src/addon/userprofilefield/datetime/datetime.module.ts index 1697b0e9f..b1946bf1e 100644 --- a/src/addon/userprofilefield/datetime/datetime.module.ts +++ b/src/addon/userprofilefield/datetime/datetime.module.ts @@ -45,4 +45,4 @@ export class AddonUserProfileFieldDatetimeModule { constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldDatetimeHandler) { userProfileFieldDelegate.registerHandler(handler); } -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/datetime/providers/handler.ts b/src/addon/userprofilefield/datetime/providers/handler.ts index 34f65bb25..d8135b94d 100644 --- a/src/addon/userprofilefield/datetime/providers/handler.ts +++ b/src/addon/userprofilefield/datetime/providers/handler.ts @@ -24,14 +24,16 @@ import { AddonUserProfileFieldDatetimeComponent } from '../component/datetime'; export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFieldHandler { name = 'datetime'; - constructor() {} + constructor() { + // Nothing to do. + } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -45,10 +47,11 @@ export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFiel * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { - let name = 'profile_field_' + field.shortname; + const name = 'profile_field_' + field.shortname; if (formValues[name]) { - let milliseconds = new Date(formValues[name]).getTime(); + const milliseconds = new Date(formValues[name]).getTime(); + return { type: 'datetime', name: 'profile_field_' + field.shortname, @@ -62,8 +65,7 @@ export class AddonUserProfileFieldDatetimeHandler implements CoreUserProfileFiel * * @return {any} The component to use, undefined if not found. */ - getComponent() { + getComponent(): any { return AddonUserProfileFieldDatetimeComponent; } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/menu/component/menu.ts b/src/addon/userprofilefield/menu/component/menu.ts index 09c44f1f5..b9e74c51a 100644 --- a/src/addon/userprofilefield/menu/component/menu.ts +++ b/src/addon/userprofilefield/menu/component/menu.ts @@ -24,17 +24,17 @@ import { FormGroup, FormBuilder, Validators } from '@angular/forms'; }) export class AddonUserProfileFieldMenuComponent implements OnInit { @Input() field: any; // The profile field to be rendered. - @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() edit? = false; // True if editing the field. Defaults to false. + @Input() disabled? = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - constructor(private fb: FormBuilder) {} + constructor(private fb: FormBuilder) { } /** * Component being initialized. */ - ngOnInit() { - let field = this.field; + ngOnInit(): void { + const field = this.field; if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; @@ -46,7 +46,7 @@ export class AddonUserProfileFieldMenuComponent implements OnInit { field.options = []; } - let formData = { + const formData = { value: field.defaultdata, disabled: this.disabled }; @@ -56,5 +56,4 @@ export class AddonUserProfileFieldMenuComponent implements OnInit { } } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/menu/menu.module.ts b/src/addon/userprofilefield/menu/menu.module.ts index dfaa1c0ea..89965cb30 100644 --- a/src/addon/userprofilefield/menu/menu.module.ts +++ b/src/addon/userprofilefield/menu/menu.module.ts @@ -45,4 +45,4 @@ export class AddonUserProfileFieldMenuModule { constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldMenuHandler) { userProfileFieldDelegate.registerHandler(handler); } -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/menu/providers/handler.ts b/src/addon/userprofilefield/menu/providers/handler.ts index 9004df1fc..931cef566 100644 --- a/src/addon/userprofilefield/menu/providers/handler.ts +++ b/src/addon/userprofilefield/menu/providers/handler.ts @@ -24,14 +24,16 @@ import { AddonUserProfileFieldMenuComponent } from '../component/menu'; export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHandler { name = 'menu'; - constructor() {} + constructor() { + // Nothing to do. + } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -45,7 +47,7 @@ export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHan * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { - let name = 'profile_field_' + field.shortname; + const name = 'profile_field_' + field.shortname; if (formValues[name]) { return { @@ -61,8 +63,7 @@ export class AddonUserProfileFieldMenuHandler implements CoreUserProfileFieldHan * * @return {any} The component to use, undefined if not found. */ - getComponent() { + getComponent(): any { return AddonUserProfileFieldMenuComponent; } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/text/component/text.ts b/src/addon/userprofilefield/text/component/text.ts index 8ef5e8f2c..e8a8ea696 100644 --- a/src/addon/userprofilefield/text/component/text.ts +++ b/src/addon/userprofilefield/text/component/text.ts @@ -25,17 +25,17 @@ import { CoreUtilsProvider } from '../../../../providers/utils/utils'; }) export class AddonUserProfileFieldTextComponent implements OnInit { @Input() field: any; // The profile field to be rendered. - @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() edit? = false; // True if editing the field. Defaults to false. + @Input() disabled? = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) {} + constructor(private fb: FormBuilder, protected utils: CoreUtilsProvider) { } /** * Component being initialized. */ - ngOnInit() { - let field = this.field; + ngOnInit(): void { + const field = this.field; if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; @@ -48,7 +48,7 @@ export class AddonUserProfileFieldTextComponent implements OnInit { // Check if it's a password or text. field.inputType = this.utils.isTrueOrOne(field.param3) ? 'password' : 'text'; - let formData = { + const formData = { value: field.defaultdata, disabled: this.disabled }; @@ -57,5 +57,4 @@ export class AddonUserProfileFieldTextComponent implements OnInit { field.required && !field.locked ? Validators.required : null)); } } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/text/providers/handler.ts b/src/addon/userprofilefield/text/providers/handler.ts index b3778b173..1e75cc866 100644 --- a/src/addon/userprofilefield/text/providers/handler.ts +++ b/src/addon/userprofilefield/text/providers/handler.ts @@ -25,14 +25,14 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHandler { name = 'text'; - constructor(private textUtils: CoreTextUtilsProvider) {} + constructor(private textUtils: CoreTextUtilsProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -46,7 +46,7 @@ export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHan * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { - let name = 'profile_field_' + field.shortname; + const name = 'profile_field_' + field.shortname; return { type: 'text', @@ -60,8 +60,7 @@ export class AddonUserProfileFieldTextHandler implements CoreUserProfileFieldHan * * @return {any} The component to use, undefined if not found. */ - getComponent() { + getComponent(): any { return AddonUserProfileFieldTextComponent; } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/text/text.module.ts b/src/addon/userprofilefield/text/text.module.ts index 4264c858c..c23f6bbea 100644 --- a/src/addon/userprofilefield/text/text.module.ts +++ b/src/addon/userprofilefield/text/text.module.ts @@ -45,4 +45,4 @@ export class AddonUserProfileFieldTextModule { constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldTextHandler) { userProfileFieldDelegate.registerHandler(handler); } -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/textarea/component/textarea.ts b/src/addon/userprofilefield/textarea/component/textarea.ts index 927dd128e..efd1dae99 100644 --- a/src/addon/userprofilefield/textarea/component/textarea.ts +++ b/src/addon/userprofilefield/textarea/component/textarea.ts @@ -13,7 +13,7 @@ // limitations under the License. import { Component, Input, OnInit } from '@angular/core'; -import { FormGroup, Validators, FormControl} from '@angular/forms'; +import { FormGroup, Validators, FormControl } from '@angular/forms'; /** * Directive to render a textarea user profile field. @@ -24,24 +24,26 @@ import { FormGroup, Validators, FormControl} from '@angular/forms'; }) export class AddonUserProfileFieldTextareaComponent implements OnInit { @Input() field: any; // The profile field to be rendered. - @Input() edit?: boolean = false; // True if editing the field. Defaults to false. - @Input() disabled?: boolean = false; // True if disabled. Defaults to false. + @Input() edit? = false; // True if editing the field. Defaults to false. + @Input() disabled? = false; // True if disabled. Defaults to false. @Input() form?: FormGroup; // Form where to add the form control. - control: FormControl + control: FormControl; - constructor() {} + constructor() { + // Nothing to do. + } /** * Component being initialized. */ - ngOnInit() { - let field = this.field; + ngOnInit(): void { + const field = this.field; if (field && this.edit && this.form) { field.modelName = 'profile_field_' + field.shortname; - let formData = { + const formData = { value: field.defaultdata, disabled: this.disabled }; @@ -50,5 +52,4 @@ export class AddonUserProfileFieldTextareaComponent implements OnInit { this.form.addControl(field.modelName, this.control); } } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/textarea/providers/handler.ts b/src/addon/userprofilefield/textarea/providers/handler.ts index 1047cbfdc..10044f151 100644 --- a/src/addon/userprofilefield/textarea/providers/handler.ts +++ b/src/addon/userprofilefield/textarea/providers/handler.ts @@ -25,14 +25,14 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFieldHandler { name = 'textarea'; - constructor(private textUtils: CoreTextUtilsProvider) {} + constructor(private textUtils: CoreTextUtilsProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -46,10 +46,10 @@ export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFiel * @return {CoreUserProfileFieldHandlerData} Data to send for the field. */ getData(field: any, signup: boolean, registerAuth: string, formValues: any): CoreUserProfileFieldHandlerData { - let name = 'profile_field_' + field.shortname; + const name = 'profile_field_' + field.shortname; if (formValues[name]) { - let text = formValues[name] || ''; + let text = formValues[name] || ''; // Add some HTML to the message in case the user edited with textarea. text = this.textUtils.formatHtmlLines(text); @@ -69,8 +69,7 @@ export class AddonUserProfileFieldTextareaHandler implements CoreUserProfileFiel * * @return {any} The component to use, undefined if not found. */ - getComponent() { + getComponent(): any { return AddonUserProfileFieldTextareaComponent; } - -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/textarea/textarea.module.ts b/src/addon/userprofilefield/textarea/textarea.module.ts index b1c2212d3..6b5f696dc 100644 --- a/src/addon/userprofilefield/textarea/textarea.module.ts +++ b/src/addon/userprofilefield/textarea/textarea.module.ts @@ -45,4 +45,4 @@ export class AddonUserProfileFieldTextareaModule { constructor(userProfileFieldDelegate: CoreUserProfileFieldDelegate, handler: AddonUserProfileFieldTextareaHandler) { userProfileFieldDelegate.registerHandler(handler); } -} \ No newline at end of file +} diff --git a/src/addon/userprofilefield/userprofilefield.module.ts b/src/addon/userprofilefield/userprofilefield.module.ts index a946dab74..b99616335 100644 --- a/src/addon/userprofilefield/userprofilefield.module.ts +++ b/src/addon/userprofilefield/userprofilefield.module.ts @@ -18,7 +18,6 @@ import { AddonUserProfileFieldMenuModule } from './menu/menu.module'; import { AddonUserProfileFieldTextModule } from './text/text.module'; import { AddonUserProfileFieldTextareaModule } from './textarea/textarea.module'; - @NgModule({ declarations: [], imports: [ @@ -32,4 +31,4 @@ import { AddonUserProfileFieldTextareaModule } from './textarea/textarea.module' ], exports: [] }) -export class AddonUserProfileFieldModule {} \ No newline at end of file +export class AddonUserProfileFieldModule { } diff --git a/src/app/app.component.ts b/src/app/app.component.ts index 72c1f61d5..4bdeb273d 100644 --- a/src/app/app.component.ts +++ b/src/app/app.component.ts @@ -25,15 +25,15 @@ import { CoreLoginHelperProvider } from '../core/login/providers/helper'; templateUrl: 'app.html' }) export class MoodleMobileApp implements OnInit { - // Use the page name (string) because the page is lazy loaded (Ionic feature). That way we can load pages without - // having to import them. The downside is that each page needs to implement a ngModule. - rootPage:any = 'CoreLoginInitPage'; + // Use page name (string) because the page is lazy loaded (Ionic feature). That way we can load pages without importing them. + // The downside is that each page needs to implement a ngModule. + rootPage: any = 'CoreLoginInitPage'; protected logger; protected lastUrls = {}; constructor(private platform: Platform, statusBar: StatusBar, splashScreen: SplashScreen, logger: CoreLoggerProvider, - private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, - private appProvider: CoreAppProvider) { + private eventsProvider: CoreEventsProvider, private loginHelper: CoreLoginHelperProvider, + private appProvider: CoreAppProvider) { this.logger = logger.getInstance('AppComponent'); platform.ready().then(() => { @@ -48,7 +48,7 @@ export class MoodleMobileApp implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { // Go to sites page when user is logged out. this.eventsProvider.on(CoreEventsProvider.LOGOUT, () => { this.appProvider.getRootNavController().setRoot('CoreLoginSitesPage'); @@ -93,7 +93,7 @@ export class MoodleMobileApp implements OnInit { }); // Handle app launched with a certain URL (custom URL scheme). - (window).handleOpenURL = (url: string) => { + ( window).handleOpenURL = (url: string): void => { // First check that the URL hasn't been treated a few seconds ago. Sometimes this function is called more than once. if (this.lastUrls[url] && Date.now() - this.lastUrls[url] < 3000) { // Function called more than once, stop. @@ -113,4 +113,3 @@ export class MoodleMobileApp implements OnInit { }); } } - diff --git a/src/app/app.module.ts b/src/app/app.module.ts index c68d1071b..3d3620622 100644 --- a/src/app/app.module.ts +++ b/src/app/app.module.ts @@ -66,9 +66,8 @@ import { CoreUserModule } from '../core/user/user.module'; import { AddonCalendarModule } from '../addon/calendar/calendar.module'; import { AddonUserProfileFieldModule } from '../addon/userprofilefield/userprofilefield.module'; - // For translate loader. AoT requires an exported function for factories. -export function createTranslateLoader(http: HttpClient) { +export function createTranslateLoader(http: HttpClient): TranslateHttpLoader { return new TranslateHttpLoader(http, './assets/lang/', '.json'); } diff --git a/src/app/main.ts b/src/app/main.ts index 70e0b3a7a..1f1c367fd 100644 --- a/src/app/main.ts +++ b/src/app/main.ts @@ -1,3 +1,17 @@ +// (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 { platformBrowserDynamic } from '@angular/platform-browser-dynamic'; import { AppModule } from './app.module'; diff --git a/src/classes/base-sync.ts b/src/classes/base-sync.ts index f87729bb9..eaa905591 100644 --- a/src/classes/base-sync.ts +++ b/src/classes/base-sync.ts @@ -32,9 +32,9 @@ export class CoreSyncBaseProvider { syncInterval = 300000; // Store sync promises. - protected syncPromises: {[siteId: string]: {[uniqueId: string]: Promise}} = {}; + protected syncPromises: { [siteId: string]: { [uniqueId: string]: Promise } } = {}; - constructor(private sitesProvider: CoreSitesProvider) {} + constructor(private sitesProvider: CoreSitesProvider) { } /** * Add an ongoing sync to the syncPromises list. On finish the promise will be removed. @@ -44,7 +44,7 @@ export class CoreSyncBaseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} The sync promise. */ - addOngoingSync(id: number, promise: Promise, siteId?: string) : Promise { + addOngoingSync(id: number, promise: Promise, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const uniqueId = this.getUniqueSyncId(id); @@ -67,12 +67,13 @@ export class CoreSyncBaseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise of the current sync or undefined if there isn't any. */ - getOngoingSync(id: number, siteId?: string) : Promise { + getOngoingSync(id: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (this.isSyncing(id, siteId)) { // There's already a sync ongoing for this discussion, return the promise. const uniqueId = this.getUniqueSyncId(id); + return this.syncPromises[siteId][uniqueId]; } } @@ -84,9 +85,9 @@ export class CoreSyncBaseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the time. */ - getSyncTime(id: number, siteId?: string) : Promise { + getSyncTime(id: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getRecord(CoreSyncProvider.SYNC_TABLE, {component: this.component, id: id}).then((entry) => { + return db.getRecord(CoreSyncProvider.SYNC_TABLE, { component: this.component, id: id }).then((entry) => { return entry.time; }).catch(() => { return 0; @@ -101,12 +102,12 @@ export class CoreSyncBaseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the warnings. */ - getSyncWarnings(id: number, siteId?: string) : Promise { + getSyncWarnings(id: number, siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getRecord(CoreSyncProvider.SYNC_TABLE, {component: this.component, id: id}).then((entry) => { + return db.getRecord(CoreSyncProvider.SYNC_TABLE, { component: this.component, id: id }).then((entry) => { try { return JSON.parse(entry.warnings); - } catch(ex) { + } catch (ex) { return []; } }).catch(() => { @@ -121,7 +122,7 @@ export class CoreSyncBaseProvider { * @param {number} id Unique sync identifier per component. * @return {string} Unique identifier from component and id. */ - protected getUniqueSyncId(id: number) : string { + protected getUniqueSyncId(id: number): string { return this.component + '#' + id; } @@ -132,10 +133,11 @@ export class CoreSyncBaseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {boolean} Whether it's synchronizing. */ - isSyncing(id: number, siteId?: string) : boolean { + isSyncing(id: number, siteId?: string): boolean { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const uniqueId = this.getUniqueSyncId(id); + return !!(this.syncPromises[siteId] && this.syncPromises[siteId][uniqueId]); } @@ -146,7 +148,7 @@ export class CoreSyncBaseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with boolean: whether sync is needed. */ - isSyncNeeded(id: number, siteId?: string) : Promise { + isSyncNeeded(id: number, siteId?: string): Promise { return this.getSyncTime(id, siteId).then((time) => { return Date.now() - this.syncInterval >= time; }); @@ -160,10 +162,11 @@ export class CoreSyncBaseProvider { * @param {number} [time] Time to set. If not defined, current time. * @return {Promise} Promise resolved when the time is set. */ - setSyncTime(id: number, siteId?: string, time?: number) : Promise { + setSyncTime(id: number, siteId?: string, time?: number): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { time = typeof time != 'undefined' ? time : Date.now(); - return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, {time: time}, {component: this.component, id: id}); + + return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, { time: time }, { component: this.component, id: id }); }); } @@ -175,11 +178,12 @@ export class CoreSyncBaseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when done. */ - setSyncWarnings(id: number, warnings: string[], siteId?: string) : Promise { + setSyncWarnings(id: number, warnings: string[], siteId?: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { warnings = warnings || []; - return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, {warnings: JSON.stringify(warnings)}, - {component: this.component, id: id}); + + return db.insertOrUpdateRecord(CoreSyncProvider.SYNC_TABLE, { warnings: JSON.stringify(warnings) }, + { component: this.component, id: id }); }); } @@ -191,11 +195,14 @@ export class CoreSyncBaseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when there's no sync going on for the identifier. */ - waitForSync(id: number, siteId?: string) : Promise { + waitForSync(id: number, siteId?: string): Promise { const promise = this.getOngoingSync(id, siteId); if (promise) { - return promise.catch(() => {}); + return promise.catch(() => { + // Ignore errors. + }); } + return Promise.resolve(); } } diff --git a/src/classes/cache.ts b/src/classes/cache.ts index c117dc71b..38ba1f3c8 100644 --- a/src/classes/cache.ts +++ b/src/classes/cache.ts @@ -23,12 +23,14 @@ export class CoreCache { protected cacheStore = {}; - constructor() {} + constructor() { + // Nothing to do. + } /** * Clear the cache. */ - clear() { + clear(): void { this.cacheStore = {}; } @@ -38,7 +40,7 @@ export class CoreCache { * @param {any} id The ID to identify the entry. * @return {any} The data from the cache. Undefined if not found. */ - getEntry(id: any) : any { + getEntry(id: any): any { if (!this.cacheStore[id]) { this.cacheStore[id] = {}; } @@ -54,7 +56,7 @@ export class CoreCache { * @param {boolean} [ignoreInvalidate] Whether it should always return the cached data, even if it's expired. * @return {any} Cached value. Undefined if not cached or expired. */ - getValue(id: any, name: string, ignoreInvalidate?: boolean) : any { + getValue(id: any, name: string, ignoreInvalidate?: boolean): any { const entry = this.getEntry(id); if (entry[name] && typeof entry[name].value != 'undefined') { @@ -73,9 +75,9 @@ export class CoreCache { * * @param {any} id The ID to identify the entry. */ - invalidate(id: any) : void { + invalidate(id: any): void { const entry = this.getEntry(id); - for (let name in entry) { + for (const name in entry) { entry[name].timemodified = 0; } } @@ -88,12 +90,13 @@ export class CoreCache { * @param {any} value Value to set. * @return {any} The set value. */ - setValue(id: any, name: string, value: any) : any { + setValue(id: any, name: string, value: any): any { const entry = this.getEntry(id); entry[name] = { value: value, timemodified: Date.now() }; + return value; } } diff --git a/src/classes/delegate.ts b/src/classes/delegate.ts index 780c6263d..cfbb953d4 100644 --- a/src/classes/delegate.ts +++ b/src/classes/delegate.ts @@ -84,7 +84,7 @@ export class CoreDelegate { * If not set, no events will be fired. */ constructor(delegateName: string, protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, - protected eventsProvider?: CoreEventsProvider) { + protected eventsProvider?: CoreEventsProvider) { this.logger = this.loggerProvider.getInstance(delegateName); if (eventsProvider) { @@ -145,7 +145,7 @@ export class CoreDelegate { * @param {boolean} [enabled] Only enabled, or any. * @return {any} Handler. */ - protected getHandler(handlerName: string, enabled = false): any { + protected getHandler(handlerName: string, enabled: boolean = false): any { return enabled ? this.enabledHandlers[handlerName] : this.handlers[handlerName]; } @@ -156,7 +156,7 @@ export class CoreDelegate { * @param {boolean} [enabled] Only enabled, or any. * @return {boolean} If the handler is registered or not. */ - hasHandler(name: string, enabled = false): boolean { + hasHandler(name: string, enabled: boolean = false): boolean { return enabled ? typeof this.enabledHandlers[name] !== 'undefined' : typeof this.handlers[name] !== 'undefined'; } @@ -171,6 +171,7 @@ export class CoreDelegate { if (!this.lastUpdateHandlersStart) { return true; } + return time == this.lastUpdateHandlersStart; } @@ -183,11 +184,13 @@ export class CoreDelegate { registerHandler(handler: CoreDelegateHandler): boolean { if (typeof this.handlers[handler.name] !== 'undefined') { this.logger.log(`Addon '${handler.name}' already registered`); + return false; } this.logger.log(`Registered addon '${handler.name}'`); this.handlers[handler.name] = handler; + return true; } @@ -199,9 +202,9 @@ export class CoreDelegate { * @return {Promise} Resolved when done. */ protected updateHandler(handler: CoreDelegateHandler, time: number): Promise { - let promise, - siteId = this.sitesProvider.getCurrentSiteId(), + const siteId = this.sitesProvider.getCurrentSiteId(), currentSite = this.sitesProvider.getCurrentSite(); + let promise; if (!this.sitesProvider.isLoggedIn()) { promise = Promise.reject(null); @@ -235,7 +238,7 @@ export class CoreDelegate { * @return {boolean} Whether is enabled or disabled in site. */ protected isFeatureDisabled(handler: CoreDelegateHandler, site: any): boolean { - return typeof this.featurePrefix != "undefined" && site.isFeatureDisabled(this.featurePrefix + handler.name); + return typeof this.featurePrefix != 'undefined' && site.isFeatureDisabled(this.featurePrefix + handler.name); } /** @@ -244,7 +247,7 @@ export class CoreDelegate { * @return {Promise} Resolved when done. */ protected updateHandlers(): Promise { - let promises = [], + const promises = [], now = Date.now(); this.logger.debug('Updating handlers for current site.'); @@ -252,7 +255,7 @@ export class CoreDelegate { this.lastUpdateHandlersStart = now; // Loop over all the handlers. - for (let name in this.handlers) { + for (const name in this.handlers) { promises.push(this.updateHandler(this.handlers[name], now)); } @@ -274,7 +277,7 @@ export class CoreDelegate { * Update handlers Data. * Override this function to update handlers data. */ - updateData() { - + updateData(): any { + // To be overridden. } -} \ No newline at end of file +} diff --git a/src/classes/interceptor.ts b/src/classes/interceptor.ts index ad78e5d95..35e5c4597 100644 --- a/src/classes/interceptor.ts +++ b/src/classes/interceptor.ts @@ -23,14 +23,16 @@ import { Observable } from 'rxjs'; @Injectable() export class CoreInterceptor implements HttpInterceptor { - constructor() {} + constructor() { + // Nothing to do. + } intercept(req: HttpRequest, next: HttpHandler): Observable { // Add the header and serialize the body if needed. const newReq = req.clone({ headers: req.headers.set('Content-Type', 'application/x-www-form-urlencoded'), body: typeof req.body == 'object' && String(req.body) != '[object File]' ? - CoreInterceptor.serialize(req.body) : req.body + CoreInterceptor.serialize(req.body) : req.body }); // Pass on the cloned request instead of the original request. @@ -44,14 +46,14 @@ export class CoreInterceptor implements HttpInterceptor { * @param {boolean} [addNull] Add null values to the serialized as empty parameters. * @return {string} Serialization of the object. */ - public static serialize(obj: any, addNull?: boolean) : string { + static serialize(obj: any, addNull?: boolean): string { let query = '', fullSubName, subValue, innerObj; - for (let name in obj) { - let value = obj[name]; + for (const name in obj) { + const value = obj[name]; if (value instanceof Array) { for (let i = 0; i < value.length; ++i) { @@ -62,7 +64,7 @@ export class CoreInterceptor implements HttpInterceptor { query += this.serialize(innerObj) + '&'; } } else if (value instanceof Object) { - for (let subName in value) { + for (const subName in value) { subValue = value[subName]; fullSubName = name + '[' + subName + ']'; innerObj = {}; diff --git a/src/classes/site.ts b/src/classes/site.ts index 43272d116..e5e280cb3 100644 --- a/src/classes/site.ts +++ b/src/classes/site.ts @@ -107,7 +107,7 @@ export interface CoreSiteWSPreSets { * @type {string} */ typeExpected?: string; -}; +} /** * Response of checking local_mobile status. @@ -190,10 +190,10 @@ export class CoreSite { protected cleanUnicode = false; protected lastAutoLogin = 0; protected moodleReleases = { - '3.1': 2016052300, - '3.2': 2016120500, - '3.3': 2017051503, - '3.4': 2017111300 + 3.1: 2016052300, + 3.2: 2016120500, + 3.3: 2017051503, + 3.4: 2017111300 }; /** @@ -211,7 +211,7 @@ export class CoreSite { constructor(injector: Injector, public id: string, public siteUrl: string, public token?: string, public infos?: any, public privateToken?: string, public config?: any, public loggedOut?: boolean) { // Inject the required services. - let logger = injector.get(CoreLoggerProvider); + const logger = injector.get(CoreLoggerProvider); this.appProvider = injector.get(CoreAppProvider); this.dbProvider = injector.get(CoreDbProvider); this.domUtils = injector.get(CoreDomUtilsProvider); @@ -235,7 +235,7 @@ export class CoreSite { /** * Initialize the database. */ - initDB() : void { + initDB(): void { this.db = this.dbProvider.getDB('Site-' + this.id); this.db.createTableFromSchema(this.tableSchema); } @@ -245,7 +245,7 @@ export class CoreSite { * * @return {string} Site ID. */ - getId() : string { + getId(): string { return this.id; } @@ -254,7 +254,7 @@ export class CoreSite { * * @return {string} Site URL. */ - getURL() : string { + getURL(): string { return this.siteUrl; } @@ -263,7 +263,7 @@ export class CoreSite { * * @return {string} Site token. */ - getToken() : string { + getToken(): string { return this.token; } @@ -272,7 +272,7 @@ export class CoreSite { * * @return {any} Site info. */ - getInfo() : any { + getInfo(): any { return this.infos; } @@ -281,7 +281,7 @@ export class CoreSite { * * @return {string} Site private token. */ - getPrivateToken() : string { + getPrivateToken(): string { return this.privateToken; } @@ -290,7 +290,7 @@ export class CoreSite { * * @return {SQLiteDB} Site DB. */ - getDb() : SQLiteDB { + getDb(): SQLiteDB { return this.db; } @@ -299,7 +299,7 @@ export class CoreSite { * * @return {number} User's ID. */ - getUserId() : number { + getUserId(): number { if (typeof this.infos != 'undefined' && typeof this.infos.userid != 'undefined') { return this.infos.userid; } @@ -310,7 +310,7 @@ export class CoreSite { * * @return {number} Site Home ID. */ - getSiteHomeId() : number { + getSiteHomeId(): number { return this.infos && this.infos.siteid || 1; } @@ -319,7 +319,7 @@ export class CoreSite { * * @param {string} New ID. */ - setId(id: string) : void { + setId(id: string): void { this.id = id; this.initDB(); } @@ -329,7 +329,7 @@ export class CoreSite { * * @param {string} New token. */ - setToken(token: string) : void { + setToken(token: string): void { this.token = token; } @@ -338,7 +338,7 @@ export class CoreSite { * * @param {string} privateToken New private token. */ - setPrivateToken(privateToken: string) : void { + setPrivateToken(privateToken: string): void { this.privateToken = privateToken; } @@ -347,7 +347,7 @@ export class CoreSite { * * @return {boolean} Whether is logged out. */ - isLoggedOut() : boolean { + isLoggedOut(): boolean { return !!this.loggedOut; } @@ -356,7 +356,7 @@ export class CoreSite { * * @param {any} New info. */ - setInfo(infos: any) : void { + setInfo(infos: any): void { this.infos = infos; } @@ -365,7 +365,7 @@ export class CoreSite { * * @param {any} Config. */ - setConfig(config: any) : void { + setConfig(config: any): void { this.config = config; } @@ -374,7 +374,7 @@ export class CoreSite { * * @param {boolean} loggedOut True if logged out and needs to authenticate again, false otherwise. */ - setLoggedOut(loggedOut: boolean) : void { + setLoggedOut(loggedOut: boolean): void { this.loggedOut = !!loggedOut; } @@ -383,8 +383,9 @@ export class CoreSite { * * @return {boolean} Whether can access my files. */ - canAccessMyFiles() : boolean { + canAccessMyFiles(): boolean { const infos = this.getInfo(); + return infos && (typeof infos.usercanmanageownfiles === 'undefined' || infos.usercanmanageownfiles); } @@ -393,8 +394,9 @@ export class CoreSite { * * @return {boolean} Whether can download files. */ - canDownloadFiles() : boolean { + canDownloadFiles(): boolean { const infos = this.getInfo(); + return infos && infos.downloadfiles; } @@ -405,15 +407,15 @@ export class CoreSite { * @param {boolean} [whenUndefined=true] The value to return when the parameter is undefined. * @return {boolean} Whether can use advanced feature. */ - canUseAdvancedFeature(feature: string, whenUndefined = true) : boolean { - let infos = this.getInfo(), - canUse = true; + canUseAdvancedFeature(feature: string, whenUndefined: boolean = true): boolean { + const infos = this.getInfo(); + let canUse = true; if (typeof infos.advancedfeatures === 'undefined') { canUse = whenUndefined; } else { - for (let i in infos.advancedfeatures) { - let item = infos.advancedfeatures[i]; + for (const i in infos.advancedfeatures) { + const item = infos.advancedfeatures[i]; if (item.name === feature && parseInt(item.value, 10) === 0) { canUse = false; } @@ -429,8 +431,9 @@ export class CoreSite { * * @return {boolean} Whether can upload files. */ - canUploadFiles() : boolean { + canUploadFiles(): boolean { const infos = this.getInfo(); + return infos && infos.uploadfiles; } @@ -439,12 +442,12 @@ export class CoreSite { * * @return {Promise} A promise to be resolved when the site info is retrieved. */ - fetchSiteInfo() : Promise { - // get_site_info won't be cached. - let preSets = { + fetchSiteInfo(): Promise { + // The get_site_info WS call won't be cached. + const preSets = { getFromCache: false, saveToCache: false - } + }; // Reset clean Unicode to check if it's supported again. this.cleanUnicode = false; @@ -460,7 +463,7 @@ export class CoreSite { * @param {CoreSiteWSPreSets} [preSets] Extra options. * @return {Promise} Promise resolved with the response, rejected with CoreWSError if it fails. */ - read(method: string, data: any, preSets?: CoreSiteWSPreSets) : Promise { + read(method: string, data: any, preSets?: CoreSiteWSPreSets): Promise { preSets = preSets || {}; if (typeof preSets.getFromCache == 'undefined') { preSets.getFromCache = true; @@ -468,6 +471,7 @@ export class CoreSite { if (typeof preSets.saveToCache == 'undefined') { preSets.saveToCache = true; } + return this.request(method, data, preSets); } @@ -479,7 +483,7 @@ export class CoreSite { * @param {CoreSiteWSPreSets} [preSets] Extra options. * @return {Promise} Promise resolved with the response, rejected with CoreWSError if it fails. */ - write(method: string, data: any, preSets?: CoreSiteWSPreSets) : Promise { + write(method: string, data: any, preSets?: CoreSiteWSPreSets): Promise { preSets = preSets || {}; if (typeof preSets.getFromCache == 'undefined') { preSets.getFromCache = false; @@ -490,6 +494,7 @@ export class CoreSite { if (typeof preSets.emergencyCache == 'undefined') { preSets.emergencyCache = false; } + return this.request(method, data, preSets); } @@ -512,8 +517,8 @@ export class CoreSite { * This method is smart which means that it will try to map the method to a compatibility one if need be, usually this * means that it will fallback on the 'local_mobile_' prefixed function if it is available and the non-prefixed is not. */ - request(method: string, data: any, preSets: CoreSiteWSPreSets, retrying?: boolean) : Promise { - let initialToken = this.token; + request(method: string, data: any, preSets: CoreSiteWSPreSets, retrying?: boolean): Promise { + const initialToken = this.token; data = data || {}; // Check if the method is available, use a prefixed version if possible. @@ -525,11 +530,12 @@ export class CoreSite { method = compatibilityMethod; } else { this.logger.error(`WS function '${method}' is not available, even in compatibility mode.`); + return Promise.reject(this.wsProvider.createFakeWSError('core.wsfunctionnotavailable', true)); } } - let wsPreSets: CoreWSPreSets = { + const wsPreSets: CoreWSPreSets = { wsToken: this.token, siteUrl: this.siteUrl, cleanUnicode: this.cleanUnicode, @@ -564,12 +570,11 @@ export class CoreSite { this.saveToCache(method, data, response, preSets); } - // We pass back a clone of the original object, this may - // prevent errors if in the callback the object is modified. + // We pass back a clone of the original object, this may prevent errors if in the callback the object is modified. return this.utils.clone(response); }).catch((error) => { if (error.errorcode == 'invalidtoken' || - (error.errorcode == 'accessexception' && error.message.indexOf('Invalid token - token expired') > -1)) { + (error.errorcode == 'accessexception' && error.message.indexOf('Invalid token - token expired') > -1)) { if (initialToken !== this.token && !retrying) { // Token has changed, retry with the new token. return this.request(method, data, preSets, true); @@ -586,41 +591,49 @@ export class CoreSite { error.message = this.translate.instant('core.lostconnection'); } else if (error.errorcode === 'userdeleted') { // User deleted, trigger event. - this.eventsProvider.trigger(CoreEventsProvider.USER_DELETED, {params: data}, this.id); + this.eventsProvider.trigger(CoreEventsProvider.USER_DELETED, { params: data }, this.id); error.message = this.translate.instant('core.userdeleted'); + return Promise.reject(error); } else if (error.errorcode === 'forcepasswordchangenotice') { // Password Change Forced, trigger event. this.eventsProvider.trigger(CoreEventsProvider.PASSWORD_CHANGE_FORCED, {}, this.id); error.message = this.translate.instant('core.forcepasswordchangenotice'); + return Promise.reject(error); } else if (error.errorcode === 'usernotfullysetup') { // User not fully setup, trigger event. this.eventsProvider.trigger(CoreEventsProvider.USER_NOT_FULLY_SETUP, {}, this.id); error.message = this.translate.instant('core.usernotfullysetup'); + return Promise.reject(error); } else if (error.errorcode === 'sitepolicynotagreed') { // Site policy not agreed, trigger event. this.eventsProvider.trigger(CoreEventsProvider.SITE_POLICY_NOT_AGREED, {}, this.id); error.message = this.translate.instant('core.sitepolicynotagreederror'); + return Promise.reject(error); } else if (error.errorcode === 'dmlwriteexception' && this.textUtils.hasUnicodeData(data)) { if (!this.cleanUnicode) { // Try again cleaning unicode. this.cleanUnicode = true; + return this.request(method, data, preSets); } // This should not happen. error.message = this.translate.instant('core.unicodenotsupported'); + return Promise.reject(error); } else if (typeof preSets.emergencyCache !== 'undefined' && !preSets.emergencyCache) { this.logger.debug(`WS call '${method}' failed. Emergency cache is forbidden, rejecting.`); + return Promise.reject(error); } this.logger.debug(`WS call '${method}' failed. Trying to use the emergency cache.`); preSets.omitExpires = true; preSets.getFromCache = true; + return this.getFromCache(method, data, preSets, true).catch(() => { return Promise.reject(error); }); @@ -635,7 +648,7 @@ export class CoreSite { * @param {boolean} [checkPrefix=true] When true also checks with the compatibility prefix. * @return {boolean} Whether the WS is available. */ - wsAvailable(method: string, checkPrefix = true) : boolean { + wsAvailable(method: string, checkPrefix: boolean = true): boolean { if (typeof this.infos == 'undefined') { return false; } @@ -662,8 +675,8 @@ export class CoreSite { * @param {any} data Arguments to pass to the method. * @return {string} Cache ID. */ - protected getCacheId(method: string, data: any) : string { - return Md5.hashAsciiStr(method + ':' + this.utils.sortAndStringify(data)); + protected getCacheId(method: string, data: any): string { + return Md5.hashAsciiStr(method + ':' + this.utils.sortAndStringify(data)); } /** @@ -675,32 +688,33 @@ export class CoreSite { * @param {boolean} emergency Whether it's an "emergency" cache call (WS call failed). * @return {Promise} Promise resolved with the WS response. */ - protected getFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, emergency?: boolean) : Promise { - let id = this.getCacheId(method, data), - promise; + protected getFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, emergency?: boolean): Promise { + const id = this.getCacheId(method, data); + let promise; if (!this.db || !preSets.getFromCache) { return Promise.reject(null); } if (preSets.getCacheUsingCacheKey || (emergency && preSets.getEmergencyCacheUsingCacheKey)) { - promise = this.db.getRecords(this.WS_CACHE_TABLE, {key: preSets.cacheKey}).then((entries) => { + promise = this.db.getRecords(this.WS_CACHE_TABLE, { key: preSets.cacheKey }).then((entries) => { if (!entries.length) { // Cache key not found, get by params sent. - return this.db.getRecord(this.WS_CACHE_TABLE, {id: id}); + return this.db.getRecord(this.WS_CACHE_TABLE, { id: id }); } else if (entries.length > 1) { // More than one entry found. Search the one with same ID as this call. for (let i = 0, len = entries.length; i < len; i++) { - let entry = entries[i]; + const entry = entries[i]; if (entry.id == id) { return entry; } } } + return entries[0]; }); } else { - promise = this.db.getRecord(this.WS_CACHE_TABLE, {id: id}); + promise = this.db.getRecord(this.WS_CACHE_TABLE, { id: id }); } return promise.then((entry) => { @@ -711,6 +725,7 @@ export class CoreSite { if (!preSets.omitExpires) { if (now > entry.expirationTime) { this.logger.debug('Cached element found, but it is expired'); + return Promise.reject(null); } } @@ -718,6 +733,7 @@ export class CoreSite { if (typeof entry != 'undefined' && typeof entry.data != 'undefined') { const expires = (entry.expirationTime - now) / 1000; this.logger.info(`Cached element found, id: ${id} expires in ${expires} seconds`); + return JSON.parse(entry.data); } @@ -734,14 +750,14 @@ export class CoreSite { * @param {CoreSiteWSPreSets} preSets Extra options. * @return {Promise} Promise resolved when the response is saved. */ - protected saveToCache(method: string, data: any, response: any, preSets: CoreSiteWSPreSets) : Promise { - let id = this.getCacheId(method, data), - cacheExpirationTime = CoreConfigConstants.cache_expiration_time, - promise, + protected saveToCache(method: string, data: any, response: any, preSets: CoreSiteWSPreSets): Promise { + const id = this.getCacheId(method, data), entry: any = { id: id, data: JSON.stringify(response) - } + }; + let cacheExpirationTime = CoreConfigConstants.cache_expiration_time, + promise; if (!this.db) { return Promise.reject(null); @@ -761,7 +777,8 @@ export class CoreSite { if (preSets.cacheKey) { entry.key = preSets.cacheKey; } - return this.db.insertOrUpdateRecord(this.WS_CACHE_TABLE, entry, {id: id}); + + return this.db.insertOrUpdateRecord(this.WS_CACHE_TABLE, entry, { id: id }); }); } } @@ -775,16 +792,16 @@ export class CoreSite { * @param {boolean} [allCacheKey] True to delete all entries with the cache key, false to delete only by ID. * @return {Promise} Promise resolved when the entries are deleted. */ - protected deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean) : Promise { + protected deleteFromCache(method: string, data: any, preSets: CoreSiteWSPreSets, allCacheKey?: boolean): Promise { const id = this.getCacheId(method, data); if (!this.db) { return Promise.reject(null); } else { if (allCacheKey) { - return this.db.deleteRecords(this.WS_CACHE_TABLE, {key: preSets.cacheKey}); + return this.db.deleteRecords(this.WS_CACHE_TABLE, { key: preSets.cacheKey }); } else { - return this.db.deleteRecords(this.WS_CACHE_TABLE, {id: id}); + return this.db.deleteRecords(this.WS_CACHE_TABLE, { id: id }); } } } @@ -797,7 +814,7 @@ export class CoreSite { * @param {Function} [onProgress] Function to call on progress. * @return {Promise} Promise resolved when uploaded. */ - uploadFile(filePath: string, options: CoreWSFileUploadOptions, onProgress?: (event: ProgressEvent) => any) : Promise { + uploadFile(filePath: string, options: CoreWSFileUploadOptions, onProgress?: (event: ProgressEvent) => any): Promise { if (!options.fileArea) { options.fileArea = 'draft'; } @@ -813,13 +830,14 @@ export class CoreSite { * * @return {Promise} Promise resolved when the cache entries are invalidated. */ - invalidateWsCache() : Promise { + invalidateWsCache(): Promise { if (!this.db) { return Promise.reject(null); } this.logger.debug('Invalidate all the cache for site: ' + this.id); - return this.db.updateRecords(this.WS_CACHE_TABLE, {expirationTime: 0}); + + return this.db.updateRecords(this.WS_CACHE_TABLE, { expirationTime: 0 }); } /** @@ -828,7 +846,7 @@ export class CoreSite { * @param {string} key Key to search. * @return {Promise} Promise resolved when the cache entries are invalidated. */ - invalidateWsCacheForKey(key: string) : Promise { + invalidateWsCacheForKey(key: string): Promise { if (!this.db) { return Promise.reject(null); } @@ -837,7 +855,8 @@ export class CoreSite { } this.logger.debug('Invalidate cache for key: ' + key); - return this.db.updateRecords(this.WS_CACHE_TABLE, {expirationTime: 0}, {key: key}); + + return this.db.updateRecords(this.WS_CACHE_TABLE, { expirationTime: 0 }, { key: key }); } /** @@ -846,7 +865,7 @@ export class CoreSite { * @param {string[]} keys Keys to search. * @return {Promise} Promise resolved when the cache entries are invalidated. */ - invalidateMultipleWsCacheForKey(keys: string[]) : Promise { + invalidateMultipleWsCacheForKey(keys: string[]): Promise { if (!this.db) { return Promise.reject(null); } @@ -854,7 +873,7 @@ export class CoreSite { return Promise.resolve(); } - let promises = []; + const promises = []; this.logger.debug('Invalidating multiple cache keys'); keys.forEach((key) => { @@ -870,7 +889,7 @@ export class CoreSite { * @param {string} key Key to search. * @return {Promise} Promise resolved when the cache entries are invalidated. */ - invalidateWsCacheForKeyStartingWith(key: string) : Promise { + invalidateWsCacheForKeyStartingWith(key: string): Promise { if (!this.db) { return Promise.reject(null); } @@ -879,8 +898,10 @@ export class CoreSite { } this.logger.debug('Invalidate cache for key starting with: ' + key); - let sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?'; - return this.db.execute(sql, [key + "%"]); + + const sql = 'UPDATE ' + this.WS_CACHE_TABLE + ' SET expirationTime=0 WHERE key LIKE ?'; + + return this.db.execute(sql, [key + '%']); } /** @@ -890,7 +911,7 @@ export class CoreSite { * @param {string} url The url to be fixed. * @return {string} Fixed URL. */ - fixPluginfileURL(url: string) : string { + fixPluginfileURL(url: string): string { return this.urlUtils.fixPluginfileURL(url, this.token); } @@ -899,7 +920,7 @@ export class CoreSite { * * @return {Promise} Promise to be resolved when the DB is deleted. */ - deleteDB() : Promise { + deleteDB(): Promise { return this.dbProvider.deleteDB('Site-' + this.id); } @@ -908,9 +929,10 @@ export class CoreSite { * * @return {Promise} Promise to be resolved when the DB is deleted. */ - deleteFolder() : Promise { + deleteFolder(): Promise { if (this.fileProvider.isAvailable()) { const siteFolder = this.fileProvider.getSiteFolder(this.id); + return this.fileProvider.removeDir(siteFolder).catch(() => { // Ignore any errors, $mmFS.removeDir fails if folder doesn't exists. }); @@ -924,9 +946,10 @@ export class CoreSite { * * @return {Promise} Promise resolved with the site space usage (size). */ - getSpaceUsage() : Promise { + getSpaceUsage(): Promise { if (this.fileProvider.isAvailable()) { const siteFolderPath = this.fileProvider.getSiteFolder(this.id); + return this.fileProvider.getDirectorySize(siteFolderPath).catch(() => { return 0; }); @@ -941,8 +964,9 @@ export class CoreSite { * @param {string} [page] Docs page to go to. * @return {Promise} Promise resolved with the Moodle docs URL. */ - getDocsUrl(page?: string) : Promise { + getDocsUrl(page?: string): Promise { const release = this.infos.release ? this.infos.release : undefined; + return this.urlUtils.getDocsUrl(release, page); } @@ -952,27 +976,29 @@ export class CoreSite { * @param {boolean} [retrying] True if we're retrying the check. * @return {Promise} Promise resolved when the check is done. */ - checkLocalMobilePlugin(retrying?: boolean) : Promise { + 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 Promise.resolve({ code: 0 }); } - let observable = this.http.post(checkUrl, {service: service}).timeout(CoreConstants.WS_TIMEOUT); + const observable = this.http.post(checkUrl, { service: service }).timeout(CoreConstants.WS_TIMEOUT); + return this.utils.observableToPromise(observable).then((data: any) => { if (typeof data != 'undefined' && data.errorcode === 'requirecorrectaccess') { if (!retrying) { this.siteUrl = this.urlUtils.addOrRemoveWWW(this.siteUrl); + return this.checkLocalMobilePlugin(true); } else { return Promise.reject(data.error); } - } else if (typeof data == 'undefined' || typeof data.code == 'undefined') { - // local_mobile returned something we didn't expect. Let's assume it's not installed. - return {code: 0, warning: 'core.login.localmobileunexpectedresponse'}; + } 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); @@ -986,7 +1012,7 @@ export class CoreSite { return Promise.reject(this.translate.instant('core.login.webservicesnotenabled')); case 3: // Extended service not enabled, but the official is enabled. - return {code: 0}; + return { code: 0 }; case 4: // Neither extended or official services enabled. return Promise.reject(this.translate.instant('core.login.mobileservicesnotenabled')); @@ -994,10 +1020,10 @@ export class CoreSite { return Promise.reject(this.translate.instant('core.unexpectederror')); } } else { - return {code: code, service: service, coresupported: !!data.coresupported}; + return { code: code, service: service, coresupported: !!data.coresupported }; } }, () => { - return {code: 0}; + return { code: 0 }; }); } @@ -1006,7 +1032,7 @@ export class CoreSite { * * @return {boolean} Whether the App is able to use local_mobile plugin for this site. */ - checkIfAppUsesLocalMobile() : boolean { + checkIfAppUsesLocalMobile(): boolean { let appUsesLocalMobile = false; if (!this.infos || !this.infos.functions) { @@ -1027,7 +1053,7 @@ export class CoreSite { * * @return {Promise} Promise resolved it local_mobile was added, rejected otherwise. */ - checkIfLocalMobileInstalledAndNotUsed() : Promise { + checkIfLocalMobileInstalledAndNotUsed(): Promise { const appUsesLocalMobile = this.checkIfAppUsesLocalMobile(); if (appUsesLocalMobile) { @@ -1035,11 +1061,12 @@ export class CoreSite { return Promise.reject(null); } - return this.checkLocalMobilePlugin().then((data: LocalMobileResponse) : any => { + return this.checkLocalMobilePlugin().then((data: LocalMobileResponse): any => { if (typeof data.service == 'undefined') { - // local_mobile NOT installed. Reject. + // The local_mobile NOT installed. Reject. return Promise.reject(null); } + return data; }); } @@ -1050,13 +1077,14 @@ export class CoreSite { * @param {string} url URL to check. * @return {boolean} Whether the URL belongs to this site. */ - containsUrl(url: string) : boolean { + containsUrl(url: string): boolean { if (!url) { return false; } const siteUrl = this.urlUtils.removeProtocolAndWWW(this.siteUrl); url = this.urlUtils.removeProtocolAndWWW(url); + return url.indexOf(siteUrl) == 0; } @@ -1065,12 +1093,13 @@ export class CoreSite { * * @return {Promise} Promise resolved with public config. Rejected with an object if error, see CoreWSProvider.callAjax. */ - getPublicConfig() : Promise { - return this.wsProvider.callAjax('tool_mobile_get_public_config', {}, {siteUrl: this.siteUrl}).then((config) => { + getPublicConfig(): Promise { + return this.wsProvider.callAjax('tool_mobile_get_public_config', {}, { siteUrl: this.siteUrl }).then((config) => { // Use the wwwroot returned by the server. if (config.httpswwwroot) { this.siteUrl = config.httpswwwroot; } + return config; }); } @@ -1082,7 +1111,7 @@ export class CoreSite { * @param {string} [alertMessage] If defined, an alert will be shown before opening the browser. * @return {Promise} Promise resolved when done, rejected otherwise. */ - openInBrowserWithAutoLogin(url: string, alertMessage?: string) : Promise { + openInBrowserWithAutoLogin(url: string, alertMessage?: string): Promise { return this.openWithAutoLogin(false, url, undefined, alertMessage); } @@ -1093,7 +1122,7 @@ export class CoreSite { * @param {string} [alertMessage] If defined, an alert will be shown before opening the browser. * @return {Promise} Promise resolved when done, rejected otherwise. */ - openInBrowserWithAutoLoginIfSameSite(url: string, alertMessage?: string) : Promise { + openInBrowserWithAutoLoginIfSameSite(url: string, alertMessage?: string): Promise { return this.openWithAutoLoginIfSameSite(false, url, undefined, alertMessage); } @@ -1105,7 +1134,7 @@ export class CoreSite { * @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser. * @return {Promise} Promise resolved when done. */ - openInAppWithAutoLogin(url: string, options?: any, alertMessage?: string) : Promise { + openInAppWithAutoLogin(url: string, options?: any, alertMessage?: string): Promise { return this.openWithAutoLogin(true, url, options, alertMessage); } @@ -1117,7 +1146,7 @@ export class CoreSite { * @param {string} [alertMessage] If defined, an alert will be shown before opening the inappbrowser. * @return {Promise} Promise resolved when done. */ - openInAppWithAutoLoginIfSameSite(url: string, options?: any, alertMessage?: string) : Promise { + openInAppWithAutoLoginIfSameSite(url: string, options?: any, alertMessage?: string): Promise { return this.openWithAutoLoginIfSameSite(true, url, options, alertMessage); } @@ -1130,16 +1159,16 @@ export class CoreSite { * @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser. * @return {Promise} Promise resolved when done. Resolve param is returned only if inApp=true. */ - openWithAutoLogin(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise { + openWithAutoLogin(inApp: boolean, url: string, options?: any, alertMessage?: string): Promise { // Convenience function to open the URL. - let open = (url) => { - return new Promise((resolve, reject) => { + const open = (url): Promise => { + return new Promise((resolve, reject): void => { if (modal) { modal.dismiss(); } if (alertMessage) { - let alert = this.domUtils.showAlert(this.translate.instant('core.notice'), alertMessage, undefined, 3000); + const alert = this.domUtils.showAlert(this.translate.instant('core.notice'), alertMessage, undefined, 3000); alert.onDidDismiss(() => { if (inApp) { resolve(this.utils.openInApp(url, options)); @@ -1157,8 +1186,8 @@ export class CoreSite { }); }; - if (!this.privateToken || !this.wsAvailable('tool_mobile_get_autologin_key') || - (this.lastAutoLogin && this.timeUtils.timestamp() - this.lastAutoLogin < 6 * CoreConstants.SECONDS_MINUTE)) { + if (!this.privateToken || !this.wsAvailable('tool_mobile_get_autologin_key') || + (this.lastAutoLogin && this.timeUtils.timestamp() - this.lastAutoLogin < CoreConstants.SECONDS_MINUTE * 6)) { // No private token, WS not available or last auto-login was less than 6 minutes ago. // Open the final URL without auto-login. return Promise.resolve(open(url)); @@ -1172,7 +1201,7 @@ export class CoreSite { // Use write to not use cache. return this.write('tool_mobile_get_autologin_key', params).then((data) => { - if (!data.autologinurl || !data.key) { + if (!data.autologinurl || !data.key) { // Not valid data, open the final URL without auto-login. return open(url); } @@ -1195,7 +1224,7 @@ export class CoreSite { * @param {string} [alertMessage] If defined, an alert will be shown before opening the browser/inappbrowser. * @return {Promise} Promise resolved when done. Resolve param is returned only if inApp=true. */ - openWithAutoLoginIfSameSite(inApp: boolean, url: string, options?: any, alertMessage?: string) : Promise { + openWithAutoLoginIfSameSite(inApp: boolean, url: string, options?: any, alertMessage?: string): Promise { if (this.containsUrl(url)) { return this.openWithAutoLogin(inApp, url, options, alertMessage); } else { @@ -1204,6 +1233,7 @@ export class CoreSite { } else { this.utils.openInBrowser(url); } + return Promise.resolve(null); } } @@ -1216,10 +1246,10 @@ export class CoreSite { * @param {boolean} [ignoreCache] True if it should ignore cached data. * @return {Promise} Promise resolved with site config. */ - getConfig(name?: string, ignoreCache?: boolean) { - let preSets: CoreSiteWSPreSets = { + getConfig(name?: string, ignoreCache?: boolean): Promise { + const preSets: CoreSiteWSPreSets = { cacheKey: this.getConfigCacheKey() - } + }; if (ignoreCache) { preSets.getFromCache = false; @@ -1229,18 +1259,20 @@ export class CoreSite { return this.read('tool_mobile_get_config', {}, preSets).then((config) => { if (name) { // Return the requested setting. - for (let x in config.settings) { + for (const x in config.settings) { if (config.settings[x].name == name) { return config.settings[x].value; } } + return Promise.reject(null); } else { // Return all settings in the same array. - let settings = {}; + const settings = {}; config.settings.forEach((setting) => { settings[setting.name] = setting.value; }); + return settings; } }); @@ -1251,7 +1283,7 @@ export class CoreSite { * * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateConfig() : Promise { + invalidateConfig(): Promise { return this.invalidateWsCacheForKey(this.getConfigCacheKey()); } @@ -1260,7 +1292,7 @@ export class CoreSite { * * @return {string} Cache key. */ - protected getConfigCacheKey() : string { + protected getConfigCacheKey(): string { return 'tool_mobile_get_config'; } @@ -1270,7 +1302,7 @@ export class CoreSite { * @param {string} [name] Name of the setting to get. If not set, all settings will be returned. * @return {any} Site config or a specific setting. */ - getStoredConfig(name?: string) : any { + getStoredConfig(name?: string): any { if (!this.config) { return; } @@ -1288,13 +1320,14 @@ export class CoreSite { * @param {string} name Name of the feature to check. * @return {boolean} Whether it's disabled. */ - isFeatureDisabled(name: string) : boolean { + isFeatureDisabled(name: string): boolean { const disabledFeatures = this.getStoredConfig('tool_mobile_disabledfeatures'); if (!disabledFeatures) { return false; } const regEx = new RegExp('(,|^)' + this.textUtils.escapeForRegex(name) + '(,|$)', 'g'); + return !!disabledFeatures.match(regEx); } @@ -1319,7 +1352,7 @@ export class CoreSite { * This function only accepts versions from 2.4.0 and above. If any of the versions supplied isn't found, it will assume * it's the last released major version. */ - isVersionGreaterEqualThan(versions: string | string[]) : boolean { + isVersionGreaterEqualThan(versions: string | string[]): boolean { const siteVersion = parseInt(this.getInfo().version, 10); if (Array.isArray(versions)) { @@ -1328,7 +1361,7 @@ export class CoreSite { } for (let i = 0; i < versions.length; i++) { - let versionNumber = this.getVersionNumber(versions[i]); + const versionNumber = this.getVersionNumber(versions[i]); if (i == versions.length - 1) { // It's the last version, check only if site version is greater than this one. return siteVersion >= versionNumber; @@ -1354,8 +1387,8 @@ export class CoreSite { * @param {string} version Release version to convert to version number. * @return {number} Version number, 0 if invalid. */ - protected getVersionNumber(version: string) : number { - let data = this.getMajorAndMinor(version); + protected getVersionNumber(version: string): number { + const data = this.getMajorAndMinor(version); if (!data) { // Invalid version. @@ -1376,17 +1409,17 @@ export class CoreSite { * @param {string} version Release version (e.g. '3.1.0'). * @return {object} Object with major and minor. Returns false if invalid version. */ - protected getMajorAndMinor(version) : any { + protected getMajorAndMinor(version: string): any { const match = version.match(/(\d)+(?:\.(\d)+)?(?:\.(\d)+)?/); - if (!match || !match[1]) { + if (!match || !match[1]) { // Invalid version. return false; } return { - major: match[1] + '.' + (match[2] || '0'), - minor: parseInt(match[3] || 0, 10) - } + major: match[1] + '.' + (match[2] || '0'), + minor: parseInt(match[3], 10) || 0 + }; } /** @@ -1395,10 +1428,10 @@ export class CoreSite { * @param {string} version Release version (e.g. '3.1.0'). * @return {number} Next major version number. */ - protected getNextMajorVersionNumber(version: string) : number { - let data = this.getMajorAndMinor(version), - position, + protected getNextMajorVersionNumber(version: string): number { + const data = this.getMajorAndMinor(version), releases = Object.keys(this.moodleReleases); + let position; if (!data) { // Invalid version. @@ -1407,7 +1440,7 @@ export class CoreSite { position = releases.indexOf(data.major); - if (position == -1 || position == releases.length -1) { + if (position == -1 || position == releases.length - 1) { // Major version not found or it's the last one. Use the last one. return this.moodleReleases[releases[position]]; } diff --git a/src/classes/sqlitedb.ts b/src/classes/sqlitedb.ts index 3c6c248d5..a4a0b160a 100644 --- a/src/classes/sqlitedb.ts +++ b/src/classes/sqlitedb.ts @@ -63,14 +63,14 @@ export class SQLiteDB { * @return SQL query. */ buildCreateTableSql(name: string, columns: any[], primaryKeys?: string[], uniqueKeys?: string[][], foreignKeys?: any[], - tableCheck?: string) : string { - let sql = `CREATE TABLE IF NOT EXISTS ${name} (`, - columnsSql = []; + tableCheck?: string): string { + const columnsSql = []; + let sql = `CREATE TABLE IF NOT EXISTS ${name} (`; // First define all the columns. - for (let index in columns) { - let column = columns[index], - columnSql: string = column.name || ''; + for (const index in columns) { + const column = columns[index]; + let columnSql: string = column.name || ''; if (column.type) { columnSql += ' ' + column.type; @@ -110,8 +110,8 @@ export class SQLiteDB { } if (uniqueKeys && uniqueKeys.length) { - for (let index in uniqueKeys) { - let setOfKeys = uniqueKeys[index]; + for (const index in uniqueKeys) { + const setOfKeys = uniqueKeys[index]; if (setOfKeys && setOfKeys.length) { sql += `, UNIQUE (${setOfKeys.join(', ')})`; } @@ -122,9 +122,8 @@ export class SQLiteDB { sql += `, CHECK (${tableCheck})`; } - - for (let index in foreignKeys) { - let foreignKey = foreignKeys[index]; + for (const index in foreignKeys) { + const foreignKey = foreignKeys[index]; if (!foreignKey.columns || !!foreignKey.columns.length) { return; @@ -146,8 +145,10 @@ export class SQLiteDB { /** * Close the database. + * + * @return {Promise} Promise resolved when done. */ - close() { + close(): Promise { return this.ready().then(() => { return this.db.close(); }); @@ -160,8 +161,9 @@ export class SQLiteDB { * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. * @return {Promise} Promise resolved with the count of records returned from the specified criteria. */ - countRecords(table: string, conditions?: object) : Promise { - let selectAndParams = this.whereClause(conditions); + countRecords(table: string, conditions?: object): Promise { + const selectAndParams = this.whereClause(conditions); + return this.countRecordsSelect(table, selectAndParams[0], selectAndParams[1]); } @@ -174,10 +176,11 @@ export class SQLiteDB { * @param {string} [countItem] The count string to be used in the SQL call. Default is COUNT('x'). * @return {Promise} Promise resolved with the count of records returned from the specified criteria. */ - countRecordsSelect(table: string, select='', params?: any, countItem="COUNT('x')") : Promise { + countRecordsSelect(table: string, select: string = '', params?: any, countItem: string = 'COUNT(\'x\')'): Promise { if (select) { select = 'WHERE ' + select; } + return this.countRecordsSql(`SELECT ${countItem} FROM ${table} ${select}`, params); } @@ -190,11 +193,12 @@ export class SQLiteDB { * @param {any} [params] An array of sql parameters. * @return {Promise} Promise resolved with the count. */ - countRecordsSql(sql: string, params?: any) : Promise { + countRecordsSql(sql: string, params?: any): Promise { return this.getFieldSql(sql, params).then((count) => { - if (typeof count != 'number' || count < 0) { + if (typeof count != 'number' || count < 0) { return 0; } + return count; }); } @@ -223,8 +227,9 @@ export class SQLiteDB { * @return {Promise} Promise resolved when success. */ createTable(name: string, columns: any[], primaryKeys?: string[], uniqueKeys?: string[][], foreignKeys?: any[], - tableCheck?: string) : Promise { - let sql = this.buildCreateTableSql(name, columns, primaryKeys, uniqueKeys, foreignKeys, tableCheck); + tableCheck?: string): Promise { + const sql = this.buildCreateTableSql(name, columns, primaryKeys, uniqueKeys, foreignKeys, tableCheck); + return this.execute(sql); } @@ -234,9 +239,9 @@ export class SQLiteDB { * @param {any} table Table schema. * @return {Promise} Promise resolved when success. */ - createTableFromSchema(table: any) : Promise { + createTableFromSchema(table: any): Promise { return this.createTable(table.name, table.columns, table.primaryKeys, table.uniqueKeys, - table.foreignKeys, table.tableCheck); + table.foreignKeys, table.tableCheck); } /** @@ -245,11 +250,12 @@ export class SQLiteDB { * @param {any[]} tables List of table schema. * @return {Promise} Promise resolved when success. */ - createTablesFromSchema(tables: any[]) : Promise { - let promises = []; + createTablesFromSchema(tables: any[]): Promise { + const promises = []; tables.forEach((table) => { promises.push(this.createTableFromSchema(table)); }); + return Promise.all(promises); } @@ -261,13 +267,14 @@ export class SQLiteDB { * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. * @return {Promise} Promise resolved when done. */ - deleteRecords(table: string, conditions?: object) : Promise { + deleteRecords(table: string, conditions?: object): Promise { if (conditions === null || typeof conditions == 'undefined') { // No conditions, delete the whole table. return this.execute(`DELETE FROM TABLE ${table}`); } - let selectAndParams = this.whereClause(conditions); + const selectAndParams = this.whereClause(conditions); + return this.deleteRecordsSelect(table, selectAndParams[0], selectAndParams[1]); } @@ -279,8 +286,9 @@ export class SQLiteDB { * @param {any[]} values The values field might take. * @return {Promise} Promise resolved when done. */ - deleteRecordsList(table: string, field: string, values: any[]) : Promise { - let selectAndParams = this.whereClauseList(field, values); + deleteRecordsList(table: string, field: string, values: any[]): Promise { + const selectAndParams = this.whereClauseList(field, values); + return this.deleteRecordsSelect(table, selectAndParams[0], selectAndParams[1]); } @@ -292,7 +300,7 @@ export class SQLiteDB { * @param {any[]} [params] Array of sql parameters. * @return {Promise} Promise resolved when done. */ - deleteRecordsSelect(table: string, select='', params?: any[]) : Promise { + deleteRecordsSelect(table: string, select: string = '', params?: any[]): Promise { if (select) { select = 'WHERE ' + select; } @@ -309,7 +317,7 @@ export class SQLiteDB { * @param {any[]} params Query parameters. * @return {Promise} Promise resolved with the result. */ - execute(sql: string, params?: any[]) : Promise { + execute(sql: string, params?: any[]): Promise { return this.ready().then(() => { return this.db.executeSql(sql, params); }); @@ -323,7 +331,7 @@ export class SQLiteDB { * @param {any[]} sqlStatements SQL statements to execute. * @return {Promise} Promise resolved with the result. */ - executeBatch(sqlStatements: any[]) : Promise { + executeBatch(sqlStatements: any[]): Promise { return this.ready().then(() => { return this.db.sqlBatch(sqlStatements); }); @@ -334,10 +342,10 @@ export class SQLiteDB { * * @param {object} data Data to insert. */ - protected formatDataToInsert(data: object) : void { + protected formatDataToInsert(data: object): void { // Remove undefined entries and convert null to "NULL". - for (let name in data) { - let value = data[name]; + for (const name in data) { + const value = data[name]; if (typeof value == 'undefined') { delete data[name]; } @@ -350,7 +358,7 @@ export class SQLiteDB { * @param {string} table The table to query. * @return {Promise} Promise resolved with the records. */ - getAllRecords(table: string) : Promise { + getAllRecords(table: string): Promise { return this.getRecords(table); } @@ -362,8 +370,9 @@ export class SQLiteDB { * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. * @return {Promise} Promise resolved with the field's value. */ - getField(table: string, field: string, conditions?: object) : Promise { - let selectAndParams = this.whereClause(conditions); + getField(table: string, field: string, conditions?: object): Promise { + const selectAndParams = this.whereClause(conditions); + return this.getFieldSelect(table, field, selectAndParams[0], selectAndParams[1]); } @@ -376,7 +385,7 @@ export class SQLiteDB { * @param {any[]} [params] Array of sql parameters. * @return {Promise} Promise resolved with the field's value. */ - getFieldSelect(table: string, field: string, select='', params?: any[]) : Promise { + getFieldSelect(table: string, field: string, select: string = '', params?: any[]): Promise { if (select) { select = 'WHERE ' + select; } @@ -391,7 +400,7 @@ export class SQLiteDB { * @param {any[]} [params] An array of sql parameters. * @return {Promise} Promise resolved with the field's value. */ - getFieldSql(sql: string, params?: any[]) : Promise { + getFieldSql(sql: string, params?: any[]): Promise { return this.getRecordSql(sql, params).then((record) => { if (!record) { return Promise.reject(null); @@ -411,7 +420,7 @@ export class SQLiteDB { * meaning return empty. Other values will become part of the returned SQL fragment. * @return {any[]} A list containing the constructed sql fragment and an array of parameters. */ - getInOrEqual(items: any, equal=true, onEmptyItems?: any) : any[] { + getInOrEqual(items: any, equal: boolean = true, onEmptyItems?: any): any[] { let sql, params; @@ -428,6 +437,7 @@ export class SQLiteDB { if (Array.isArray(items) && !items.length) { if (onEmptyItems === null) { // Special case, NULL value. sql = equal ? ' IS NULL' : ' IS NOT NULL'; + return [sql, []]; } else { items = [onEmptyItems]; // Rest of cases, prepare items for processing. @@ -438,7 +448,7 @@ export class SQLiteDB { sql = equal ? '= ?' : '<> ?'; params = Array.isArray(items) ? items : [items]; } else { - sql = (equal ? '' : 'NOT ') + 'IN (' + ',?'.repeat(items.length).substr(1) + ')'; + sql = (equal ? '' : 'NOT ') + 'IN (' + ',?'.repeat(items.length).substr(1) + ')'; params = items; } @@ -450,7 +460,7 @@ export class SQLiteDB { * * @return {string} Database name. */ - getName() : string { + getName(): string { return this.name; } @@ -462,8 +472,9 @@ export class SQLiteDB { * @param {string} [fields='*'] A comma separated list of fields to return. * @return {Promise} Promise resolved with the record, rejected if not found. */ - getRecord(table: string, conditions?: object, fields='*') : Promise { - let selectAndParams = this.whereClause(conditions); + getRecord(table: string, conditions?: object, fields: string = '*'): Promise { + const selectAndParams = this.whereClause(conditions); + return this.getRecordSelect(table, selectAndParams[0], selectAndParams[1], fields); } @@ -476,7 +487,7 @@ export class SQLiteDB { * @param {string} [fields='*'] A comma separated list of fields to return. * @return {Promise} Promise resolved with the record, rejected if not found. */ - getRecordSelect(table: string, select='', params=[], fields='*') : Promise { + getRecordSelect(table: string, select: string = '', params: any[] = [], fields: string = '*'): Promise { if (select) { select = ' WHERE ' + select; } @@ -494,7 +505,7 @@ export class SQLiteDB { * @param {any[]} [params] List of sql parameters * @return {Promise} Promise resolved with the records. */ - getRecordSql(sql: string, params?: any[]) : Promise { + getRecordSql(sql: string, params?: any[]): Promise { return this.getRecordsSql(sql, params, 0, 1).then((result) => { if (!result || !result.length) { // Not found, reject. @@ -517,8 +528,10 @@ export class SQLiteDB { * @param {number} [limitNum=0] Return a subset comprising this many records in total. * @return {Promise} Promise resolved with the records. */ - getRecords(table: string, conditions?: object, sort='', fields='*', limitFrom=0, limitNum=0) : Promise { - let selectAndParams = this.whereClause(conditions); + getRecords(table: string, conditions?: object, sort: string = '', fields: string = '*', limitFrom: number = 0, + limitNum: number = 0): Promise { + const selectAndParams = this.whereClause(conditions); + return this.getRecordsSelect(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum); } @@ -534,8 +547,10 @@ export class SQLiteDB { * @param {number} [limitNum=0] Return a subset comprising this many records in total. * @return {Promise} Promise resolved with the records. */ - getRecordsList(table: string, field: string, values: any[], sort='', fields='*', limitFrom=0, limitNum=0) : Promise { - let selectAndParams = this.whereClauseList(field, values); + getRecordsList(table: string, field: string, values: any[], sort: string = '', fields: string = '*', limitFrom: number = 0, + limitNum: number = 0): Promise { + const selectAndParams = this.whereClauseList(field, values); + return this.getRecordsSelect(table, selectAndParams[0], selectAndParams[1], sort, fields, limitFrom, limitNum); } @@ -551,7 +566,8 @@ export class SQLiteDB { * @param {number} [limitNum=0] Return a subset comprising this many records in total. * @return {Promise} Promise resolved with the records. */ - getRecordsSelect(table: string, select='', params=[], sort='', fields='*', limitFrom=0, limitNum=0) : Promise { + getRecordsSelect(table: string, select: string = '', params: any[] = [], sort: string = '', fields: string = '*', + limitFrom: number = 0, limitNum: number = 0): Promise { if (select) { select = ' WHERE ' + select; } @@ -559,7 +575,8 @@ export class SQLiteDB { sort = ' ORDER BY ' + sort; } - let sql = `SELECT ${fields} FROM ${table} ${select} ${sort}`; + const sql = `SELECT ${fields} FROM ${table} ${select} ${sort}`; + return this.getRecordsSql(sql, params, limitFrom, limitNum); } @@ -572,8 +589,8 @@ export class SQLiteDB { * @param {number} [limitNum] Return a subset comprising this many records. * @return {Promise} Promise resolved with the records. */ - getRecordsSql(sql: string, params?: any[], limitFrom?: number, limitNum?: number) : Promise { - let limits = this.normaliseLimitFromNum(limitFrom, limitNum); + getRecordsSql(sql: string, params?: any[], limitFrom?: number, limitNum?: number): Promise { + const limits = this.normaliseLimitFromNum(limitFrom, limitNum); if (limits[0] || limits[1]) { if (limits[1] < 1) { @@ -584,10 +601,11 @@ export class SQLiteDB { return this.execute(sql, params).then((result) => { // Retrieve the records. - let records = []; + const records = []; for (let i = 0; i < result.rows.length; i++) { records.push(result.rows.item(i)); } + return records; }); } @@ -599,23 +617,23 @@ export class SQLiteDB { * @param {object} data A data object with values for one or more fields in the record. * @return {any[]} Array with the SQL query and the params. */ - protected getSqlInsertQuery(table: string, data: object) : any[] { + protected getSqlInsertQuery(table: string, data: object): any[] { this.formatDataToInsert(data); - let keys = Object.keys(data), + const keys = Object.keys(data), fields = keys.join(','), questionMarks = ',?'.repeat(keys.length).substr(1); return [ `INSERT INTO ${table} (${fields}) VALUES (${questionMarks})`, - keys.map(key => data[key]) + keys.map((key) => data[key]) ]; } /** * Initialize the database. */ - init() : void { + init(): void { this.promise = this.platform.ready().then(() => { return this.sqlite.create({ name: this.name, @@ -634,8 +652,8 @@ export class SQLiteDB { * @param {object} conditions The conditions to check if the record already exists. * @return {Promise} Promise resolved with done. */ - insertOrUpdateRecord(table: string, data: object, conditions: object) : Promise { - return this.getRecord(table, conditions || data).then(() => { + insertOrUpdateRecord(table: string, data: object, conditions: object): Promise { + return this.getRecord(table, conditions || data).then(() => { // It exists, update it. return this.updateRecords(table, data, conditions); }).catch(() => { @@ -651,8 +669,8 @@ export class SQLiteDB { * @param {object} data A data object with values for one or more fields in the record. * @return {Promise} Promise resolved with new rowId. Please notice this rowId is internal from SQLite. */ - insertRecord(table: string, data: object) : Promise { - let sqlAndParams = this.getSqlInsertQuery(table, data); + insertRecord(table: string, data: object): Promise { + const sqlAndParams = this.getSqlInsertQuery(table, data); return this.execute(sqlAndParams[0], sqlAndParams[1]).then((result) => { return result.insertId; @@ -666,12 +684,12 @@ export class SQLiteDB { * @param {object[]} dataObjects List of objects to be inserted. * @return {Promise} Promise resolved when done. */ - insertRecords(table: string, dataObjects: object[]) : Promise { + insertRecords(table: string, dataObjects: object[]): Promise { if (!Array.isArray(dataObjects)) { return Promise.reject(null); } - let statements = []; + const statements = []; dataObjects.forEach((dataObject) => { statements.push(this.getSqlInsertQuery(table, dataObject)); @@ -689,7 +707,7 @@ export class SQLiteDB { * @param {any} limitNum How many results to return. * @return {number[]} Normalised limit params in array: [limitFrom, limitNum]. */ - normaliseLimitFromNum(limitFrom: any, limitNum: any) : number[] { + normaliseLimitFromNum(limitFrom: any, limitNum: any): number[] { // We explicilty treat these cases as 0. if (typeof limitFrom == 'undefined' || limitFrom === null || limitFrom === '' || limitFrom === -1) { limitFrom = 0; @@ -699,17 +717,19 @@ export class SQLiteDB { } limitFrom = parseInt(limitFrom, 10); - limitNum = parseInt(limitNum, 10); + limitNum = parseInt(limitNum, 10); limitFrom = Math.max(0, limitFrom); - limitNum = Math.max(0, limitNum); + limitNum = Math.max(0, limitNum); return [limitFrom, limitNum]; } /** * Open the database. Only needed if it was closed before, a database is automatically opened when created. + * + * @return {Promise} Promise resolved when open. */ - open() { + open(): Promise { return this.ready().then(() => { return this.db.open(); }); @@ -720,7 +740,7 @@ export class SQLiteDB { * * @return {Promise} Promise resolved when ready. */ - ready() : Promise { + ready(): Promise { return this.promise; } @@ -731,7 +751,7 @@ export class SQLiteDB { * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. * @return {Promise} Promise resolved if exists, rejected otherwise. */ - recordExists(table: string, conditions?: object) : Promise { + recordExists(table: string, conditions?: object): Promise { return this.getRecord(table, conditions).then((record) => { if (!record) { return Promise.reject(null); @@ -747,7 +767,7 @@ export class SQLiteDB { * @param {any[]} [params] An array of sql parameters. * @return {Promise} Promise resolved if exists, rejected otherwise. */ - recordExistsSelect(table: string, select='', params=[]) : Promise { + recordExistsSelect(table: string, select: string = '', params: any[] = []): Promise { return this.getRecordSelect(table, select, params).then((record) => { if (!record) { return Promise.reject(null); @@ -762,7 +782,7 @@ export class SQLiteDB { * @param {any[]} [params] An array of sql parameters. * @return {Promise} Promise resolved if exists, rejected otherwise. */ - recordExistsSql(sql: string, params?: any[]) : Promise { + recordExistsSql(sql: string, params?: any[]): Promise { return this.getRecordSql(sql, params).then((record) => { if (!record) { return Promise.reject(null); @@ -778,27 +798,27 @@ export class SQLiteDB { * @param {any} [conditions] The conditions to build the where clause. Must not contain numeric indexes. * @return {Promise} Promise resolved when updated. */ - updateRecords(table: string, data: any, conditions?: any) : Promise { + updateRecords(table: string, data: any, conditions?: any): Promise { - if (!data || !Object.keys(data).length) { + if (!data || !Object.keys(data).length) { // No fields to update, consider it's done. return Promise.resolve(); } - let whereAndParams = this.whereClause(conditions), - sets = [], - sql, + const whereAndParams = this.whereClause(conditions), + sets = []; + let sql, params; this.formatDataToInsert(data); - for (let key in data) { + for (const key in data) { sets.push(`${key} = ?`); } sql = `UPDATE ${table} SET ${sets.join(', ')} WHERE ${whereAndParams[0]}`; // Create the list of params using the "data" object and the params for the where clause. - params = Object.keys(data).map(key => data[key]).concat(whereAndParams[1]); + params = Object.keys(data).map((key) => data[key]).concat(whereAndParams[1]); return this.execute(sql, params); } @@ -812,17 +832,17 @@ export class SQLiteDB { * @param {any[]} [whereParams] Params for the where clause. * @return {Promise} Promise resolved when updated. */ - updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]) : Promise { - if (!data || !Object.keys(data).length) { + updateRecordsWhere(table: string, data: any, where?: string, whereParams?: any[]): Promise { + if (!data || !Object.keys(data).length) { // No fields to update, consider it's done. return Promise.resolve(); } - let sets = [], - sql, + const sets = []; + let sql, params; - for (let key in data) { + for (const key in data) { sets.push(`${key} = ?`); } @@ -832,7 +852,7 @@ export class SQLiteDB { } // Create the list of params using the "data" object and the params for the where clause. - params = Object.keys(data).map(key => data[key]); + params = Object.keys(data).map((key) => data[key]); if (where && whereParams) { params = params.concat(whereParams[1]); } @@ -846,16 +866,16 @@ export class SQLiteDB { * @param {object} [conditions] The conditions to build the where clause. Must not contain numeric indexes. * @return {any[]} An array list containing sql 'where' part and 'params'. */ - whereClause(conditions={}) : any[] { + whereClause(conditions: any = {}): any[] { if (!conditions || !Object.keys(conditions).length) { return ['', []]; } - let where = [], + const where = [], params = []; - for (let key in conditions) { - let value = conditions[key]; + for (const key in conditions) { + const value = conditions[key]; if (typeof value == 'undefined' || value === null) { where.push(key + ' IS NULL'); @@ -875,13 +895,13 @@ export class SQLiteDB { * @param {any[]} values The values field might take. * @return {any[]} An array containing sql 'where' part and 'params'. */ - whereClauseList(field: string, values: any[]) : any[] { + whereClauseList(field: string, values: any[]): any[] { if (!values || !values.length) { - return ["1 = 2", []]; // Fake condition, won't return rows ever. + return ['1 = 2', []]; // Fake condition, won't return rows ever. } - let params = [], - select = ''; + const params = []; + let select = ''; values.forEach((value) => { if (typeof value == 'boolean') { @@ -903,7 +923,7 @@ export class SQLiteDB { if (params.length == 1) { select = select + field + ' = ?'; } else { - let questionMarks = ',?'.repeat(params.length).substr(1); + const questionMarks = ',?'.repeat(params.length).substr(1); select = select + field + ' IN (' + questionMarks + ')'; } } diff --git a/src/components/chrono/chrono.ts b/src/components/chrono/chrono.ts index ee0d421bf..885d62565 100644 --- a/src/components/chrono/chrono.ts +++ b/src/components/chrono/chrono.ts @@ -32,12 +32,12 @@ import { Component, Input, OnChanges, OnDestroy, Output, EventEmitter, SimpleCha }) export class CoreChronoComponent implements OnChanges, OnDestroy { @Input() running: boolean; // Set it to true to start the chrono. Set it to false to stop it. - @Input() startTime?: number = 0; // Number of milliseconds to put in the chrono before starting. + @Input() startTime? = 0; // Number of milliseconds to put in the chrono before starting. @Input() endTime?: number; // Number of milliseconds to stop the chrono. @Input() reset?: boolean; // Set it to true to reset the chrono. @Output() onEnd?: EventEmitter; // Will emit an event when the endTime is reached. - time: number = 0; + time = 0; protected interval; constructor(private cdr: ChangeDetectorRef) { @@ -47,14 +47,14 @@ export class CoreChronoComponent implements OnChanges, OnDestroy { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.time = this.startTime || 0; } /** * Component being initialized. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (changes && changes.running) { if (changes.running.currentValue) { this.start(); @@ -70,7 +70,7 @@ export class CoreChronoComponent implements OnChanges, OnDestroy { /** * Reset the chrono, stopping it and setting it to startTime. */ - protected resetChrono() : void { + protected resetChrono(): void { this.stop(); this.time = this.startTime || 0; } @@ -78,7 +78,7 @@ export class CoreChronoComponent implements OnChanges, OnDestroy { /** * Start the chrono if it isn't running. */ - protected start() : void { + protected start(): void { if (this.interval) { // Already setup. return; @@ -105,12 +105,12 @@ export class CoreChronoComponent implements OnChanges, OnDestroy { /** * Stop the chrono, leaving the same time it has. */ - protected stop() : void { + protected stop(): void { clearInterval(this.interval); delete this.interval; } - ngOnDestroy() { + ngOnDestroy(): void { this.stop(); } } diff --git a/src/components/context-menu/context-menu-item.ts b/src/components/context-menu/context-menu-item.ts index 7527c3953..6700a1521 100644 --- a/src/components/context-menu/context-menu-item.ts +++ b/src/components/context-menu/context-menu-item.ts @@ -15,7 +15,6 @@ import { Component, Input, Output, OnInit, OnDestroy, EventEmitter, OnChanges, SimpleChange } from '@angular/core'; import { CoreContextMenuComponent } from './context-menu'; - /** * This directive adds a item to the Context Menu popover. * @@ -36,15 +35,16 @@ import { CoreContextMenuComponent } from './context-menu'; export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChanges { @Input() content: string; // Content of the item. @Input() iconDescription?: string; // Name of the icon to be shown on the left side of the item. - @Input() iconAction?: string; // Name of the icon to be shown on the right side of the item. It represents the action to do on - // click. If is "spinner" an spinner will be shown. If no icon or spinner is selected, no action - // or link will work. If href but no iconAction is provided arrow-right will be used. + @Input() iconAction?: string; // Name of the icon to show on the right side of the item. Represents the action to do on click. + // If is "spinner" an spinner will be shown. + // If no icon or spinner is selected, no action or link will work. + // If href but no iconAction is provided arrow-right will be used. @Input() ariaDescription?: string; // Aria label to add to iconDescription. @Input() ariaAction?: string; // Aria label to add to iconAction. If not set, it will be equal to content. @Input() href?: string; // Link to go if no action provided. - @Input() captureLink?: boolean|string; // Whether the link needs to be captured by the app. + @Input() captureLink?: boolean | string; // Whether the link needs to be captured by the app. @Input() autoLogin?: string; // Whether the link needs to be opened using auto-login. - @Input() closeOnClick?: boolean|string = true; // Whether to close the popover when the item is clicked. + @Input() closeOnClick?: boolean | string = true; // Whether to close the popover when the item is clicked. @Input() priority?: number; // Used to sort items. The highest priority, the highest position. @Input() badge?: string; // A badge to show in the item. @Input() badgeClass?: number; // A class to set in the badge. @@ -61,7 +61,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { // Initialize values. this.priority = this.priority || 1; this.closeOnClick = this.getBooleanValue(this.closeOnClick, true); @@ -88,17 +88,18 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange * @param {boolean} defaultValue Value to use if undefined. * @return {boolean} Boolean value. */ - protected getBooleanValue(value: any, defaultValue: boolean) : boolean { + protected getBooleanValue(value: any, defaultValue: boolean): boolean { if (typeof value == 'undefined') { return defaultValue; } + return value && value !== 'false'; } /** * Component destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.destroyed = true; this.ctxtMenu.removeItem(this); } @@ -106,7 +107,7 @@ export class CoreContextMenuItemComponent implements OnInit, OnDestroy, OnChange /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (changes.hidden && !changes.hidden.firstChange) { this.ctxtMenu.itemsChanged(); } diff --git a/src/components/context-menu/context-menu-popover.ts b/src/components/context-menu/context-menu-popover.ts index 19ad6f0d2..0dfc26b31 100644 --- a/src/components/context-menu/context-menu-popover.ts +++ b/src/components/context-menu/context-menu-popover.ts @@ -38,7 +38,7 @@ export class CoreContextMenuPopoverComponent { /** * Close the popover. */ - closeMenu() : void { + closeMenu(): void { this.viewCtrl.dismiss(); } @@ -49,13 +49,14 @@ export class CoreContextMenuPopoverComponent { * @param {CoreContextMenuItemComponent} item Item clicked. * @return {boolean} Return true if success, false if error. */ - itemClicked(event: Event, item: CoreContextMenuItemComponent) : boolean { + itemClicked(event: Event, item: CoreContextMenuItemComponent): boolean { if (item.action.observers.length > 0) { event.preventDefault(); event.stopPropagation(); if (!item.iconAction) { this.logger.warn('Items with action must have an icon action to work', item); + return false; } else if (item.iconAction == 'spinner') { return false; diff --git a/src/components/context-menu/context-menu.ts b/src/components/context-menu/context-menu.ts index 43ce298cc..89857016b 100644 --- a/src/components/context-menu/context-menu.ts +++ b/src/components/context-menu/context-menu.ts @@ -43,15 +43,15 @@ export class CoreContextMenuComponent implements OnInit { this.hideMenu = !this.items.some((item) => { return !item.hidden; }); - }) + }); } /** * Component being initialized. */ - ngOnInit() { - this.icon = this.icon || 'more'; - this.ariaLabel = this.title || this.translate.instant('core.info'); + ngOnInit(): void { + this.icon = this.icon || 'more'; + this.ariaLabel = this.title || this.translate.instant('core.info'); } /** @@ -59,7 +59,7 @@ export class CoreContextMenuComponent implements OnInit { * * @param {CoreContextMenuItemComponent} item The item to add. */ - addItem(item: CoreContextMenuItemComponent) : void { + addItem(item: CoreContextMenuItemComponent): void { this.items.push(item); this.itemsChanged(); } @@ -67,7 +67,7 @@ export class CoreContextMenuComponent implements OnInit { /** * Function called when the items change. */ - itemsChanged() { + itemsChanged(): void { this.itemsChangedStream.next(); } @@ -76,8 +76,8 @@ export class CoreContextMenuComponent implements OnInit { * * @param {CoreContextMenuItemComponent} item The item to remove. */ - removeItem(item: CoreContextMenuItemComponent) : void { - let index = this.items.indexOf(item); + removeItem(item: CoreContextMenuItemComponent): void { + const index = this.items.indexOf(item); if (index >= 0) { this.items.splice(index, 1); } @@ -89,8 +89,8 @@ export class CoreContextMenuComponent implements OnInit { * * @param {MouseEvent} event Event. */ - showContextMenu(event: MouseEvent) : void { - let popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, {title: this.title, items: this.items}); + showContextMenu(event: MouseEvent): void { + const popover = this.popoverCtrl.create(CoreContextMenuPopoverComponent, { title: this.title, items: this.items }); popover.present({ ev: event }); diff --git a/src/components/course-picker-menu/course-picker-menu-popover.ts b/src/components/course-picker-menu/course-picker-menu-popover.ts index fec561f64..e9a6a7e56 100644 --- a/src/components/course-picker-menu/course-picker-menu-popover.ts +++ b/src/components/course-picker-menu/course-picker-menu-popover.ts @@ -38,8 +38,9 @@ export class CoreCoursePickerMenuPopoverComponent { * @param {any} course Course object clicked. * @return {boolean} Return true if success, false if error. */ - coursePicked(event: Event, course: any) : boolean { + coursePicked(event: Event, course: any): boolean { this.viewCtrl.dismiss(course); + return true; } } diff --git a/src/components/empty-box/empty-box.ts b/src/components/empty-box/empty-box.ts index 340445940..488458824 100644 --- a/src/components/empty-box/empty-box.ts +++ b/src/components/empty-box/empty-box.ts @@ -29,5 +29,7 @@ export class CoreEmptyBoxComponent { @Input() icon?: string; // Name of the icon to use. @Input() image?: string; // Image source. If an icon is provided, image won't be used. - constructor() {} + constructor() { + // Nothing to do. + } } diff --git a/src/components/file/file.ts b/src/components/file/file.ts index 4c33517d8..60825b2f8 100644 --- a/src/components/file/file.ts +++ b/src/components/file/file.ts @@ -35,12 +35,12 @@ import { CoreConstants } from '../../core/constants'; export class CoreFileComponent implements OnInit, OnDestroy { @Input() file: any; // The file. Must have a property 'filename' and a 'fileurl' or 'url' @Input() component?: string; // Component the file belongs to. - @Input() componentId?: string|number; // Component ID. + @Input() componentId?: string | number; // Component ID. @Input() timemodified?: number; // If set, the value will be used to check if the file is outdated. - @Input() canDelete?: boolean|string; // Whether file can be deleted. - @Input() alwaysDownload?: boolean|string; // Whether it should always display the refresh button when the file is downloaded. - // Use it for files that you cannot determine if they're outdated or not. - @Input() canDownload?: boolean|string = true; // Whether file can be downloaded. + @Input() canDelete?: boolean | string; // Whether file can be deleted. + @Input() alwaysDownload?: boolean | string; // Whether it should always display the refresh button when the file is downloaded. + // Use it for files that you cannot determine if they're outdated or not. + @Input() canDownload?: boolean | string = true; // Whether file can be downloaded. @Output() onDelete?: EventEmitter; // Will notify when the delete button is clicked. isDownloaded: boolean; @@ -64,7 +64,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.canDelete = this.utils.isTrueOrOne(this.canDelete); this.alwaysDownload = this.utils.isTrueOrOne(this.alwaysDownload); this.canDownload = this.utils.isTrueOrOne(this.canDownload); @@ -98,14 +98,14 @@ export class CoreFileComponent implements OnInit, OnDestroy { * * @return {Promise} Promise resolved when state has been calculated. */ - protected calculateState() : Promise { + protected calculateState(): Promise { return this.filepoolProvider.getFileStateByUrl(this.siteId, this.fileUrl, this.timemodified).then((state) => { - let canDownload = this.sitesProvider.getCurrentSite().canDownloadFiles(); + const canDownload = this.sitesProvider.getCurrentSite().canDownloadFiles(); - this.isDownloaded = state === CoreConstants.DOWNLOADED || state === CoreConstants.OUTDATED; + this.isDownloaded = state === CoreConstants.DOWNLOADED || state === CoreConstants.OUTDATED; this.isDownloading = canDownload && state === CoreConstants.DOWNLOADING; - this.showDownload = canDownload && (state === CoreConstants.NOT_DOWNLOADED || state === CoreConstants.OUTDATED || - (this.alwaysDownload && state === CoreConstants.DOWNLOADED)); + this.showDownload = canDownload && (state === CoreConstants.NOT_DOWNLOADED || state === CoreConstants.OUTDATED || + (this.alwaysDownload && state === CoreConstants.DOWNLOADED)); }); } @@ -114,25 +114,27 @@ export class CoreFileComponent implements OnInit, OnDestroy { * * @return {Promise} Promise resolved when file is downloaded. */ - protected downloadFile() : Promise { + protected downloadFile(): Promise { if (!this.sitesProvider.getCurrentSite().canDownloadFiles()) { this.domUtils.showErrorModal('core.cannotdownloadfiles', true); + return Promise.reject(null); } this.isDownloading = true; - return this.filepoolProvider.downloadUrl(this.siteId, this.fileUrl, false, this.component, this.componentId, - this.timemodified, undefined, undefined, this.file).catch(() => { - // Call calculateState to make sure we have the right state. - return this.calculateState().then(() => { - if (this.isDownloaded) { - return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl); - } else { - return Promise.reject(null); - } + return this.filepoolProvider.downloadUrl(this.siteId, this.fileUrl, false, this.component, this.componentId, + this.timemodified, undefined, undefined, this.file).catch(() => { + + // Call calculateState to make sure we have the right state. + return this.calculateState().then(() => { + if (this.isDownloaded) { + return this.filepoolProvider.getInternalUrlByUrl(this.siteId, this.fileUrl); + } else { + return Promise.reject(null); + } + }); }); - }); } /** @@ -140,33 +142,35 @@ export class CoreFileComponent implements OnInit, OnDestroy { * * @return {Promise} Promise resolved when file is opened. */ - protected openFile() : Promise { - let fixedUrl = this.sitesProvider.getCurrentSite().fixPluginfileURL(this.fileUrl), - promise; + protected openFile(): Promise { + const fixedUrl = this.sitesProvider.getCurrentSite().fixPluginfileURL(this.fileUrl); + let promise; if (this.fileProvider.isAvailable()) { promise = Promise.resolve().then(() => { // The file system is available. - let isWifi = !this.appProvider.isNetworkAccessLimited(), + const isWifi = !this.appProvider.isNetworkAccessLimited(), isOnline = this.appProvider.isOnline(); if (this.isDownloaded && !this.showDownload) { // File is downloaded, get the local file URL. return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl, - this.component, this.componentId, this.timemodified, false, false, this.file); + this.component, this.componentId, this.timemodified, false, false, this.file); } else { if (!isOnline && !this.isDownloaded) { // Not downloaded and user is offline, reject. return Promise.reject(this.translate.instant('core.networkerrormsg')); } - let isDownloading = this.isDownloading; + const isDownloading = this.isDownloading; this.isDownloading = true; // This check could take a while, show spinner. + return this.filepoolProvider.shouldDownloadBeforeOpen(fixedUrl, this.fileSize).then(() => { if (isDownloading) { // It's already downloading, stop. return; } + // Download and then return the local URL. return this.downloadFile(); }, () => { @@ -181,7 +185,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { } else { // Outdated but offline, so we return the local URL. return this.filepoolProvider.getUrlByUrl(this.siteId, this.fileUrl, - this.component, this.componentId, this.timemodified, false, false, this.file); + this.component, this.componentId, this.timemodified, false, false, this.file); } }); } @@ -231,7 +235,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { * @param {Event} e Click event. * @param {boolean} openAfterDownload Whether the file should be opened after download. */ - download(e: Event, openAfterDownload: boolean) : void { + download(e: Event, openAfterDownload: boolean): void { e.preventDefault(); e.stopPropagation(); @@ -243,6 +247,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { if (!this.appProvider.isOnline() && (!openAfterDownload || (openAfterDownload && !this.isDownloaded))) { this.domUtils.showErrorModal('core.networkerrormsg', true); + return; } @@ -253,27 +258,27 @@ export class CoreFileComponent implements OnInit, OnDestroy { }); } else { // File doesn't need to be opened (it's a prefetch). Show confirm modal if file size is defined and it's big. - promise = this.fileSize ? this.domUtils.confirmDownloadSize({size: this.fileSize, total: true}) : Promise.resolve(); + promise = this.fileSize ? this.domUtils.confirmDownloadSize({ size: this.fileSize, total: true }) : Promise.resolve(); promise.then(() => { // User confirmed, add the file to queue. this.filepoolProvider.invalidateFileByUrl(this.siteId, this.fileUrl).finally(() => { this.isDownloading = true; this.filepoolProvider.addToQueueByUrl(this.siteId, this.fileUrl, this.component, - this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => { - this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); - this.calculateState(); - }); + this.componentId, this.timemodified, undefined, undefined, 0, this.file).catch((error) => { + this.domUtils.showErrorModalDefault(error, 'core.errordownloading', true); + this.calculateState(); + }); }); }); } - }; + } /** * Delete the file. * * @param {Event} e Click event. */ - deleteFile(e: Event) : void { + deleteFile(e: Event): void { e.preventDefault(); e.stopPropagation(); @@ -285,7 +290,7 @@ export class CoreFileComponent implements OnInit, OnDestroy { /** * Component destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.observer && this.observer.off(); } } diff --git a/src/components/iframe/iframe.ts b/src/components/iframe/iframe.ts index 0513bf4c0..01686ac9e 100644 --- a/src/components/iframe/iframe.ts +++ b/src/components/iframe/iframe.ts @@ -51,8 +51,8 @@ export class CoreIframeComponent implements OnInit { /** * Component being initialized. */ - ngOnInit() { - let iframe: HTMLIFrameElement = this.iframe && this.iframe.nativeElement; + ngOnInit(): void { + const iframe: HTMLIFrameElement = this.iframe && this.iframe.nativeElement; this.safeUrl = this.sanitizer.bypassSecurityTrustResourceUrl(this.src); this.iframeWidth = this.domUtils.formatPixelsSize(this.iframeWidth) || '100%'; @@ -85,7 +85,7 @@ export class CoreIframeComponent implements OnInit { * @param {any} element Element to treat. * @return {{ window: Window, document: Document }} Window and Document. */ - protected getContentWindowAndDocument(element: any) : { window: Window, document: Document } { + protected getContentWindowAndDocument(element: any): { window: Window, document: Document } { let contentWindow: Window = element.contentWindow, contentDocument: Document = element.contentDocument || (contentWindow && contentWindow.document); @@ -106,7 +106,7 @@ export class CoreIframeComponent implements OnInit { } } - return {window: contentWindow, document: contentDocument}; + return { window: contentWindow, document: contentDocument }; } /** @@ -115,7 +115,7 @@ export class CoreIframeComponent implements OnInit { * * @param {any} element Element to treat. */ - protected treatFrame(element: any) : void { + protected treatFrame(element: any): void { if (element) { let winAndDoc = this.getContentWindowAndDocument(element); // Redefine window.open in this element and sub frames, it might have been loaded already. @@ -139,10 +139,10 @@ export class CoreIframeComponent implements OnInit { * @param {Window} contentWindow The window of the element contents. * @param {Document} contentDocument The document of the element contents. */ - protected redefineWindowOpen(element: any, contentWindow: Window, contentDocument: Document) : void { + protected redefineWindowOpen(element: any, contentWindow: Window, contentDocument: Document): void { if (contentWindow) { // Intercept window.open. - contentWindow.open = (url: string) : Window => { + contentWindow.open = (url: string): Window => { const scheme = this.urlUtils.getUrlScheme(url); if (!scheme) { // It's a relative URL, use the frame src to create the full URL. @@ -153,10 +153,12 @@ export class CoreIframeComponent implements OnInit { url = this.textUtils.concatenatePaths(dirAndFile.directory, url); } else { this.logger.warn('Cannot get iframe dir path to open relative url', url, element); + return new Window(); // Return new Window object. } } else { this.logger.warn('Cannot get iframe src to open relative url', url, element); + return new Window(); // Return new Window object. } } @@ -198,7 +200,7 @@ export class CoreIframeComponent implements OnInit { * @param {any} element Element to treat. * @param {Document} contentDocument The document of the element contents. */ - protected treatLinks(element: any, contentDocument: Document) : void { + protected treatLinks(element: any, contentDocument: Document): void { if (!contentDocument) { return; } diff --git a/src/components/input-errors/input-errors.ts b/src/components/input-errors/input-errors.ts index 4ff9e833d..3a40005b8 100644 --- a/src/components/input-errors/input-errors.ts +++ b/src/components/input-errors/input-errors.ts @@ -47,12 +47,12 @@ export class CoreInputErrorsComponent implements OnInit { @Input() errorMessages?: any; errorKeys: any[]; - constructor(private translate: TranslateService) {} + constructor(private translate: TranslateService) { } /** * Component is being initialized. */ - ngOnInit() { + ngOnInit(): void { this.initErrorMessages(); this.errorKeys = Object.keys(this.errorMessages); @@ -61,11 +61,11 @@ export class CoreInputErrorsComponent implements OnInit { /** * Initialize some common errors if they aren't set. */ - protected initErrorMessages() { - this.errorMessages = this.errorMessages || {}; + protected initErrorMessages(): void { + this.errorMessages = this.errorMessages || {}; - this.errorMessages.required = this.errorMessages.required || this.translate.instant('core.required'); - this.errorMessages.email = this.errorMessages.email || this.translate.instant('core.login.invalidemail'); + this.errorMessages.required = this.errorMessages.required || this.translate.instant('core.required'); + this.errorMessages.email = this.errorMessages.email || this.translate.instant('core.login.invalidemail'); this.errorMessages.date = this.errorMessages.date || this.translate.instant('core.login.invaliddate'); this.errorMessages.datetime = this.errorMessages.datetime || this.translate.instant('core.login.invaliddate'); this.errorMessages.datetimelocal = this.errorMessages.datetimelocal || this.translate.instant('core.login.invaliddate'); @@ -73,24 +73,6 @@ export class CoreInputErrorsComponent implements OnInit { this.errorMessages.url = this.errorMessages.url || this.translate.instant('core.login.invalidurl'); // @todo: Check how to handle min/max errors once we have a test case to use. Also, review previous errors. - // ['min', 'max'].forEach((type) => { - // // Initialize min/max errors if needed. - // if (!this.errorMessages[type]) { - // if (input && typeof input[type] != 'undefined' && input[type] !== '') { - // var value = input[type]; - // if (input.type == 'date' || input.type == 'datetime' || input.type == 'datetime-local') { - // var date = moment(value); - // if (date.isValid()) { - // value = moment(value).format($translate.instant('core.dfdaymonthyear')); - // } - // } - - // scope.errorMessages[type] = $translate.instant('core.login.invalidvalue' + type, {$a: value}); - // } else { - // scope.errorMessages[type] = $translate.instant('core.login.profileinvaliddata'); - // } - // } - // }); } } diff --git a/src/components/loading/loading.ts b/src/components/loading/loading.ts index 60a74be91..624c5a825 100644 --- a/src/components/loading/loading.ts +++ b/src/components/loading/loading.ts @@ -43,12 +43,12 @@ export class CoreLoadingComponent implements OnInit { @Input() hideUntil: boolean; // Determine when should the contents be shown. @Input() message?: string; // Message to show while loading. - constructor(private translate: TranslateService) {} + constructor(private translate: TranslateService) { } /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { if (!this.message) { // Default loading message. this.message = this.translate.instant('core.loading'); diff --git a/src/components/local-file/local-file.ts b/src/components/local-file/local-file.ts index 1961a8064..f2573959b 100644 --- a/src/components/local-file/local-file.ts +++ b/src/components/local-file/local-file.ts @@ -33,8 +33,8 @@ import * as moment from 'moment'; }) export class CoreLocalFileComponent implements OnInit { @Input() file: any; // A fileEntry retrieved using CoreFileProvider.getFile or similar. - @Input() manage?: boolean|string; // Whether the user can manage the file (edit and delete). - @Input() overrideClick?: boolean|string; // Whether the default item click should be overridden. + @Input() manage?: boolean | string; // Whether the user can manage the file (edit and delete). + @Input() overrideClick?: boolean | string; // Whether the default item click should be overridden. @Output() onDelete?: EventEmitter; // Will notify when the file is deleted. @Output() onRename?: EventEmitter; // Will notify when the file is renamed. Receives the FileEntry as the param. @Output() onClick?: EventEmitter; // Will notify when the file is clicked. Only if overrideClick is true. @@ -44,7 +44,7 @@ export class CoreLocalFileComponent implements OnInit { fileExtension: string; size: string; timemodified: string; - newFileName: string = ''; + newFileName = ''; editMode: boolean; relativePath: string; @@ -59,7 +59,7 @@ export class CoreLocalFileComponent implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.manage = this.utils.isTrueOrOne(this.manage); // Let's calculate the relative path for the file. @@ -83,11 +83,8 @@ export class CoreLocalFileComponent implements OnInit { /** * Load the basic data for the file. - * - * @param {[type]} scope [description] - * @param {[type]} file [description] */ - protected loadFileBasicData() { + protected loadFileBasicData(): void { this.fileName = this.file.name; this.fileIcon = this.mimeUtils.getFileIcon(this.file.name); this.fileExtension = this.mimeUtils.getFileExtension(this.file.name); @@ -98,7 +95,7 @@ export class CoreLocalFileComponent implements OnInit { * * @param {Event} e Click event. */ - fileClicked(e: Event) : void { + fileClicked(e: Event): void { e.preventDefault(); e.stopPropagation(); @@ -107,14 +104,14 @@ export class CoreLocalFileComponent implements OnInit { } else { this.utils.openFile(this.file.toURL()); } - }; + } /** * Activate the edit mode. * * @param {Event} e Click event. */ - activateEdit(e: Event) : void { + activateEdit(e: Event): void { e.preventDefault(); e.stopPropagation(); this.editMode = true; @@ -124,21 +121,22 @@ export class CoreLocalFileComponent implements OnInit { // $timeout(function() { // $mmUtil.focusElement(element[0].querySelector('input')); // }); - }; + } /** * Rename the file. * * @param {string} newName New name. */ - changeName(newName: string) : void { + changeName(newName: string): void { if (newName == this.file.name) { // Name hasn't changed, stop. this.editMode = false; + return; } - let modal = this.domUtils.showModalLoading(), + const modal = this.domUtils.showModalLoading(), fileAndDir = this.fileProvider.getFileAndDirectoryFromPath(this.relativePath), newPath = this.textUtils.concatenatePaths(fileAndDir.directory, newName); @@ -152,27 +150,27 @@ export class CoreLocalFileComponent implements OnInit { this.editMode = false; this.file = fileEntry; this.loadFileBasicData(); - this.onRename.emit({file: this.file}); + this.onRename.emit({ file: this.file }); }).catch(() => { this.domUtils.showErrorModal('core.errorrenamefile', true); }); }).finally(() => { modal.dismiss(); }); - }; + } /** * Delete the file. * * @param {Event} e Click event. */ - deleteFile(e: Event) : void { + deleteFile(e: Event): void { e.preventDefault(); e.stopPropagation(); // Ask confirmation. this.domUtils.showConfirm(this.translate.instant('core.confirmdeletefile')).then(() => { - let modal = this.domUtils.showModalLoading(); + const modal = this.domUtils.showModalLoading(); this.fileProvider.removeFile(this.relativePath).then(() => { this.onDelete.emit(); }).catch(() => { diff --git a/src/components/mark-required/mark-required.ts b/src/components/mark-required/mark-required.ts index 95bdaf952..bbc85d34b 100644 --- a/src/components/mark-required/mark-required.ts +++ b/src/components/mark-required/mark-required.ts @@ -32,7 +32,7 @@ import { CoreUtilsProvider } from '../../providers/utils/utils'; templateUrl: 'mark-required.html' }) export class CoreMarkRequiredComponent implements OnInit, AfterViewInit { - @Input('core-mark-required') coreMarkRequired: boolean|string = true; + @Input('core-mark-required') coreMarkRequired: boolean | string = true; protected element: HTMLElement; requiredLabel: string; @@ -45,14 +45,14 @@ export class CoreMarkRequiredComponent implements OnInit, AfterViewInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.coreMarkRequired = this.utils.isTrueOrOne(this.coreMarkRequired); } /** * Called after the view is initialized. */ - ngAfterViewInit() : void { + ngAfterViewInit(): void { if (this.coreMarkRequired) { // Add the "required" to the aria-label. const ariaLabel = this.element.getAttribute('aria-label') || this.textUtils.cleanTags(this.element.innerHTML, true); diff --git a/src/components/progress-bar/progress-bar.ts b/src/components/progress-bar/progress-bar.ts index a7b6df126..fa2a83c47 100644 --- a/src/components/progress-bar/progress-bar.ts +++ b/src/components/progress-bar/progress-bar.ts @@ -27,17 +27,17 @@ import { DomSanitizer, SafeStyle } from '@angular/platform-browser'; changeDetection: ChangeDetectionStrategy.OnPush }) export class CoreProgressBarComponent implements OnChanges { - @Input() progress: number|string; // Percentage from 0 to 100. + @Input() progress: number | string; // Percentage from 0 to 100. @Input() text?: string; // Percentage in text to be shown at the right. If not defined, progress will be used. width: SafeStyle; protected textSupplied = false; - constructor(private sanitizer: DomSanitizer) {} + constructor(private sanitizer: DomSanitizer) { } /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (changes.text && typeof changes.text.currentValue != 'undefined') { // User provided a custom text, don't use default. this.textSupplied = true; diff --git a/src/components/rich-text-editor/rich-text-editor.ts b/src/components/rich-text-editor/rich-text-editor.ts index 41a2df9c3..354e1d57d 100644 --- a/src/components/rich-text-editor/rich-text-editor.ts +++ b/src/components/rich-text-editor/rich-text-editor.ts @@ -12,7 +12,6 @@ // See the License for the specific language governing permissions and // limitations under the License. - import { Component, Input, Output, EventEmitter, ViewChild, ElementRef } from '@angular/core'; import { TextInput } from 'ionic-angular'; import { CoreDomUtilsProvider } from '../../providers/utils/dom'; @@ -22,7 +21,7 @@ import { Keyboard } from '@ionic-native/keyboard'; /** * Directive to display a rich text editor if enabled. * -* If enabled, this directive will show a rich text editor. Otherwise it'll show a regular textarea. + * If enabled, this directive will show a rich text editor. Otherwise it'll show a regular textarea. * * This directive requires an OBJECT model. The text written in the editor or textarea will be stored inside * a "text" property in that object. This is to ensure 2-way data-binding, since using a string as a model @@ -41,15 +40,15 @@ export class CoreRichTextEditorComponent { // Based on: https://github.com/judgewest2000/Ionic3RichText/ // @todo: Resize, images, anchor button, fullscreen... - @Input() placeholder?: string = ""; // Placeholder to set in textarea. + @Input() placeholder? = ''; // Placeholder to set in textarea. @Input() control: FormControl; // Form control. - @Output() public contentChanged: EventEmitter; + @Output() contentChanged: EventEmitter; @ViewChild('editor') editor: ElementRef; // WYSIWYG editor. @ViewChild('textarea') textarea: TextInput; // Textarea editor. @ViewChild('decorate') decorate: ElementRef; // Buttons. - rteEnabled: boolean = false; + rteEnabled = false; uniqueId = `rte{Math.floor(Math.random() * 1000000)}`; editorElement: HTMLDivElement; @@ -60,7 +59,7 @@ export class CoreRichTextEditorComponent { /** * Init editor */ - ngAfterContentInit() { + ngAfterContentInit(): void { this.domUtils.isRichTextEditorEnabled().then((enabled) => { this.rteEnabled = !!enabled; }); @@ -77,14 +76,14 @@ export class CoreRichTextEditorComponent { this.editorElement.oninput = this.onChange.bind(this); // Setup button actions. - let buttons = (this.decorate.nativeElement as HTMLDivElement).getElementsByTagName('button'); + const buttons = (this.decorate.nativeElement as HTMLDivElement).getElementsByTagName('button'); for (let i = 0; i < buttons.length; i++) { - let button = buttons[i], - command = button.getAttribute('data-command'); + const button = buttons[i]; + let command = button.getAttribute('data-command'); if (command) { if (command.includes('|')) { - let parameter = command.split('|')[1]; + const parameter = command.split('|')[1]; command = command.split('|')[0]; button.addEventListener('click', ($event) => { @@ -101,8 +100,10 @@ export class CoreRichTextEditorComponent { /** * On change function to sync with form data. + * + * @param {Event} $event The event. */ - onChange($event) { + onChange($event: Event): void { if (this.rteEnabled) { if (this.isNullOrWhiteSpace(this.editorElement.innerText)) { this.clearText(); @@ -121,8 +122,10 @@ export class CoreRichTextEditorComponent { /** * Toggle from rte editor to textarea syncing values. + * + * @param {Event} $event The event. */ - toggleEditor($event) { + toggleEditor($event: Event): void { $event.preventDefault(); $event.stopPropagation(); @@ -139,10 +142,12 @@ export class CoreRichTextEditorComponent { setTimeout(() => { if (this.rteEnabled) { this.editorElement.focus(); - let range = document.createRange(); + + const range = document.createRange(); range.selectNodeContents(this.editorElement); range.collapse(false); - let sel = window.getSelection(); + + const sel = window.getSelection(); sel.removeAllRanges(); sel.addRange(range); } else { @@ -158,10 +163,11 @@ export class CoreRichTextEditorComponent { * Check if text is empty. * @param {string} value text */ - private isNullOrWhiteSpace(value: string) { - if (value == null || typeof value == "undefined") { + protected isNullOrWhiteSpace(value: string): boolean { + if (value == null || typeof value == 'undefined') { return true; } + value = value.replace(/[\n\r]/g, ''); value = value.split(' ').join(''); @@ -171,7 +177,7 @@ export class CoreRichTextEditorComponent { /** * Clear the text. */ - clearText() { + clearText(): void { this.editorElement.innerHTML = '

'; this.textarea.value = ''; this.control.setValue(null); @@ -185,9 +191,9 @@ export class CoreRichTextEditorComponent { * @param {string} command Command to execute. * @param {any} [parameters] Parameters of the command. */ - private buttonAction($event: any, command: string, parameters: any = null) { + protected buttonAction($event: any, command: string, parameters: any = null): void { $event.preventDefault(); $event.stopPropagation(); document.execCommand(command, false, parameters); } -} \ No newline at end of file +} diff --git a/src/components/search-box/search-box.ts b/src/components/search-box/search-box.ts index a6e70b3f9..497de1fb6 100644 --- a/src/components/search-box/search-box.ts +++ b/src/components/search-box/search-box.ts @@ -31,20 +31,20 @@ import { CoreUtilsProvider } from '../../providers/utils/utils'; templateUrl: 'search-box.html' }) export class CoreSearchBoxComponent implements OnInit { - @Input() initialValue?: string = ''; // Initial value for search text. - @Input() searchLabel?: string ; // Label to be used on action button. + @Input() initialValue? = ''; // Initial value for search text. + @Input() searchLabel?: string; // Label to be used on action button. @Input() placeholder?: string; // Placeholder text for search text input. - @Input() autocorrect?: string = 'on'; // Enables/disable Autocorrection on search text input. - @Input() spellcheck?: string|boolean = true; // Enables/disable Spellchecker on search text input. - @Input() autoFocus?: string|boolean; // Enables/disable Autofocus when entering view. - @Input() lengthCheck?: number = 3; // Check value length before submit. If 0, any string will be submitted. + @Input() autocorrect? = 'on'; // Enables/disable Autocorrection on search text input. + @Input() spellcheck?: string | boolean = true; // Enables/disable Spellchecker on search text input. + @Input() autoFocus?: string | boolean; // Enables/disable Autofocus when entering view. + @Input() lengthCheck? = 3; // Check value length before submit. If 0, any string will be submitted. @Output() onSubmit: EventEmitter; // Send data when submitting the search form. constructor(private translate: TranslateService, private utils: CoreUtilsProvider) { this.onSubmit = new EventEmitter(); } - ngOnInit() { + ngOnInit(): void { this.searchLabel = this.searchLabel || this.translate.instant('core.search'); this.placeholder = this.placeholder || this.translate.instant('core.search'); this.spellcheck = this.utils.isTrueOrOne(this.spellcheck); @@ -55,7 +55,7 @@ export class CoreSearchBoxComponent implements OnInit { * * @param {string} value Entered value. */ - submitForm(value: string) { + submitForm(value: string): void { if (value.length < this.lengthCheck) { // The view should handle this case, but we check it here too just in case. return; @@ -63,5 +63,4 @@ export class CoreSearchBoxComponent implements OnInit { this.onSubmit.emit(value); } - } diff --git a/src/components/show-password/show-password.ts b/src/components/show-password/show-password.ts index f4c2fd949..6317be448 100644 --- a/src/components/show-password/show-password.ts +++ b/src/components/show-password/show-password.ts @@ -35,12 +35,12 @@ import { CoreUtilsProvider } from '../../providers/utils/utils'; }) export class CoreShowPasswordComponent implements OnInit, AfterViewInit { @Input() name: string; // Name of the input affected. - @Input() initialShown?: boolean|string; // Whether the password should be shown at start. + @Input() initialShown?: boolean | string; // Whether the password should be shown at start. shown: boolean; // Whether the password is shown. label: string; // Label for the button to show/hide. iconName: string; // Name of the icon of the button to show/hide. - selector: string = ''; // Selector to identify the input. + selector = ''; // Selector to identify the input. protected input: HTMLInputElement; // Input affected. protected element: HTMLElement; // Current element. @@ -52,7 +52,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.shown = this.utils.isTrueOrOne(this.initialShown); this.selector = 'input[name="' + this.name + '"]'; this.setData(); @@ -61,14 +61,14 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit { /** * View has been initialized. */ - ngAfterViewInit() { + ngAfterViewInit(): void { this.searchInput(); } /** * Search the input to show/hide. */ - protected searchInput() { + protected searchInput(): void { // Search the input. this.input = this.element.querySelector(this.selector); @@ -89,7 +89,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit { /** * Set label, icon name and input type. */ - protected setData() { + protected setData(): void { this.label = this.shown ? 'core.hide' : 'core.show'; this.iconName = this.shown ? 'eye-off' : 'eye'; if (this.input) { @@ -100,7 +100,7 @@ export class CoreShowPasswordComponent implements OnInit, AfterViewInit { /** * Toggle show/hide password. */ - toggle() : void { + toggle(): void { this.shown = !this.shown; this.setData(); } diff --git a/src/components/site-picker/site-picker.ts b/src/components/site-picker/site-picker.ts index 1f4f92662..9562f207a 100644 --- a/src/components/site-picker/site-picker.ts +++ b/src/components/site-picker/site-picker.ts @@ -36,16 +36,16 @@ export class CoreSitePickerComponent implements OnInit { sites: any[]; constructor(private translate: TranslateService, private sitesProvider: CoreSitesProvider, - private textUtils: CoreTextUtilsProvider) { + private textUtils: CoreTextUtilsProvider) { this.siteSelected = new EventEmitter(); } - ngOnInit() { + ngOnInit(): void { this.selectedSite = this.initialSite || this.sitesProvider.getCurrentSiteId(); // Load the sites. this.sitesProvider.getSites().then((sites) => { - let promises = []; + const promises = []; sites.forEach((site: any) => { // Format the site name. @@ -53,7 +53,7 @@ export class CoreSitePickerComponent implements OnInit { return site.siteName; }).then((formatted) => { site.fullNameAndSiteName = this.translate.instant('core.fullnameandsitename', - {fullname: site.fullName, sitename: formatted}); + { fullname: site.fullName, sitename: formatted }); })); }); diff --git a/src/components/split-view/placeholder/placeholder.ts b/src/components/split-view/placeholder/placeholder.ts index 34233ea46..13e5ddfaa 100644 --- a/src/components/split-view/placeholder/placeholder.ts +++ b/src/components/split-view/placeholder/placeholder.ts @@ -17,13 +17,15 @@ import { Component } from '@angular/core'; import { IonicPage } from 'ionic-angular'; -@IonicPage({segment: "core-placeholder"}) +@IonicPage({ segment: 'core-placeholder' }) @Component({ selector: 'core-placeholder', templateUrl: 'placeholder.html', }) export class CoreSplitViewPlaceholderPage { - constructor() { } + constructor() { + // Nothing to do. + } } diff --git a/src/components/split-view/split-view.ts b/src/components/split-view/split-view.ts index 3c65bee2b..51056847b 100644 --- a/src/components/split-view/split-view.ts +++ b/src/components/split-view/split-view.ts @@ -44,9 +44,9 @@ export class CoreSplitViewComponent implements OnInit { // @todo Mix both panels header buttons @ViewChild('detailNav') detailNav: Nav; - @Input() when?: string | boolean = "md"; // - protected isEnabled: boolean = false; - protected masterPageName: string = ""; + @Input() when?: string | boolean = 'md'; + protected isEnabled = false; + protected masterPageName = ''; protected loadDetailPage: any = false; protected element: HTMLElement; // Current element. @@ -60,7 +60,7 @@ export class CoreSplitViewComponent implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { // Get the master page name and set an empty page as a placeholder. this.masterPageName = this.masterNav.getActive().component.name; this.emptyDetails(); @@ -81,7 +81,7 @@ export class CoreSplitViewComponent implements OnInit { * @param {any} page The component class or deeplink name you want to push onto the navigation stack. * @param {any} params Any NavParams you want to pass along to the next view. */ - push(page: any, params?: any, element?: HTMLElement) { + push(page: any, params?: any, element?: HTMLElement): void { if (this.isEnabled) { this.detailNav.setRoot(page, params); } else { @@ -96,7 +96,7 @@ export class CoreSplitViewComponent implements OnInit { /** * Set the details panel to default info. */ - emptyDetails() { + emptyDetails(): void { this.loadDetailPage = false; this.detailNav.setRoot('CoreSplitViewPlaceholderPage'); } @@ -106,7 +106,7 @@ export class CoreSplitViewComponent implements OnInit { * * @param {Boolean} isOn If it fits both panels at the same time. */ - onSplitPaneChanged(isOn) { + onSplitPaneChanged(isOn: boolean): void { this.isEnabled = isOn; if (this.masterNav && this.detailNav) { (isOn) ? this.activateSplitView() : this.deactivateSplitView(); @@ -116,14 +116,14 @@ export class CoreSplitViewComponent implements OnInit { /** * Enable the split view, show both panels and do some magical navigation. */ - activateSplitView() { - let currentView = this.masterNav.getActive(), + activateSplitView(): void { + const currentView = this.masterNav.getActive(), currentPageName = currentView.component.name; if (currentPageName != this.masterPageName) { // CurrentView is a 'Detail' page remove it from the 'master' nav stack. this.masterNav.pop(); - // and add it to the 'detail' nav stack. + // And add it to the 'detail' nav stack. this.detailNav.setRoot(currentView.component, currentView.data); } else if (this.loadDetailPage) { // MasterPage is shown, load the last detail page if found. @@ -135,12 +135,12 @@ export class CoreSplitViewComponent implements OnInit { /** * Disabled the split view, show only one panel and do some magical navigation. */ - deactivateSplitView() { - let detailView = this.detailNav.getActive(), + deactivateSplitView(): void { + const detailView = this.detailNav.getActive(), currentPageName = detailView.component.name; if (currentPageName != 'CoreSplitViewPlaceholderPage') { // Current detail view is a 'Detail' page so, not the placeholder page, push it on 'master' nav stack. this.masterNav.push(detailView.component, detailView.data); } } -} \ No newline at end of file +} diff --git a/src/components/tabs/tab.ts b/src/components/tabs/tab.ts index efeb1ef4d..46467c2e6 100644 --- a/src/components/tabs/tab.ts +++ b/src/components/tabs/tab.ts @@ -44,12 +44,12 @@ export class CoreTabComponent implements OnInit, OnDestroy { @Input() icon?: string; // The tab icon. @Input() badge?: string; // A badge to add in the tab. @Input() badgeStyle?: string; // The badge color. - @Input() enabled?: boolean = true; // Whether the tab is enabled. - @Input() show?: boolean = true; // Whether the tab should be shown. + @Input() enabled? = true; // Whether the tab is enabled. + @Input() show? = true; // Whether the tab should be shown. @Input() id?: string; // An ID to identify the tab. @Output() ionSelect: EventEmitter = new EventEmitter(); - @ContentChild(TemplateRef) template: TemplateRef // Template defined by the content. + @ContentChild(TemplateRef) template: TemplateRef; // Template defined by the content. @ContentChild(Content) scroll: Content; element: HTMLElement; // The core-tab element. @@ -62,21 +62,21 @@ export class CoreTabComponent implements OnInit, OnDestroy { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.tabs.addTab(this); } /** * Component destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.tabs.removeTab(this); } /** * Select tab. */ - selectTab() { + selectTab(): void { this.element.classList.add('selected'); this.loaded = true; @@ -85,9 +85,9 @@ export class CoreTabComponent implements OnInit, OnDestroy { // Setup tab scrolling. setTimeout(() => { if (this.scroll) { - this.scroll.getScrollElement().onscroll = (e) => { + this.scroll.getScrollElement().onscroll = (e): void => { this.tabs.showHideTabs(e); - } + }; } }, 1); } @@ -95,7 +95,7 @@ export class CoreTabComponent implements OnInit, OnDestroy { /** * Unselect tab. */ - unselectTab() { + unselectTab(): void { this.element.classList.remove('selected'); } } diff --git a/src/components/tabs/tabs.ts b/src/components/tabs/tabs.ts index 2ad6402d2..f5e056984 100644 --- a/src/components/tabs/tabs.ts +++ b/src/components/tabs/tabs.ts @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, Output, EventEmitter, OnInit, OnChanges, AfterViewInit, ViewChild, ElementRef, - SimpleChange } from '@angular/core'; +import { + Component, Input, Output, EventEmitter, OnInit, OnChanges, AfterViewInit, ViewChild, ElementRef, + SimpleChange +} from '@angular/core'; import { CoreTabComponent } from './tab'; import { Content } from 'ionic-angular'; @@ -40,7 +42,7 @@ import { Content } from 'ionic-angular'; templateUrl: 'tabs.html' }) export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { - @Input() selectedIndex?: number = 0; // Index of the tab to select. + @Input() selectedIndex = 0; // Index of the tab to select. @Input() hideUntil: boolean; // Determine when should the contents be shown. @Output() ionChange: EventEmitter = new EventEmitter(); // Emitted when the tab changes. @ViewChild('originalTabs') originalTabsRef: ElementRef; @@ -70,7 +72,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.originalTabsContainer = this.originalTabsRef.nativeElement; this.topTabsElement = this.topTabs.nativeElement; } @@ -78,7 +80,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { /** * View has been initialized. */ - ngAfterViewInit() { + ngAfterViewInit(): void { this.afterViewInitTriggered = true; if (!this.initialized && this.hideUntil) { // Tabs should be shown, initialize them. @@ -89,7 +91,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: { [name: string]: SimpleChange }): void { // We need to wait for ngAfterViewInit because we need core-tab components to be executed. if (!this.initialized && this.hideUntil && this.afterViewInitTriggered) { // Tabs should be shown, initialize them. @@ -105,7 +107,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { * * @param {CoreTabComponent} tab The tab to add. */ - addTab(tab: CoreTabComponent) : void { + addTab(tab: CoreTabComponent): void { // Check if tab is already in the list. if (this.getIndex(tab) == -1) { this.tabs.push(tab); @@ -119,13 +121,14 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { * @param {any} tab [description] * @return {number} [description] */ - getIndex(tab: any) : number { + getIndex(tab: any): number { for (let i = 0; i < this.tabs.length; i++) { - let t = this.tabs[i]; + const t = this.tabs[i]; if (t === tab || (typeof t.id != 'undefined' && t.id === tab.id)) { return i; } } + return -1; } @@ -134,14 +137,14 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { * * @return {CoreTabComponent} Selected tab. */ - getSelected() : CoreTabComponent { + getSelected(): CoreTabComponent { return this.tabs[this.selected]; } /** * Initialize the tabs, determining the first tab to be shown. */ - protected initializeTabs() : void { + protected initializeTabs(): void { let selectedIndex = this.selectedIndex || 0, selectedTab = this.tabs[selectedIndex]; @@ -150,8 +153,10 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { selectedTab = this.tabs.find((tab, index) => { if (tab.enabled && tab.show) { selectedIndex = index; + return true; } + return false; }); } @@ -162,7 +167,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { // Setup tab scrolling. this.tabBarHeight = this.topTabsElement.offsetHeight; - this.originalTabsContainer.style.paddingBottom = this.tabBarHeight + 'px'; + this.originalTabsContainer.style.paddingBottom = this.tabBarHeight + 'px'; if (this.scroll) { this.scroll.classList.add('no-scroll'); } @@ -175,7 +180,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { * * @param {any} e Scroll event. */ - showHideTabs(e: any) : void { + showHideTabs(e: any): void { if (e.target.scrollTop < this.tabBarHeight) { if (!this.tabsShown) { this.tabBarElement.classList.remove('tabs-hidden'); @@ -194,7 +199,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { * * @param {CoreTabComponent} tab The tab to remove. */ - removeTab(tab: CoreTabComponent) : void { + removeTab(tab: CoreTabComponent): void { const index = this.getIndex(tab); this.tabs.splice(index, 1); } @@ -204,7 +209,7 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { * * @param {number} index The index of the tab to select. */ - selectTab(index: number) : void { + selectTab(index: number): void { if (index == this.selected) { // Already selected. return; @@ -236,13 +241,13 @@ export class CoreTabsComponent implements OnInit, AfterViewInit, OnChanges { /** * Sort the tabs, keeping the same order as in the original list. */ - protected sortTabs() { + protected sortTabs(): void { if (this.originalTabsContainer) { - let newTabs = [], - newSelected; + const newTabs = []; + let newSelected; this.tabs.forEach((tab, index) => { - let originalIndex = Array.prototype.indexOf.call(this.originalTabsContainer.children, tab.element); + const originalIndex = Array.prototype.indexOf.call(this.originalTabsContainer.children, tab.element); if (originalIndex != -1) { newTabs[originalIndex] = tab; if (this.selected == index) { diff --git a/src/core/constants.ts b/src/core/constants.ts index 519460a68..dfff32e96 100644 --- a/src/core/constants.ts +++ b/src/core/constants.ts @@ -16,34 +16,34 @@ * Static class to contain all the core constants. */ export class CoreConstants { - public static SECONDS_YEAR = 31536000; - public static SECONDS_WEEK = 604800; - public static SECONDS_DAY = 86400; - public static SECONDS_HOUR = 3600; - public static SECONDS_MINUTE = 60; - public static WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB. - public static DOWNLOAD_THRESHOLD = 10485760; // 10MB. - public static DONT_SHOW_ERROR = 'CoreDontShowError'; - public static NO_SITE_ID = 'NoSite'; + static SECONDS_YEAR = 31536000; + static SECONDS_WEEK = 604800; + static SECONDS_DAY = 86400; + static SECONDS_HOUR = 3600; + static SECONDS_MINUTE = 60; + static WIFI_DOWNLOAD_THRESHOLD = 104857600; // 100MB. + static DOWNLOAD_THRESHOLD = 10485760; // 10MB. + static DONT_SHOW_ERROR = 'CoreDontShowError'; + static NO_SITE_ID = 'NoSite'; // Settings constants. - public static SETTINGS_RICH_TEXT_EDITOR = 'CoreSettingsRichTextEditor'; - public static SETTINGS_NOTIFICATION_SOUND = 'CoreSettingsNotificationSound'; - public static SETTINGS_SYNC_ONLY_ON_WIFI = 'CoreSettingsSyncOnlyOnWifi'; + static SETTINGS_RICH_TEXT_EDITOR = 'CoreSettingsRichTextEditor'; + static SETTINGS_NOTIFICATION_SOUND = 'CoreSettingsNotificationSound'; + static SETTINGS_SYNC_ONLY_ON_WIFI = 'CoreSettingsSyncOnlyOnWifi'; // WS constants. - public static WS_TIMEOUT = 30000; - public static WS_PREFIX = 'local_mobile_'; + static WS_TIMEOUT = 30000; + static WS_PREFIX = 'local_mobile_'; // Login constants. - public static LOGIN_SSO_CODE = 2; // SSO in browser window is required. - public static LOGIN_SSO_INAPP_CODE = 3; // SSO in embedded browser is required. - public static LOGIN_LAUNCH_DATA = 'CoreLoginLaunchData'; + static LOGIN_SSO_CODE = 2; // SSO in browser window is required. + static LOGIN_SSO_INAPP_CODE = 3; // SSO in embedded browser is required. + static LOGIN_LAUNCH_DATA = 'CoreLoginLaunchData'; // Download status constants. - public static DOWNLOADED = 'downloaded'; - public static DOWNLOADING = 'downloading'; - public static NOT_DOWNLOADED = 'notdownloaded'; - public static OUTDATED = 'outdated'; - public static NOT_DOWNLOADABLE = 'notdownloadable'; + static DOWNLOADED = 'downloaded'; + static DOWNLOADING = 'downloading'; + static NOT_DOWNLOADED = 'notdownloaded'; + static OUTDATED = 'outdated'; + static NOT_DOWNLOADABLE = 'notdownloadable'; } diff --git a/src/core/contentlinks/classes/base-handler.ts b/src/core/contentlinks/classes/base-handler.ts index 15b7c156a..1b29c509d 100644 --- a/src/core/contentlinks/classes/base-handler.ts +++ b/src/core/contentlinks/classes/base-handler.ts @@ -54,7 +54,9 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { */ pattern?: RegExp; - constructor() {} + constructor() { + // Nothing to do. + } /** * Get the list of actions for a link (url). @@ -65,8 +67,8 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise { + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { return []; } @@ -76,7 +78,7 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { * @param {string} url The URL to check. * @return {boolean} Whether the URL is handled by this handler */ - handles(url: string) : boolean { + handles(url: string): boolean { return this.pattern && url.search(this.pattern) >= 0; } @@ -86,9 +88,9 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { * @param {string} url The URL to check. * @return {string} Site URL if it is handled, undefined otherwise. */ - getSiteUrl(url: string) : string { + getSiteUrl(url: string): string { if (this.pattern) { - var position = url.search(this.pattern); + const position = url.search(this.pattern); if (position > -1) { return url.substr(0, position); } @@ -105,7 +107,7 @@ export class CoreContentLinksHandlerBase implements CoreContentLinksHandler { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {boolean|Promise} Whether the handler is enabled for the URL and site. */ - isEnabled(siteId: string, url: string, params: any, courseId?: number) : boolean|Promise { + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return true; } } diff --git a/src/core/contentlinks/classes/module-grade-handler.ts b/src/core/contentlinks/classes/module-grade-handler.ts index a6f8bf3c0..89b744cd2 100644 --- a/src/core/contentlinks/classes/module-grade-handler.ts +++ b/src/core/contentlinks/classes/module-grade-handler.ts @@ -60,12 +60,13 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise { + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { courseId = courseId || params.courseid || params.cid; + return [{ - action: (siteId, navCtrl?) : void => { + action: (siteId, navCtrl?): void => { // Check if userid is the site's current user. const modal = this.domUtils.showModalLoading(); this.sitesProvider.getSite(siteId).then((site) => { @@ -96,7 +97,7 @@ export class CoreContentLinksModuleGradeHandler extends CoreContentLinksHandlerB * @param {NavController} [navCtrl] Nav Controller to use to navigate. * @return {Promise} Promise resolved when done. */ - protected goToReview(url: string, params: any, courseId: number, siteId: string, navCtrl?: NavController) : Promise { + protected goToReview(url: string, params: any, courseId: number, siteId: string, navCtrl?: NavController): Promise { // This function should be overridden. return Promise.resolve(); } diff --git a/src/core/contentlinks/classes/module-index-handler.ts b/src/core/contentlinks/classes/module-index-handler.ts index 89c1c4aef..f7058468b 100644 --- a/src/core/contentlinks/classes/module-index-handler.ts +++ b/src/core/contentlinks/classes/module-index-handler.ts @@ -50,12 +50,13 @@ export class CoreContentLinksModuleIndexHandler extends CoreContentLinksHandlerB * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise { + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { courseId = courseId || params.courseid || params.cid; + return [{ - action: (siteId, navCtrl?) => { + action: (siteId, navCtrl?): void => { this.courseHelper.navigateToModule(parseInt(params.id, 10), siteId, courseId); } }]; diff --git a/src/core/contentlinks/pages/choose-site/choose-site.ts b/src/core/contentlinks/pages/choose-site/choose-site.ts index 7c71471b6..922e743ea 100644 --- a/src/core/contentlinks/pages/choose-site/choose-site.ts +++ b/src/core/contentlinks/pages/choose-site/choose-site.ts @@ -22,7 +22,7 @@ import { CoreContentLinksHelperProvider } from '../../providers/helper'; /** * Page to display the list of sites to choose one to perform a content link action. */ -@IonicPage({segment: 'core-content-links-choose-site'}) +@IonicPage({ segment: 'core-content-links-choose-site' }) @Component({ selector: 'page-core-content-links-choose-site', templateUrl: 'choose-site.html', @@ -43,7 +43,7 @@ export class CoreContentLinksChooseSitePage implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { if (!this.url) { return this.leaveView(); } @@ -70,7 +70,7 @@ export class CoreContentLinksChooseSitePage implements OnInit { /** * Cancel. */ - cancel() : void { + cancel(): void { this.leaveView(); } @@ -79,17 +79,16 @@ export class CoreContentLinksChooseSitePage implements OnInit { * * @param {string} siteId Site ID. */ - siteClicked(siteId: string) : void { + siteClicked(siteId: string): void { this.action.action(siteId, this.navCtrl); } /** * Cancel and leave the view. */ - protected leaveView() { + protected leaveView(): void { this.sitesProvider.logout().finally(() => { this.navCtrl.setRoot('CoreLoginSitesPage'); }); } - -} \ No newline at end of file +} diff --git a/src/core/contentlinks/providers/delegate.ts b/src/core/contentlinks/providers/delegate.ts index 6ab53e226..dc293023a 100644 --- a/src/core/contentlinks/providers/delegate.ts +++ b/src/core/contentlinks/providers/delegate.ts @@ -58,8 +58,8 @@ export interface CoreContentLinksHandler { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise; + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise; /** * Check if a URL is handled by this handler. @@ -67,7 +67,7 @@ export interface CoreContentLinksHandler { * @param {string} url The URL to check. * @return {boolean} Whether the URL is handled by this handler */ - handles(url: string) : boolean; + handles(url: string): boolean; /** * If the URL is handled by this handler, return the site URL. @@ -75,7 +75,7 @@ export interface CoreContentLinksHandler { * @param {string} url The URL to check. * @return {string} Site URL if it is handled, undefined otherwise. */ - getSiteUrl(url: string) : string; + getSiteUrl(url: string): string; /** * Check if the handler is enabled for a certain site (site + user) and a URL. @@ -87,8 +87,8 @@ export interface CoreContentLinksHandler { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {boolean|Promise} Whether the handler is enabled for the URL and site. */ - isEnabled?(siteId: string, url: string, params: any, courseId?: number) : boolean|Promise; -}; + isEnabled?(siteId: string, url: string, params: any, courseId?: number): boolean | Promise; +} /** * Action to perform when a link is clicked. @@ -118,8 +118,8 @@ export interface CoreContentLinksAction { * @param {string} siteId The site ID. * @param {NavController} [navCtrl] Nav Controller to use to navigate. */ - action(siteId: string, navCtrl?: NavController) : void; -}; + action(siteId: string, navCtrl?: NavController): void; +} /** * Actions and priority for a handler and URL. @@ -136,7 +136,7 @@ export interface CoreContentLinksHandlerActions { * @type {CoreContentLinksAction[]} */ actions: CoreContentLinksAction[]; -}; +} /** * Delegate to register handlers to handle links. @@ -144,7 +144,7 @@ export interface CoreContentLinksHandlerActions { @Injectable() export class CoreContentLinksDelegate { protected logger; - protected handlers: {[s: string]: CoreContentLinksHandler} = {}; // All registered handlers. + protected handlers: { [s: string]: CoreContentLinksHandler } = {}; // All registered handlers. constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider, private utils: CoreUtilsProvider) { @@ -159,7 +159,7 @@ export class CoreContentLinksDelegate { * @param {string} [username] Username to use to filter sites. * @return {Promise} Promise resolved with the actions. */ - getActionsFor(url: string, courseId?: number, username?: string) : Promise { + getActionsFor(url: string, courseId?: number, username?: string): Promise { if (!url) { return Promise.resolve([]); } @@ -170,7 +170,7 @@ export class CoreContentLinksDelegate { promises = [], params = this.urlUtils.extractUrlParams(url); - for (let name in this.handlers) { + for (const name in this.handlers) { const handler = this.handlers[name], checkAll = handler.checkAllUsers, isEnabledFn = this.isHandlerEnabled.bind(this, handler, url, params, courseId); @@ -192,8 +192,8 @@ export class CoreContentLinksDelegate { // Set default values if any value isn't supplied. actions.forEach((action) => { action.message = action.message || 'core.view'; - action.icon = action.icon || 'eye'; - action.sites = action.sites || siteIds; + action.icon = action.icon || 'eye'; + action.sites = action.sites || siteIds; }); // Add them to the list. @@ -221,13 +221,13 @@ export class CoreContentLinksDelegate { * @param {string} url URL to handle. * @return {string} Site URL if the URL is supported by any handler, undefined otherwise. */ - getSiteUrl(url: string) : string { + getSiteUrl(url: string): string { if (!url) { return; } // Check if any handler supports this URL. - for (let name in this.handlers) { + for (const name in this.handlers) { const handler = this.handlers[name], siteUrl = handler.getSiteUrl(url); @@ -264,7 +264,7 @@ export class CoreContentLinksDelegate { } if (!handler.isEnabled) { - // isEnabled function not provided, assume it's enabled. + // Handler doesn't implement isEnabled, assume it's enabled. return true; } @@ -278,13 +278,15 @@ export class CoreContentLinksDelegate { * @param {CoreContentLinksHandler} handler The handler to register. * @return {boolean} True if registered successfully, false otherwise. */ - registerHandler(handler: CoreContentLinksHandler) : boolean { + registerHandler(handler: CoreContentLinksHandler): boolean { if (typeof this.handlers[handler.name] !== 'undefined') { this.logger.log(`Addon '${handler.name}' already registered`); + return false; } this.logger.log(`Registered addon '${handler.name}'`); this.handlers[handler.name] = handler; + return true; } @@ -294,7 +296,7 @@ export class CoreContentLinksDelegate { * @param {CoreContentLinksHandlerActions[]} actions Actions to sort. * @return {CoreContentLinksAction[]} Sorted actions. */ - protected sortActionsByPriority(actions: CoreContentLinksHandlerActions[]) : CoreContentLinksAction[] { + protected sortActionsByPriority(actions: CoreContentLinksHandlerActions[]): CoreContentLinksAction[] { let sorted: CoreContentLinksAction[] = []; // Sort by priority. @@ -306,6 +308,7 @@ export class CoreContentLinksDelegate { actions.forEach((entry) => { sorted = sorted.concat(entry.actions); }); + return sorted; } } diff --git a/src/core/contentlinks/providers/helper.ts b/src/core/contentlinks/providers/helper.ts index 35768d6f7..72d68da66 100644 --- a/src/core/contentlinks/providers/helper.ts +++ b/src/core/contentlinks/providers/helper.ts @@ -50,7 +50,7 @@ export class CoreContentLinksHelperProvider { * @param {CoreContentLinksAction[]} actions List of actions. * @return {CoreContentLinksAction} First valid action. Returns undefined if no valid action found. */ - getFirstValidAction(actions: CoreContentLinksAction[]) : CoreContentLinksAction { + getFirstValidAction(actions: CoreContentLinksAction[]): CoreContentLinksAction { if (actions) { for (let i = 0; i < actions.length; i++) { const action = actions[i]; @@ -70,7 +70,7 @@ export class CoreContentLinksHelperProvider { * @param {any} [pageParams] Params to send to the page. * @param {string} [siteId] Site ID. If not defined, current site. */ - goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string) : void { + goInSite(navCtrl: NavController, pageName: string, pageParams: any, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (navCtrl && siteId == this.sitesProvider.getCurrentSiteId()) { navCtrl.push(pageName, pageParams); @@ -84,8 +84,8 @@ export class CoreContentLinksHelperProvider { * * @param {string} url URL to treat. */ - goToChooseSite(url: string) : void { - this.appProvider.getRootNavController().setRoot('CoreContentLinksChooseSitePage', {url: url}); + goToChooseSite(url: string): void { + this.appProvider.getRootNavController().setRoot('CoreContentLinksChooseSitePage', { url: url }); } /** @@ -94,20 +94,20 @@ export class CoreContentLinksHelperProvider { * @param {string} url URL to handle. * @return {boolean} True if the URL should be handled by this component, false otherwise. */ - handleCustomUrl(url: string) : boolean { + handleCustomUrl(url: string): boolean { const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link'; if (url.indexOf(contentLinksScheme) == -1) { return false; } + const modal = this.domUtils.showModalLoading(); + let username; + url = decodeURIComponent(url); // App opened using custom URL scheme. this.logger.debug('Treating custom URL scheme: ' + url); - let modal = this.domUtils.showModalLoading(), - username; - // Delete the scheme from the URL. url = url.replace(contentLinksScheme + '=', ''); @@ -124,6 +124,7 @@ export class CoreContentLinksHelperProvider { }).then((siteIds) => { if (siteIds.length) { modal.dismiss(); // Dismiss modal so it doesn't collide with confirms. + return this.handleLink(url, username).then((treated) => { if (!treated) { this.domUtils.showErrorModal('core.contentlinks.errornoactions', true); @@ -134,14 +135,14 @@ export class CoreContentLinksHelperProvider { const siteUrl = this.contentLinksDelegate.getSiteUrl(url); if (!siteUrl) { this.domUtils.showErrorModal('core.login.invalidsite', true); + return; } // Check that site exists. return this.sitesProvider.checkSite(siteUrl).then((result) => { // Site exists. We'll allow to add it. - let promise, - ssoNeeded = this.loginHelper.isSSOLoginNeeded(result.code), + const ssoNeeded = this.loginHelper.isSSOLoginNeeded(result.code), hasRemoteAddonsLoaded = false, pageName = 'CoreLoginCredentialsPage', pageParams = { @@ -150,6 +151,7 @@ export class CoreContentLinksHelperProvider { urlToOpen: url, siteConfig: result.config }; + let promise; modal.dismiss(); // Dismiss modal so it doesn't collide with confirms. @@ -161,7 +163,7 @@ export class CoreContentLinksHelperProvider { const confirmMsg = this.translate.instant('core.contentlinks.confirmurlothersite'); promise = this.domUtils.showConfirm(confirmMsg).then(() => { if (!ssoNeeded) { - // hasRemoteAddonsLoaded = $mmAddonManager.hasRemoteAddonsLoaded(); @todo + // @todo hasRemoteAddonsLoaded = $mmAddonManager.hasRemoteAddonsLoaded(); @todo if (hasRemoteAddonsLoaded) { // Store the redirect since logout will restart the app. this.appProvider.storeRedirect(CoreConstants.NO_SITE_ID, pageName, pageParams); @@ -177,7 +179,7 @@ export class CoreContentLinksHelperProvider { return promise.then(() => { if (ssoNeeded) { this.loginHelper.confirmAndOpenBrowserForSSOLogin( - result.siteUrl, result.code, result.service, result.config && result.config.launchurl); + result.siteUrl, result.code, result.service, result.config && result.config.launchurl); } else if (!hasRemoteAddonsLoaded) { this.appProvider.getRootNavController().setRoot(pageName, pageParams); } @@ -205,7 +207,7 @@ export class CoreContentLinksHelperProvider { * @param {NavController} [navCtrl] Nav Controller to use to navigate. * @return {Promise} Promise resolved with a boolean: true if URL was treated, false otherwise. */ - handleLink(url: string, username?: string, navCtrl?: NavController) : Promise { + handleLink(url: string, username?: string, navCtrl?: NavController): Promise { // Check if the link should be treated by some component/addon. return this.contentLinksDelegate.getActionsFor(url, undefined, username).then((actions) => { const action = this.getFirstValidAction(actions); @@ -230,8 +232,10 @@ export class CoreContentLinksHelperProvider { } }); } + return true; } + return false; }).catch(() => { return false; diff --git a/src/core/course/classes/module-prefetch-handler.ts b/src/core/course/classes/module-prefetch-handler.ts index 392296bff..7d1c9623d 100644 --- a/src/core/course/classes/module-prefetch-handler.ts +++ b/src/core/course/classes/module-prefetch-handler.ts @@ -37,7 +37,7 @@ import { CoreConstants } from '../../constants'; * @return {Promise} Promise resolved when the prefetch finishes. The string returned will be stored as "extra" data in the * filepool package. If you don't need to store extra data, don't return anything. */ -export type prefetchFunction = (module: any, courseId: number, single: boolean, siteId: string, ...args) => Promise; +export type prefetchFunction = (module: any, courseId: number, single: boolean, siteId: string, ...args: any[]) => Promise; /** * Base prefetch handler to be registered in CoreCourseModulePrefetchDelegate. It is useful to minimize the amount of @@ -88,10 +88,10 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * List of download promises to prevent downloading the module twice at the same time. * @type {{[s: string]: {[s: string]: Promise}}} */ - protected downloadPromises: {[s: string]: {[s: string]: Promise}} = {}; + protected downloadPromises: { [s: string]: { [s: string]: Promise } } = {}; - // List of services that will be injected using injector. It's done like this so subclasses don't have to send all the - // services to the parent in the constructor. + // List of services that will be injected using injector. + // It's done like this so subclasses don't have to send all the services to the parent in the constructor. protected translate: TranslateService; protected appProvider: CoreAppProvider; protected courseProvider: CoreCourseProvider; @@ -118,7 +118,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {String} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise of the current download. */ - addOngoingDownload(id: number, promise: Promise, siteId?: string) : Promise { + addOngoingDownload(id: number, promise: Promise, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const uniqueId = this.getUniqueId(id); @@ -141,7 +141,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {number} courseId Course ID. * @return {Promise} Promise resolved when all content is downloaded. */ - download(module: any, courseId: number) : Promise { + download(module: any, courseId: number): Promise { return this.downloadOrPrefetch(module, courseId, false); } @@ -156,7 +156,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * in the filepool root folder. * @return {Promise} Promise resolved when all content is downloaded. Data returned is not reliable. */ - downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string) : Promise { + downloadOrPrefetch(module: any, courseId: number, prefetch?: boolean, dirPath?: string): Promise { if (!this.appProvider.isOnline()) { // Cannot download in offline. return Promise.reject(this.translate.instant('core.networkerrormsg')); @@ -169,21 +169,21 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref // Get the intro files. return this.getIntroFiles(module, courseId); }).then((introFiles) => { - let downloadFn = prefetch ? this.filepoolProvider.prefetchPackage.bind(this.filepoolProvider) : - this.filepoolProvider.downloadPackage.bind(this.filepoolProvider), + const downloadFn = prefetch ? this.filepoolProvider.prefetchPackage.bind(this.filepoolProvider) : + this.filepoolProvider.downloadPackage.bind(this.filepoolProvider), contentFiles = this.getContentDownloadableFiles(module), promises = []; if (dirPath) { // Download intro files in filepool root folder. promises.push(this.filepoolProvider.downloadOrPrefetchFiles(siteId, introFiles, prefetch, false, - this.component, module.id)); + this.component, module.id)); // Download content files inside dirPath. promises.push(downloadFn(siteId, contentFiles, this.component, module.id, undefined, dirPath)); } else { // No dirPath, download everything in filepool root folder. - let files = introFiles.concat(contentFiles); + const files = introFiles.concat(contentFiles); promises.push(downloadFn(siteId, files, this.component, module.id)); } @@ -197,8 +197,8 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {any} module The module object returned by WS. * @return {any[]} List of files. */ - getContentDownloadableFiles(module: any) { - let files = []; + getContentDownloadableFiles(module: any): any[] { + const files = []; if (module.contents && module.contents.length) { module.contents.forEach((content) => { @@ -220,11 +220,11 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @return {Promise<{size: number, total: boolean}>} Promise resolved with the size and a boolean indicating if it was able * to calculate the total size. */ - getDownloadSize(module: any, courseId: number, single?: boolean) : Promise<{size: number, total: boolean}> { + getDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> { return this.getFiles(module, courseId).then((files) => { return this.utils.sumFileSizes(files); }).catch(() => { - return {size: -1, total: false}; + return { size: -1, total: false }; }); } @@ -235,8 +235,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {number} courseId Course ID the module belongs to. * @return {number|Promise} Size, or promise resolved with the size. */ - getDownloadedSize?(module: any, courseId: number) : number|Promise { + getDownloadedSize?(module: any, courseId: number): number | Promise { const siteId = this.sitesProvider.getCurrentSiteId(); + return this.filepoolProvider.getFilesSizeByComponent(siteId, this.component, module.id); } @@ -248,7 +249,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {boolean} [single] True if we're downloading a single module, false if we're downloading a whole section. * @return {Promise} Promise resolved with the list of files. */ - getFiles(module: any, courseId: number, single?: boolean) : Promise { + getFiles(module: any, courseId: number, single?: boolean): Promise { // Load module contents if needed. return this.loadContents(module, courseId).then(() => { return this.getIntroFiles(module, courseId).then((files) => { @@ -264,7 +265,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {number} courseId Course ID. * @return {Promise} Promise resolved with list of intro files. */ - getIntroFiles(module: any, courseId: number) : Promise { + getIntroFiles(module: any, courseId: number): Promise { return Promise.resolve(this.getIntroFilesFromInstance(module)); } @@ -275,7 +276,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {any} [instance] The instance to get the intro files (book, assign, ...). If not defined, module will be used. * @return {any[]} List of intro files. */ - getIntroFilesFromInstance(module: any, instance?: any) { + getIntroFilesFromInstance(module: any, instance?: any): any[] { if (instance) { if (typeof instance.introfiles != 'undefined') { return instance.introfiles; @@ -298,13 +299,14 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise of the current download. */ - getOngoingDownload(id: number, siteId?: string) : Promise { + getOngoingDownload(id: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (this.isDownloading(id, siteId)) { // There's already a download ongoing, return the promise. return this.downloadPromises[siteId][this.getUniqueId(id)]; } + return Promise.resolve(); } @@ -314,7 +316,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {number} id Unique ID inside component. * @return {string} Unique ID. */ - getUniqueId(id: number) { + getUniqueId(id: number): string { return this.component + '#' + id; } @@ -324,7 +326,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {number} moduleId The module ID. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateContent(moduleId: number) : Promise { + invalidateContent(moduleId: number): Promise { const promises = [], siteId = this.sitesProvider.getCurrentSiteId(); @@ -342,7 +344,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {number} courseId Course ID the module belongs to. * @return {Promise} Promise resolved when invalidated. */ - invalidateModule(module: any, courseId: number) : Promise { + invalidateModule(module: any, courseId: number): Promise { return this.courseProvider.invalidateModule(module.id); } @@ -353,7 +355,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {number} courseId Course ID the module belongs to. * @return {boolean|Promise} Whether the module can be downloaded. The promise should never be rejected. */ - isDownloadable(module: any, courseId: number) : boolean|Promise { + isDownloadable(module: any, courseId: number): boolean | Promise { // By default, mark all instances as downloadable. return true; } @@ -361,12 +363,13 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref /** * Check if a there's an ongoing download for the given identifier. * - * @param {number} id Unique identifier per component. + * @param {number} id Unique identifier per component. * @param {string} [siteId] Site ID. If not defined, current site. - * @return {Boolean} True if downloading, false otherwise. + * @return {boolean} True if downloading, false otherwise. */ - isDownloading(id: number, siteId?: string) : boolean { + isDownloading(id: number, siteId?: string): boolean { siteId = siteId || this.sitesProvider.getCurrentSiteId(); + return !!(this.downloadPromises[siteId] && this.downloadPromises[siteId][this.getUniqueId(id)]); } @@ -375,7 +378,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * * @return {boolean|Promise} A boolean, or a promise resolved with a boolean, indicating if the handler is enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -385,7 +388,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {any} file File to check. * @return {boolean} Whether the file is downloadable. */ - isFileDownloadable(file: any) : boolean { + isFileDownloadable(file: any): boolean { return file.type === 'file'; } @@ -397,10 +400,11 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {boolean} [ignoreCache] True if it should ignore cached data (it will always fail in offline or server down). * @return {Promise} Promise resolved when loaded. */ - loadContents(module: any, courseId: number, ignoreCache?: boolean) : Promise { + loadContents(module: any, courseId: number, ignoreCache?: boolean): Promise { if (this.isResource) { return this.courseProvider.loadModuleContents(module, courseId, undefined, false, ignoreCache); } + return Promise.resolve(); } @@ -432,8 +436,8 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the module has been downloaded. Data returned is not reliable. */ - prefetchPackage(module: any, courseId: number, single: boolean, downloadFn: prefetchFunction, siteId?: string, ...args) : - Promise { + prefetchPackage(module: any, courseId: number, single: boolean, downloadFn: prefetchFunction, siteId?: string, ...args: any[]) + : Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (!this.appProvider.isOnline()) { @@ -469,8 +473,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {string} [extra] Extra data to store. * @return {Promise} Promise resolved when done. */ - setDownloaded(id: number, siteId?: string, extra?: string) : Promise { + setDownloaded(id: number, siteId?: string, extra?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); + return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.DOWNLOADED, this.component, id, extra); } @@ -481,8 +486,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when done. */ - setDownloading(id: number, siteId?: string) : Promise { + setDownloading(id: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); + return this.filepoolProvider.storePackageStatus(siteId, CoreConstants.DOWNLOADING, this.component, id); } @@ -494,8 +500,9 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Rejected promise. */ - setPreviousStatusAndReject(id: number, error?: any, siteId?: string) : Promise { + setPreviousStatusAndReject(id: number, error?: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); + return this.filepoolProvider.setPackagePreviousStatus(siteId, this.component, id).then(() => { return Promise.reject(error); }); @@ -508,7 +515,7 @@ export class CoreCourseModulePrefetchHandlerBase implements CoreCourseModulePref * @param {number} courseId Course ID the module belongs to. * @return {Promise} Promise resolved when done. */ - removeFiles(module: any, courseId: number) : Promise { + removeFiles(module: any, courseId: number): Promise { return this.filepoolProvider.removeFilesByComponent(this.sitesProvider.getCurrentSiteId(), this.component, module.id); } } diff --git a/src/core/course/components/format/format.ts b/src/core/course/components/format/format.ts index 20d84c694..626218146 100644 --- a/src/core/course/components/format/format.ts +++ b/src/core/course/components/format/format.ts @@ -12,8 +12,10 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, ComponentFactoryResolver, ViewChild, ChangeDetectorRef, - SimpleChange, Output, EventEmitter } from '@angular/core'; +import { + Component, Input, OnInit, OnChanges, OnDestroy, ViewContainerRef, ComponentFactoryResolver, ViewChild, ChangeDetectorRef, + SimpleChange, Output, EventEmitter +} from '@angular/core'; import { TranslateService } from '@ngx-translate/core'; import { CoreEventsProvider } from '../../../../providers/events'; import { CoreLoggerProvider } from '../../../../providers/logger'; @@ -54,23 +56,23 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { // The component hasn't been initialized yet. Store the container. this.componentContainers['courseFormat'] = el; } - }; + } @ViewChild('courseSummary', { read: ViewContainerRef }) set courseSummary(el: ViewContainerRef) { this.createComponent('courseSummary', this.cfDelegate.getCourseSummaryComponent(this.course), el); - }; + } @ViewChild('sectionSelector', { read: ViewContainerRef }) set sectionSelector(el: ViewContainerRef) { this.createComponent('sectionSelector', this.cfDelegate.getSectionSelectorComponent(this.course), el); - }; + } @ViewChild('singleSection', { read: ViewContainerRef }) set singleSection(el: ViewContainerRef) { this.createComponent('singleSection', this.cfDelegate.getSingleSectionComponent(this.course), el); - }; + } @ViewChild('allSections', { read: ViewContainerRef }) set allSections(el: ViewContainerRef) { this.createComponent('allSections', this.cfDelegate.getAllSectionsComponent(this.course), el); - }; + } // Instances and containers of all the components that the handler could define. - protected componentContainers: {[type: string]: ViewContainerRef} = {}; - componentInstances: {[type: string]: any} = {}; + protected componentContainers: { [type: string]: ViewContainerRef } = {}; + componentInstances: { [type: string]: any } = {}; displaySectionSelector: boolean; selectedSection: any; @@ -94,10 +96,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { // Listen for section status changes. this.sectionStatusObserver = eventsProvider.on(CoreEventsProvider.SECTION_STATUS_CHANGED, (data) => { if (this.downloadEnabled && this.sections && this.sections.length && this.course && data.sectionId && - data.courseId == this.course.id) { - // Check if the affected section is being downloaded. If so, we don't update section status - // because it'll already be updated when the download finishes. - let downloadId = this.courseHelper.getSectionDownloadId({id: data.sectionId}); + data.courseId == this.course.id) { + // Check if the affected section is being downloaded. + // If so, we don't update section status because it'll already be updated when the download finishes. + const downloadId = this.courseHelper.getSectionDownloadId({ id: data.sectionId }); if (prefetchDelegate.isBeingDownloaded(downloadId)) { return; } @@ -105,7 +107,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { // Get the affected section. let section; for (let i = 0; i < this.sections.length; i++) { - let s = this.sections[i]; + const s = this.sections[i]; if (s.id === data.sectionId) { section = s; break; @@ -131,24 +133,24 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.displaySectionSelector = this.cfDelegate.displaySectionSelector(this.course); this.createComponent( - 'courseFormat', this.cfDelegate.getCourseFormatComponent(this.course), this.componentContainers['courseFormat']); + 'courseFormat', this.cfDelegate.getCourseFormatComponent(this.course), this.componentContainers['courseFormat']); } /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (changes.sections && this.sections) { if (!this.selectedSection) { // There is no selected section yet, calculate which one to load. if (this.initialSectionId || this.initialSectionNumber) { // We have an input indicating the section ID to load. Search the section. for (let i = 0; i < this.sections.length; i++) { - let section = this.sections[i]; + const section = this.sections[i]; if (section.id == this.initialSectionId || section.section == this.initialSectionNumber) { this.loaded = true; this.sectionChanged(section); @@ -166,7 +168,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { // We have a selected section, but the list has changed. Search the section in the list. let newSection; for (let i = 0; i < this.sections.length; i++) { - let section = this.sections[i]; + const section = this.sections[i]; if (this.compareSections(section, this.selectedSection)) { newSection = section; break; @@ -186,10 +188,10 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { } // Apply the changes to the components and call ngOnChanges if it exists. - for (let type in this.componentInstances) { - let instance = this.componentInstances[type]; + for (const type in this.componentInstances) { + const instance = this.componentInstances[type]; - for (let name in changes) { + for (const name in changes) { instance[name] = changes[name].currentValue; } @@ -207,7 +209,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * @param {ViewContainerRef} container The container to add the component to. * @return {boolean} Whether the component was successfully created. */ - protected createComponent(type: string, componentClass: any, container: ViewContainerRef) : boolean { + protected createComponent(type: string, componentClass: any, container: ViewContainerRef): boolean { if (!componentClass || !container) { // No component to instantiate or container doesn't exist right now. return false; @@ -236,8 +238,9 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { this.cdr.detectChanges(); // The instances are used in ngIf, tell Angular that something has changed. return true; - } catch(ex) { + } catch (ex) { this.logger.error('Error creating component', type, ex); + return false; } } @@ -247,8 +250,8 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * * @param {any} newSection The new selected section. */ - sectionChanged(newSection: any) { - let previousValue = this.selectedSection; + sectionChanged(newSection: any): void { + const previousValue = this.selectedSection; this.selectedSection = newSection; // If there is a component to render the current section, update its section. @@ -269,7 +272,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * @param {any} s2 Second section. * @return {boolean} Whether they're equal. */ - compareSections(s1: any, s2: any) : boolean { + compareSections(s1: any, s2: any): boolean { return s1 && s2 ? s1.id === s2.id : s1 === s2; } @@ -278,7 +281,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * * @param {boolean} refresh [description] */ - protected calculateSectionsStatus(refresh?: boolean) : void { + protected calculateSectionsStatus(refresh?: boolean): void { this.courseHelper.calculateSectionsStatus(this.sections, this.course.id, refresh).catch(() => { // Ignore errors (shouldn't happen). }); @@ -290,7 +293,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * @param {Event} e Click event. * @param {any} section Section to download. */ - prefetch(e: Event, section: any) : void { + prefetch(e: Event, section: any): void { e.preventDefault(); e.stopPropagation(); @@ -314,7 +317,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { * @param {boolean} [manual] Whether the prefetch was started manually or it was automatically started because all modules * are being downloaded. */ - protected prefetchSection(section: any, manual?: boolean) { + protected prefetchSection(section: any, manual?: boolean): void { this.courseHelper.prefetchSection(section, this.course.id, this.sections).catch((error) => { // Don't show error message if it's an automatic download. if (!manual) { @@ -328,7 +331,7 @@ export class CoreCourseFormatComponent implements OnInit, OnChanges, OnDestroy { /** * Component destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { if (this.sectionStatusObserver) { this.sectionStatusObserver.off(); } diff --git a/src/core/course/components/module-completion/module-completion.ts b/src/core/course/components/module-completion/module-completion.ts index 169363ca4..56b910978 100644 --- a/src/core/course/components/module-completion/module-completion.ts +++ b/src/core/course/components/module-completion/module-completion.ts @@ -48,7 +48,7 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (changes.completion && this.completion) { this.showStatus(); } @@ -59,7 +59,7 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { * * @param {Event} e The click event. */ - completionClicked(e: Event) : void { + completionClicked(e: Event): void { if (this.completion) { if (typeof this.completion.cmid == 'undefined' || this.completion.tracking !== 1) { return; @@ -68,7 +68,7 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { e.preventDefault(); e.stopPropagation(); - let modal = this.domUtils.showModalLoading(), + const modal = this.domUtils.showModalLoading(), params = { cmid: this.completion.cmid, completed: this.completion.state === 1 ? 0 : 1 @@ -92,9 +92,9 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { /** * Set image and description to show as completion icon. */ - protected showStatus() : void { + protected showStatus(): void { + const moduleName = this.moduleName || ''; let langKey, - moduleName = this.moduleName || '', image; if (this.completion.tracking === 1 && this.completion.state === 0) { @@ -132,18 +132,18 @@ export class CoreCourseModuleCompletionComponent implements OnChanges { langKey += '-override'; promise = this.userProvider.getProfile(this.completion.overrideby, this.completion.courseId, true).then( - (profile) => { - return { - overrideuser: profile.fullname, - modname: modNameFormatted - }; - }); + (profile) => { + return { + overrideuser: profile.fullname, + modname: modNameFormatted + }; + }); } else { promise = Promise.resolve(modNameFormatted); } return promise.then((translateParams) => { - this.completionDescription = this.translate.instant(langKey, {$a: translateParams}); + this.completionDescription = this.translate.instant(langKey, { $a: translateParams }); }); }); } diff --git a/src/core/course/components/module-description/module-description.ts b/src/core/course/components/module-description/module-description.ts index d10994df4..ccf534459 100644 --- a/src/core/course/components/module-description/module-description.ts +++ b/src/core/course/components/module-description/module-description.ts @@ -38,8 +38,10 @@ export class CoreCourseModuleDescriptionComponent { @Input() description: string; // The description to display. @Input() note?: string; // A note to display along with the description. @Input() component?: string; // Component for format text directive. - @Input() componentId?: string|number; // Component ID to use in conjunction with the component. - @Input() showFull?: string|boolean; // Whether to always display the full description. + @Input() componentId?: string | number; // Component ID to use in conjunction with the component. + @Input() showFull?: string | boolean; // Whether to always display the full description. - constructor() {} + constructor() { + // Nothing to do. + } } diff --git a/src/core/course/components/module/module.ts b/src/core/course/components/module/module.ts index 1353c4d64..b450093b1 100644 --- a/src/core/course/components/module/module.ts +++ b/src/core/course/components/module/module.ts @@ -39,7 +39,7 @@ export class CoreCourseModuleComponent implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { // Handler data must be defined. If it isn't, set it to prevent errors. if (this.module && !this.module.handlerData) { this.module.handlerData = {}; @@ -51,7 +51,7 @@ export class CoreCourseModuleComponent implements OnInit { * * @param {Event} event Click event. */ - moduleClicked(event: Event) { + moduleClicked(event: Event): void { if (this.module.uservisible !== false && this.module.handlerData.action) { this.module.handlerData.action(event, this.navCtrl, this.module, this.courseId); } @@ -63,7 +63,7 @@ export class CoreCourseModuleComponent implements OnInit { * @param {Event} event Click event. * @param {CoreCourseModuleHandlerButton} button The clicked button. */ - buttonClicked(event: Event, button: CoreCourseModuleHandlerButton) { + buttonClicked(event: Event, button: CoreCourseModuleHandlerButton): void { if (button && button.action) { button.action(event, this.navCtrl, this.module, this.courseId); } diff --git a/src/core/course/components/unsupported-module/unsupported-module.ts b/src/core/course/components/unsupported-module/unsupported-module.ts index 7c4fca123..f2b70b175 100644 --- a/src/core/course/components/unsupported-module/unsupported-module.ts +++ b/src/core/course/components/unsupported-module/unsupported-module.ts @@ -31,12 +31,12 @@ export class CoreCourseUnsupportedModuleComponent implements OnInit { isSupportedByTheApp: boolean; moduleName: string; - constructor(private courseProvider: CoreCourseProvider, private moduleDelegate: CoreCourseModuleDelegate) {} + constructor(private courseProvider: CoreCourseProvider, private moduleDelegate: CoreCourseModuleDelegate) { } /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.isDisabledInSite = this.moduleDelegate.isModuleDisabledInSite(this.module.modname); this.isSupportedByTheApp = this.moduleDelegate.hasHandler(this.module.modname); this.moduleName = this.courseProvider.translateModuleName(this.module.modname); diff --git a/src/core/course/course.module.ts b/src/core/course/course.module.ts index 6aec264d9..9154daaab 100644 --- a/src/core/course/course.module.ts +++ b/src/core/course/course.module.ts @@ -22,7 +22,7 @@ import { CoreCourseOptionsDelegate } from './providers/options-delegate'; import { CoreCourseFormatDefaultHandler } from './providers/default-format'; import { CoreCourseFormatSingleActivityModule } from './formats/singleactivity/singleactivity.module'; import { CoreCourseFormatSocialModule } from './formats/social/social.module'; -import { CoreCourseFormatTopicsModule} from './formats/topics/topics.module'; +import { CoreCourseFormatTopicsModule } from './formats/topics/topics.module'; import { CoreCourseFormatWeeksModule } from './formats/weeks/weeks.module'; @NgModule({ diff --git a/src/core/course/formats/singleactivity/components/format.ts b/src/core/course/formats/singleactivity/components/format.ts index 571f258f0..001e0e692 100644 --- a/src/core/course/formats/singleactivity/components/format.ts +++ b/src/core/course/formats/singleactivity/components/format.ts @@ -12,8 +12,7 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Component, Input, OnChanges, ViewContainerRef, ComponentFactoryResolver, ChangeDetectorRef, - SimpleChange } from '@angular/core'; +import { Component, Input, OnChanges, ViewContainerRef, ComponentFactoryResolver, SimpleChange } from '@angular/core'; import { CoreLoggerProvider } from '../../../../../providers/logger'; import { CoreCourseModuleDelegate } from '../../../providers/module-delegate'; import { CoreCourseUnsupportedModuleComponent } from '../../../components/unsupported-module/unsupported-module'; @@ -37,17 +36,17 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges { protected componentInstance: any; constructor(logger: CoreLoggerProvider, private viewRef: ViewContainerRef, private factoryResolver: ComponentFactoryResolver, - private cdr: ChangeDetectorRef, private moduleDelegate: CoreCourseModuleDelegate) { + private moduleDelegate: CoreCourseModuleDelegate) { this.logger = logger.getInstance('CoreCourseFormatSingleActivityComponent'); } /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (this.course && this.sections && this.sections.length) { // In single activity the module should only have 1 section and 1 module. Get the module. - let module = this.sections[0] && this.sections[0].modules && this.sections[0].modules[0]; + const module = this.sections[0] && this.sections[0].modules && this.sections[0].modules[0]; if (module && !this.componentInstance) { // We haven't created the component yet. Create it now. this.createComponent(module); @@ -55,11 +54,11 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges { if (this.componentInstance && this.componentInstance.ngOnChanges) { // Call ngOnChanges of the component. - let newChanges: {[name: string]: SimpleChange} = {}; + const newChanges: { [name: string]: SimpleChange } = {}; // Check if course has changed. if (changes.course) { - newChanges.course = changes.course + newChanges.course = changes.course; this.componentInstance.course = this.course; } @@ -69,7 +68,7 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges { currentValue: module, firstChange: changes.sections.firstChange, previousValue: this.module, - isFirstChange: () => { + isFirstChange: (): boolean => { return newChanges.module.firstChange; } }; @@ -90,8 +89,8 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges { * @param {any} module The module. * @return {boolean} Whether the component was successfully created. */ - protected createComponent(module: any) : boolean { - let componentClass = this.moduleDelegate.getMainComponent(this.course, module) || CoreCourseUnsupportedModuleComponent; + protected createComponent(module: any): boolean { + const componentClass = this.moduleDelegate.getMainComponent(this.course, module) || CoreCourseUnsupportedModuleComponent; if (!componentClass) { // No component to instantiate. return false; @@ -108,11 +107,10 @@ export class CoreCourseFormatSingleActivityComponent implements OnChanges { this.componentInstance.courseId = this.course.id; this.componentInstance.module = module; - // this.cdr.detectChanges(); // The instances are used in ngIf, tell Angular that something has changed. - return true; - } catch(ex) { + } catch (ex) { this.logger.error('Error creating component', ex); + return false; } } diff --git a/src/core/course/formats/singleactivity/providers/handler.ts b/src/core/course/formats/singleactivity/providers/handler.ts index 396c94971..1f232fe78 100644 --- a/src/core/course/formats/singleactivity/providers/handler.ts +++ b/src/core/course/formats/singleactivity/providers/handler.ts @@ -23,14 +23,16 @@ import { CoreCourseFormatSingleActivityComponent } from '../components/format'; export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHandler { name = 'singleactivity'; - constructor() {} + constructor() { + // Nothing to do. + } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -40,7 +42,7 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa * @param {any} course The course to check. * @type {boolean} Whether it can view all sections. */ - canViewAllSections(course: any) : boolean { + canViewAllSections(course: any): boolean { return false; } @@ -52,10 +54,11 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa * @param {any[]} [sections] List of sections. * @return {string} Title. */ - getCourseTitle(course: any, sections?: any[]) : string { + getCourseTitle(course: any, sections?: any[]): string { if (sections && sections[0] && sections[0].modules && sections[0].modules[0]) { return sections[0].modules[0].name; } + return course.fullname || ''; } @@ -65,7 +68,7 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa * @param {any} course The course to check. * @type {boolean} Whether the default section selector should be displayed. */ - displaySectionSelector(course: any) : boolean { + displaySectionSelector(course: any): boolean { return false; } @@ -77,7 +80,7 @@ export class CoreCourseFormatSingleActivityHandler implements CoreCourseFormatHa * @param {any} course The course to render. * @return {any} The component to use, undefined if not found. */ - getCourseFormatComponent(course: any) : any { + getCourseFormatComponent(course: any): any { return CoreCourseFormatSingleActivityComponent; } } diff --git a/src/core/course/formats/topics/providers/handler.ts b/src/core/course/formats/topics/providers/handler.ts index 18d2c1759..bfac65132 100644 --- a/src/core/course/formats/topics/providers/handler.ts +++ b/src/core/course/formats/topics/providers/handler.ts @@ -22,14 +22,16 @@ import { CoreCourseFormatHandler } from '../../../providers/format-delegate'; export class CoreCourseFormatTopicsHandler implements CoreCourseFormatHandler { name = 'topics'; - constructor() {} + constructor() { + // Nothing to do. + } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } } diff --git a/src/core/course/formats/weeks/providers/handler.ts b/src/core/course/formats/weeks/providers/handler.ts index b6f5beff7..6feca32cd 100644 --- a/src/core/course/formats/weeks/providers/handler.ts +++ b/src/core/course/formats/weeks/providers/handler.ts @@ -24,14 +24,14 @@ import { CoreConstants } from '../../../../constants'; export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler { name = 'weeks'; - constructor(private timeUtils: CoreTimeUtilsProvider) {} + constructor(private timeUtils: CoreTimeUtilsProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -42,8 +42,8 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler { * @param {any[]} sections List of sections. * @return {any|Promise} Current section (or promise resolved with current section). */ - getCurrentSection(course: any, sections: any[]) : any|Promise { - let now = this.timeUtils.timestamp(); + getCurrentSection(course: any, sections: any[]): any | Promise { + const now = this.timeUtils.timestamp(); if (now < course.startdate || (course.enddate && now > course.enddate)) { // Course hasn't started yet or it has ended already. Return the first section. @@ -51,12 +51,12 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler { } for (let i = 0; i < sections.length; i++) { - let section = sections[i]; + const section = sections[i]; if (typeof section.section == 'undefined' || section.section < 1) { continue; } - let dates = this.getSectionDates(section, course.startdate); + const dates = this.getSectionDates(section, course.startdate); if ((now >= dates.start) && (now < dates.end)) { return section; } @@ -73,15 +73,16 @@ export class CoreCourseFormatWeeksHandler implements CoreCourseFormatHandler { * @param {number} startDate The course start date (in seconds). * @return {{start: number, end: number}} An object with the start and end date of the section. */ - protected getSectionDates(section: any, startDate: number) : {start: number, end: number} { + protected getSectionDates(section: any, startDate: number): { start: number, end: number } { // Hack alert. We add 2 hours to avoid possible DST problems. (e.g. we go into daylight savings and the date changes). startDate = startDate + 7200; - let dates = { + const dates = { start: startDate + (CoreConstants.SECONDS_WEEK * (section.section - 1)), end: 0 }; dates.end = dates.start + CoreConstants.SECONDS_WEEK; + return dates; } } diff --git a/src/core/course/pages/section/section.ts b/src/core/course/pages/section/section.ts index 27c0c2aa3..b33a10b48 100644 --- a/src/core/course/pages/section/section.ts +++ b/src/core/course/pages/section/section.ts @@ -22,13 +22,14 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; import { CoreCourseProvider } from '../../providers/course'; import { CoreCourseHelperProvider } from '../../providers/helper'; import { CoreCourseFormatDelegate } from '../../providers/format-delegate'; +import { CoreCourseModulePrefetchDelegate } from '../../providers/module-prefetch-delegate'; import { CoreCourseOptionsDelegate, CoreCourseOptionsHandlerToDisplay } from '../../providers/options-delegate'; import { CoreCoursesProvider } from '../../../courses/providers/courses'; /** * Page that displays the list of courses the user is enrolled in. */ -@IonicPage({segment: 'core-course-section'}) +@IonicPage({ segment: 'core-course-section' }) @Component({ selector: 'page-core-course-section', templateUrl: 'section.html', @@ -44,7 +45,7 @@ export class CoreCourseSectionPage implements OnDestroy { courseHandlers: CoreCourseOptionsHandlerToDisplay[]; dataLoaded: boolean; downloadEnabled: boolean; - downloadEnabledIcon: string = 'square-outline'; // Disabled by default. + downloadEnabledIcon = 'square-outline'; // Disabled by default. prefetchCourseData = { prefetchCourseIcon: 'spinner' }; @@ -57,7 +58,8 @@ export class CoreCourseSectionPage implements OnDestroy { private courseFormatDelegate: CoreCourseFormatDelegate, private courseOptionsDelegate: CoreCourseOptionsDelegate, private translate: TranslateService, private courseHelper: CoreCourseHelperProvider, eventsProvider: CoreEventsProvider, private textUtils: CoreTextUtilsProvider, private coursesProvider: CoreCoursesProvider, - sitesProvider: CoreSitesProvider, private navCtrl: NavController) { + sitesProvider: CoreSitesProvider, private navCtrl: NavController, + private prefetchDelegate: CoreCourseModulePrefetchDelegate) { this.course = navParams.get('course'); this.sectionId = navParams.get('sectionId'); this.sectionNumber = navParams.get('sectionNumber'); @@ -82,9 +84,9 @@ export class CoreCourseSectionPage implements OnDestroy { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { - let module = this.navParams.get('module'); + const module = this.navParams.get('module'); if (module) { this.courseHelper.openModule(this.navCtrl, module, this.course.id, this.sectionId); } @@ -118,11 +120,11 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Fetch and load all the data required for the view. */ - protected loadData(refresh?: boolean) { + protected loadData(refresh?: boolean): Promise { // First of all, get the course because the data might have changed. return this.coursesProvider.getUserCourse(this.course.id).then((course) => { - let promises = [], - promise; + const promises = []; + let promise; this.course = course; @@ -148,10 +150,10 @@ export class CoreCourseSectionPage implements OnDestroy { section.formattedName = name; }); section.hasContent = this.courseHelper.sectionHasContent(section); + return section; }); - if (this.courseFormatDelegate.canViewAllSections(this.course)) { // Add a fake first section (all sections). this.sections.unshift({ @@ -181,7 +183,7 @@ export class CoreCourseSectionPage implements OnDestroy { * * @param {any} refresher Refresher. */ - doRefresh(refresher: any) { + doRefresh(refresher: any): void { this.invalidateData().finally(() => { this.loadData(true).finally(() => { refresher.complete(); @@ -192,7 +194,7 @@ export class CoreCourseSectionPage implements OnDestroy { /** * The completion of any of the modules have changed. */ - onCompletionChange() { + onCompletionChange(): void { this.invalidateData().finally(() => { this.refreshAfterCompletionChange(); }); @@ -201,16 +203,16 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Invalidate the data. */ - protected invalidateData() { - let promises = []; + protected invalidateData(): Promise { + const promises = []; promises.push(this.courseProvider.invalidateSections(this.course.id)); promises.push(this.coursesProvider.invalidateUserCourses()); promises.push(this.courseFormatDelegate.invalidateData(this.course, this.sections)); - // if ($scope.sections) { - // promises.push($mmCoursePrefetchDelegate.invalidateCourseUpdates(courseId)); - // } + if (this.sections) { + promises.push(this.prefetchDelegate.invalidateCourseUpdates(this.course.id)); + } return Promise.all(promises); } @@ -218,9 +220,9 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Refresh list after a completion change since there could be new activities. */ - protected refreshAfterCompletionChange() { + protected refreshAfterCompletionChange(): void { // Save scroll position to restore it once done. - let scrollElement = this.content.getScrollElement(), + const scrollElement = this.content.getScrollElement(), scrollTop = scrollElement.scrollTop || 0, scrollLeft = scrollElement.scrollLeft || 0; @@ -235,8 +237,10 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Determines the prefetch icon of the course. + * + * @return {Promise} Promise resolved when done. */ - protected determineCoursePrefetchIcon() { + protected determineCoursePrefetchIcon(): Promise { return this.courseHelper.getCourseStatusIcon(this.course.id).then((icon) => { this.prefetchCourseData.prefetchCourseIcon = icon; }); @@ -245,26 +249,26 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Prefetch the whole course. */ - prefetchCourse() { + prefetchCourse(): void { this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, this.sections, this.courseHandlers) - .then((downloaded) => { - if (downloaded && this.downloadEnabled) { - // Recalculate the status. - this.courseHelper.calculateSectionsStatus(this.sections, this.course.id).catch(() => { - // Ignore errors (shouldn't happen). - }); - } - }).catch((error) => { - if (!this.isDestroyed) { - this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); - } - }); + .then((downloaded) => { + if (downloaded && this.downloadEnabled) { + // Recalculate the status. + this.courseHelper.calculateSectionsStatus(this.sections, this.course.id).catch(() => { + // Ignore errors (shouldn't happen). + }); + } + }).catch((error) => { + if (!this.isDestroyed) { + this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); + } + }); } /** * Toggle download enabled. */ - toggleDownload() { + toggleDownload(): void { this.downloadEnabled = !this.downloadEnabled; this.downloadEnabledIcon = this.downloadEnabled ? 'checkbox-outline' : 'square-outline'; } @@ -272,7 +276,7 @@ export class CoreCourseSectionPage implements OnDestroy { /** * Page destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.isDestroyed = true; if (this.completionObserver) { this.completionObserver.off(); diff --git a/src/core/course/pages/unsupported-module/unsupported-module.ts b/src/core/course/pages/unsupported-module/unsupported-module.ts index 3cbe20d7c..7e8554f59 100644 --- a/src/core/course/pages/unsupported-module/unsupported-module.ts +++ b/src/core/course/pages/unsupported-module/unsupported-module.ts @@ -20,7 +20,7 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; /** * Page that displays info about an unsupported module. */ -@IonicPage({segment: 'core-course-unsupported-module'}) +@IonicPage({ segment: 'core-course-unsupported-module' }) @Component({ selector: 'page-core-course-unsupported-module', templateUrl: 'unsupported-module.html', @@ -35,7 +35,7 @@ export class CoreCourseUnsupportedModulePage { /** * Expand the description. */ - expandDescription() { + expandDescription(): void { this.textUtils.expandText(this.translate.instant('core.description'), this.module.description); } } diff --git a/src/core/course/providers/course.ts b/src/core/course/providers/course.ts index f26cfb569..b814689d7 100644 --- a/src/core/course/providers/course.ts +++ b/src/core/course/providers/course.ts @@ -27,9 +27,9 @@ import { CoreConstants } from '../../constants'; */ @Injectable() export class CoreCourseProvider { - public static ALL_SECTIONS_ID = -1; - public static ACCESS_GUEST = 'courses_access_guest'; - public static ACCESS_DEFAULT = 'courses_access_default'; + static ALL_SECTIONS_ID = -1; + static ACCESS_GUEST = 'courses_access_guest'; + static ACCESS_DEFAULT = 'courses_access_default'; protected ROOT_CACHE_KEY = 'mmCourse:'; @@ -88,10 +88,10 @@ export class CoreCourseProvider { * @param {number} courseId Course ID. * @param {any} completion Completion status of the module. */ - checkModuleCompletion(courseId: number, completion: any) : void { + checkModuleCompletion(courseId: number, completion: any): void { if (completion && completion.tracking === 2 && completion.state === 0) { this.invalidateSections(courseId).finally(() => { - this.eventsProvider.trigger(CoreEventsProvider.COMPLETION_MODULE_VIEWED, {courseId: courseId}); + this.eventsProvider.trigger(CoreEventsProvider.COMPLETION_MODULE_VIEWED, { courseId: courseId }); }); } } @@ -102,7 +102,7 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when all status are cleared. */ - clearAllCoursesStatus(siteId?: string) : Promise { + clearAllCoursesStatus(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { this.logger.debug('Clear all course status for site ' + site.id); @@ -120,13 +120,13 @@ export class CoreCourseProvider { * @param {number} [userId] User ID. If not defined, current user. * @return {Promise} Promise resolved with the completion statuses: object where the key is module ID. */ - getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number) : Promise { + getActivitiesCompletionStatus(courseId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); this.logger.debug(`Getting completion status for user ${userId} in course ${courseId}`); - let params = { + const params = { courseid: courseId, userid: userId }, @@ -138,6 +138,7 @@ export class CoreCourseProvider { if (data && data.statuses) { return this.utils.arrayToObject(data.statuses, 'cmid'); } + return Promise.reject(null); }); }); @@ -150,7 +151,7 @@ export class CoreCourseProvider { * @param {number} userId User ID. * @return {string} Cache key. */ - protected getActivitiesCompletionCacheKey(courseId: number, userId: number) : string { + protected getActivitiesCompletionCacheKey(courseId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'activitiescompletion:' + courseId + ':' + userId; } @@ -161,12 +162,13 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the data. */ - getCourseStatusData(courseId: number, siteId?: string) : Promise { + getCourseStatusData(courseId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.COURSE_STATUS_TABLE, {id: courseId}).then((entry) => { + return site.getDb().getRecord(this.COURSE_STATUS_TABLE, { id: courseId }).then((entry) => { if (!entry) { return Promise.reject(null); } + return entry; }); }); @@ -179,7 +181,7 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the status. */ - getCourseStatus(courseId: number, siteId?: string) : Promise { + getCourseStatus(courseId: number, siteId?: string): Promise { return this.getCourseStatusData(courseId, siteId).then((entry) => { return entry.status || CoreConstants.NOT_DOWNLOADED; }).catch(() => { @@ -199,7 +201,7 @@ export class CoreCourseProvider { * @return {Promise} Promise resolved with the module. */ getModule(moduleId: number, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean, - siteId?: string) : Promise { + siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); let promise; @@ -222,7 +224,7 @@ export class CoreCourseProvider { // We have courseId, we can use core_course_get_contents for compatibility. this.logger.debug(`Getting module ${moduleId} in course ${courseId}`); - let params = { + const params = { courseid: courseId, options: [ { @@ -253,15 +255,17 @@ export class CoreCourseProvider { return this.getSections(courseId, false, false, preSets, siteId); }).then((sections) => { for (let i = 0; i < sections.length; i++) { - let section = sections[i]; + const section = sections[i]; for (let j = 0; j < section.modules.length; j++) { - let module = section.modules[j]; + const module = section.modules[j]; if (module.id == moduleId) { module.course = courseId; + return module; } } } + return Promise.reject(null); }); }); @@ -274,9 +278,9 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the module's info. */ - getModuleBasicInfo(moduleId: number, siteId?: string) : Promise { + getModuleBasicInfo(moduleId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let params = { + const params = { cmid: moduleId }, preSets = { @@ -284,11 +288,12 @@ export class CoreCourseProvider { }; return site.read('core_course_get_course_module', params, preSets).then((response) => { - if (response.warnings && response.warnings.length) { + if (response.warnings && response.warnings.length) { return Promise.reject(response.warnings[0]); } else if (response.cm) { return response.cm; } + return Promise.reject(null); }); }); @@ -301,9 +306,9 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the module's grade info. */ - getModuleBasicGradeInfo(moduleId: number, siteId?: string) : Promise { + getModuleBasicGradeInfo(moduleId: number, siteId?: string): Promise { return this.getModuleBasicInfo(moduleId, siteId).then((info) => { - let grade = { + const grade = { advancedgrading: info.advancedgrading || false, grade: info.grade || false, gradecat: info.gradecat || false, @@ -315,6 +320,7 @@ export class CoreCourseProvider { if (grade.grade !== false || grade.advancedgrading !== false || grade.outcomes !== false) { return grade; } + return false; }); } @@ -327,9 +333,9 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the module's info. */ - getModuleBasicInfoByInstance(id: number, module: string, siteId?: string) : Promise { + getModuleBasicInfoByInstance(id: number, module: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let params = { + const params = { instance: id, module: module }, @@ -338,11 +344,12 @@ export class CoreCourseProvider { }; return site.read('core_course_get_course_module_by_instance', params, preSets).then((response) => { - if (response.warnings && response.warnings.length) { + if (response.warnings && response.warnings.length) { return Promise.reject(response.warnings[0]); } else if (response.cm) { return response.cm; } + return Promise.reject(null); }); }); @@ -355,7 +362,7 @@ export class CoreCourseProvider { * @param {string} module Name of the module. E.g. 'glossary'. * @return {string} Cache key. */ - protected getModuleBasicInfoByInstanceCacheKey(id: number, module: string) : string { + protected getModuleBasicInfoByInstanceCacheKey(id: number, module: string): string { return this.ROOT_CACHE_KEY + 'moduleByInstance:' + module + ':' + id; } @@ -365,7 +372,7 @@ export class CoreCourseProvider { * @param {number} moduleId Module ID. * @return {string} Cache key. */ - protected getModuleCacheKey(moduleId: number) : string { + protected getModuleCacheKey(moduleId: number): string { return this.ROOT_CACHE_KEY + 'module:' + moduleId; } @@ -375,7 +382,7 @@ export class CoreCourseProvider { * @param {string} moduleName The module name. * @return {string} The IMG src. */ - getModuleIconSrc(moduleName: string) : string { + getModuleIconSrc(moduleName: string): string { if (this.CORE_MODULES.indexOf(moduleName) < 0) { moduleName = 'external-tool'; } @@ -390,7 +397,7 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the section ID. */ - getModuleSectionId(moduleId: number, siteId?: string) : Promise { + getModuleSectionId(moduleId: number, siteId?: string): Promise { // Try to get the section using getModuleBasicInfo. return this.getModuleBasicInfo(moduleId, siteId).then((module) => { return module.section; @@ -408,7 +415,7 @@ export class CoreCourseProvider { * @return {Promise} Promise resolved with the section. */ getSection(courseId: number, sectionId?: number, excludeModules?: boolean, excludeContents?: boolean, siteId?: string) - : Promise { + : Promise { if (sectionId < 0) { return Promise.reject('Invalid section ID'); @@ -436,30 +443,30 @@ export class CoreCourseProvider { * @return {Promise} The reject contains the error message, else contains the sections. */ getSections(courseId?: number, excludeModules?: boolean, excludeContents?: boolean, preSets?: CoreSiteWSPreSets, - siteId?: string) : Promise { + siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { preSets = preSets || {}; preSets.cacheKey = this.getSectionsCacheKey(courseId); preSets.getCacheUsingCacheKey = true; // This is to make sure users don't lose offline access when updating. - let params = { - courseid: courseId, - options: [ - { - name: 'excludemodules', - value: excludeModules ? 1 : 0 - }, - { - name: 'excludecontents', - value: excludeContents ? 1 : 0 - } - ] - }; + const params = { + courseid: courseId, + options: [ + { + name: 'excludemodules', + value: excludeModules ? 1 : 0 + }, + { + name: 'excludecontents', + value: excludeContents ? 1 : 0 + } + ] + }; return site.read('core_course_get_contents', params, preSets).then((sections) => { - let siteHomeId = site.getSiteHomeId(), - showSections = true; + const siteHomeId = site.getSiteHomeId(); + let showSections = true; if (courseId == siteHomeId) { showSections = site.getStoredConfig('numsections'); @@ -481,7 +488,7 @@ export class CoreCourseProvider { * @param {number} courseId Course ID. * @return {string} Cache key. */ - protected getSectionsCacheKey(courseId) : string { + protected getSectionsCacheKey(courseId: number): string { return this.ROOT_CACHE_KEY + 'sections:' + courseId; } @@ -491,8 +498,8 @@ export class CoreCourseProvider { * @param {any[]} sections Sections. * @return {any[]} Modules. */ - getSectionsModules(sections: any[]) : any[] { - if (!sections || !sections.length) { + getSectionsModules(sections: any[]): any[] { + if (!sections || !sections.length) { return []; } @@ -502,6 +509,7 @@ export class CoreCourseProvider { modules = modules.concat(section.modules); } }); + return modules; } @@ -512,7 +520,7 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateModule(moduleId: number, siteId?: string) : Promise { + invalidateModule(moduleId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getModuleCacheKey(moduleId)); }); @@ -526,7 +534,7 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateModuleByInstance(id: number, module: string, siteId?: string) : Promise { + invalidateModuleByInstance(id: number, module: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getModuleBasicInfoByInstanceCacheKey(id, module)); }); @@ -540,9 +548,9 @@ export class CoreCourseProvider { * @param {number} [userId] User ID. If not defined, current user. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateSections(courseId: number, siteId?: string, userId?: number) : Promise { + invalidateSections(courseId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let promises = [], + const promises = [], siteHomeId = site.getSiteHomeId(); userId = userId || site.getUserId(); @@ -552,6 +560,7 @@ export class CoreCourseProvider { if (courseId == siteHomeId) { promises.push(site.invalidateConfig()); } + return Promise.all(promises); }); } @@ -568,7 +577,7 @@ export class CoreCourseProvider { * @return {Promise} Promise resolved when loaded. */ loadModuleContents(module: any, courseId?: number, sectionId?: number, preferCache?: boolean, ignoreCache?: boolean, - siteId?: string) : Promise { + siteId?: string): Promise { if (!ignoreCache && module.contents && module.contents.length) { // Already loaded. return Promise.resolve(); @@ -587,10 +596,11 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the WS call is successful. */ - logView(courseId: number, sectionNumber?: number, siteId?: string) : Promise { - let params: any = { + logView(courseId: number, sectionNumber?: number, siteId?: string): Promise { + const params: any = { courseid: courseId }; + if (typeof sectionNumber != 'undefined') { params.sectionnumber = sectionNumber; } @@ -600,7 +610,7 @@ export class CoreCourseProvider { if (!response.status) { return Promise.reject(null); } - }) + }); }); } @@ -611,13 +621,13 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the status is changed. Resolve param: new status. */ - setCoursePreviousStatus(courseId: number, siteId?: string) : Promise { + setCoursePreviousStatus(courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); this.logger.debug(`Set previous status for course ${courseId} in site ${siteId}`); return this.sitesProvider.getSite(siteId).then((site) => { - let db = site.getDb(), + const db = site.getDb(), newData: any = {}; // Get current stored data. @@ -631,9 +641,10 @@ export class CoreCourseProvider { newData.downloadTime = entry.previousDownloadTime; } - return db.updateRecords(this.COURSE_STATUS_TABLE, newData, {id: courseId}).then(() => { + return db.updateRecords(this.COURSE_STATUS_TABLE, newData, { id: courseId }).then(() => { // Success updating, trigger event. this.triggerCourseStatusChanged(courseId, newData.status, siteId); + return newData.status; }); }); @@ -648,8 +659,8 @@ export class CoreCourseProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the status is stored. */ - setCourseStatus(courseId: number, status: string, siteId?: string) : Promise { - siteId = siteId || this.sitesProvider.getCurrentSiteId() + setCourseStatus(courseId: number, status: string, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); this.logger.debug(`Set status '${status}' for course ${courseId} in site ${siteId}`); @@ -669,7 +680,7 @@ export class CoreCourseProvider { downloadTime = entry.downloadTime; previousDownloadTime = entry.previousDownloadTime; } else { - // downloadTime will be updated, store current time as previous. + // The downloadTime will be updated, store current time as previous. previousDownloadTime = entry.downloadTime; } @@ -679,7 +690,7 @@ export class CoreCourseProvider { }).then((previousStatus) => { if (previousStatus != status) { // Status has changed, update it. - let data = { + const data = { id: courseId, status: status, previous: previousStatus, @@ -688,7 +699,7 @@ export class CoreCourseProvider { previousDownloadTime: previousDownloadTime }; - return site.getDb().insertOrUpdateRecord(this.COURSE_STATUS_TABLE, data, {id: courseId}); + return site.getDb().insertOrUpdateRecord(this.COURSE_STATUS_TABLE, data, { id: courseId }); } }).then(() => { // Success inserting, trigger event. @@ -703,7 +714,7 @@ export class CoreCourseProvider { * @param {string} moduleName The module name. * @return {string} Translated name. */ - translateModuleName(moduleName: string) : string { + translateModuleName(moduleName: string): string { if (this.CORE_MODULES.indexOf(moduleName) < 0) { moduleName = 'external-tool'; } @@ -721,7 +732,7 @@ export class CoreCourseProvider { * @param {string} status New course status. * @param {string} [siteId] Site ID. If not defined, current site. */ - protected triggerCourseStatusChanged(courseId: number, status: string, siteId?: string) : void { + protected triggerCourseStatusChanged(courseId: number, status: string, siteId?: string): void { this.eventsProvider.trigger(CoreEventsProvider.COURSE_STATUS_CHANGED, { courseId: courseId, status: status diff --git a/src/core/course/providers/default-format.ts b/src/core/course/providers/default-format.ts index 6a8b294e4..7112efa56 100644 --- a/src/core/course/providers/default-format.ts +++ b/src/core/course/providers/default-format.ts @@ -25,14 +25,14 @@ import { CoreCourseProvider } from './course'; export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { name = 'default'; - constructor(private coursesProvider: CoreCoursesProvider) {} + constructor(private coursesProvider: CoreCoursesProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled() : boolean|Promise { + isEnabled(): boolean | Promise { return true; } @@ -42,7 +42,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * @param {any} course The course. * @return {string} Title. */ - getCourseTitle?(course: any) : string { + getCourseTitle?(course: any): string { return course.fullname || ''; } @@ -52,7 +52,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * @param {any} course The course to check. * @type {boolean} Whether it can view all sections. */ - canViewAllSections(course: any) : boolean { + canViewAllSections(course: any): boolean { return true; } @@ -62,7 +62,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * @param {any} course The course to check. * @type {boolean} Whether the default section selector should be displayed. */ - displaySectionSelector(course: any) : boolean { + displaySectionSelector(course: any): boolean { return true; } @@ -73,16 +73,16 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * @param {any[]} sections List of sections. * @return {any|Promise} Current section (or promise resolved with current section). */ - getCurrentSection(course: any, sections: any[]) : any|Promise { + getCurrentSection(course: any, sections: any[]): any | Promise { // We need the "marker" to determine the current section. return this.coursesProvider.getCoursesByField('id', course.id).catch(() => { // Ignore errors. }).then((courses) => { if (courses && courses[0]) { // Find the marked section. - let course = courses[0]; + const course = courses[0]; for (let i = 0; i < sections.length; i++) { - let section = sections[i]; + const section = sections[i]; if (section.section == course.marker) { return section; } @@ -91,7 +91,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { // Marked section not found or we couldn't retrieve the marker. Return the first section. for (let i = 0; i < sections.length; i++) { - let section = sections[i]; + const section = sections[i]; if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) { return section; } @@ -108,7 +108,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * @param {any[]} sections List of sections. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateData(course: any, sections: any[]) : Promise { + invalidateData(course: any, sections: any[]): Promise { return this.coursesProvider.invalidateCoursesByField('id', course.id); } @@ -122,7 +122,7 @@ export class CoreCourseFormatDefaultHandler implements CoreCourseFormatHandler { * @param {any} course The course to open. It should contain a "format" attribute. * @return {Promise} Promise resolved when done. */ - openCourse(navCtrl: NavController, course: any) : Promise { - return navCtrl.push('CoreCourseSectionPage', {course: course}); + openCourse(navCtrl: NavController, course: any): Promise { + return navCtrl.push('CoreCourseSectionPage', { course: course }); } } diff --git a/src/core/course/providers/format-delegate.ts b/src/core/course/providers/format-delegate.ts index d4d5d38fb..27553d52b 100644 --- a/src/core/course/providers/format-delegate.ts +++ b/src/core/course/providers/format-delegate.ts @@ -136,7 +136,7 @@ export class CoreCourseFormatDelegate extends CoreDelegate { protected featurePrefix = 'CoreCourseFormatHandler_'; constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, - protected defaultHandler: CoreCourseFormatDefaultHandler) { + protected defaultHandler: CoreCourseFormatDefaultHandler) { super('CoreCoursesCourseFormatDelegate', loggerProvider, sitesProvider, eventsProvider); } @@ -170,7 +170,7 @@ export class CoreCourseFormatDelegate extends CoreDelegate { * @return {any} Function returned value or default value. */ protected executeFunction(format: string, fnName: string, params?: any[]): any { - let handler = this.enabledHandlers[format]; + const handler = this.enabledHandlers[format]; if (handler && handler[fnName]) { return handler[fnName].apply(handler, params); } else if (this.defaultHandler[fnName]) { diff --git a/src/core/course/providers/helper.ts b/src/core/course/providers/helper.ts index e8315122c..8eee00931 100644 --- a/src/core/course/providers/helper.ts +++ b/src/core/course/providers/helper.ts @@ -107,14 +107,14 @@ export type CoreCourseCoursesProgress = { @Injectable() export class CoreCourseHelperProvider { - protected courseDwnPromises: {[s: string]: {[id: number]: Promise}} = {}; + protected courseDwnPromises: { [s: string]: { [id: number]: Promise } } = {}; constructor(private courseProvider: CoreCourseProvider, private domUtils: CoreDomUtilsProvider, - private moduleDelegate: CoreCourseModuleDelegate, private prefetchDelegate: CoreCourseModulePrefetchDelegate, - private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, - private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, - private utils: CoreUtilsProvider, private translate: TranslateService, private loginHelper: CoreLoginHelperProvider, - private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider) {} + private moduleDelegate: CoreCourseModuleDelegate, private prefetchDelegate: CoreCourseModulePrefetchDelegate, + private filepoolProvider: CoreFilepoolProvider, private sitesProvider: CoreSitesProvider, + private textUtils: CoreTextUtilsProvider, private timeUtils: CoreTimeUtilsProvider, + private utils: CoreUtilsProvider, private translate: TranslateService, private loginHelper: CoreLoginHelperProvider, + private courseOptionsDelegate: CoreCourseOptionsDelegate, private siteHomeProvider: CoreSiteHomeProvider) { } /** * This function treats every module on the sections provided to load the handler data, treat completion @@ -125,7 +125,7 @@ export class CoreCourseHelperProvider { * @param {any[]} [completionStatus] List of completion status. * @return {boolean} Whether the sections have content. */ - addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any) { + addHandlerDataForModules(sections: any[], courseId: number, completionStatus?: any): boolean { let hasContent = false; sections.forEach((section) => { @@ -157,7 +157,7 @@ export class CoreCourseHelperProvider { * @param {boolean} [refresh] True if it shouldn't use module status cache (slower). * @return {Promise} Promise resolved when the status is calculated. */ - calculateSectionStatus(section: any, courseId: number, refresh?: boolean) : Promise { + calculateSectionStatus(section: any, courseId: number, refresh?: boolean): Promise { if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { return Promise.reject(null); @@ -199,10 +199,10 @@ export class CoreCourseHelperProvider { * @param {boolean} [refresh] True if it shouldn't use module status cache (slower). * @return {Promise} Promise resolved when the states are calculated. */ - calculateSectionsStatus(sections: any[], courseId: number, refresh?: boolean) : Promise { + calculateSectionsStatus(sections: any[], courseId: number, refresh?: boolean): Promise { + const promises = []; let allSectionsSection, - allSectionsStatus, - promises = []; + allSectionsStatus; sections.forEach((section) => { if (section.id === CoreCourseProvider.ALL_SECTIONS_ID) { @@ -248,9 +248,10 @@ export class CoreCourseHelperProvider { */ confirmAndPrefetchCourse(iconData: any, course: any, sections?: any[], courseHandlers?: CoreCourseOptionsHandlerToDisplay[]) : Promise { - let initialIcon = iconData.prefetchCourseIcon, - promise, + + const initialIcon = iconData.prefetchCourseIcon, siteId = this.sitesProvider.getCurrentSiteId(); + let promise; iconData.prefetchCourseIcon = 'spinner'; @@ -278,16 +279,17 @@ export class CoreCourseHelperProvider { // Download successful. return true; }); - }, (error) : any => { + }, (error): any => { // User cancelled or there was an error calculating the size. iconData.prefetchCourseIcon = initialIcon; if (error) { return Promise.reject(error); } + return false; }); }); - }; + } /** * Confirm and prefetches a list of courses. @@ -296,18 +298,18 @@ export class CoreCourseHelperProvider { * @param {Function} [onProgress] Function to call everytime a course is downloaded. * @return {Promise} Resolved with true when downloaded, resolved with false if user cancels, rejected if error. */ - confirmAndPrefetchCourses(courses: any[], onProgress?: (data: CoreCourseCoursesProgress) => void) : Promise { + confirmAndPrefetchCourses(courses: any[], onProgress?: (data: CoreCourseCoursesProgress) => void): Promise { const siteId = this.sitesProvider.getCurrentSiteId(); // Confirm the download without checking size because it could take a while. return this.domUtils.showConfirm(this.translate.instant('core.areyousure')).then(() => { - let promises = [], - total = courses.length, - count = 0; + const promises = [], + total = courses.length; + let count = 0; courses.forEach((course) => { - let subPromises = [], - sections, + const subPromises = []; + let sections, handlers, success = true; @@ -323,19 +325,20 @@ export class CoreCourseHelperProvider { return this.prefetchCourse(course, sections, handlers, siteId); }).catch((error) => { success = false; + return Promise.reject(error); }).finally(() => { // Course downloaded or failed, notify the progress. count++; if (onProgress) { - onProgress({count: count, total: total, courseId: course.id, success: success}); + onProgress({ count: count, total: total, courseId: course.id, success: success }); } })); }); if (onProgress) { // Notify the start of the download. - onProgress({count: 0, total: total, success: true}); + onProgress({ count: 0, total: total, success: true }); } return this.utils.allPromises(promises).then(() => { @@ -345,7 +348,7 @@ export class CoreCourseHelperProvider { // User cancelled. return false; }); - }; + } /** * Show confirmation dialog and then remove a module files. @@ -354,7 +357,7 @@ export class CoreCourseHelperProvider { * @param {number} courseId Course ID the module belongs to. * @return {Promise} Promise resolved when done. */ - confirmAndRemoveFiles(module: any, courseId: number) : Promise { + confirmAndRemoveFiles(module: any, courseId: number): Promise { return this.domUtils.showConfirm(this.translate.instant('course.confirmdeletemodulefiles')).then(() => { return this.prefetchDelegate.removeModuleFiles(module, courseId); }); @@ -369,14 +372,14 @@ export class CoreCourseHelperProvider { * @param {boolean} [alwaysConfirm] True to show a confirm even if the size isn't high, false otherwise. * @return {Promise} Promise resolved if the user confirms or there's no need to confirm. */ - confirmDownloadSizeSection(courseId: number, section?: any, sections?: any[], alwaysConfirm?: boolean) : Promise { + confirmDownloadSizeSection(courseId: number, section?: any, sections?: any[], alwaysConfirm?: boolean): Promise { let sizePromise; // Calculate the size of the download. if (section && section.id != CoreCourseProvider.ALL_SECTIONS_ID) { sizePromise = this.prefetchDelegate.getDownloadSize(section.modules, courseId); } else { - let promises = [], + const promises = [], results = { size: 0, total: true @@ -408,7 +411,7 @@ export class CoreCourseHelperProvider { * @param {any[]} courses Courses * @return {Promise} Promise resolved with the status. */ - determineCoursesStatus(courses: any[]) : Promise { + determineCoursesStatus(courses: any[]): Promise { // Get the status of each course. const promises = [], siteId = this.sitesProvider.getCurrentSiteId(); @@ -423,6 +426,7 @@ export class CoreCourseHelperProvider { for (let i = 1; i < statuses.length; i++) { status = this.filepoolProvider.determinePackagesStatus(status, statuses[i]); } + return status; }); } @@ -434,8 +438,9 @@ export class CoreCourseHelperProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Download promise, undefined if not found. */ - getCourseDownloadPromise(courseId: number, siteId?: string) : Promise { + getCourseDownloadPromise(courseId: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); + return this.courseDwnPromises[siteId] && this.courseDwnPromises[siteId][courseId]; } @@ -446,7 +451,7 @@ export class CoreCourseHelperProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the icon name. */ - getCourseStatusIcon(courseId: number, siteId?: string) : Promise { + getCourseStatusIcon(courseId: number, siteId?: string): Promise { return this.courseProvider.getCourseStatus(courseId, siteId).then((status) => { return this.getCourseStatusIconFromStatus(status); }); @@ -461,7 +466,7 @@ export class CoreCourseHelperProvider { * @param {String} status Course status. * @return {String} Icon name. */ - getCourseStatusIconFromStatus(status: string) : string { + getCourseStatusIconFromStatus(status: string): string { if (status == CoreConstants.DOWNLOADED) { // Always show refresh icon, we cannot knew if there's anything new in course options. return 'refresh'; @@ -480,11 +485,12 @@ export class CoreCourseHelperProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the module's course ID. */ - getModuleCourseIdByInstance(id: number, module: any, siteId?: string) : Promise { + getModuleCourseIdByInstance(id: number, module: any, siteId?: string): Promise { return this.courseProvider.getModuleBasicInfoByInstance(id, module, siteId).then((cm) => { return cm.course; }).catch((error) => { this.domUtils.showErrorModalDefault(error, 'core.course.errorgetmodule', true); + return Promise.reject(null); }); } @@ -500,7 +506,7 @@ export class CoreCourseHelperProvider { */ getModulePrefetchInfo(module: any, courseId: number, invalidateCache?: boolean, component?: string) : Promise { - let moduleInfo: CoreCourseModulePrefetchInfo = {}, + const moduleInfo: CoreCourseModulePrefetchInfo = {}, siteId = this.sitesProvider.getCurrentSiteId(), promises = []; @@ -514,19 +520,6 @@ export class CoreCourseHelperProvider { })); // @todo: Decide what to display instead of timemodified. Last check_updates? - // promises.push(this.prefetchDelegate.getModuleTimemodified(module, courseId).then(function(moduleModified) { - // moduleInfo.timemodified = moduleModified; - // if (moduleModified > 0) { - // var now = $mmUtil.timestamp(); - // if (now - moduleModified < 7 * 86400) { - // moduleInfo.timemodifiedReadable = moment(moduleModified * 1000).fromNow(); - // } else { - // moduleInfo.timemodifiedReadable = moment(moduleModified * 1000).calendar(); - // } - // } else { - // moduleInfo.timemodifiedReadable = ""; - // } - // })); promises.push(this.prefetchDelegate.getModuleStatus(module, courseId).then((moduleStatus) => { moduleInfo.status = moduleStatus; @@ -573,7 +566,7 @@ export class CoreCourseHelperProvider { * @param {any} section Section. * @return {string} Section download ID. */ - getSectionDownloadId(section: any) : string { + getSectionDownloadId(section: any): string { return 'Section-' + section.id; } @@ -586,11 +579,11 @@ export class CoreCourseHelperProvider { * @param {number} [sectionId] Section the module belongs to. If not defined we'll try to retrieve it from the site. * @return {Promise} Promise resolved when done. */ - navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number) : Promise { + navigateToModule(moduleId: number, siteId?: string, courseId?: number, sectionId?: number): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - let modal = this.domUtils.showModalLoading(), - promise, + const modal = this.domUtils.showModalLoading(); + let promise, site: CoreSite; if (courseId && sectionId) { @@ -619,7 +612,7 @@ export class CoreCourseHelperProvider { return this.courseProvider.getModule(moduleId, courseId, sectionId, false, false, siteId); }).then((module) => { const params = { - course: {id: courseId}, + course: { id: courseId }, module: module, sectionId: sectionId }; @@ -649,12 +642,12 @@ export class CoreCourseHelperProvider { * @param {number} courseId The course ID of the module. * @param {number} [sectionId] The section ID of the module. */ - openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number) : void { + openModule(navCtrl: NavController, module: any, courseId: number, sectionId?: number): void { if (!module.handlerData) { module.handlerData = this.moduleDelegate.getModuleDataFor(module.modname, module, courseId, sectionId); } - module.handlerData.action(new Event('click'), navCtrl, module, courseId, {animate: false}); + module.handlerData.action(new Event('click'), navCtrl, module, courseId, { animate: false }); } /** @@ -679,35 +672,35 @@ export class CoreCourseHelperProvider { // First of all, mark the course as being downloaded. this.courseDwnPromises[siteId][course.id] = this.courseProvider.setCourseStatus(course.id, CoreConstants.DOWNLOADING, - siteId).then(() => { - let promises = [], - allSectionsSection = sections[0]; + siteId).then(() => { + const promises = []; + let allSectionsSection = sections[0]; - // Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections". - if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) { - allSectionsSection = {id: CoreCourseProvider.ALL_SECTIONS_ID}; - } - promises.push(this.prefetchSection(allSectionsSection, course.id, sections)); - - // Prefetch course options. - courseHandlers.forEach((handler) => { - if (handler.prefetch) { - promises.push(handler.prefetch(course)); + // Prefetch all the sections. If the first section is "All sections", use it. Otherwise, use a fake "All sections". + if (sections[0].id != CoreCourseProvider.ALL_SECTIONS_ID) { + allSectionsSection = { id: CoreCourseProvider.ALL_SECTIONS_ID }; } - }); + promises.push(this.prefetchSection(allSectionsSection, course.id, sections)); - return this.utils.allPromises(promises); - }).then(() => { - // Download success, mark the course as downloaded. - return this.courseProvider.setCourseStatus(course.id, CoreConstants.DOWNLOADED, siteId); - }).catch((error) => { - // Error, restore previous status. - return this.courseProvider.setCoursePreviousStatus(course.id, siteId).then(() => { - return Promise.reject(error); + // Prefetch course options. + courseHandlers.forEach((handler) => { + if (handler.prefetch) { + promises.push(handler.prefetch(course)); + } + }); + + return this.utils.allPromises(promises); + }).then(() => { + // Download success, mark the course as downloaded. + return this.courseProvider.setCourseStatus(course.id, CoreConstants.DOWNLOADED, siteId); + }).catch((error) => { + // Error, restore previous status. + return this.courseProvider.setCoursePreviousStatus(course.id, siteId).then(() => { + return Promise.reject(error); + }); + }).finally(() => { + delete this.courseDwnPromises[siteId][course.id]; }); - }).finally(() => { - delete this.courseDwnPromises[siteId][course.id]; - }); return this.courseDwnPromises[siteId][course.id]; } @@ -723,11 +716,12 @@ export class CoreCourseHelperProvider { * @param {boolean} [refresh] True if refreshing, false otherwise. * @return {Promise} Promise resolved when downloaded. */ - prefetchModule(handler: any, module: any, size: any, courseId: number, refresh?: boolean) : Promise { + prefetchModule(handler: any, module: any, size: any, courseId: number, refresh?: boolean): Promise { // Show confirmation if needed. return this.domUtils.confirmDownloadSize(size).then(() => { // Invalidate content if refreshing and download the data. - let promise = refresh ? handler.invalidateContent(module.id, courseId) : Promise.resolve(); + const promise = refresh ? handler.invalidateContent(module.id, courseId) : Promise.resolve(); + return promise.catch(() => { // Ignore errors. }).then(() => { @@ -745,7 +739,7 @@ export class CoreCourseHelperProvider { * @param {any[]} [sections] List of sections. Used when downloading all the sections. * @return {Promise} Promise resolved when the prefetch is finished. */ - prefetchSection(section: any, courseId: number, sections?: any[]) : Promise { + prefetchSection(section: any, courseId: number, sections?: any[]): Promise { if (section.id != CoreCourseProvider.ALL_SECTIONS_ID) { // Download only this section. return this.prefetchSingleSectionIfNeeded(section, courseId).then(() => { @@ -754,8 +748,8 @@ export class CoreCourseHelperProvider { }); } else { // Download all the sections except "All sections". - let promises = [], - allSectionsStatus; + const promises = []; + let allSectionsStatus; section.isDownloading = true; sections.forEach((section) => { @@ -789,7 +783,7 @@ export class CoreCourseHelperProvider { * @param {number} courseId Course ID the section belongs to. * @return {Promise} Promise resolved when the section is prefetched. */ - protected prefetchSingleSectionIfNeeded(section: any, courseId: number) : Promise { + protected prefetchSingleSectionIfNeeded(section: any, courseId: number): Promise { if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { return Promise.resolve(); } @@ -802,9 +796,11 @@ export class CoreCourseHelperProvider { // Section is downloaded or not downloadable, nothing to do. return; } + return this.prefetchSingleSection(section, result, courseId); }, (error) => { section.isDownloading = false; + return Promise.reject(error); }); } @@ -818,7 +814,7 @@ export class CoreCourseHelperProvider { * @param {number} courseId Course ID the section belongs to. * @return {Promise} Promise resolved when the section has been prefetched. */ - protected prefetchSingleSection(section: any, result: any, courseId: number) { + protected prefetchSingleSection(section: any, result: any, courseId: number): Promise { if (section.id == CoreCourseProvider.ALL_SECTIONS_ID) { return Promise.resolve(); } @@ -829,14 +825,13 @@ export class CoreCourseHelperProvider { } // We only download modules with status notdownloaded, downloading or outdated. - let modules = result[CoreConstants.OUTDATED].concat(result[CoreConstants.NOT_DOWNLOADED]) - .concat(result[CoreConstants.DOWNLOADING]), + const modules = result[CoreConstants.OUTDATED].concat(result[CoreConstants.NOT_DOWNLOADED]) + .concat(result[CoreConstants.DOWNLOADING]), downloadId = this.getSectionDownloadId(section); section.isDownloading = true; - // We prefetch all the modules to prevent incoeherences in the download count - // and also to download stale data that might not be marked as outdated. + // Prefetch all modules to prevent incoeherences in download count and to download stale data not marked as outdated. return this.prefetchDelegate.prefetchModules(downloadId, modules, courseId, (data) => { section.count = data.count; section.total = data.total; @@ -849,12 +844,12 @@ export class CoreCourseHelperProvider { * @param {any} section Section to check. * @return {boolean} Whether the section has content. */ - sectionHasContent(section: any) : boolean { + sectionHasContent(section: any): boolean { if (section.id == CoreCourseProvider.ALL_SECTIONS_ID || section.hiddenbynumsections) { return false; } return (typeof section.availabilityinfo != 'undefined' && section.availabilityinfo != '') || - section.summary != '' || (section.modules && section.modules.length > 0); + section.summary != '' || (section.modules && section.modules.length > 0); } } diff --git a/src/core/course/providers/module-delegate.ts b/src/core/course/providers/module-delegate.ts index ae2c952ea..dd2dec7c9 100644 --- a/src/core/course/providers/module-delegate.ts +++ b/src/core/course/providers/module-delegate.ts @@ -43,7 +43,7 @@ export interface CoreCourseModuleHandler extends CoreDelegateHandler { * @return {any} The component to use, undefined if not found. */ getMainComponent(course: any, module: any): any; -}; +} /** * Data needed to render the module in course contents. @@ -134,7 +134,7 @@ export interface CoreCourseModuleHandlerButton { * @param {number} courseId The course ID. */ action(event: Event, navCtrl: NavController, module: any, courseId: number): void; -}; +} /** * Delegate to register module handlers. @@ -146,7 +146,7 @@ export class CoreCourseModuleDelegate extends CoreDelegate { protected featurePrefix = '$mmCourseDelegate_'; constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, eventsProvider: CoreEventsProvider, - protected courseProvider: CoreCourseProvider) { + protected courseProvider: CoreCourseProvider) { super('CoreCourseModuleDelegate', loggerProvider, sitesProvider, eventsProvider); } @@ -158,9 +158,9 @@ export class CoreCourseModuleDelegate extends CoreDelegate { * @return {any} The component to use, undefined if not found. */ getMainComponent?(course: any, module: any): any { - let handler = this.enabledHandlers[module.modname]; + const handler = this.enabledHandlers[module.modname]; if (handler && handler.getMainComponent) { - let component = handler.getMainComponent(course, module); + const component = handler.getMainComponent(course, module); if (component) { return component; } @@ -182,11 +182,11 @@ export class CoreCourseModuleDelegate extends CoreDelegate { } // Return the default data. - let defaultData: CoreCourseModuleHandlerData = { + const defaultData: CoreCourseModuleHandlerData = { icon: this.courseProvider.getModuleIconSrc(module.modname), title: module.name, class: 'core-course-default-handler core-course-module-' + module.modname + '-handler', - action: (event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions) => { + action: (event: Event, navCtrl: NavController, module: any, courseId: number, options?: NavOptions): void => { event.preventDefault(); event.stopPropagation(); @@ -198,7 +198,7 @@ export class CoreCourseModuleDelegate extends CoreDelegate { defaultData.buttons = [{ icon: 'open', label: 'core.openinbrowser', - action: (e: Event) => { + action: (e: Event): void => { e.preventDefault(); e.stopPropagation(); this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(module.url); diff --git a/src/core/course/providers/module-prefetch-delegate.ts b/src/core/course/providers/module-prefetch-delegate.ts index 4b95c86b5..6980c3cad 100644 --- a/src/core/course/providers/module-prefetch-delegate.ts +++ b/src/core/course/providers/module-prefetch-delegate.ts @@ -210,8 +210,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { // Promises for check updates, to prevent performing the same request twice at the same time. protected courseUpdatesPromises: { [s: string]: { [s: string]: Promise } } = {}; - // Promises and observables for prefetching, to prevent downloading the same section twice at the same time - // and notify the progress of the download. + // Promises and observables for prefetching, to prevent downloading same section twice at the same time and notify progress. protected prefetchData: { [s: string]: { [s: string]: { @@ -222,9 +221,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } } = {}; - constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected eventsProvider: CoreEventsProvider, - private courseProvider: CoreCourseProvider, private filepoolProvider: CoreFilepoolProvider, - private timeUtils: CoreTimeUtilsProvider, private utils: CoreUtilsProvider, private fileProvider: CoreFileProvider) { + constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, + private courseProvider: CoreCourseProvider, private filepoolProvider: CoreFilepoolProvider, + private timeUtils: CoreTimeUtilsProvider, private fileProvider: CoreFileProvider, + protected eventsProvider: CoreEventsProvider) { super('CoreCourseModulePrefetchDelegate', loggerProvider, sitesProvider); this.sitesProvider.createTableFromSchema(this.checkUpdatesTableSchema); @@ -277,10 +277,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @return {Promise<{toCheck: any[], cannotUse: any[]}>} Promise resolved with the lists. */ protected createToCheckList(modules: any[], courseId: number): Promise<{ toCheck: any[], cannotUse: any[] }> { - let result = { - toCheck: [], - cannotUse: [] - }, + const result = { + toCheck: [], + cannotUse: [] + }, promises = []; modules.forEach((module) => { @@ -341,6 +341,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { return handler.determineStatus(module, status, canCheck); } } + return status; } @@ -358,7 +359,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } // Check if there's already a getCourseUpdates in progress. - let id = Md5.hashAsciiStr(courseId + '#' + JSON.stringify(modules)), + const id = Md5.hashAsciiStr(courseId + '#' + JSON.stringify(modules)), siteId = this.sitesProvider.getCurrentSiteId(); if (this.courseUpdatesPromises[siteId] && this.courseUpdatesPromises[siteId][id]) { @@ -369,7 +370,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } this.courseUpdatesPromises[siteId][id] = this.createToCheckList(modules, courseId).then((data) => { - let result = {}; + const result = {}; // Mark as false the modules that cannot use check updates WS. data.cannotUse.forEach((module) => { @@ -383,10 +384,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { // Get the site, maybe the user changed site. return this.sitesProvider.getSite(siteId).then((site) => { - let params = { - courseid: courseId, - tocheck: data.toCheck - }, + const params = { + courseid: courseId, + tocheck: data.toCheck + }, preSets: CoreSiteWSPreSets = { cacheKey: this.getCourseUpdatesCacheKey(courseId), emergencyCache: false, // If downloaded data has changed and offline, just fail. See MOBILE-2085. @@ -399,7 +400,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } // Store the last execution of the check updates call. - let entry = { + const entry = { courseId: courseId, time: this.timeUtils.timestamp() }; @@ -407,8 +408,8 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { return this.treatCheckUpdatesResult(data.toCheck, response, result); }).catch((error) => { - // Cannot get updates. Get the cached entries but discard the modules with a download time higher - // than the last execution of check updates. + // Cannot get updates. + // Get cached entries but discard modules with a download time higher than the last execution of check updates. return site.getDb().getRecord(this.CHECK_UPDATES_TIMES_TABLE, { courseId: courseId }).then((entry) => { preSets.getCacheUsingCacheKey = true; preSets.omitExpires = true; @@ -502,9 +503,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * to calculate the total size. */ getModuleDownloadSize(module: any, courseId: number, single?: boolean): Promise<{ size: number, total: boolean }> { + const handler = this.getPrefetchHandlerFor(module); let downloadSize, - packageId, - handler = this.getPrefetchHandlerFor(module); + packageId; // Check if the module has a prefetch handler. if (handler) { @@ -526,6 +527,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { if (cachedSize) { return cachedSize; } + return Promise.reject(error); }); }); @@ -542,10 +544,10 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @return {Promise} Promise resolved with the size. */ getModuleDownloadedSize(module: any, courseId: number): Promise { + const handler = this.getPrefetchHandlerFor(module); let downloadedSize, packageId, - promise, - handler = this.getPrefetchHandlerFor(module); + promise; // Check if the module has a prefetch handler. if (handler) { @@ -566,9 +568,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } else { // Handler doesn't implement it, get the module files and check if they're downloaded. promise = this.getModuleFiles(module, courseId).then((files) => { - let siteId = this.sitesProvider.getCurrentSiteId(), - promises = [], - size = 0; + const siteId = this.sitesProvider.getCurrentSiteId(), + promises = []; + let size = 0; // Retrieve file size if it's downloaded. files.forEach((file) => { @@ -641,15 +643,15 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @return {Promise} Promise resolved with the status. */ getModuleStatus(module: any, courseId: number, updates?: any, refresh?: boolean, sectionId?: number): Promise { - let handler = this.getPrefetchHandlerFor(module), + const handler = this.getPrefetchHandlerFor(module), siteId = this.sitesProvider.getCurrentSiteId(), canCheck = this.canCheckUpdates(); if (handler) { // Check if the status is cached. - let component = handler.component, - packageId = this.filepoolProvider.getPackageId(component, module.id), - status = this.statusCache.getValue(packageId, 'status'), + const component = handler.component, + packageId = this.filepoolProvider.getPackageId(component, module.id); + let status = this.statusCache.getValue(packageId, 'status'), updateStatus = true, promise; @@ -696,6 +698,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { // Has updates, mark the module as outdated. status = CoreConstants.OUTDATED; + return this.filepoolProvider.storePackageStatus(siteId, component, module.id, status).catch(() => { // Ignore errors. }).then(() => { @@ -704,11 +707,13 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { }).catch(() => { // Error checking if module has updates. const status = this.statusCache.getValue(packageId, 'status', true); + return this.determineModuleStatus(module, status, canCheck); }); }, () => { // Error getting updates, show the stored status. updateStatus = false; + return currentStatus; }); }); @@ -716,6 +721,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { if (updateStatus) { this.updateStatusCache(status, courseId, component, module.id, sectionId); } + return this.determineModuleStatus(module, status, canCheck); }); } @@ -741,11 +747,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * - CoreConstants.OUTDATED (any[]) Modules with state OUTDATED. */ getModulesStatus(modules: any[], courseId: number, sectionId?: number, refresh?: boolean): any { - let promises = [], - status = CoreConstants.NOT_DOWNLOADABLE, + const promises = [], result: any = { total: 0 }; + let status = CoreConstants.NOT_DOWNLOADABLE; // Init result. result[CoreConstants.NOT_DOWNLOADED] = []; @@ -761,9 +767,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { modules.forEach((module) => { // Check if the module has a prefetch handler. - let handler = this.getPrefetchHandlerFor(module); + const handler = this.getPrefetchHandlerFor(module); if (handler) { - let packageId = this.filepoolProvider.getPackageId(handler.component, module.id); + const packageId = this.filepoolProvider.getPackageId(handler.component, module.id); promises.push(this.getModuleStatus(module, courseId, updates, refresh).then((modStatus) => { if (modStatus != CoreConstants.NOT_DOWNLOADABLE) { @@ -793,6 +799,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { return Promise.all(promises).then(() => { result.status = status; + return result; }); }); @@ -806,12 +813,12 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @return {Promise<{status: string, downloadTime?: number}>} Promise resolved with the data. */ protected getModuleStatusAndDownloadTime(module: any, courseId: number): Promise<{ status: string, downloadTime?: number }> { - let handler = this.getPrefetchHandlerFor(module), + const handler = this.getPrefetchHandlerFor(module), siteId = this.sitesProvider.getCurrentSiteId(); if (handler) { // Get the status from the cache. - let packageId = this.filepoolProvider.getPackageId(handler.component, module.id), + const packageId = this.filepoolProvider.getPackageId(handler.component, module.id), status = this.statusCache.getValue(packageId, 'status'); if (typeof status != 'undefined' && status != CoreConstants.DOWNLOADED) { @@ -873,7 +880,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @return {Promise} Promise resolved when modules are invalidated. */ invalidateModules(modules: any[], courseId: number): Promise { - let promises = []; + const promises = []; modules.forEach((module) => { const handler = this.getPrefetchHandlerFor(module); @@ -914,6 +921,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { */ isBeingDownloaded(id: string): boolean { const siteId = this.sitesProvider.getCurrentSiteId(); + return !!(this.prefetchData[siteId] && this.prefetchData[siteId][id]); } @@ -925,11 +933,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @return {Promise} Promise resolved with true if downloadable, false otherwise. */ isModuleDownloadable(module: any, courseId: number): Promise { - let handler = this.getPrefetchHandlerFor(module); + const handler = this.getPrefetchHandlerFor(module); if (handler) { if (typeof handler.isDownloadable == 'function') { - let packageId = this.filepoolProvider.getPackageId(handler.component, module.id), + const packageId = this.filepoolProvider.getPackageId(handler.component, module.id), downloadable = this.statusCache.getValue(packageId, 'downloadable'); if (typeof downloadable != 'undefined') { @@ -961,7 +969,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @return {Promise} Promise resolved with boolean: whether the module has updates. */ moduleHasUpdates(module: any, courseId: number, updates: any): Promise { - let handler = this.getPrefetchHandlerFor(module), + const handler = this.getPrefetchHandlerFor(module), moduleUpdates = updates[module.id]; if (handler && handler.hasUpdates) { @@ -1000,6 +1008,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { if (handler) { return handler.prefetch(module, courseId, single); } + return Promise.resolve(); } @@ -1023,22 +1032,21 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { if (onProgress) { currentData.subscriptions.push(currentData.observable.subscribe(onProgress)); } + return currentData.promise; } - let promises = [], - count = 0, + let count = 0; + const promises = [], total = modules.length, moduleIds = modules.map((module) => { return module.id; - }); - - // Initialize the prefetch data. - const prefetchData = { - observable: new BehaviorSubject({ count: count, total: total }), - promise: undefined, - subscriptions: [] - }; + }), + prefetchData = { + observable: new BehaviorSubject({ count: count, total: total }), + promise: undefined, + subscriptions: [] + }; if (onProgress) { prefetchData.observable.subscribe(onProgress); @@ -1054,7 +1062,7 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } return handler.prefetch(module, courseId).then(() => { - let index = moduleIds.indexOf(id); + const index = moduleIds.indexOf(id); if (index > -1) { // It's one of the modules we were expecting to download. moduleIds.splice(index, 1); @@ -1092,9 +1100,9 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @return {Promise} Promise resolved when done. */ removeModuleFiles(module: any, courseId: number): Promise { - let handler = this.getPrefetchHandlerFor(module), - siteId = this.sitesProvider.getCurrentSiteId(), - promise; + const handler = this.getPrefetchHandlerFor(module), + siteId = this.sitesProvider.getCurrentSiteId(); + let promise; if (handler && handler.removeFiles) { // Handler implements a method to remove the files, use it. @@ -1102,12 +1110,13 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { } else { // No method to remove files, use get files to try to remove the files. promise = this.getModuleFiles(module, courseId).then((files) => { - let promises = []; + const promises = []; files.forEach((file) => { promises.push(this.filepoolProvider.removeFileByUrl(siteId, file.url || file.fileurl).catch(() => { // Ignore errors. })); }); + return Promise.all(promises); }); } @@ -1180,10 +1189,11 @@ export class CoreCourseModulePrefetchDelegate extends CoreDelegate { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @param {number} [sectionId] Section ID of the module. */ - updateStatusCache(status: string, courseId: number, component: string, componentId?: string | number, sectionId?: number): void { - let notify, - packageId = this.filepoolProvider.getPackageId(component, componentId), + updateStatusCache(status: string, courseId: number, component: string, componentId?: string | number, sectionId?: number) + : void { + const packageId = this.filepoolProvider.getPackageId(component, componentId), cachedStatus = this.statusCache.getValue(packageId, 'status', true); + let notify; // If the status has changed, notify that the section has changed. notify = typeof cachedStatus != 'undefined' && cachedStatus !== status; diff --git a/src/core/course/providers/options-delegate.ts b/src/core/course/providers/options-delegate.ts index 191eb26d0..ec74e848f 100644 --- a/src/core/course/providers/options-delegate.ts +++ b/src/core/course/providers/options-delegate.ts @@ -154,7 +154,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { protected featurePrefix = '$mmCoursesDelegate_'; constructor(loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, private utils: CoreUtilsProvider, - protected eventsProvider: CoreEventsProvider, private coursesProvider: CoreCoursesProvider) { + protected eventsProvider: CoreEventsProvider, private coursesProvider: CoreCoursesProvider) { super('CoreCourseOptionsDelegate', loggerProvider, sitesProvider, eventsProvider); eventsProvider.on(CoreEventsProvider.LOGOUT, () => { @@ -194,7 +194,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * @return {Promise} Promise resolved when done. */ clearAndInvalidateCoursesOptions(courseId?: number): Promise { - let promises = []; + const promises = []; this.eventsProvider.trigger(CoreCoursesProvider.EVENT_MY_COURSES_REFRESHED); @@ -208,7 +208,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { promises.push(this.coursesProvider.invalidateUserNavigationOptions()); promises.push(this.coursesProvider.invalidateUserAdministrationOptions()); - for (let cId in this.coursesHandlers) { + for (const cId in this.coursesHandlers) { promises.push(this.invalidateCourseHandlers(parseInt(cId, 10))); } } @@ -229,7 +229,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * @return {Promise} Promise resolved with array of handlers. */ protected getHandlersForAccess(courseId: number, refresh: boolean, accessData: any, navOptions?: any, - admOptions?: any): Promise { + admOptions?: any): Promise { // If the handlers aren't loaded, do not refresh. if (!this.loaded[courseId]) { @@ -264,10 +264,10 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * @return {Promise} Promise resolved with array of handlers. */ getHandlersToDisplay(course: any, refresh?: boolean, isGuest?: boolean, navOptions?: any, admOptions?: any): - Promise { + Promise { course.id = parseInt(course.id, 10); - let accessData = { + const accessData = { type: isGuest ? CoreCourseProvider.ACCESS_GUEST : CoreCourseProvider.ACCESS_DEFAULT }; @@ -282,9 +282,9 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { // Call getHandlersForAccess to make sure the handlers have been loaded. return this.getHandlersForAccess(course.id, refresh, accessData, course.navOptions, course.admOptions); }).then(() => { - let handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] = [], - promises = [], - promise; + const handlersToDisplay: CoreCourseOptionsHandlerToDisplay[] = [], + promises = []; + let promise; this.coursesHandlers[course.id].enabledHandlers.forEach((handler) => { if (handler.shouldDisplayForCourse) { @@ -342,9 +342,10 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { */ hasHandlersForDefault(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any): Promise { // Default access. - let accessData = { + const accessData = { type: CoreCourseProvider.ACCESS_DEFAULT }; + return this.getHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions).then((handlers) => { return !!(handlers && handlers.length); }); @@ -361,9 +362,10 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { */ hasHandlersForGuest(courseId: number, refresh?: boolean, navOptions?: any, admOptions?: any): Promise { // Guest access. - var accessData = { + const accessData = { type: CoreCourseProvider.ACCESS_GUEST }; + return this.getHandlersForAccess(courseId, refresh, accessData, navOptions, admOptions).then((handlers) => { return !!(handlers && handlers.length); }); @@ -376,7 +378,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * @return {Promise} Promise resolved when done. */ invalidateCourseHandlers(courseId: number): Promise { - let promises = [], + const promises = [], courseData = this.coursesHandlers[courseId]; if (!courseData) { @@ -405,6 +407,7 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { if (!this.lastUpdateHandlersForCoursesStart[courseId]) { return true; } + return time == this.lastUpdateHandlersForCoursesStart[courseId]; } @@ -434,8 +437,8 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { updateData(siteId?: string): void { if (this.sitesProvider.getCurrentSiteId() === siteId) { // Update handlers for all courses. - for (let courseId in this.coursesHandlers) { - let handler = this.coursesHandlers[courseId]; + for (const courseId in this.coursesHandlers) { + const handler = this.coursesHandlers[courseId]; this.updateHandlersForCourse(parseInt(courseId, 10), handler.access, handler.navOptions, handler.admOptions); } } @@ -452,15 +455,15 @@ export class CoreCourseOptionsDelegate extends CoreDelegate { * @protected */ updateHandlersForCourse(courseId: number, accessData: any, navOptions?: any, admOptions?: any): Promise { - let promises = [], + const promises = [], enabledForCourse = [], siteId = this.sitesProvider.getCurrentSiteId(), now = Date.now(); this.lastUpdateHandlersForCoursesStart[courseId] = now; - for (let name in this.enabledHandlers) { - let handler = this.enabledHandlers[name]; + for (const name in this.enabledHandlers) { + const handler = this.enabledHandlers[name]; // Checks if the handler is enabled for the user. promises.push(Promise.resolve(handler.isEnabledForCourse(courseId, accessData, navOptions, admOptions)) diff --git a/src/core/courses/components/course-list-item/course-list-item.ts b/src/core/courses/components/course-list-item/course-list-item.ts index bdc378218..d3805a1f0 100644 --- a/src/core/courses/components/course-list-item/course-list-item.ts +++ b/src/core/courses/components/course-list-item/course-list-item.ts @@ -37,9 +37,9 @@ export class CoreCoursesCourseListItemComponent implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { // Check if the user is enrolled in the course. - return this.coursesProvider.getUserCourse(this.course.id).then(() => { + this.coursesProvider.getUserCourse(this.course.id).then(() => { this.course.isEnrolled = true; }).catch(() => { this.course.isEnrolled = false; @@ -75,9 +75,10 @@ export class CoreCoursesCourseListItemComponent implements OnInit { /** * Open a course. + * + * @param {any} course The course to open. */ - openCourse(course) { + openCourse(course: any): void { this.navCtrl.push('CoreCoursesCoursePreviewPage', {course: course}); } - } diff --git a/src/core/courses/components/course-progress/course-progress.ts b/src/core/courses/components/course-progress/course-progress.ts index b5052c506..59e4b9f85 100644 --- a/src/core/courses/components/course-progress/course-progress.ts +++ b/src/core/courses/components/course-progress/course-progress.ts @@ -58,7 +58,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { // Determine course prefetch icon. this.courseHelper.getCourseStatusIcon(this.course.id).then((icon) => { this.prefetchCourseData.prefetchCourseIcon = icon; @@ -83,8 +83,10 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { /** * Open a course. + * + * @param {any} course The course to open. */ - openCourse(course) { + openCourse(course: any): void { this.courseFormatDelegate.openCourse(this.navCtrl, course); } @@ -93,7 +95,7 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { * * @param {Event} e Click event. */ - prefetchCourse(e: Event) { + prefetchCourse(e: Event): void { e.preventDefault(); e.stopPropagation(); @@ -101,13 +103,13 @@ export class CoreCoursesCourseProgressComponent implements OnInit, OnDestroy { if (!this.isDestroyed) { this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); } - }) + }); } /** * Component destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.isDestroyed = true; if (this.courseStatusObserver) { diff --git a/src/core/courses/components/overview-events/overview-events.ts b/src/core/courses/components/overview-events/overview-events.ts index 6e67a0149..6ca8f682d 100644 --- a/src/core/courses/components/overview-events/overview-events.ts +++ b/src/core/courses/components/overview-events/overview-events.ts @@ -31,7 +31,7 @@ import * as moment from 'moment'; }) export class CoreCoursesOverviewEventsComponent implements OnChanges { @Input() events: any[]; // The events to render. - @Input() showCourse?: boolean|string; // Whether to show the course name. + @Input() showCourse?: boolean | string; // Whether to show the course name. @Input() canLoadMore?: boolean; // Whether more events can be loaded. @Output() loadMore: EventEmitter; // Notify that more events should be loaded. @@ -52,7 +52,7 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges { /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: {[name: string]: SimpleChange}): void { this.showCourse = this.utils.isTrueOrOne(this.showCourse); if (changes.events) { @@ -65,8 +65,9 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges { * * @param {number} start Number of days to start getting events from today. E.g. -1 will get events from yesterday. * @param {number} [end] Number of days after the start. + * @return {any[]} Filtered events. */ - protected filterEventsByTime(start: number, end?: number) { + protected filterEventsByTime(start: number, end?: number): any[] { start = moment().add(start, 'days').unix(); end = typeof end != 'undefined' ? moment().add(end, 'days').unix() : end; @@ -78,6 +79,7 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges { return start <= event.timesort; }).map((event) => { event.iconUrl = this.courseProvider.getModuleIconSrc(event.icon.component); + return event; }); } @@ -85,7 +87,7 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges { /** * Update the events displayed. */ - protected updateEvents() { + protected updateEvents(): void { this.empty = !this.events || this.events.length <= 0; if (!this.empty) { this.recentlyOverdue = this.filterEventsByTime(-14, 0); @@ -99,7 +101,7 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges { /** * Load more events clicked. */ - loadMoreEvents() { + loadMoreEvents(): void { this.loadingMore = true; this.loadMore.emit(); } @@ -110,14 +112,14 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges { * @param {Event} e Click event. * @param {string} url Url of the action. */ - action(e: Event, url: string) { + action(e: Event, url: string): void { e.preventDefault(); e.stopPropagation(); // Fix URL format. url = this.textUtils.decodeHTMLEntities(url); - let modal = this.domUtils.showModalLoading(); + const modal = this.domUtils.showModalLoading(); this.contentLinksHelper.handleLink(url, undefined, this.navCtrl).then((treated) => { if (!treated) { return this.sitesProvider.getCurrentSite().openInBrowserWithAutoLoginIfSameSite(url); @@ -125,7 +127,5 @@ export class CoreCoursesOverviewEventsComponent implements OnChanges { }).finally(() => { modal.dismiss(); }); - - return false; } } diff --git a/src/core/courses/pages/available-courses/available-courses.ts b/src/core/courses/pages/available-courses/available-courses.ts index 084e2782f..9df1dcc54 100644 --- a/src/core/courses/pages/available-courses/available-courses.ts +++ b/src/core/courses/pages/available-courses/available-courses.ts @@ -21,7 +21,7 @@ import { CoreCoursesProvider } from '../../providers/courses'; /** * Page that displays available courses in current site. */ -@IonicPage({segment: "core-courses-available-courses"}) +@IonicPage({ segment: 'core-courses-available-courses' }) @Component({ selector: 'page-core-courses-available-courses', templateUrl: 'available-courses.html', @@ -31,12 +31,12 @@ export class CoreCoursesAvailableCoursesPage { coursesLoaded: boolean; constructor(private coursesProvider: CoreCoursesProvider, private domUtils: CoreDomUtilsProvider, - private sitesProvider: CoreSitesProvider) {} + private sitesProvider: CoreSitesProvider) { } /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.loadCourses().finally(() => { this.coursesLoaded = true; }); @@ -44,9 +44,12 @@ export class CoreCoursesAvailableCoursesPage { /** * Load the courses. + * + * @return {Promise} Promise resolved when done. */ - protected loadCourses() { + protected loadCourses(): Promise { const frontpageCourseId = this.sitesProvider.getCurrentSite().getSiteHomeId(); + return this.coursesProvider.getCoursesByField().then((courses) => { this.courses = courses.filter((course) => { return course.id != frontpageCourseId; @@ -61,8 +64,8 @@ export class CoreCoursesAvailableCoursesPage { * * @param {any} refresher Refresher. */ - refreshCourses(refresher: any) { - let promises = []; + refreshCourses(refresher: any): void { + const promises = []; promises.push(this.coursesProvider.invalidateUserCourses()); promises.push(this.coursesProvider.invalidateCoursesByField()); @@ -72,5 +75,5 @@ export class CoreCoursesAvailableCoursesPage { refresher.complete(); }); }); - }; + } } diff --git a/src/core/courses/pages/categories/categories.ts b/src/core/courses/pages/categories/categories.ts index 3c78fa63a..149e78d24 100644 --- a/src/core/courses/pages/categories/categories.ts +++ b/src/core/courses/pages/categories/categories.ts @@ -23,7 +23,7 @@ import { CoreCoursesProvider } from '../../providers/courses'; /** * Page that displays a list of categories and the courses in the current category if any. */ -@IonicPage({segment: "core-courses-categories"}) +@IonicPage({ segment: 'core-courses-categories' }) @Component({ selector: 'page-core-courses-categories', templateUrl: 'categories.html', @@ -47,7 +47,7 @@ export class CoreCoursesCategoriesPage { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.fetchCategories().finally(() => { this.categoriesLoaded = true; }); @@ -55,8 +55,10 @@ export class CoreCoursesCategoriesPage { /** * Fetch the categories. + * + * @return {Promise} Promise resolved when done. */ - protected fetchCategories() { + protected fetchCategories(): Promise { return this.coursesProvider.getCategories(this.categoryId, true).then((cats) => { this.currentCategory = undefined; @@ -69,10 +71,11 @@ export class CoreCoursesCategoriesPage { }); // Sort by depth and sortorder to avoid problems formatting Tree. - cats.sort((a,b) => { + cats.sort((a, b) => { if (a.depth == b.depth) { return (a.sortorder > b.sortorder) ? 1 : ((b.sortorder > a.sortorder) ? -1 : 0); } + return a.depth > b.depth ? 1 : -1; }); @@ -97,8 +100,8 @@ export class CoreCoursesCategoriesPage { * * @param {any} refresher Refresher. */ - refreshCategories(refresher: any) { - let promises = []; + refreshCategories(refresher: any): void { + const promises = []; promises.push(this.coursesProvider.invalidateUserCourses()); promises.push(this.coursesProvider.invalidateCategories(this.categoryId, true)); @@ -116,7 +119,7 @@ export class CoreCoursesCategoriesPage { * * @param {number} categoryId The category ID. */ - openCategory(categoryId: number) { - this.navCtrl.push('CoreCoursesCategoriesPage', {categoryId: categoryId}); + openCategory(categoryId: number): void { + this.navCtrl.push('CoreCoursesCategoriesPage', { categoryId: categoryId }); } } diff --git a/src/core/courses/pages/course-preview/course-preview.ts b/src/core/courses/pages/course-preview/course-preview.ts index 24d275fac..966148945 100644 --- a/src/core/courses/pages/course-preview/course-preview.ts +++ b/src/core/courses/pages/course-preview/course-preview.ts @@ -28,7 +28,7 @@ import { CoreCourseHelperProvider } from '../../../course/providers/helper'; /** * Page that allows "previewing" a course and enrolling in it if enabled and not enrolled. */ -@IonicPage({segment: "core-courses-course-preview"}) +@IonicPage({ segment: 'core-courses-course-preview' }) @Component({ selector: 'page-core-courses-course-preview', templateUrl: 'course-preview.html', @@ -36,7 +36,7 @@ import { CoreCourseHelperProvider } from '../../../course/providers/helper'; export class CoreCoursesCoursePreviewPage implements OnDestroy { course: any; isEnrolled: boolean; - handlersShouldBeShown: boolean = true; + handlersShouldBeShown = true; handlersLoaded: boolean; component = 'CoreCoursesCoursePreview'; selfEnrolInstances: any[] = []; @@ -47,7 +47,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { }; protected guestWSAvailable: boolean; - protected isGuestEnabled: boolean = false; + protected isGuestEnabled = false; protected guestInstanceId: number; protected enrollmentMethods: any[]; protected waitStart = 0; @@ -82,7 +82,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { const currentSite = this.sitesProvider.getCurrentSite(), currentSiteUrl = currentSite && currentSite.getURL(); @@ -107,7 +107,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { if (icon == 'spinner') { // Course is being downloaded. Get the download promise. - let promise = this.courseHelper.getCourseDownloadPromise(this.course.id); + const promise = this.courseHelper.getCourseDownloadPromise(this.course.id); if (promise) { // There is a download promise. If it fails, show an error. promise.catch((error) => { @@ -127,7 +127,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Page destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.pageDestroyed = true; if (this.courseStatusObserver) { @@ -141,7 +141,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { * @return {Promise} Promise resolved if can access as guest, rejected otherwise. Resolve param indicates if * password is required for guest access. */ - protected canAccessAsGuest() : Promise { + protected canAccessAsGuest(): Promise { if (!this.isGuestEnabled) { return Promise.reject(null); } @@ -149,7 +149,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { // Search instance ID of guest enrolment method. this.guestInstanceId = undefined; for (let i = 0; i < this.enrollmentMethods.length; i++) { - let method = this.enrollmentMethods[i]; + const method = this.enrollmentMethods[i]; if (method.type == 'guest') { this.guestInstanceId = method.id; break; @@ -162,6 +162,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { // Not active, reject. return Promise.reject(null); } + return info.passwordrequired; }); } @@ -174,9 +175,10 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { * * @param {boolean} refresh Whether the user is refreshing the data. */ - protected getCourse(refresh?: boolean) : Promise { + protected getCourse(refresh?: boolean): Promise { // Get course enrolment methods. this.selfEnrolInstances = []; + return this.coursesProvider.getCourseEnrolmentMethods(this.course.id).then((methods) => { this.enrollmentMethods = methods; @@ -193,15 +195,18 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { // Check if user is enrolled in the course. return this.coursesProvider.getUserCourse(this.course.id).then((course) => { this.isEnrolled = true; + return course; }).catch(() => { // The user is not enrolled in the course. Use getCourses to see if it's an admin/manager and can see the course. this.isEnrolled = false; + return this.coursesProvider.getCourse(this.course.id); }).then((course) => { // Success retrieving the course, we can assume the user has permissions to view it. this.course.fullname = course.fullname || this.course.fullname; this.course.summary = course.summary || this.course.summary; + return this.loadCourseHandlers(refresh, false); }).catch(() => { // The user is not an admin/manager. Check if we can provide guest access to the course. @@ -227,7 +232,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { * @param {boolean} refresh Whether the user is refreshing the data. * @param {boolean} guest Whether it's guest access. */ - protected loadCourseHandlers(refresh: boolean, guest: boolean) : Promise { + protected loadCourseHandlers(refresh: boolean, guest: boolean): Promise { return this.courseOptionsDelegate.getHandlersToDisplay(this.course, refresh, guest, true).then((handlers) => { this.course._handlers = handlers; this.handlersShouldBeShown = true; @@ -238,26 +243,27 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Open the course. */ - openCourse() { + openCourse(): void { if (!this.handlersShouldBeShown) { // Course cannot be opened. return; } - this.navCtrl.push('CoreCourseSectionPage', {course: this.course}); + this.navCtrl.push('CoreCourseSectionPage', { course: this.course }); } /** * Enrol using PayPal. */ - paypalEnrol() { + paypalEnrol(): void { let window, hasReturnedFromPaypal = false, inAppLoadSubscription, inAppFinishSubscription, inAppExitSubscription, - appResumeSubscription, - urlLoaded = (event) => { + appResumeSubscription; + + const urlLoaded = (event): void => { if (event.url.indexOf(this.paypalReturnUrl) != -1) { hasReturnedFromPaypal = true; } else if (event.url.indexOf(this.courseUrl) != -1 && hasReturnedFromPaypal) { @@ -266,7 +272,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { window.close(); } }, - inAppClosed = () => { + inAppClosed = (): void => { // InAppBrowser closed, refresh data. unsubscribeAll(); @@ -276,7 +282,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { this.dataLoaded = false; this.refreshData(); }, - unsubscribeAll = () => { + unsubscribeAll = (): void => { inAppLoadSubscription && inAppLoadSubscription.unsubscribe(); inAppFinishSubscription && inAppFinishSubscription.unsubscribe(); inAppExitSubscription && inAppExitSubscription.unsubscribe(); @@ -315,7 +321,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { * * @param {number} instanceId The instance ID of the enrolment method. */ - selfEnrolClicked(instanceId: number) { + selfEnrolClicked(instanceId: number): void { this.domUtils.showConfirm(this.translate.instant('core.courses.confirmselfenrol')).then(() => { this.selfEnrolInCourse('', instanceId); }).catch(() => { @@ -330,8 +336,8 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { * @param {number} instanceId The instance ID. * @return {Promise} Promise resolved when self enrolled. */ - selfEnrolInCourse(password: string, instanceId: number) : Promise { - let modal = this.domUtils.showModalLoading('core.loading', true); + selfEnrolInCourse(password: string, instanceId: number): Promise { + const modal = this.domUtils.showModalLoading('core.loading', true); return this.coursesProvider.selfEnrol(this.course.id, password, instanceId).then(() => { // Close modal and refresh data. @@ -343,7 +349,7 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { this.refreshData().finally(() => { // My courses have been updated, trigger event. this.eventsProvider.trigger( - CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {}, this.sitesProvider.getCurrentSiteId()); + CoreCoursesProvider.EVENT_MY_COURSES_UPDATED, {}, this.sitesProvider.getCurrentSiteId()); }); }); }).catch((error) => { @@ -369,8 +375,8 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { * * @param {any} [refresher] The refresher if this was triggered by a Pull To Refresh. */ - refreshData(refresher?: any) : Promise { - let promises = []; + refreshData(refresher?: any): Promise { + const promises = []; promises.push(this.coursesProvider.invalidateUserCourses()); promises.push(this.coursesProvider.invalidateCourse(this.course.id)); @@ -393,8 +399,9 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { * Wait for the user to be enrolled in the course. * * @param {boolean} first If it's the first call (true) or it's a recursive call (false). + * @return {Promise} Promise resolved when enrolled or timeout. */ - protected waitForEnrolled(first?: boolean) { + protected waitForEnrolled(first?: boolean): Promise { if (first) { this.waitStart = Date.now(); } @@ -406,12 +413,12 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { return this.coursesProvider.getUserCourse(this.course.id); }).catch(() => { // Not enrolled, wait a bit and try again. - if (this.pageDestroyed || (Date.now() - this.waitStart > 60000)) { + if (this.pageDestroyed || (Date.now() - this.waitStart > 60000)) { // Max time reached or the user left the view, stop. return; } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { setTimeout(() => { if (!this.pageDestroyed) { // Wait again. @@ -427,12 +434,12 @@ export class CoreCoursesCoursePreviewPage implements OnDestroy { /** * Prefetch the course. */ - prefetchCourse() { + prefetchCourse(): void { this.courseHelper.confirmAndPrefetchCourse(this.prefetchCourseData, this.course, undefined, this.course._handlers) .catch((error) => { if (!this.pageDestroyed) { this.domUtils.showErrorModalDefault(error, 'core.course.errordownloadingcourse', true); } - }) + }); } } diff --git a/src/core/courses/pages/my-courses/my-courses.ts b/src/core/courses/pages/my-courses/my-courses.ts index 4793aa8c2..8f6742853 100644 --- a/src/core/courses/pages/my-courses/my-courses.ts +++ b/src/core/courses/pages/my-courses/my-courses.ts @@ -24,7 +24,7 @@ import { CoreCourseOptionsDelegate } from '../../../course/providers/options-del /** * Page that displays the list of courses the user is enrolled in. */ -@IonicPage({segment: "core-courses-my-courses"}) +@IonicPage({ segment: 'core-courses-my-courses' }) @Component({ selector: 'page-core-courses-my-courses', templateUrl: 'my-courses.html', @@ -46,12 +46,12 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { constructor(private navCtrl: NavController, private coursesProvider: CoreCoursesProvider, private domUtils: CoreDomUtilsProvider, private eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider, private courseHelper: CoreCourseHelperProvider, - private courseOptionsDelegate: CoreCourseOptionsDelegate) {} + private courseOptionsDelegate: CoreCourseOptionsDelegate) { } /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); this.fetchCourses().finally(() => { @@ -69,8 +69,10 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { /** * Fetch the user courses. + * + * @return {Promise} Promise resolved when done. */ - protected fetchCourses() { + protected fetchCourses(): Promise { return this.coursesProvider.getUserCourses().then((courses) => { const courseIds = courses.map((course) => { @@ -98,8 +100,8 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { * * @param {any} refresher Refresher. */ - refreshCourses(refresher: any) { - let promises = []; + refreshCourses(refresher: any): void { + const promises = []; promises.push(this.coursesProvider.invalidateUserCourses()); promises.push(this.courseOptionsDelegate.clearAndInvalidateCoursesOptions()); @@ -116,7 +118,7 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { /** * Show or hide the filter. */ - switchFilter() { + switchFilter(): void { this.filter = ''; this.showFilter = !this.showFilter; this.filteredCourses = this.courses; @@ -125,7 +127,7 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { /** * Go to search courses. */ - openSearch() { + openSearch(): void { this.navCtrl.push('CoreCoursesSearchPage'); } @@ -134,7 +136,7 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { * * @param {string} newValue New filter value. */ - filterChanged(newValue: string) { + filterChanged(newValue: string): void { if (!newValue || !this.courses) { this.filteredCourses = this.courses; } else { @@ -146,12 +148,15 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { /** * Prefetch all the courses. + * + * @return {Promise} Promise resolved when done. */ - prefetchCourses() { - let initialIcon = this.prefetchCoursesData.icon; + prefetchCourses(): Promise { + const initialIcon = this.prefetchCoursesData.icon; this.prefetchCoursesData.icon = 'spinner'; this.prefetchCoursesData.badge = ''; + return this.courseHelper.confirmAndPrefetchCourses(this.courses, (progress) => { this.prefetchCoursesData.badge = progress.count + ' / ' + progress.total; }).then((downloaded) => { @@ -169,7 +174,7 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { /** * Initialize the prefetch icon for the list of courses. */ - protected initPrefetchCoursesIcon() { + protected initPrefetchCoursesIcon(): void { if (this.prefetchIconInitialized) { // Already initialized. return; @@ -180,6 +185,7 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { if (!this.courses || this.courses.length < 2) { // Not enough courses. this.prefetchCoursesData.icon = ''; + return; } @@ -196,7 +202,7 @@ export class CoreCoursesMyCoursesPage implements OnDestroy { /** * Page destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.isDestroyed = true; this.myCoursesObserver && this.myCoursesObserver.off(); this.siteUpdatedObserver && this.siteUpdatedObserver.off(); diff --git a/src/core/courses/pages/my-overview/my-overview.ts b/src/core/courses/pages/my-overview/my-overview.ts index c9752eea8..20c919f51 100644 --- a/src/core/courses/pages/my-overview/my-overview.ts +++ b/src/core/courses/pages/my-overview/my-overview.ts @@ -26,7 +26,7 @@ import * as moment from 'moment'; /** * Page that displays My Overview. */ -@IonicPage({segment: "core-courses-my-overview"}) +@IonicPage({ segment: 'core-courses-my-overview' }) @Component({ selector: 'page-core-courses-my-overview', templateUrl: 'my-overview.html', @@ -34,7 +34,7 @@ import * as moment from 'moment'; export class CoreCoursesMyOverviewPage implements OnDestroy { firstSelectedTab: number; siteHomeEnabled: boolean; - tabsReady: boolean = false; + tabsReady = false; tabShown = 'courses'; timeline = { sort: 'sortbydates', @@ -71,17 +71,17 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { constructor(private navCtrl: NavController, private coursesProvider: CoreCoursesProvider, private domUtils: CoreDomUtilsProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider, private courseHelper: CoreCourseHelperProvider, private sitesProvider: CoreSitesProvider, - private siteHomeProvider: CoreSiteHomeProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate) {} + private siteHomeProvider: CoreSiteHomeProvider, private courseOptionsDelegate: CoreCourseOptionsDelegate) { } /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.searchEnabled = !this.coursesProvider.isSearchCoursesDisabledInSite(); // Decide which tab to load first. this.siteHomeProvider.isAvailable().then((enabled) => { - let site = this.sitesProvider.getCurrentSite(), + const site = this.sitesProvider.getCurrentSite(), displaySiteHome = site.getInfo() && site.getInfo().userhomepage === 0; this.siteHomeEnabled = enabled; @@ -96,7 +96,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { * @param {number} [afterEventId] The last event id. * @return {Promise} Promise resolved when done. */ - protected fetchMyOverviewTimeline(afterEventId?: number) : Promise { + protected fetchMyOverviewTimeline(afterEventId?: number): Promise { return this.myOverviewProvider.getActionEventsByTimesort(afterEventId).then((events) => { this.timeline.events = events.events; this.timeline.canLoadMore = events.canLoadMore; @@ -110,10 +110,10 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { * * @return {Promise} Promise resolved when done. */ - protected fetchMyOverviewTimelineByCourses() : Promise { + protected fetchMyOverviewTimelineByCourses(): Promise { return this.fetchUserCourses().then((courses) => { - let today = moment().unix(), - courseIds; + const today = moment().unix(); + let courseIds; courses = courses.filter((course) => { return course.startdate <= today && (!course.enddate || course.enddate >= today); }); @@ -141,7 +141,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { * * @return {Promise} Promise resolved when done. */ - protected fetchMyOverviewCourses() : Promise { + protected fetchMyOverviewCourses(): Promise { return this.fetchUserCourses().then((courses) => { const today = moment().unix(); @@ -177,10 +177,9 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { * * @return {Promise} Promise resolved when done. */ - protected fetchUserCourses() : Promise { - let courseIds; + protected fetchUserCourses(): Promise { return this.coursesProvider.getUserCourses().then((courses) => { - courseIds = courses.map((course) => { + const courseIds = courses.map((course) => { return course.id; }); @@ -204,7 +203,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { /** * Show or hide the filter. */ - switchFilter() { + switchFilter(): void { this.showFilter = !this.showFilter; this.courses.filter = ''; this.filteredCourses = this.courses[this.courses.selected]; @@ -215,7 +214,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { * * @param {string} newValue New filter value. */ - filterChanged(newValue: string) { + filterChanged(newValue: string): void { if (!newValue || !this.courses[this.courses.selected]) { this.filteredCourses = this.courses[this.courses.selected]; } else { @@ -229,9 +228,10 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { * Refresh the data. * * @param {any} refresher Refresher. + * @return {Promise} Promise resolved when done. */ - refreshMyOverview(refresher: any) { - let promises = []; + refreshMyOverview(refresher: any): Promise { + const promises = []; if (this.tabShown == 'timeline') { promises.push(this.myOverviewProvider.invalidateActionEventsByTimesort()); @@ -249,11 +249,14 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { return this.fetchMyOverviewTimeline(); case 'sortbycourses': return this.fetchMyOverviewTimelineByCourses(); + default: } break; case 'courses': this.prefetchIconsInitialized = false; + return this.fetchMyOverviewCourses(); + default: } }).finally(() => { refresher.complete(); @@ -263,7 +266,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { /** * Change timeline sort being viewed. */ - switchSort() { + switchSort(): void { switch (this.timeline.sort) { case 'sortbydates': if (!this.timeline.loaded) { @@ -279,6 +282,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { }); } break; + default: } } @@ -287,7 +291,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { * * @param {string} tab Name of the new tab. */ - tabChanged(tab: string) { + tabChanged(tab: string): void { this.tabShown = tab; switch (this.tabShown) { case 'timeline': @@ -304,13 +308,14 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { }); } break; + default: } } /** * Load more events. */ - loadMoreTimeline() : Promise { + loadMoreTimeline(): Promise { return this.fetchMyOverviewTimeline(this.timeline.canLoadMore); } @@ -318,8 +323,9 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { * Load more events. * * @param {any} course Course. + * @return {Promise} Promise resolved when done. */ - loadMoreCourse(course) { + loadMoreCourse(course: any): Promise { return this.myOverviewProvider.getActionEventsByCourse(course.id, course.canLoadMore).then((courseEvents) => { course.events = course.events.concat(courseEvents.events); course.canLoadMore = courseEvents.canLoadMore; @@ -329,27 +335,30 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { /** * Go to search courses. */ - openSearch() { + openSearch(): void { this.navCtrl.push('CoreCoursesSearchPage'); } /** * The selected courses have changed. */ - selectedChanged() { + selectedChanged(): void { this.filteredCourses = this.courses[this.courses.selected]; } /** * Prefetch all the shown courses. + * + * @return {Promise} Promise resolved when done. */ - prefetchCourses() { - let selected = this.courses.selected, + prefetchCourses(): Promise { + const selected = this.courses.selected, selectedData = this.prefetchCoursesData[selected], initialIcon = selectedData.icon; selectedData.icon = 'spinner'; selectedData.badge = ''; + return this.courseHelper.confirmAndPrefetchCourses(this.courses[selected], (progress) => { selectedData.badge = progress.count + ' / ' + progress.total; }).then((downloaded) => { @@ -367,7 +376,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { /** * Initialize the prefetch icon for selected courses. */ - protected initPrefetchCoursesIcons() { + protected initPrefetchCoursesIcons(): void { if (this.prefetchIconsInitialized) { // Already initialized. return; @@ -379,6 +388,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { if (!this.courses[filter] || this.courses[filter].length < 2) { // Not enough courses. this.prefetchCoursesData[filter].icon = ''; + return; } @@ -397,7 +407,7 @@ export class CoreCoursesMyOverviewPage implements OnDestroy { /** * Component being destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.isDestroyed = true; } } diff --git a/src/core/courses/pages/search/search.ts b/src/core/courses/pages/search/search.ts index 0e7de9151..696215a1a 100644 --- a/src/core/courses/pages/search/search.ts +++ b/src/core/courses/pages/search/search.ts @@ -20,7 +20,7 @@ import { CoreCoursesProvider } from '../../providers/courses'; /** * Page that allows searching for courses. */ -@IonicPage({segment: "core-courses-search"}) +@IonicPage({ segment: 'core-courses-search' }) @Component({ selector: 'page-core-courses-search', templateUrl: 'search.html', @@ -33,19 +33,19 @@ export class CoreCoursesSearchPage { protected page = 0; protected currentSearch = ''; - constructor(private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider) {} + constructor(private domUtils: CoreDomUtilsProvider, private coursesProvider: CoreCoursesProvider) { } /** * Search a new text. * * @param {string} text The text to search. */ - search(text: string) { + search(text: string): void { this.currentSearch = text; this.courses = undefined; this.page = 0; - let modal = this.domUtils.showModalLoading('core.searching', true); + const modal = this.domUtils.showModalLoading('core.searching', true); this.searchCourses().finally(() => { modal.dismiss(); }); @@ -53,8 +53,10 @@ export class CoreCoursesSearchPage { /** * Load more results. + * + * @param {any} infiniteScroll The infinit scroll instance. */ - loadMoreResults(infiniteScroll) { + loadMoreResults(infiniteScroll: any): void { this.searchCourses().finally(() => { infiniteScroll.complete(); }); @@ -62,8 +64,10 @@ export class CoreCoursesSearchPage { /** * Search courses or load the next page of current search. + * + * @return {Promise} Promise resolved when done. */ - protected searchCourses() { + protected searchCourses(): Promise { return this.coursesProvider.search(this.currentSearch, this.page).then((response) => { if (this.page === 0) { this.courses = response.courses; diff --git a/src/core/courses/pages/self-enrol-password/self-enrol-password.ts b/src/core/courses/pages/self-enrol-password/self-enrol-password.ts index 7e9716a43..95187568a 100644 --- a/src/core/courses/pages/self-enrol-password/self-enrol-password.ts +++ b/src/core/courses/pages/self-enrol-password/self-enrol-password.ts @@ -18,18 +18,18 @@ import { IonicPage, ViewController } from 'ionic-angular'; /** * Page that displays a form to enter a password to self enrol in a course. */ -@IonicPage({segment: "core-courses-self-enrol-password"}) +@IonicPage({ segment: 'core-courses-self-enrol-password' }) @Component({ selector: 'page-core-courses-self-enrol-password', templateUrl: 'self-enrol-password.html', }) export class CoreCoursesSelfEnrolPasswordPage { - constructor(private viewCtrl: ViewController) {} + constructor(private viewCtrl: ViewController) { } /** * Close help modal. */ - close() : void { + close(): void { this.viewCtrl.dismiss(); } @@ -38,7 +38,7 @@ export class CoreCoursesSelfEnrolPasswordPage { * * @param {string} password Password to submit. */ - submitPassword(password: string) { + submitPassword(password: string): void { this.viewCtrl.dismiss(password); } -} \ No newline at end of file +} diff --git a/src/core/courses/providers/course-link-handler.ts b/src/core/courses/providers/course-link-handler.ts index fa1fa3235..cec8933d3 100644 --- a/src/core/courses/providers/course-link-handler.ts +++ b/src/core/courses/providers/course-link-handler.ts @@ -47,15 +47,15 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise { + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { courseId = parseInt(params.id, 10); - let sectionId = params.sectionid ? parseInt(params.sectionid, 10) : null, + const sectionId = params.sectionid ? parseInt(params.sectionid, 10) : null, sectionNumber = typeof params.section != 'undefined' ? parseInt(params.section, 10) : NaN, pageParams: any = { - course: {id: courseId}, - sectionId: sectionId || null + course: { id: courseId }, + sectionId: sectionId || null }; if (!isNaN(sectionNumber)) { @@ -63,7 +63,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { } return [{ - action: (siteId, navCtrl?) => { + action: (siteId, navCtrl?): void => { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (siteId == this.sitesProvider.getCurrentSiteId()) { this.actionEnrol(courseId, url, pageParams).catch(() => { @@ -87,7 +87,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {boolean|Promise} Whether the handler is enabled for the URL and site. */ - isEnabled(siteId: string, url: string, params: any, courseId?: number) : boolean|Promise { + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { courseId = parseInt(params.id, 10); if (!courseId) { @@ -96,8 +96,8 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { // Get the course id of Site Home. return this.sitesProvider.getSiteHomeId(siteId).then((siteHomeId) => { - return courseId != siteHomeId; - }); + return courseId != siteHomeId; + }); } /** @@ -108,8 +108,8 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { * @param {any} pageParams Params to send to the new page. * @return {Promise} Promise resolved when done. */ - protected actionEnrol(courseId: number, url: string, pageParams: any) : Promise { - let modal = this.domUtils.showModalLoading(), + protected actionEnrol(courseId: number, url: string, pageParams: any): Promise { + const modal = this.domUtils.showModalLoading(), isEnrolUrl = !!url.match(/(\/enrol\/index\.php)|(\/course\/enrol\.php)/); // Check if user is enrolled in the course. @@ -119,8 +119,8 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { modal.dismiss(); // The user can self enrol. If it's not a enrolment URL we'll ask for confirmation. - let promise = isEnrolUrl ? Promise.resolve() : - this.domUtils.showConfirm(this.translate.instant('core.courses.confirmselfenrol')); + const promise = isEnrolUrl ? Promise.resolve() : + this.domUtils.showConfirm(this.translate.instant('core.courses.confirmselfenrol')); return promise.then(() => { // Enrol URL or user confirmed. @@ -128,6 +128,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { if (error) { this.domUtils.showErrorModal(error); } + return Promise.reject(null); }); }, () => { @@ -147,13 +148,14 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { error = this.translate.instant('core.courses.notenroled'); } - let body = this.translate.instant('core.twoparagraphs', - {p1: error, p2: this.translate.instant('core.confirmopeninbrowser')}); + const body = this.translate.instant('core.twoparagraphs', + { p1: error, p2: this.translate.instant('core.confirmopeninbrowser') }); this.domUtils.showConfirm(body).then(() => { this.sitesProvider.getCurrentSite().openInBrowserWithAutoLogin(url); }).catch(() => { // User cancelled. }); + return Promise.reject(null); }); }); @@ -171,7 +173,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { * @param {number} courseId Course ID. * @return {Promise} Promise resolved if user can be enrolled in a course, rejected otherwise. */ - protected canSelfEnrol(courseId: number) : Promise { + protected canSelfEnrol(courseId: number): Promise { // Check that the course has self enrolment enabled. return this.coursesProvider.getCourseEnrolmentMethods(courseId).then((methods) => { let isSelfEnrolEnabled = false, @@ -198,8 +200,9 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { * @param {string} [password] Password. * @return {Promise} Promise resolved when the user is enrolled, rejected otherwise. */ - protected selfEnrol(courseId: number, password?: string) : Promise { + protected selfEnrol(courseId: number, password?: string): Promise { const modal = this.domUtils.showModalLoading(); + return this.coursesProvider.selfEnrol(courseId, password).then(() => { // Success self enrolling the user, invalidate the courses list. return this.coursesProvider.invalidateUserCourses().catch(() => { @@ -215,7 +218,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { modal.dismiss(); if (error && error.code === CoreCoursesProvider.ENROL_INVALID_KEY) { // Invalid password. Allow the user to input password. - let title = this.translate.instant('core.courses.selfenrolment'), + const title = this.translate.instant('core.courses.selfenrolment'), body = ' ', // Empty message. placeholder = this.translate.instant('core.courses.password'); @@ -240,7 +243,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { * @param {boolean} first If it's the first call (true) or it's a recursive call (false). * @return {Promise} Promise resolved when enrolled or timeout. */ - protected waitForEnrolled(courseId: number, first?: boolean) : Promise { + protected waitForEnrolled(courseId: number, first?: boolean): Promise { if (first) { this.waitStart = Date.now(); } @@ -257,7 +260,7 @@ export class CoreCoursesCourseLinkHandler extends CoreContentLinksHandlerBase { return; } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { setTimeout(() => { this.waitForEnrolled(courseId).then(resolve); }, 5000); diff --git a/src/core/courses/providers/courses-index-link-handler.ts b/src/core/courses/providers/courses-index-link-handler.ts index 051adf797..ebf41c4cf 100644 --- a/src/core/courses/providers/courses-index-link-handler.ts +++ b/src/core/courses/providers/courses-index-link-handler.ts @@ -38,14 +38,14 @@ export class CoreCoursesIndexLinkHandler extends CoreContentLinksHandlerBase { * @param {string} url The URL to treat. * @param {any} params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} * @param {number} [courseId] Course ID related to the URL. Optional but recommended. - * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. + * @return {CoreContentLinksAction[] | Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise { + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { return [{ - action: (siteId, navCtrl?) => { - var page = 'CoreCoursesMyCoursesPage', // By default, go to My Courses. - pageParams: any = {}; + action: (siteId, navCtrl?): void => { + let page = 'CoreCoursesMyCoursesPage'; // By default, go to My Courses. + const pageParams: any = {}; if (this.coursesProvider.isGetCoursesByFieldAvailable()) { if (params.categoryid) { diff --git a/src/core/courses/providers/courses.ts b/src/core/courses/providers/courses.ts index 787e38be1..f71deddfe 100644 --- a/src/core/courses/providers/courses.ts +++ b/src/core/courses/providers/courses.ts @@ -22,10 +22,10 @@ import { CoreSite } from '../../../classes/site'; */ @Injectable() export class CoreCoursesProvider { - public static SEARCH_PER_PAGE = 20; - public static ENROL_INVALID_KEY = 'CoreCoursesEnrolInvalidKey'; - public static EVENT_MY_COURSES_UPDATED = 'courses_my_courses_updated'; - public static EVENT_MY_COURSES_REFRESHED = 'courses_my_courses_refreshed'; + static SEARCH_PER_PAGE = 20; + static ENROL_INVALID_KEY = 'CoreCoursesEnrolInvalidKey'; + static EVENT_MY_COURSES_UPDATED = 'courses_my_courses_updated'; + static EVENT_MY_COURSES_REFRESHED = 'courses_my_courses_refreshed'; protected ROOT_CACHE_KEY = 'mmCourses:'; protected logger; @@ -41,10 +41,10 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site to get the courses from. If not defined, use current site. * @return {Promise} Promise resolved with the categories. */ - getCategories(categoryId: number, addSubcategories?: boolean, siteId?: string) : Promise { + getCategories(categoryId: number, addSubcategories?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { // Get parent when id is the root category. - let criteriaKey = categoryId == 0 ? 'parent' : 'id', + const criteriaKey = categoryId == 0 ? 'parent' : 'id', data = { criteria: [ { key: criteriaKey, value: categoryId } @@ -53,7 +53,7 @@ export class CoreCoursesProvider { }, preSets = { cacheKey: this.getCategoriesCacheKey(categoryId, addSubcategories) - } + }; return site.read('core_course_get_categories', data, preSets); }); @@ -66,7 +66,7 @@ export class CoreCoursesProvider { * @param {boolean} [addSubcategories] If add subcategories to the list. * @return {string} Cache key. */ - protected getCategoriesCacheKey(categoryId: number, addSubcategories?: boolean) : string { + protected getCategoriesCacheKey(categoryId: number, addSubcategories?: boolean): string { return this.ROOT_CACHE_KEY + 'categories:' + categoryId + ':' + !!addSubcategories; } @@ -77,15 +77,15 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved with the list of course IDs. */ - protected getCourseIdsForAdminAndNavOptions(courseIds: number[], siteId?: string) : Promise { + protected getCourseIdsForAdminAndNavOptions(courseIds: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const siteHomeId = site.getSiteHomeId(); if (courseIds.length == 1) { // Only 1 course, check if it belongs to the user courses. If so, use all user courses. return this.getUserCourses(true, siteId).then((courses) => { - let courseId = courseIds[0], - useAllCourses = false; + const courseId = courseIds[0]; + let useAllCourses = false; if (courseId == siteHomeId) { // It's site home, use all courses. @@ -126,7 +126,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. */ - isMyCoursesDisabled(siteId?: string) : Promise { + isMyCoursesDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return this.isMyCoursesDisabledInSite(site); }); @@ -138,8 +138,9 @@ export class CoreCoursesProvider { * @param {CoreSite} [site] Site. If not defined, use current site. * @return {boolean} Whether it's disabled. */ - isMyCoursesDisabledInSite(site?: CoreSite) : boolean { + isMyCoursesDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); + return site.isFeatureDisabled('$mmSideMenuDelegate_mmCourses'); } @@ -149,7 +150,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. */ - isSearchCoursesDisabled(siteId?: string) : Promise { + isSearchCoursesDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return this.isSearchCoursesDisabledInSite(site); }); @@ -161,8 +162,9 @@ export class CoreCoursesProvider { * @param {CoreSite} [site] Site. If not defined, use current site. * @return {boolean} Whether it's disabled. */ - isSearchCoursesDisabledInSite(site?: CoreSite) : boolean { + isSearchCoursesDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); + return site.isFeatureDisabled('$mmCoursesDelegate_search'); } @@ -173,11 +175,12 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site to get the courses from. If not defined, use current site. * @return {Promise} Promise resolved with the course. */ - getCourse(id: number, siteId?: string) : Promise { + getCourse(id: number, siteId?: string): Promise { return this.getCourses([id], siteId).then((courses) => { if (courses && courses.length > 0) { return courses[0]; } + return Promise.reject(null); }); } @@ -189,14 +192,14 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise { + getCourseEnrolmentMethods(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let params = { + const params = { courseid: id }, preSets = { cacheKey: this.getCourseEnrolmentMethodsCacheKey(id) - } + }; return site.read('core_enrol_get_course_enrolment_methods', params, preSets); }); @@ -208,7 +211,7 @@ export class CoreCoursesProvider { * @param {number} id Course ID. * @return {string} Cache key. */ - protected getCourseEnrolmentMethodsCacheKey(id: number) : string { + protected getCourseEnrolmentMethodsCacheKey(id: number): string { return this.ROOT_CACHE_KEY + 'enrolmentmethods:' + id; } @@ -219,14 +222,14 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise} Promise resolved when the info is retrieved. */ - getCourseGuestEnrolmentInfo(instanceId: number, siteId?: string) : Promise { + getCourseGuestEnrolmentInfo(instanceId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let params = { + const params = { instanceid: instanceId }, preSets = { cacheKey: this.getCourseGuestEnrolmentInfoCacheKey(instanceId) - } + }; return site.read('enrol_guest_get_instance_info', params, preSets).then((response) => { return response.instanceinfo; @@ -240,7 +243,7 @@ export class CoreCoursesProvider { * @param {number} instanceId Guest instance ID. * @return {string} Cache key. */ - protected getCourseGuestEnrolmentInfoCacheKey(instanceId: number) : string { + protected getCourseGuestEnrolmentInfoCacheKey(instanceId: number): string { return this.ROOT_CACHE_KEY + 'guestinfo:' + instanceId; } @@ -253,7 +256,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site to get the courses from. If not defined, use current site. * @return {Promise} Promise resolved with the courses. */ - getCourses(ids: number[], siteId?: string) : Promise { + getCourses(ids: number[], siteId?: string): Promise { if (!Array.isArray(ids)) { return Promise.reject(null); } else if (ids.length === 0) { @@ -261,14 +264,14 @@ export class CoreCoursesProvider { } return this.sitesProvider.getSite(siteId).then((site) => { - let data = { + const data = { options: { ids: ids } }, preSets = { cacheKey: this.getCoursesCacheKey(ids) - } + }; return site.read('core_course_get_courses', data, preSets); }); @@ -280,7 +283,7 @@ export class CoreCoursesProvider { * @param {number[]} ids Courses IDs. * @return {string} Cache key. */ - protected getCoursesCacheKey(ids: number[]) : string { + protected getCoursesCacheKey(ids: number[]): string { return this.ROOT_CACHE_KEY + 'course:' + JSON.stringify(ids); } @@ -297,15 +300,15 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise} Promise resolved with the courses. */ - getCoursesByField(field?: string, value?: any, siteId?: string) : Promise { + getCoursesByField(field?: string, value?: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let data = { + const data = { field: field || '', value: field ? value : '' }, preSets = { cacheKey: this.getCoursesByFieldCacheKey(field, value) - } + }; return site.read('core_course_get_courses_by_field', data, preSets).then((courses) => { if (courses.courses) { @@ -339,9 +342,10 @@ export class CoreCoursesProvider { * @param {any} [value] The value to match. * @return {string} Cache key. */ - protected getCoursesByFieldCacheKey(field?: string, value?: any) : string { + protected getCoursesByFieldCacheKey(field?: string, value?: any): string { field = field || ''; value = field ? value : ''; + return this.ROOT_CACHE_KEY + 'coursesbyfield:' + field + ':' + value; } @@ -350,9 +354,8 @@ export class CoreCoursesProvider { * * @return {boolean} Whether get courses by field is available. */ - isGetCoursesByFieldAvailable() : boolean { - let currentSite = this.sitesProvider.getCurrentSite(); - return currentSite.wsAvailable('core_course_get_courses_by_field'); + isGetCoursesByFieldAvailable(): boolean { + return this.sitesProvider.getCurrentSite().wsAvailable('core_course_get_courses_by_field'); } /** @@ -362,13 +365,13 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise<{navOptions: any, admOptions: any}>} Promise resolved with the options for each course. */ - getCoursesAdminAndNavOptions(courseIds: number[], siteId?: string) : Promise<{navOptions: any, admOptions: any}> { + getCoursesAdminAndNavOptions(courseIds: number[], siteId?: string): Promise<{ navOptions: any, admOptions: any }> { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Get the list of courseIds to use based on the param. return this.getCourseIdsForAdminAndNavOptions(courseIds, siteId).then((courseIds) => { - let promises = [], - navOptions, + const promises = []; + let navOptions, admOptions; // Get user navigation and administration options. @@ -387,7 +390,7 @@ export class CoreCoursesProvider { })); return Promise.all(promises).then(() => { - return {navOptions: navOptions, admOptions: admOptions}; + return { navOptions: navOptions, admOptions: admOptions }; }); }); } @@ -397,7 +400,7 @@ export class CoreCoursesProvider { * * @return {string} Cache key. */ - protected getUserAdministrationOptionsCommonCacheKey() : string { + protected getUserAdministrationOptionsCommonCacheKey(): string { return this.ROOT_CACHE_KEY + 'administrationOptions:'; } @@ -407,7 +410,7 @@ export class CoreCoursesProvider { * @param {number[]} courseIds IDs of courses to get. * @return {string} Cache key. */ - protected getUserAdministrationOptionsCacheKey(courseIds: number[]) : string { + protected getUserAdministrationOptionsCacheKey(courseIds: number[]): string { return this.getUserAdministrationOptionsCommonCacheKey() + courseIds.join(','); } @@ -418,14 +421,14 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with administration options for each course. */ - getUserAdministrationOptions(courseIds: number[], siteId?: string) : Promise { + getUserAdministrationOptions(courseIds: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let params = { + const params = { courseids: courseIds }, preSets = { cacheKey: this.getUserAdministrationOptionsCacheKey(courseIds) - } + }; return site.read('core_course_get_user_administration_options', params, preSets).then((response) => { // Format returned data. @@ -440,7 +443,7 @@ export class CoreCoursesProvider { * @param {number[]} courseIds IDs of courses to get. * @return {string} Cache key. */ - protected getUserNavigationOptionsCommonCacheKey() : string { + protected getUserNavigationOptionsCommonCacheKey(): string { return this.ROOT_CACHE_KEY + 'navigationOptions:'; } @@ -449,7 +452,7 @@ export class CoreCoursesProvider { * * @return {string} Cache key. */ - protected getUserNavigationOptionsCacheKey(courseIds: number[]) : string { + protected getUserNavigationOptionsCacheKey(courseIds: number[]): string { return this.getUserNavigationOptionsCommonCacheKey() + courseIds.join(','); } @@ -460,14 +463,14 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with navigation options for each course. */ - getUserNavigationOptions(courseIds: number[], siteId?: string) : Promise { + getUserNavigationOptions(courseIds: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let params = { + const params = { courseids: courseIds }, preSets = { cacheKey: this.getUserNavigationOptionsCacheKey(courseIds) - } + }; return site.read('core_course_get_user_navigation_options', params, preSets).then((response) => { // Format returned data. @@ -482,11 +485,11 @@ export class CoreCoursesProvider { * @param {any[]} courses Navigation or administration options for each course. * @return {any} Formatted options. */ - protected formatUserAdminOrNavOptions(courses: any[]) : any { - let result = {}; + protected formatUserAdminOrNavOptions(courses: any[]): any { + const result = {}; courses.forEach((course) => { - let options = {}; + const options = {}; if (course.options) { course.options.forEach((option) => { @@ -509,14 +512,14 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site to get the courses from. If not defined, use current site. * @return {Promise} Promise resolved with the course. */ - getUserCourse(id: number, preferCache?: boolean, siteId?: string) : Promise { + getUserCourse(id: number, preferCache?: boolean, siteId?: string): Promise { if (!id) { return Promise.reject(null); } return this.getUserCourses(preferCache, siteId).then((courses) => { let course; - for (let i in courses) { + for (const i in courses) { if (courses[i].id == id) { course = courses[i]; break; @@ -534,10 +537,10 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site to get the courses from. If not defined, use current site. * @return {Promise} Promise resolved with the courses. */ - getUserCourses(preferCache?: boolean, siteId?: string) : Promise { + getUserCourses(preferCache?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let userId = site.getUserId(), + const userId = site.getUserId(), data = { userid: userId }, @@ -555,7 +558,7 @@ export class CoreCoursesProvider { * * @return {string} Cache key. */ - protected getUserCoursesCacheKey() : string { + protected getUserCoursesCacheKey(): string { return this.ROOT_CACHE_KEY + 'usercourses'; } @@ -567,7 +570,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateCategories(categoryId: number, addSubcategories?: boolean, siteId?: string) : Promise { + invalidateCategories(categoryId: number, addSubcategories?: boolean, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getCategoriesCacheKey(categoryId, addSubcategories)); }); @@ -580,7 +583,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateCourse(id: number, siteId?: string) : Promise { + invalidateCourse(id: number, siteId?: string): Promise { return this.invalidateCourses([id], siteId); } @@ -591,7 +594,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateCourseEnrolmentMethods(id: number, siteId?: string) : Promise { + invalidateCourseEnrolmentMethods(id: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getCourseEnrolmentMethodsCacheKey(id)); }); @@ -604,7 +607,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateCourseGuestEnrolmentInfo(instanceId: number, siteId?: string) : Promise { + invalidateCourseGuestEnrolmentInfo(instanceId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getCourseGuestEnrolmentInfoCacheKey(instanceId)); }); @@ -617,11 +620,11 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateCoursesAdminAndNavOptions(courseIds: number[], siteId?: string) : Promise { + invalidateCoursesAdminAndNavOptions(courseIds: number[], siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); return this.getCourseIdsForAdminAndNavOptions(courseIds, siteId).then((ids) => { - let promises = []; + const promises = []; promises.push(this.invalidateUserAdministrationOptionsForCourses(ids, siteId)); promises.push(this.invalidateUserNavigationOptionsForCourses(ids, siteId)); @@ -637,7 +640,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateCourses(ids: number[], siteId?: string) : Promise { + invalidateCourses(ids: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getCoursesCacheKey(ids)); }); @@ -651,7 +654,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateCoursesByField(field?: string, value?: any, siteId?: string) : Promise { + invalidateCoursesByField(field?: string, value?: any, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getCoursesByFieldCacheKey(field, value)); }); @@ -663,7 +666,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateUserAdministrationOptions(siteId?: string) : Promise { + invalidateUserAdministrationOptions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKeyStartingWith(this.getUserAdministrationOptionsCommonCacheKey()); }); @@ -676,7 +679,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateUserAdministrationOptionsForCourses(courseIds: number[], siteId?: string) : Promise { + invalidateUserAdministrationOptionsForCourses(courseIds: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getUserAdministrationOptionsCacheKey(courseIds)); }); @@ -688,7 +691,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateUserCourses(siteId?: string) : Promise { + invalidateUserCourses(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getUserCoursesCacheKey()); }); @@ -700,7 +703,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateUserNavigationOptions(siteId?: string) : Promise { + invalidateUserNavigationOptions(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKeyStartingWith(this.getUserNavigationOptionsCommonCacheKey()); }); @@ -713,7 +716,7 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateUserNavigationOptionsForCourses(courseIds: number[], siteId?: string) : Promise { + invalidateUserNavigationOptionsForCourses(courseIds: number[], siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getUserNavigationOptionsCacheKey(courseIds)); }); @@ -724,8 +727,9 @@ export class CoreCoursesProvider { * * @return {boolean} Whether guest WS is available. */ - isGuestWSAvailable() : boolean { - let currentSite = this.sitesProvider.getCurrentSite(); + isGuestWSAvailable(): boolean { + const currentSite = this.sitesProvider.getCurrentSite(); + return currentSite && currentSite.wsAvailable('enrol_guest_get_instance_info'); } @@ -738,21 +742,22 @@ export class CoreCoursesProvider { * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise<{total: number, courses: any[]}>} Promise resolved with the courses and the total of matches. */ - search(text: string, page = 0, perPage?: number, siteId?: string) : Promise<{total: number, courses: any[]}> { + search(text: string, page: number = 0, perPage?: number, siteId?: string): Promise<{ total: number, courses: any[] }> { perPage = perPage || CoreCoursesProvider.SEARCH_PER_PAGE; return this.sitesProvider.getSite(siteId).then((site) => { - let params = { + const params = { criterianame: 'search', criteriavalue: text, page: page, perpage: perPage - }, preSets = { + }, + preSets = { getFromCache: false - } + }; return site.read('core_course_search_courses', params, preSets).then((response) => { - return {total: response.total, courses: response.courses}; + return { total: response.total, courses: response.courses }; }); }); } @@ -767,18 +772,19 @@ export class CoreCoursesProvider { * @return {Promise} Promise resolved if the user is enrolled. If the password is invalid, the promise is rejected * with an object with code = CoreCoursesProvider.ENROL_INVALID_KEY. */ - selfEnrol(courseId: number, password = '', instanceId?: number, siteId?: string) : Promise { + selfEnrol(courseId: number, password: string = '', instanceId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let params: any = { - courseid: courseId, - password: password - } + const params: any = { + courseid: courseId, + password: password + }; + if (instanceId) { params.instanceid = instanceId; } - return site.write('enrol_self_enrol_user', params).then((response) : any => { + return site.write('enrol_self_enrol_user', params).then((response): any => { if (response) { if (response.status) { return true; @@ -786,18 +792,19 @@ export class CoreCoursesProvider { let message; response.warnings.forEach((warning) => { // Invalid password warnings. - if (warning.warningcode == '2' || warning.warningcode == '3' || warning.warningcode == '4') { + if (warning.warningcode == '2' || warning.warningcode == '3' || warning.warningcode == '4') { message = warning.message; } }); if (message) { - return Promise.reject({code: CoreCoursesProvider.ENROL_INVALID_KEY, message: message}); + return Promise.reject({ code: CoreCoursesProvider.ENROL_INVALID_KEY, message: message }); } else { return Promise.reject(response.warnings[0]); } } } + return Promise.reject(null); }); }); diff --git a/src/core/courses/providers/mainmenu-handler.ts b/src/core/courses/providers/mainmenu-handler.ts index 3dad056d8..da4a035af 100644 --- a/src/core/courses/providers/mainmenu-handler.ts +++ b/src/core/courses/providers/mainmenu-handler.ts @@ -26,14 +26,14 @@ export class CoreCoursesMainMenuHandler implements CoreMainMenuHandler { priority = 1100; isOverviewEnabled: boolean; - constructor(private coursesProvider: CoreCoursesProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider) {} + constructor(private coursesProvider: CoreCoursesProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider) { } /** * Check if the handler is enabled on a site level. * - * @return {boolean} Whether or not the handler is enabled on a site level. + * @return {boolean | Promise} Whether or not the handler is enabled on a site level. */ - isEnabled(): boolean|Promise { + isEnabled(): boolean | Promise { // Check if my overview is enabled. return this.myOverviewProvider.isEnabled().then((enabled) => { this.isOverviewEnabled = enabled; diff --git a/src/core/courses/providers/my-overview-link-handler.ts b/src/core/courses/providers/my-overview-link-handler.ts index fb9d1f648..1622bc5e7 100644 --- a/src/core/courses/providers/my-overview-link-handler.ts +++ b/src/core/courses/providers/my-overview-link-handler.ts @@ -39,10 +39,10 @@ export class CoreCoursesMyOverviewLinkHandler extends CoreContentLinksHandlerBas * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise { + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { return [{ - action: (siteId, navCtrl?) => { + action: (siteId, navCtrl?): void => { // Always use redirect to make it the new history root (to avoid "loops" in history). this.loginHelper.redirect('CoreCoursesMyOverviewPage', undefined, siteId); } diff --git a/src/core/courses/providers/my-overview.ts b/src/core/courses/providers/my-overview.ts index a6b785fde..f188f6d82 100644 --- a/src/core/courses/providers/my-overview.ts +++ b/src/core/courses/providers/my-overview.ts @@ -22,11 +22,11 @@ import * as moment from 'moment'; */ @Injectable() export class CoreCoursesMyOverviewProvider { - public static EVENTS_LIMIT = 20; - public static EVENTS_LIMIT_PER_COURSE = 10; + static EVENTS_LIMIT = 20; + static EVENTS_LIMIT_PER_COURSE = 10; protected ROOT_CACHE_KEY = 'myoverview:'; - constructor(private sitesProvider: CoreSitesProvider) {} + constructor(private sitesProvider: CoreSitesProvider) { } /** * Get calendar action events for the given course. @@ -36,11 +36,11 @@ export class CoreCoursesMyOverviewProvider { * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise<{events: any[], canLoadMore: number}>} Promise resolved when the info is retrieved. */ - getActionEventsByCourse(courseId: number, afterEventId?: number, siteId?: string) : - Promise<{events: any[], canLoadMore: number}> { + getActionEventsByCourse(courseId: number, afterEventId?: number, siteId?: string): + Promise<{ events: any[], canLoadMore: number }> { return this.sitesProvider.getSite(siteId).then((site) => { - let time = moment().subtract(14, 'days').unix(), // Check two weeks ago. + const time = moment().subtract(14, 'days').unix(), // Check two weeks ago. data: any = { timesortfrom: time, courseid: courseId, @@ -54,10 +54,11 @@ export class CoreCoursesMyOverviewProvider { data.aftereventid = afterEventId; } - return site.read('core_calendar_get_action_events_by_course', data, preSets).then((courseEvents) : any => { + return site.read('core_calendar_get_action_events_by_course', data, preSets).then((courseEvents): any => { if (courseEvents && courseEvents.events) { return this.treatCourseEvents(courseEvents, time); } + return Promise.reject(null); }); }); @@ -69,7 +70,7 @@ export class CoreCoursesMyOverviewProvider { * @param {number} courseId Only events in this course. * @return {string} Cache key. */ - protected getActionEventsByCourseCacheKey(courseId: number) : string { + protected getActionEventsByCourseCacheKey(courseId: number): string { return this.getActionEventsByCoursesCacheKey() + ':' + courseId; } @@ -80,9 +81,10 @@ export class CoreCoursesMyOverviewProvider { * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise<{[s: string]: {events: any[], canLoadMore: number}}>} Promise resolved when the info is retrieved. */ - getActionEventsByCourses(courseIds: number[], siteId?: string) : Promise<{[s: string]: {events: any[], canLoadMore: number}}> { + getActionEventsByCourses(courseIds: number[], siteId?: string): Promise<{ [s: string]: + { events: any[], canLoadMore: number } }> { return this.sitesProvider.getSite(siteId).then((site) => { - let time = moment().subtract(14, 'days').unix(), // Check two weeks ago. + const time = moment().subtract(14, 'days').unix(), // Check two weeks ago. data = { timesortfrom: time, courseids: courseIds, @@ -92,9 +94,9 @@ export class CoreCoursesMyOverviewProvider { cacheKey: this.getActionEventsByCoursesCacheKey() }; - return site.read('core_calendar_get_action_events_by_courses', data, preSets).then((events) : any => { + return site.read('core_calendar_get_action_events_by_courses', data, preSets).then((events): any => { if (events && events.groupedbycourse) { - let courseEvents = {}; + const courseEvents = {}; events.groupedbycourse.forEach((course) => { courseEvents[course.courseid] = this.treatCourseEvents(course, time); @@ -113,7 +115,7 @@ export class CoreCoursesMyOverviewProvider { * * @return {string} Cache key. */ - protected getActionEventsByCoursesCacheKey() : string { + protected getActionEventsByCoursesCacheKey(): string { return this.ROOT_CACHE_KEY + 'bycourse'; } @@ -124,9 +126,9 @@ export class CoreCoursesMyOverviewProvider { * @param {string} [siteId] Site ID. If not defined, use current site. * @return {Promise<{events: any[], canLoadMore: number}>} Promise resolved when the info is retrieved. */ - getActionEventsByTimesort(afterEventId: number, siteId?: string) : Promise<{events: any[], canLoadMore: number}> { + getActionEventsByTimesort(afterEventId: number, siteId?: string): Promise<{ events: any[], canLoadMore: number }> { return this.sitesProvider.getSite(siteId).then((site) => { - let time = moment().subtract(14, 'days').unix(), // Check two weeks ago. + const time = moment().subtract(14, 'days').unix(), // Check two weeks ago. data: any = { timesortfrom: time, limitnum: CoreCoursesMyOverviewProvider.EVENTS_LIMIT @@ -141,9 +143,9 @@ export class CoreCoursesMyOverviewProvider { data.aftereventid = afterEventId; } - return site.read('core_calendar_get_action_events_by_timesort', data, preSets).then((events) : any => { + return site.read('core_calendar_get_action_events_by_timesort', data, preSets).then((events): any => { if (events && events.events) { - let canLoadMore = events.events.length >= data.limitnum ? events.lastid : undefined; + const canLoadMore = events.events.length >= data.limitnum ? events.lastid : undefined; // Filter events by time in case it uses cache. events = events.events.filter((element) => { @@ -155,6 +157,7 @@ export class CoreCoursesMyOverviewProvider { canLoadMore: canLoadMore }; } + return Promise.reject(null); }); }); @@ -165,7 +168,7 @@ export class CoreCoursesMyOverviewProvider { * * @return {string} Cache key. */ - protected getActionEventsByTimesortPrefixCacheKey() : string { + protected getActionEventsByTimesortPrefixCacheKey(): string { return this.ROOT_CACHE_KEY + 'bytimesort:'; } @@ -176,9 +179,10 @@ export class CoreCoursesMyOverviewProvider { * @param {number} [limit] Limit num of the call. * @return {string} Cache key. */ - protected getActionEventsByTimesortCacheKey(afterEventId?: number, limit?: number) : string { + protected getActionEventsByTimesortCacheKey(afterEventId?: number, limit?: number): string { afterEventId = afterEventId || 0; limit = limit || 0; + return this.getActionEventsByTimesortPrefixCacheKey() + afterEventId + ':' + limit; } @@ -188,7 +192,7 @@ export class CoreCoursesMyOverviewProvider { * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateActionEventsByCourses(siteId?: string) : Promise { + invalidateActionEventsByCourses(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKeyStartingWith(this.getActionEventsByCoursesCacheKey()); }); @@ -200,7 +204,7 @@ export class CoreCoursesMyOverviewProvider { * @param {string} [siteId] Site ID to invalidate. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateActionEventsByTimesort(siteId?: string) : Promise { + invalidateActionEventsByTimesort(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKeyStartingWith(this.getActionEventsByTimesortPrefixCacheKey()); }); @@ -212,7 +216,7 @@ export class CoreCoursesMyOverviewProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with true if available, resolved with false or rejected otherwise. */ - isAvailable(siteId?: string) : Promise { + isAvailable(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.wsAvailable('core_calendar_get_action_events_by_courses'); }); @@ -224,8 +228,9 @@ export class CoreCoursesMyOverviewProvider { * @param {CoreSite} [site] Site. If not defined, use current site. * @return {boolean} Whether it's disabled. */ - isDisabledInSite(site?: CoreSite) : boolean { + isDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); + return site.isFeatureDisabled('$mmSideMenuDelegate_mmaMyOverview'); } @@ -234,12 +239,13 @@ export class CoreCoursesMyOverviewProvider { * * @return {Promise} Promise resolved with true if enabled, resolved with false otherwise. */ - isEnabled() : Promise { + isEnabled(): Promise { if (!this.isDisabledInSite()) { return this.isAvailable().catch(() => { return false; }); } + return Promise.resolve(false); } @@ -250,8 +256,8 @@ export class CoreCoursesMyOverviewProvider { * @param {number} timeFrom Current time to filter events from. * @return {{events: any[], canLoadMore: number}} Object with course events and last loaded event id if more can be loaded. */ - protected treatCourseEvents(course: any, timeFrom: number) : {events: any[], canLoadMore: number} { - let canLoadMore : number = + protected treatCourseEvents(course: any, timeFrom: number): { events: any[], canLoadMore: number } { + const canLoadMore: number = course.events.length >= CoreCoursesMyOverviewProvider.EVENTS_LIMIT_PER_COURSE ? course.lastid : undefined; // Filter events by time in case it uses cache. diff --git a/src/core/emulator/classes/inappbrowserobject.ts b/src/core/emulator/classes/inappbrowserobject.ts index c680f6606..f13df5d8c 100644 --- a/src/core/emulator/classes/inappbrowserobject.ts +++ b/src/core/emulator/classes/inappbrowserobject.ts @@ -29,7 +29,7 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { protected isLinux: boolean; constructor(appProvider: CoreAppProvider, private fileProvider: CoreFileProvider, private urlUtils: CoreUrlUtilsProvider, - private url: string, target?: string, options = '') { + private url: string, target?: string, options: string = '') { super(url, target, options); if (!appProvider.isDesktop()) { @@ -44,8 +44,8 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { let width = 800, height = 600, - display, - bwOptions: any = {}; + display; + const bwOptions: any = {}; if (screen) { display = this.screen.getPrimaryDisplay(); @@ -74,7 +74,7 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { if (this.isLinux && this.isSSO) { // SSO in Linux. Simulate it's an iOS device so we can retrieve the launch URL. // This is needed because custom URL scheme is not supported in Linux. - let userAgent = 'Mozilla/5.0 (iPad) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60'; + const userAgent = 'Mozilla/5.0 (iPad) AppleWebKit/603.3.8 (KHTML, like Gecko) Mobile/14G60'; this.window.webContents.setUserAgent(userAgent); } } @@ -82,7 +82,7 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { /** * Close the window. */ - close() : void { + close(): void { this.window.close(); } @@ -92,8 +92,8 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { * @param {any} details Details of the script to run, specifying either a file or code key. * @return {Promise} Promise resolved when done. */ - executeScript(details: any) : Promise { - return new Promise((resolve, reject) => { + executeScript(details: any): Promise { + return new Promise((resolve, reject): void => { if (details.code) { this.window.webContents.executeJavaScript(details.code, false, resolve); } else if (details.file) { @@ -112,11 +112,11 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { * @param {number} [retry=0] Retry number. * @return {Promise} Promise resolved with the launch URL. */ - protected getLaunchUrl(retry = 0) : Promise { - return new Promise((resolve, reject) => { + protected getLaunchUrl(retry: number = 0): Promise { + return new Promise((resolve, reject): void => { // Execute Javascript to retrieve the launch link. - let jsCode = 'var el = document.querySelector("#launchapp"); el && el.href;', - found = false; + const jsCode = 'var el = document.querySelector("#launchapp"); el && el.href;'; + let found = false; this.window.webContents.executeJavaScript(jsCode).then((launchUrl) => { found = true; @@ -140,7 +140,7 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { /** * Hide the window. */ - hide() : void { + hide(): void { this.window.hide(); } @@ -150,8 +150,8 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { * @param {any} details Details of the CSS to insert, specifying either a file or code key. * @return {Promise} Promise resolved when done. */ - insertCSS(details: any) : Promise { - return new Promise((resolve, reject) => { + insertCSS(details: any): Promise { + return new Promise((resolve, reject): void => { if (details.code) { this.window.webContents.insertCSS(details.code); resolve(); @@ -173,18 +173,20 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { * @return {Observable} Observable that will listen to the event on subscribe, and will stop listening * to the event on unsubscribe. */ - on(name: string) : Observable { + on(name: string): Observable { // Create the observable. - return new Observable((observer: Observer) => { + return new Observable((observer: Observer): any => { // Helper functions to handle events. - let received = (event, url) => { + const received = (event, url): void => { try { event.url = url || this.window.getURL(); event.type = name; observer.next(event); - } catch(ex) {} + } catch (ex) { + // Ignore errors. + } }, - finishLoad = (event) => { + finishLoad = (event): void => { // Check if user is back to launch page. if (this.urlUtils.removeUrlParams(this.url) == this.urlUtils.removeUrlParams(this.window.getURL())) { // The launch page was loaded. Search for the launch link. @@ -203,7 +205,6 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { if (this.isLinux && this.isSSO) { // Linux doesn't support custom URL Schemes. Check if launch page is loaded. - // listeners[callback].push(finishLoad); this.window.webContents.on('did-finish-load', finishLoad); } break; @@ -218,9 +219,10 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { case 'exit': this.window.on('close', received); break; + default: } - return () => { + return (): void => { // Unsubscribing. We need to remove the listeners. switch (name) { case 'loadstart': @@ -239,6 +241,7 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { case 'exit': this.window.removeListener('close', received); break; + default: } }; }); @@ -247,7 +250,7 @@ export class InAppBrowserObjectMock extends InAppBrowserObject { /** * Show the window. */ - show() : void { + show(): void { this.window.show(); } } diff --git a/src/core/emulator/classes/sqlitedb.ts b/src/core/emulator/classes/sqlitedb.ts index 7b7b91baa..60e72126b 100644 --- a/src/core/emulator/classes/sqlitedb.ts +++ b/src/core/emulator/classes/sqlitedb.ts @@ -34,7 +34,7 @@ export class SQLiteDBMock extends SQLiteDB { * * @return {Promise} Promise resolved when done. */ - close() : Promise { + close(): Promise { // WebSQL databases aren't closed. return Promise.resolve(); } @@ -44,11 +44,11 @@ export class SQLiteDBMock extends SQLiteDB { * * @return {Promise} Promise resolved when done. */ - emptyDatabase() : Promise { - return new Promise((resolve, reject) => { + emptyDatabase(): Promise { + return new Promise((resolve, reject): void => { this.db.transaction((tx) => { // Query all tables from sqlite_master that we have created and can modify. - let args = [], + const args = [], query = `SELECT * FROM sqlite_master WHERE name NOT LIKE 'sqlite\\_%' escape '\\' AND name NOT LIKE '\\_%' escape '\\'`; @@ -56,16 +56,17 @@ export class SQLiteDBMock extends SQLiteDB { if (result.rows.length <= 0) { // No tables to delete, stop. resolve(); + return; } // Drop all the tables. - let promises = []; + const promises = []; for (let i = 0; i < result.rows.length; i++) { - promises.push(new Promise((resolve, reject) => { + promises.push(new Promise((resolve, reject): void => { // Drop the table. - let name = JSON.stringify(result.rows.item(i).name); + const name = JSON.stringify(result.rows.item(i).name); tx.executeSql('DROP TABLE ' + name, [], resolve, reject); })); } @@ -85,8 +86,8 @@ export class SQLiteDBMock extends SQLiteDB { * @param {any[]} params Query parameters. * @return {Promise} Promise resolved with the result. */ - execute(sql: string, params?: any[]) : Promise { - return new Promise((resolve, reject) => { + execute(sql: string, params?: any[]): Promise { + return new Promise((resolve, reject): void => { // With WebSQL, all queries must be run in a transaction. this.db.transaction((tx) => { tx.executeSql(sql, params, (tx, results) => { @@ -106,15 +107,15 @@ export class SQLiteDBMock extends SQLiteDB { * @param {any[]} sqlStatements SQL statements to execute. * @return {Promise} Promise resolved with the result. */ - executeBatch(sqlStatements: any[]) : Promise { - return new Promise((resolve, reject) => { + executeBatch(sqlStatements: any[]): Promise { + return new Promise((resolve, reject): void => { // Create a transaction to execute the queries. this.db.transaction((tx) => { - let promises = []; + const promises = []; // Execute all the queries. Each statement can be a string or an array. sqlStatements.forEach((statement) => { - promises.push(new Promise((resolve, reject) => { + promises.push(new Promise((resolve, reject): void => { let query, params; @@ -142,7 +143,7 @@ export class SQLiteDBMock extends SQLiteDB { */ init(): void { // This DB is for desktop apps, so use a big size to be sure it isn't filled. - this.db = (window).openDatabase(this.name, '1.0', this.name, 500 * 1024 * 1024); + this.db = ( window).openDatabase(this.name, '1.0', this.name, 500 * 1024 * 1024); this.promise = Promise.resolve(); } @@ -151,7 +152,7 @@ export class SQLiteDBMock extends SQLiteDB { * * @return {Promise} Promise resolved when done. */ - open() : Promise { + open(): Promise { // WebSQL databases can't closed, so the open method isn't needed. return Promise.resolve(); } diff --git a/src/core/emulator/emulator.module.ts b/src/core/emulator/emulator.module.ts index 1701696d3..0437615a4 100644 --- a/src/core/emulator/emulator.module.ts +++ b/src/core/emulator/emulator.module.ts @@ -73,21 +73,21 @@ import { CoreInitDelegate } from '../../providers/init'; { provide: Camera, deps: [CoreAppProvider, CoreEmulatorCaptureHelperProvider], - useFactory: (appProvider: CoreAppProvider, captureHelper: CoreEmulatorCaptureHelperProvider) => { + useFactory: (appProvider: CoreAppProvider, captureHelper: CoreEmulatorCaptureHelperProvider): Camera => { return appProvider.isMobile() ? new Camera() : new CameraMock(captureHelper); } }, { provide: Clipboard, deps: [CoreAppProvider], - useFactory: (appProvider: CoreAppProvider) => { + useFactory: (appProvider: CoreAppProvider): Clipboard => { return appProvider.isMobile() ? new Clipboard() : new ClipboardMock(appProvider); } }, { provide: File, deps: [CoreAppProvider, CoreTextUtilsProvider], - useFactory: (appProvider: CoreAppProvider, textUtils: CoreTextUtilsProvider) => { + useFactory: (appProvider: CoreAppProvider, textUtils: CoreTextUtilsProvider): File => { // Use platform instead of CoreAppProvider to prevent circular dependencies. return appProvider.isMobile() ? new File() : new FileMock(appProvider, textUtils); } @@ -95,7 +95,7 @@ import { CoreInitDelegate } from '../../providers/init'; { provide: FileTransfer, deps: [CoreAppProvider, CoreFileProvider], - useFactory: (appProvider: CoreAppProvider, fileProvider: CoreFileProvider) => { + useFactory: (appProvider: CoreAppProvider, fileProvider: CoreFileProvider): FileTransfer => { // Use platform instead of CoreAppProvider to prevent circular dependencies. return appProvider.isMobile() ? new FileTransfer() : new FileTransferMock(appProvider, fileProvider); } @@ -103,14 +103,15 @@ import { CoreInitDelegate } from '../../providers/init'; { provide: Globalization, deps: [CoreAppProvider], - useFactory: (appProvider: CoreAppProvider) => { + useFactory: (appProvider: CoreAppProvider): Globalization => { return appProvider.isMobile() ? new Globalization() : new GlobalizationMock(appProvider); } }, { provide: InAppBrowser, deps: [CoreAppProvider, CoreFileProvider, CoreUrlUtilsProvider], - useFactory: (appProvider: CoreAppProvider, fileProvider: CoreFileProvider, urlUtils: CoreUrlUtilsProvider) => { + useFactory: (appProvider: CoreAppProvider, fileProvider: CoreFileProvider, urlUtils: CoreUrlUtilsProvider) + : InAppBrowser => { return !appProvider.isDesktop() ? new InAppBrowser() : new InAppBrowserMock(appProvider, fileProvider, urlUtils); } }, @@ -118,7 +119,7 @@ import { CoreInitDelegate } from '../../providers/init'; { provide: LocalNotifications, deps: [CoreAppProvider, CoreUtilsProvider], - useFactory: (appProvider: CoreAppProvider, utils: CoreUtilsProvider) => { + useFactory: (appProvider: CoreAppProvider, utils: CoreUtilsProvider): LocalNotifications => { // Use platform instead of CoreAppProvider to prevent circular dependencies. return appProvider.isMobile() ? new LocalNotifications() : new LocalNotificationsMock(appProvider, utils); } @@ -126,14 +127,14 @@ import { CoreInitDelegate } from '../../providers/init'; { provide: MediaCapture, deps: [CoreAppProvider, CoreEmulatorCaptureHelperProvider], - useFactory: (appProvider: CoreAppProvider, captureHelper: CoreEmulatorCaptureHelperProvider) => { + useFactory: (appProvider: CoreAppProvider, captureHelper: CoreEmulatorCaptureHelperProvider): MediaCapture => { return appProvider.isMobile() ? new MediaCapture() : new MediaCaptureMock(captureHelper); } }, { provide: Network, deps: [Platform], - useFactory: (platform: Platform) => { + useFactory: (platform: Platform): Network => { // Use platform instead of CoreAppProvider to prevent circular dependencies. return platform.is('cordova') ? new Network() : new NetworkMock(); } @@ -144,7 +145,7 @@ import { CoreInitDelegate } from '../../providers/init'; { provide: Zip, deps: [CoreAppProvider, File, CoreMimetypeUtilsProvider, CoreTextUtilsProvider], - useFactory: (appProvider: CoreAppProvider, file: File, mimeUtils: CoreMimetypeUtilsProvider) => { + useFactory: (appProvider: CoreAppProvider, file: File, mimeUtils: CoreMimetypeUtilsProvider): Zip => { // Use platform instead of CoreAppProvider to prevent circular dependencies. return appProvider.isMobile() ? new Zip() : new ZipMock(file, mimeUtils); } @@ -153,7 +154,7 @@ import { CoreInitDelegate } from '../../providers/init'; }) export class CoreEmulatorModule { constructor(appProvider: CoreAppProvider, initDelegate: CoreInitDelegate, helper: CoreEmulatorHelperProvider) { - let win = window; // Convert the "window" to "any" type to be able to use non-standard properties. + const win = window; // Convert the "window" to "any" type to be able to use non-standard properties. // Emulate Custom URL Scheme plugin in desktop apps. if (appProvider.isDesktop()) { diff --git a/src/core/emulator/pages/capture-media/capture-media.ts b/src/core/emulator/pages/capture-media/capture-media.ts index 353648b3e..5ccdc7f7a 100644 --- a/src/core/emulator/pages/capture-media/capture-media.ts +++ b/src/core/emulator/pages/capture-media/capture-media.ts @@ -22,7 +22,7 @@ import { CoreTimeUtilsProvider } from '../../../../providers/utils/time'; /** * Page to capture media in browser or desktop. */ -@IonicPage({segment: "core-emulator-capture-media"}) +@IonicPage({ segment: 'core-emulator-capture-media' }) @Component({ selector: 'page-core-emulator-capture-media', templateUrl: 'capture-media.html', @@ -55,7 +55,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { protected mediaRecorder; // To record video/audio. protected audioDrawer; // To start/stop the display of audio sound. protected quality; // Image only. - protected previewMedia: HTMLAudioElement|HTMLVideoElement; // The element to preview the audio/video captured. + protected previewMedia: HTMLAudioElement | HTMLVideoElement; // The element to preview the audio/video captured. protected mediaBlob: Blob; // A Blob where the captured data is stored. protected localMediaStream: MediaStream; @@ -75,13 +75,13 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.initVariables(); - let constraints = { - video: this.isAudio ? false : {facingMode: this.facingMode}, - audio: !this.isImage - }; + const constraints = { + video: this.isAudio ? false : { facingMode: this.facingMode }, + audio: !this.isImage + }; navigator.mediaDevices.getUserMedia(constraints).then((stream) => { let chunks = []; @@ -96,17 +96,17 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { this.audioDrawer.start(); } - this.mediaRecorder = new this.window.MediaRecorder(this.localMediaStream, {mimeType: this.mimetype}); + this.mediaRecorder = new this.window.MediaRecorder(this.localMediaStream, { mimeType: this.mimetype }); // When video or audio is recorded, add it to the list of chunks. - this.mediaRecorder.ondataavailable = (e) => { + this.mediaRecorder.ondataavailable = (e): void => { if (e.data.size > 0) { chunks.push(e.data); } }; // When recording stops, create a Blob element with the recording and set it to the video or audio. - this.mediaRecorder.onstop = () => { + this.mediaRecorder.onstop = (): void => { this.mediaBlob = new Blob(chunks); chunks = []; @@ -119,7 +119,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { waitTimeout; // Listen for stream ready to display the stream. - this.streamVideo.nativeElement.onloadedmetadata = () => { + this.streamVideo.nativeElement.onloadedmetadata = (): void => { if (hasLoaded) { // Already loaded or timeout triggered, stop. return; @@ -149,7 +149,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { this.readyToCapture = true; } }).catch((error) => { - this.dismissWithError(-1, error.message || error); + this.dismissWithError(-1, error.message || error); }); } @@ -159,8 +159,11 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { * * @param {MediaStream} stream Stream returned by getUserMedia. */ - protected initAudioDrawer(stream: MediaStream) : void { - let audioCtx = new (this.window.AudioContext || this.window.webkitAudioContext)(), + protected initAudioDrawer(stream: MediaStream): void { + let skip = true, + running = false; + + const audioCtx = new (this.window.AudioContext || this.window.webkitAudioContext)(), canvasCtx = this.streamAudio.nativeElement.getContext('2d'), source = audioCtx.createMediaStreamSource(stream), analyser = audioCtx.createAnalyser(), @@ -168,9 +171,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { dataArray = new Uint8Array(bufferLength), width = this.streamAudio.nativeElement.width, height = this.streamAudio.nativeElement.height, - running = false, - skip = true, - drawAudio = () => { + drawAudio = (): void => { if (!running) { return; } @@ -178,14 +179,14 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { // Update the draw every animation frame. requestAnimationFrame(drawAudio); - // Skip half of the frames to improve performance, shouldn't affect the smoothness. + // Skip half of the frames to improve performance, shouldn't affect the smoothness. skip = !skip; if (skip) { return; } - let sliceWidth = width / bufferLength, - x = 0; + const sliceWidth = width / bufferLength; + let x = 0; analyser.getByteTimeDomainData(dataArray); @@ -198,7 +199,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { canvasCtx.beginPath(); for (let i = 0; i < bufferLength; i++) { - let v = dataArray[i] / 128.0, + const v = dataArray[i] / 128.0, y = v * height / 2; if (i === 0) { @@ -218,7 +219,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { source.connect(analyser); this.audioDrawer = { - start: () => { + start: (): void => { if (running) { return; } @@ -226,7 +227,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { running = true; drawAudio(); }, - stop: () => { + stop: (): void => { running = false; } }; @@ -235,7 +236,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { /** * Initialize some variables based on the params. */ - protected initVariables() { + protected initVariables(): void { if (this.type == 'captureimage') { this.isCaptureImage = true; this.type = 'image'; @@ -257,7 +258,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { /** * Main action clicked: record or stop recording. */ - actionClicked() : void { + actionClicked(): void { if (this.isCapturing) { // It's capturing, stop. this.stopCapturing(); @@ -271,15 +272,15 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { this.cdr.detectChanges(); } else { // Get the image from the video and set it to the canvas, using video width/height. - let width = this.streamVideo.nativeElement.videoWidth, - height = this.streamVideo.nativeElement.videoHeight; + const width = this.streamVideo.nativeElement.videoWidth, + height = this.streamVideo.nativeElement.videoHeight, + loadingModal = this.domUtils.showModalLoading(); this.imgCanvas.nativeElement.width = width; this.imgCanvas.nativeElement.height = height; this.imgCanvas.nativeElement.getContext('2d').drawImage(this.streamVideo.nativeElement, 0, 0, width, height); // Convert the image to blob and show it in an image element. - let loadingModal = this.domUtils.showModalLoading(); this.imgCanvas.nativeElement.toBlob((blob) => { loadingModal.dismiss(); @@ -294,7 +295,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { /** * User cancelled. */ - cancel() : void { + cancel(): void { // Send a "cancelled" error like the Cordova plugin does. this.dismissWithError(3, 'Canceled.', 'Camera cancelled'); } @@ -302,7 +303,7 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { /** * Discard the captured media. */ - discard() : void { + discard(): void { this.previewMedia && this.previewMedia.pause(); this.streamVideo && this.streamVideo.nativeElement.play(); this.audioDrawer && this.audioDrawer.start(); @@ -312,14 +313,14 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { this.resetChrono = true; delete this.mediaBlob; this.cdr.detectChanges(); - }; + } /** * Close the modal, returning some data (success). * * @param {any} data Data to return. */ - dismissWithData(data: any) : void { + dismissWithData(data: any): void { this.viewCtrl.dismiss(data, 'success'); } @@ -330,41 +331,44 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { * @param {string} message Error message. * @param {string} [cameraMessage] A specific message to use if it's a Camera capture. If not set, message will be used. */ - dismissWithError(code: number, message: string, cameraMessage?: string) : void { - let isCamera = this.isImage && !this.isCaptureImage, - error = isCamera ? (cameraMessage || message) : {code: code, message: message}; + dismissWithError(code: number, message: string, cameraMessage?: string): void { + const isCamera = this.isImage && !this.isCaptureImage, + error = isCamera ? (cameraMessage || message) : { code: code, message: message }; this.viewCtrl.dismiss(error, 'error'); } /** * Done capturing, write the file. */ - done() : void { + done(): void { if (this.returnDataUrl) { // Return the image as a base64 string. this.dismissWithData(this.imgCanvas.nativeElement.toDataURL(this.mimetype, this.quality)); + return; } if (!this.mediaBlob) { // Shouldn't happen. this.domUtils.showErrorModal('Please capture the media first.'); + return; } // Create the file and return it. - let fileName = this.type + '_' + this.timeUtils.readableTimestamp() + '.' + this.extension, - path = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'media/' + fileName); - - let loadingModal = this.domUtils.showModalLoading(); + const fileName = this.type + '_' + this.timeUtils.readableTimestamp() + '.' + this.extension, + path = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'media/' + fileName), + loadingModal = this.domUtils.showModalLoading(); this.fileProvider.writeFile(path, this.mediaBlob).then((fileEntry) => { if (this.isImage && !this.isCaptureImage) { this.dismissWithData(fileEntry.toURL()); } else { - // The capture plugin returns a MediaFile, not a FileEntry. The only difference is that - // it supports a new function that won't be supported in desktop. - fileEntry.getFormatData = (successFn, errorFn) => {}; + // The capture plugin returns a MediaFile, not a FileEntry. + // The only difference is that it supports a new function that won't be supported in desktop. + fileEntry.getFormatData = (successFn, errorFn): any => { + // Nothing to do. + }; this.dismissWithData([fileEntry]); } @@ -373,23 +377,23 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { }).finally(() => { loadingModal.dismiss(); }); - }; + } /** * Stop capturing. Only for video and audio. */ - stopCapturing() : void { + stopCapturing(): void { this.streamVideo && this.streamVideo.nativeElement.pause(); this.audioDrawer && this.audioDrawer.stop(); this.mediaRecorder && this.mediaRecorder.stop(); this.isCapturing = false; this.hasCaptured = true; - }; + } /** * Page destroyed. */ - ngOnDestroy() : void { + ngOnDestroy(): void { const tracks = this.localMediaStream.getTracks(); tracks.forEach((track) => { track.stop(); @@ -399,4 +403,4 @@ export class CoreEmulatorCaptureMediaPage implements OnInit, OnDestroy { this.audioDrawer && this.audioDrawer.stop(); delete this.mediaBlob; } -} \ No newline at end of file +} diff --git a/src/core/emulator/providers/camera.ts b/src/core/emulator/providers/camera.ts index 681fe1ca6..323b6d7fe 100644 --- a/src/core/emulator/providers/camera.ts +++ b/src/core/emulator/providers/camera.ts @@ -31,8 +31,8 @@ export class CameraMock extends Camera { * * @return {Promise} Promise resolved when done. */ - cleanup() : Promise { - // iOS only, nothing to do. + cleanup(): Promise { + // This function is iOS only, nothing to do. return Promise.resolve(); } @@ -42,7 +42,7 @@ export class CameraMock extends Camera { * @param {CameraOptions} options Options that you want to pass to the camera. * @return {Promise} Promise resolved when captured. */ - getPicture(options: CameraOptions) : Promise { + getPicture(options: CameraOptions): Promise { return this.captureHelper.captureMedia('image', options); } } diff --git a/src/core/emulator/providers/capture-helper.ts b/src/core/emulator/providers/capture-helper.ts index d71a44690..4d4ab87d9 100644 --- a/src/core/emulator/providers/capture-helper.ts +++ b/src/core/emulator/providers/capture-helper.ts @@ -38,7 +38,7 @@ export class CoreEmulatorCaptureHelperProvider { constructor(private utils: CoreUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, private modalCtrl: ModalController) { // Convert the window to "any" type because some of the variables used (like MediaRecorder) aren't in the window spec. - this.win = window; + this.win = window; } /** @@ -48,16 +48,16 @@ export class CoreEmulatorCaptureHelperProvider { * @param {any} [options] Optional options. * @return {Promise} Promise resolved when captured, rejected if error. */ - captureMedia(type: string, options: any) : Promise { - options = options || {}; + captureMedia(type: string, options: any): Promise { + options = options || {}; try { // Build the params to send to the modal. - let deferred = this.utils.promiseDefer(), + const deferred = this.utils.promiseDefer(), params: any = { type: type - }, - mimeAndExt, + }; + let mimeAndExt, modal: Modal; // Initialize some data based on the type of media to capture. @@ -86,7 +86,7 @@ export class CoreEmulatorCaptureHelperProvider { params.extension = 'jpeg'; } - if (options.quality >= 0 && options.quality <= 100) { + if (options.quality >= 0 && options.quality <= 100) { params.quality = options.quality / 100; } @@ -110,7 +110,7 @@ export class CoreEmulatorCaptureHelperProvider { }); return deferred.promise; - } catch(ex) { + } catch (ex) { return Promise.reject(ex.toString()); } } @@ -122,13 +122,13 @@ export class CoreEmulatorCaptureHelperProvider { * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. * @return {{extension: string, mimetype: string}} An object with mimetype and extension to use. */ - protected getMimeTypeAndExtension(type: string, mimetypes) : {extension: string, mimetype: string} { - var result: any = {}; + protected getMimeTypeAndExtension(type: string, mimetypes: string[]): { extension: string, mimetype: string } { + const result: any = {}; if (mimetypes && mimetypes.length) { // Search for a supported mimetype. for (let i = 0; i < mimetypes.length; i++) { - let mimetype = mimetypes[i], + const mimetype = mimetypes[i], matches = mimetype.match(new RegExp('^' + type + '/')); if (matches && matches.length && this.win.MediaRecorder.isTypeSupported(mimetype)) { @@ -159,8 +159,8 @@ export class CoreEmulatorCaptureHelperProvider { * * @return {boolean} Whether the function is supported. */ - protected initGetUserMedia() : boolean { - let nav = navigator; + protected initGetUserMedia(): boolean { + const nav = navigator; // Check if there is a function to get user media. if (typeof nav.mediaDevices == 'undefined') { nav.mediaDevices = {}; @@ -172,9 +172,10 @@ export class CoreEmulatorCaptureHelperProvider { if (nav.getUserMedia) { // Deprecated function exists, support the new function using the deprecated one. - navigator.mediaDevices.getUserMedia = (constraints) => { - let deferred = this.utils.promiseDefer(); + navigator.mediaDevices.getUserMedia = (constraints): Promise => { + const deferred = this.utils.promiseDefer(); nav.getUserMedia(constraints, deferred.resolve, deferred.reject); + return deferred.promise; }; } else { @@ -188,16 +189,16 @@ export class CoreEmulatorCaptureHelperProvider { /** * Initialize the mimetypes to use when capturing. */ - protected initMimeTypes() : void { + protected initMimeTypes(): void { // Determine video and audio mimetype to use. - for (let mimeType in this.possibleVideoMimeTypes) { + for (const mimeType in this.possibleVideoMimeTypes) { if (this.win.MediaRecorder.isTypeSupported(mimeType)) { this.videoMimeType = mimeType; break; } } - for (let mimeType in this.possibleAudioMimeTypes) { + for (const mimeType in this.possibleAudioMimeTypes) { if (this.win.MediaRecorder.isTypeSupported(mimeType)) { this.audioMimeType = mimeType; break; @@ -210,7 +211,7 @@ export class CoreEmulatorCaptureHelperProvider { * * @return {Promise} Promise resolved when loaded. */ - load() : Promise { + load(): Promise { if (typeof this.win.MediaRecorder != 'undefined' && this.initGetUserMedia()) { this.initMimeTypes(); } diff --git a/src/core/emulator/providers/clipboard.ts b/src/core/emulator/providers/clipboard.ts index 2cdd7435e..eb47fd6d6 100644 --- a/src/core/emulator/providers/clipboard.ts +++ b/src/core/emulator/providers/clipboard.ts @@ -46,8 +46,8 @@ export class ClipboardMock extends Clipboard { * @param {string} text The text to copy. * @return {Promise} Promise resolved when copied. */ - copy(text: string) : Promise { - return new Promise((resolve, reject) => { + copy(text: string): Promise { + return new Promise((resolve, reject): void => { if (this.isDesktop) { this.clipboard.writeText(text); resolve(); @@ -76,8 +76,8 @@ export class ClipboardMock extends Clipboard { * * @return {Promise} Promise resolved with the text. */ - paste() : Promise { - return new Promise((resolve, reject) => { + paste(): Promise { + return new Promise((resolve, reject): void => { if (this.isDesktop) { resolve(this.clipboard.readText()); } else { diff --git a/src/core/emulator/providers/file-transfer.ts b/src/core/emulator/providers/file-transfer.ts index e6c0e193e..7c05fd8dd 100644 --- a/src/core/emulator/providers/file-transfer.ts +++ b/src/core/emulator/providers/file-transfer.ts @@ -21,16 +21,16 @@ import { CoreFileProvider } from '../../../providers/file'; * Mock the File Transfer Error. */ export class FileTransferErrorMock implements FileTransferError { - public static FILE_NOT_FOUND_ERR = 1; - public static INVALID_URL_ERR = 2; - public static CONNECTION_ERR = 3; - public static ABORT_ERR = 4; - public static NOT_MODIFIED_ERR = 5; + static FILE_NOT_FOUND_ERR = 1; + static INVALID_URL_ERR = 2; + static CONNECTION_ERR = 3; + static ABORT_ERR = 4; + static NOT_MODIFIED_ERR = 5; + // tslint:disable-next-line: variable-name constructor(public code: number, public source: string, public target: string, public http_status: number, - public body: string, public exception: string) { - } -}; + public body: string, public exception: string) { } +} /** * Emulates the Cordova FileTransfer plugin in desktop apps and in browser. @@ -60,7 +60,7 @@ export class FileTransferObjectMock extends FileTransferObject { source: string; target: string; xhr: XMLHttpRequest; - private reject: Function; + protected reject: Function; constructor(private appProvider: CoreAppProvider, private fileProvider: CoreFileProvider) { super(); @@ -87,12 +87,12 @@ export class FileTransferObjectMock extends FileTransferObject { * @returns {Promise} Returns a Promise that resolves to a FileEntry object. */ download(source: string, target: string, trustAllHosts?: boolean, options?: { [s: string]: any; }): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { // Use XMLHttpRequest instead of HttpClient to support onprogress and abort. - let basicAuthHeader = this.getBasicAuthHeader(source), + const basicAuthHeader = this.getBasicAuthHeader(source), xhr = new XMLHttpRequest(), - isDesktop = this.appProvider.isDesktop(), - headers = null; + isDesktop = this.appProvider.isDesktop(); + let headers = null; this.xhr = xhr; this.source = source; @@ -114,21 +114,21 @@ export class FileTransferObjectMock extends FileTransferObject { // Prepare the request. xhr.open('GET', source, true); xhr.responseType = isDesktop ? 'arraybuffer' : 'blob'; - for (let name in headers) { + for (const name in headers) { xhr.setRequestHeader(name, headers[name]); } - (xhr).onprogress = (xhr, ev) => { + ( xhr).onprogress = (xhr, ev): void => { if (this.progressListener) { this.progressListener(ev); } }; - xhr.onerror = (err) => { + xhr.onerror = (err): void => { reject(new FileTransferError(-1, source, target, xhr.status, xhr.statusText)); }; - xhr.onload = () => { + xhr.onload = (): void => { // Finished dowloading the file. let response = xhr.response; if (!response) { @@ -158,10 +158,9 @@ export class FileTransferObjectMock extends FileTransferObject { * @return {any} The header with the credentials, null if no credentials. */ protected getBasicAuthHeader(urlString: string): any { - let header = null; + let header = null; - // This is changed due to MS Windows doesn't support credentials in http uris - // so we detect them by regexp and strip off from result url. + // MS Windows doesn't support credentials in http uris so we detect them by regexp and strip off from result url. if (window.btoa) { const credentials = this.getUrlCredentials(urlString); if (credentials) { @@ -184,13 +183,13 @@ export class FileTransferObjectMock extends FileTransferObject { * @param {XMLHttpRequest} xhr XMLHttpRequest instance. * @return {{[s: string]: any}} Object with the headers. */ - protected getHeadersAsObject(xhr: XMLHttpRequest) : { [s: string]: any } { - const headersString = xhr.getAllResponseHeaders(); - let result = {}; + protected getHeadersAsObject(xhr: XMLHttpRequest): { [s: string]: any } { + const headersString = xhr.getAllResponseHeaders(), + result = {}; if (headersString) { const headers = headersString.split('\n'); - for (let i in headers) { + for (const i in headers) { const headerString = headers[i], separatorPos = headerString.indexOf(':'); if (separatorPos != -1) { @@ -198,6 +197,7 @@ export class FileTransferObjectMock extends FileTransferObject { } } } + return result; } @@ -208,7 +208,7 @@ export class FileTransferObjectMock extends FileTransferObject { * @param {string} urlString The URL to get the credentials from. * @return {string} Retrieved credentials. */ - protected getUrlCredentials(urlString: string) : string { + protected getUrlCredentials(urlString: string): string { const credentialsPattern = /^https?\:\/\/(?:(?:(([^:@\/]*)(?::([^@\/]*))?)?@)?([^:\/?#]*)(?::(\d*))?).*$/, credentials = credentialsPattern.exec(urlString); @@ -234,14 +234,14 @@ export class FileTransferObjectMock extends FileTransferObject { * @returns {Promise} Promise that resolves to a FileUploadResult and rejects with FileTransferError. */ upload(fileUrl: string, url: string, options?: FileUploadOptions, trustAllHosts?: boolean): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { + const basicAuthHeader = this.getBasicAuthHeader(url); let fileKey = null, fileName = null, mimeType = null, params = null, headers = null, - httpMethod = null, - basicAuthHeader = this.getBasicAuthHeader(url); + httpMethod = null; if (basicAuthHeader) { url = url.replace(this.getUrlCredentials(url) + '@', ''); @@ -258,7 +258,7 @@ export class FileTransferObjectMock extends FileTransferObject { headers = options.headers; httpMethod = options.httpMethod || 'POST'; - if (httpMethod.toUpperCase() == "PUT"){ + if (httpMethod.toUpperCase() == 'PUT') { httpMethod = 'PUT'; } else { httpMethod = 'POST'; @@ -275,11 +275,11 @@ export class FileTransferObjectMock extends FileTransferObject { headers = headers || {}; if (!headers['Content-Disposition']) { headers['Content-Disposition'] = 'form-data;' + (fileKey ? ' name="' + fileKey + '";' : '') + - (fileName ? ' filename="' + fileName + '"' : '') + (fileName ? ' filename="' + fileName + '"' : ''); } - // For some reason, adding a Content-Type header with the mimeType makes the request fail (it doesn't detect - // the token in the params). Don't include this header, and delete it if it's supplied. + // Adding a Content-Type header with the mimeType makes the request fail (it doesn't detect the token in the params). + // Don't include this header, and delete it if it's supplied. delete headers['Content-Type']; // Get the file to upload. @@ -287,16 +287,16 @@ export class FileTransferObjectMock extends FileTransferObject { return this.fileProvider.getFileObjectFromFileEntry(fileEntry); }).then((file) => { // Use XMLHttpRequest instead of HttpClient to support onprogress and abort. - let xhr = new XMLHttpRequest(); - xhr.open(httpMethod || 'POST', url); - for (let name in headers) { + const xhr = new XMLHttpRequest(); + xhr.open(httpMethod || 'POST', url); + for (const name in headers) { // Filter "unsafe" headers. if (name != 'Connection') { xhr.setRequestHeader(name, headers[name]); } } - xhr.onprogress = (ev: ProgressEvent) : any => { + xhr.onprogress = (ev: ProgressEvent): any => { if (this.progressListener) { this.progressListener(ev); } @@ -307,24 +307,23 @@ export class FileTransferObjectMock extends FileTransferObject { this.target = url; this.reject = reject; - xhr.onerror = () => { + xhr.onerror = (): void => { reject(new FileTransferError(-1, fileUrl, url, xhr.status, xhr.statusText)); }; - xhr.onload = () => { + xhr.onload = (): void => { // Finished uploading the file. - let result: FileUploadResult = { + resolve({ bytesSent: file.size, responseCode: xhr.status, response: xhr.response, headers: this.getHeadersAsObject(xhr) - }; - resolve(result); + }); }; // Create a form data to send params and the file. - let fd = new FormData(); - for (var name in params) { + const fd = new FormData(); + for (const name in params) { fd.append(name, params[name]); } fd.append('file', file); diff --git a/src/core/emulator/providers/file.ts b/src/core/emulator/providers/file.ts index 4ffb1a4c5..8f756821c 100644 --- a/src/core/emulator/providers/file.ts +++ b/src/core/emulator/providers/file.ts @@ -36,8 +36,9 @@ export class FileMock extends File { * @param {string} dir Name of directory to check * @returns {Promise} Returns a Promise that resolves to true if the directory exists or rejects with an error. */ - checkDir(path: string, dir: string) : Promise { - let fullPath = this.textUtils.concatenatePaths(path, dir); + checkDir(path: string, dir: string): Promise { + const fullPath = this.textUtils.concatenatePaths(path, dir); + return this.resolveDirectoryUrl(fullPath).then(() => { return true; }); @@ -55,8 +56,9 @@ export class FileMock extends File { if (fse.isFile) { return true; } else { - let err = new FileError(13); + const err = new FileError(13); err.message = 'input is not a file'; + return Promise.reject(err); } }); @@ -71,7 +73,7 @@ export class FileMock extends File { * @returns {Promise} Returns a Promise that resolves to the new Entry object or rejects with an error. */ private copyMock(srce: Entry, destDir: DirectoryEntry, newName: string): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { srce.copyTo(destDir, newName, (deste) => { resolve(deste); }, (err) => { @@ -132,7 +134,7 @@ export class FileMock extends File { * @returns {Promise} Returns a Promise that resolves with a DirectoryEntry or rejects with an error. */ createDir(path: string, dirName: string, replace: boolean): Promise { - let options: any = { + const options: any = { create: true }; @@ -156,7 +158,7 @@ export class FileMock extends File { * @returns {Promise} Returns a Promise that resolves to a FileEntry or rejects with an error. */ createFile(path: string, fileName: string, replace: boolean): Promise { - let options: any = { + const options: any = { create: true }; @@ -176,7 +178,7 @@ export class FileMock extends File { * @returns {Promise} Promise resolved with the FileWriter. */ private createWriterMock(fe: FileEntry): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { fe.createWriter((writer) => { resolve(writer); }, (err) => { @@ -194,7 +196,9 @@ export class FileMock extends File { private fillErrorMessageMock(err: any): void { try { err.message = this.cordovaFileError[err.code]; - } catch (e) { } + } catch (e) { + // Ignore errors. + } } /** @@ -206,7 +210,7 @@ export class FileMock extends File { * @returns {Promise} */ getDirectory(directoryEntry: DirectoryEntry, directoryName: string, flags: Flags): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { try { directoryEntry.getDirectory(directoryName, flags, (de) => { resolve(de); @@ -229,7 +233,7 @@ export class FileMock extends File { * @returns {Promise} */ getFile(directoryEntry: DirectoryEntry, fileName: string, flags: Flags): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { try { directoryEntry.getFile(fileName, flags, resolve, (err) => { this.fillErrorMessageMock(err); @@ -247,18 +251,19 @@ export class FileMock extends File { * * @return {Promise} Promise resolved with the free space. */ - getFreeDiskSpace() : Promise { + getFreeDiskSpace(): Promise { // FRequest a file system instance with a minimum size until we get an error. if (window.requestFileSystem) { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { let iterations = 0, - maxIterations = 50, - calculateByRequest = (size: number, ratio: number) => { - return new Promise((resolve, reject) => { + maxIterations = 50; + const calculateByRequest = (size: number, ratio: number): Promise => { + return new Promise((resolve, reject): void => { window.requestFileSystem(LocalFileSystem.PERSISTENT, size, () => { iterations++; if (iterations > maxIterations) { resolve(size); + return; } calculateByRequest(size * ratio, ratio).then(resolve); @@ -295,7 +300,8 @@ export class FileMock extends File { return this.resolveDirectoryUrl(path).then((fse) => { return this.getDirectory(fse, dirName, { create: false, exclusive: false }); }).then((de) => { - let reader: any = de.createReader(); + const reader: any = de.createReader(); + return this.readEntriesMock(reader); }); } @@ -305,10 +311,10 @@ export class FileMock extends File { * * @return {Promise} Promise resolved when loaded. */ - load() : Promise { - return new Promise((resolve, reject) => { - let basePath, - win = window; // Convert to to be able to use non-standard properties. + load(): Promise { + return new Promise((resolve, reject): void => { + const win = window; // Convert to to be able to use non-standard properties. + let basePath; if (typeof win.requestFileSystem == 'undefined') { win.requestFileSystem = win.webkitRequestFileSystem; @@ -321,15 +327,16 @@ export class FileMock extends File { }; if (this.appProvider.isDesktop()) { - let fs = require('fs'), + const fs = require('fs'), app = require('electron').remote.app; - // emulateCordovaFileForDesktop(fs); + // @todo emulateCordovaFileForDesktop(fs); // Initialize File System. Get the path to use. basePath = app.getPath('documents') || app.getPath('home'); if (!basePath) { reject('Cannot calculate base path for file system.'); + return; } @@ -339,7 +346,7 @@ export class FileMock extends File { fs.mkdir(basePath, (e) => { if (!e || (e && e.code === 'EEXIST')) { // Create successful or it already exists. Resolve. - // this.fileProvider.setHTMLBasePath(basePath); + // @todo this.fileProvider.setHTMLBasePath(basePath); resolve(basePath); } else { reject('Error creating base path.'); @@ -347,7 +354,7 @@ export class FileMock extends File { }); } else { // It's browser, request a quota to use. Request 500MB. - (navigator).webkitPersistentStorage.requestQuota(500 * 1024 * 1024, (granted) => { + ( navigator).webkitPersistentStorage.requestQuota(500 * 1024 * 1024, (granted) => { window.requestFileSystem(LocalFileSystem.PERSISTENT, granted, (entry) => { basePath = entry.root.toURL(); resolve(basePath); @@ -367,7 +374,7 @@ export class FileMock extends File { * @returns {Promise} Returns a Promise that resolves to the new Entry object or rejects with an error. */ private moveMock(srce: Entry, destDir: DirectoryEntry, newName: string): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { srce.moveTo(destDir, newName, (deste) => { resolve(deste); }, (err) => { @@ -443,7 +450,6 @@ export class FileMock extends File { * Read file and return data as a base64 encoded data url. * A data url is of the form: * data: [][;base64], - * @param {string} path Base FileSystem. * @param {string} file Name of file, relative to path. * @returns {Promise} Returns a Promise that resolves with the contents of the file as data URL or rejects @@ -471,7 +477,7 @@ export class FileMock extends File { * @return {Promise} Promise resolved with the list of files/dirs. */ private readEntriesMock(dr: DirectoryReader): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { dr.readEntries((entries: any) => { resolve(entries); }, (err) => { @@ -494,10 +500,11 @@ export class FileMock extends File { return this.getFile(directoryEntry, file, { create: false }); }).then((fileEntry: FileEntry) => { const reader = new FileReader(); - return new Promise((resolve, reject) => { - reader.onloadend = () => { + + return new Promise((resolve, reject): void => { + reader.onloadend = (): void => { if (reader.result !== undefined || reader.result !== null) { - resolve(reader.result); + resolve( reader.result); } else if (reader.error !== undefined || reader.error !== null) { reject(reader.error); } else { @@ -507,7 +514,7 @@ export class FileMock extends File { fileEntry.file((file) => { reader[`readAs${readAs}`].call(reader, file); - }, error => { + }, (error) => { reject(error); }); }); @@ -521,7 +528,7 @@ export class FileMock extends File { * @return {Promise} Promise resolved when done. */ private removeMock(fe: Entry): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { fe.remove(() => { resolve({ success: true, fileRemoved: fe }); }, (err) => { @@ -584,10 +591,11 @@ export class FileMock extends File { resolveDirectoryUrl(directoryUrl: string): Promise { return this.resolveLocalFilesystemUrl(directoryUrl).then((de) => { if (de.isDirectory) { - return de; + return de; } else { const err = new FileError(13); err.message = 'input is not a directory'; + return Promise.reject(err); } }); @@ -599,7 +607,7 @@ export class FileMock extends File { * @returns {Promise} */ resolveLocalFilesystemUrl(fileUrl: string): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { try { window.resolveLocalFileSystemURL(fileUrl, (entry: any) => { resolve(entry); @@ -621,7 +629,7 @@ export class FileMock extends File { * @return {Promise} Promise resolved when done. */ private rimrafMock(de: DirectoryEntry): Promise { - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { de.removeRecursively(() => { resolve({ success: true, fileRemoved: de }); }, (err) => { @@ -643,8 +651,8 @@ export class FileMock extends File { return this.writeFileInChunksMock(writer, data); } - return new Promise((resolve, reject) => { - writer.onwriteend = (evt) => { + return new Promise((resolve, reject): void => { + writer.onwriteend = (evt): void => { if (writer.error) { reject(writer.error); } else { @@ -667,7 +675,8 @@ export class FileMock extends File { return this.writeFile(path, fileName, text, { replace: true }); } - /** Write a new file to the desired location. + /** + * Write a new file to the desired location. * * @param {string} path Base FileSystem. Please refer to the iOS and Android filesystems above * @param {string} fileName path relative to base path @@ -696,7 +705,7 @@ export class FileMock extends File { * @param {IWriteOptions} options replace file if set to true. See WriteOptions for more information. * @returns {Promise} Returns a Promise that resolves to updated file entry or rejects with an error. */ - private writeFileEntryMock(fe: FileEntry, text: string | Blob | ArrayBuffer, options: IWriteOptions) { + private writeFileEntryMock(fe: FileEntry, text: string | Blob | ArrayBuffer, options: IWriteOptions): Promise { return this.createWriterMock(fe).then((writer) => { if (options.append) { writer.seek(writer.length); @@ -717,21 +726,20 @@ export class FileMock extends File { * @param {Blob} data Data to write. * @return {Promise} Promise resolved when done. */ - private writeFileInChunksMock(writer: FileWriter, data: Blob) : Promise { - const BLOCK_SIZE = 1024 * 1024; + private writeFileInChunksMock(writer: FileWriter, data: Blob): Promise { let writtenSize = 0; + const BLOCK_SIZE = 1024 * 1024, + writeNextChunk = (): void => { + const size = Math.min(BLOCK_SIZE, data.size - writtenSize); + const chunk = data.slice(writtenSize, writtenSize + size); - function writeNextChunk() { - const size = Math.min(BLOCK_SIZE, data.size - writtenSize); - const chunk = data.slice(writtenSize, writtenSize + size); + writtenSize += size; + writer.write(chunk); + }; - writtenSize += size; - writer.write(chunk); - } - - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { writer.onerror = reject; - writer.onwrite = () => { + writer.onwrite = (): void => { if (writtenSize < data.size) { writeNextChunk(); } else { diff --git a/src/core/emulator/providers/globalization.ts b/src/core/emulator/providers/globalization.ts index 5ffa2e142..5ceeec8a9 100644 --- a/src/core/emulator/providers/globalization.ts +++ b/src/core/emulator/providers/globalization.ts @@ -31,18 +31,17 @@ export class GlobalizationMock extends Globalization { * * @return {string} Locale name. */ - private getCurrentlocale() : string { + private getCurrentlocale(): string { // Get browser language. - var navLang = (navigator).userLanguage || navigator.language; + const navLang = ( navigator).userLanguage || navigator.language; try { if (this.appProvider.isDesktop()) { - var locale = require('electron').remote.app.getLocale(); - return locale || navLang; + return require('electron').remote.app.getLocale() || navLang; } else { return navLang; } - } catch(ex) { + } catch (ex) { // Something went wrong, return browser language. return navLang; } @@ -53,12 +52,13 @@ export class GlobalizationMock extends Globalization { * * @return {Promise<{value: string}>} Promise resolved with an object with the language string. */ - getLocaleName() : Promise<{value: string}> { - var locale = this.getCurrentlocale(); + getLocaleName(): Promise<{ value: string }> { + const locale = this.getCurrentlocale(); if (locale) { - return Promise.resolve({value: locale}); + return Promise.resolve({ value: locale }); } else { - var error = {code: GlobalizationError.UNKNOWN_ERROR, message: 'Cannot get language'}; + const error = { code: GlobalizationError.UNKNOWN_ERROR, message: 'Cannot get language' }; + return Promise.reject(error); } } @@ -68,7 +68,7 @@ export class GlobalizationMock extends Globalization { * * @return {Promise<{value: string}>} Promise resolved with an object with the language string. */ - getPreferredLanguage() : Promise<{value: string}> { + getPreferredLanguage(): Promise<{ value: string }> { return this.getLocaleName(); } } diff --git a/src/core/emulator/providers/helper.ts b/src/core/emulator/providers/helper.ts index 1a6e2fa86..cea59f24d 100644 --- a/src/core/emulator/providers/helper.ts +++ b/src/core/emulator/providers/helper.ts @@ -32,23 +32,23 @@ export class CoreEmulatorHelperProvider implements CoreInitHandler { constructor(private file: File, private fileProvider: CoreFileProvider, private utils: CoreUtilsProvider, initDelegate: CoreInitDelegate, private localNotif: LocalNotifications, - private captureHelper: CoreEmulatorCaptureHelperProvider) {} + private captureHelper: CoreEmulatorCaptureHelperProvider) { } /** * Load the Mocks that need it. * * @return {Promise} Promise resolved when loaded. */ - load() : Promise { - let promises = []; + load(): Promise { + const promises = []; - promises.push((this.file).load().then((basePath: string) => { + promises.push(( this.file).load().then((basePath: string) => { this.fileProvider.setHTMLBasePath(basePath); })); - promises.push((this.localNotif).load()); + promises.push(( this.localNotif).load()); promises.push(this.captureHelper.load()); - (window).FileTransferError = FileTransferErrorMock; + ( window).FileTransferError = FileTransferErrorMock; return this.utils.allPromises(promises); } diff --git a/src/core/emulator/providers/inappbrowser.ts b/src/core/emulator/providers/inappbrowser.ts index 1aa83f800..bcae07e73 100644 --- a/src/core/emulator/providers/inappbrowser.ts +++ b/src/core/emulator/providers/inappbrowser.ts @@ -38,7 +38,7 @@ export class InAppBrowserMock extends InAppBrowser { * @param {string} [options] Options for the InAppBrowser. * @return {InAppBrowserObject} The new instance. */ - create(url: string, target?: string, options = '') : InAppBrowserObject { + create(url: string, target?: string, options: string = ''): InAppBrowserObject { if (!this.appProvider.isDesktop()) { return super.create(url, target, options); } diff --git a/src/core/emulator/providers/local-notifications.ts b/src/core/emulator/providers/local-notifications.ts index 5f0e23abb..9ea0c3318 100644 --- a/src/core/emulator/providers/local-notifications.ts +++ b/src/core/emulator/providers/local-notifications.ts @@ -30,14 +30,14 @@ export class LocalNotificationsMock extends LocalNotifications { protected winNotif; // Library for Windows notifications. // Templates for Windows ToastNotifications and TileNotifications. protected toastTemplate = '%s' + - '%s'; - protected tileBindingTemplate = '%s' + - '%s'; + '%s'; + protected tileBindingTemplate = '%s' + + '%s'; protected tileTemplate = '' + - '' + this.tileBindingTemplate + '' + - '' + this.tileBindingTemplate + '' + - '' + this.tileBindingTemplate + '' + - ''; + '' + this.tileBindingTemplate + '' + + '' + this.tileBindingTemplate + '' + + '' + this.tileBindingTemplate + '' + + ''; // Variables for database. protected DESKTOP_NOTIFS_TABLE = 'desktop_local_notifications'; @@ -57,18 +57,18 @@ export class LocalNotificationsMock extends LocalNotifications { }; protected appDB: SQLiteDB; - protected scheduled: {[i: number] : any} = {}; - protected triggered: {[i: number] : any} = {}; + protected scheduled: { [i: number]: any } = {}; + protected triggered: { [i: number]: any } = {}; protected observers; protected defaults = { - text: '', + text: '', title: '', sound: '', badge: 0, - id: 0, - data: undefined, + id: 0, + data: undefined, every: undefined, - at: undefined + at: undefined }; constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider) { @@ -90,14 +90,13 @@ export class LocalNotificationsMock extends LocalNotifications { }; } - /** * Cancels single or multiple notifications * @param notificationId {any} A single notification id, or an array of notification ids. * @returns {Promise} Returns a promise when the notification is canceled */ cancel(notificationId: any): Promise { - let promises = []; + const promises = []; notificationId = Array.isArray(notificationId) ? notificationId : [notificationId]; notificationId = this.convertIds(notificationId); @@ -118,8 +117,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @returns {Promise} Returns a promise when all notifications are canceled. */ cancelAll(): Promise { - let ids = Object.keys(this.scheduled); - return this.cancel(ids).then(() => { + return this.cancel(Object.keys(this.scheduled)).then(() => { this.triggerEvent('cancelall', 'foreground'); }); } @@ -132,8 +130,8 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {string} eventName Name of the event to trigger. * @return {Void} */ - protected cancelNotification(id: number, omitEvent: boolean, eventName: string) : void { - let notification = this.scheduled[id].notification; + protected cancelNotification(id: number, omitEvent: boolean, eventName: string): void { + const notification = this.scheduled[id].notification; clearTimeout(this.scheduled[id].timeout); clearInterval(this.scheduled[id].interval); @@ -154,7 +152,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @returns {Promise} Returns a promise when the notification had been cleared. */ clear(notificationId: any): Promise { - let promises = []; + const promises = []; notificationId = Array.isArray(notificationId) ? notificationId : [notificationId]; notificationId = this.convertIds(notificationId); @@ -175,8 +173,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @returns {Promise} Returns a promise when all notifications have cleared */ clearAll(): Promise { - let ids = Object.keys(this.scheduled); - return this.clear(ids).then(() => { + return this.clear(Object.keys(this.scheduled)).then(() => { this.triggerEvent('clearall', 'foreground'); }); } @@ -186,10 +183,10 @@ export class LocalNotificationsMock extends LocalNotifications { * Code extracted from the Cordova plugin. * * @param {any[]} ids List of IDs. - * @return {Number[]} List of IDs as numbers. + * @return {number[]} List of IDs as numbers. */ - protected convertIds(ids: any[]) : number[] { - let convertedIds = []; + protected convertIds(ids: any[]): number[] { + const convertedIds = []; for (let i = 0; i < ids.length; i++) { convertedIds.push(Number(ids[i])); @@ -205,7 +202,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {ILocalNotification} notification Notification. * @return {ILocalNotification} Converted notification. */ - protected convertProperties(notification: ILocalNotification) : ILocalNotification { + protected convertProperties(notification: ILocalNotification): ILocalNotification { if (notification.id) { if (isNaN(notification.id)) { notification.id = this.defaults.id; @@ -219,7 +216,7 @@ export class LocalNotificationsMock extends LocalNotifications { } if (notification.text) { - notification.text = notification.text.toString(); + notification.text = notification.text.toString(); } if (notification.badge) { @@ -274,6 +271,7 @@ export class LocalNotificationsMock extends LocalNotifications { ids = ids.map((id) => { return Number(id); }); + return Promise.resolve(ids); } @@ -282,7 +280,7 @@ export class LocalNotificationsMock extends LocalNotifications { * * @return {Promise} Promise resolved with the notifications. */ - protected getAllNotifications() : Promise { + protected getAllNotifications(): Promise { return this.appDB.getAllRecords(this.DESKTOP_NOTIFS_TABLE); } @@ -312,11 +310,11 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {boolean} [getTriggered] Get triggered notifications. * @return {ILocalNotification[]} List of notifications. */ - protected getNotifications(ids?: number[], getScheduled?: boolean, getTriggered?: boolean) : ILocalNotification[] { - let notifications = []; + protected getNotifications(ids?: number[], getScheduled?: boolean, getTriggered?: boolean): ILocalNotification[] { + const notifications = []; if (getScheduled) { - for (let id in this.scheduled) { + for (const id in this.scheduled) { if (!ids || ids.indexOf(Number(id)) != -1) { notifications.push(this.scheduled[id].notification); } @@ -324,8 +322,8 @@ export class LocalNotificationsMock extends LocalNotifications { } if (getTriggered) { - for (let id in this.triggered) { - if ((!getScheduled || !this.scheduled[id]) && (!ids || ids.indexOf(Number(id)) != -1)) { + for (const id in this.triggered) { + if ((!getScheduled || !this.scheduled[id]) && (!ids || ids.indexOf(Number(id)) != -1)) { notifications.push(this.triggered[id].notification); } } @@ -350,9 +348,10 @@ export class LocalNotificationsMock extends LocalNotifications { * @returns {Promise>} Returns a promise */ getScheduledIds(): Promise> { - let ids = Object.keys(this.scheduled).map((id) => { + const ids = Object.keys(this.scheduled).map((id) => { return Number(id); }); + return Promise.resolve(ids); } @@ -372,9 +371,10 @@ export class LocalNotificationsMock extends LocalNotifications { * @returns {Promise>} */ getTriggeredIds(): Promise> { - let ids = Object.keys(this.triggered).map((id) => { + const ids = Object.keys(this.triggered).map((id) => { return Number(id); }); + return Promise.resolve(ids); } @@ -386,8 +386,8 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {any} ...args List of keys to check. * @return {any} First value found. */ - protected getValueFor(notification: ILocalNotification, ...args) : any { - for (let i in args) { + protected getValueFor(notification: ILocalNotification, ...args: any[]): any { + for (const i in args) { const key = args[i]; if (notification.hasOwnProperty(key)) { return notification[key]; @@ -438,7 +438,7 @@ export class LocalNotificationsMock extends LocalNotifications { * * @return {Promise} Promise resolved when done. */ - load() : Promise { + load(): Promise { if (!this.appProvider.isDesktop()) { return Promise.resolve(); } @@ -446,10 +446,11 @@ export class LocalNotificationsMock extends LocalNotifications { if (this.appProvider.isWindows()) { try { this.winNotif = require('electron-windows-notifications'); - } catch(ex) {} + } catch (ex) { + // Ignore errors. + } } - // App is being loaded, re-schedule all the notifications that were scheduled before. return this.getAllNotifications().catch(() => { return []; @@ -481,7 +482,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {ILocalNotification} notification Notification. * @return {ILocalNotification} Treated notification. */ - protected mergeWithDefaults(notification: ILocalNotification) : ILocalNotification { + protected mergeWithDefaults(notification: ILocalNotification): ILocalNotification { notification.at = this.getValueFor(notification, 'at', 'firstAt', 'date'); notification.text = this.getValueFor(notification, 'text', 'message'); notification.data = this.getValueFor(notification, 'data', 'json'); @@ -490,9 +491,9 @@ export class LocalNotificationsMock extends LocalNotifications { notification.at = new Date(); } - for (let key in this.defaults) { + for (const key in this.defaults) { if (notification[key] === null || notification[key] === undefined) { - if (notification.hasOwnProperty(key) && ['data','sound'].indexOf(key) > -1) { + if (notification.hasOwnProperty(key) && ['data', 'sound'].indexOf(key) > -1) { notification[key] = undefined; } else { notification[key] = this.defaults[key]; @@ -500,7 +501,7 @@ export class LocalNotificationsMock extends LocalNotifications { } } - for (let key in notification) { + for (const key in notification) { if (!this.defaults.hasOwnProperty(key)) { delete notification[key]; } @@ -514,7 +515,7 @@ export class LocalNotificationsMock extends LocalNotifications { * * @param {ILocalNotification} notification Clicked notification. */ - protected notificationClicked(notification: ILocalNotification) : void { + protected notificationClicked(notification: ILocalNotification): void { this.triggerEvent('click', notification, 'foreground'); // Focus the app. require('electron').ipcRenderer.send('focusApp'); @@ -541,7 +542,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {string} every Interval to convert. * @return {number} Number of milliseconds of the interval- */ - protected parseInterval(every: string) { + protected parseInterval(every: string): number { let interval; every = String(every).toLowerCase(); @@ -591,8 +592,8 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {number} id ID of the notification. * @return {Promise} Promise resolved when done. */ - protected removeNotification(id: number) : Promise { - return this.appDB.deleteRecords(this.DESKTOP_NOTIFS_TABLE, {id: id}); + protected removeNotification(id: number): Promise { + return this.appDB.deleteRecords(this.DESKTOP_NOTIFS_TABLE, { id: id }); } /** @@ -610,7 +611,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {ILocalNotification | Array} [options] Notification or notifications. * @param {string} [eventName] Name of the event: schedule or update. */ - protected scheduleOrUpdate(options?: ILocalNotification | Array, eventName = 'schedule'): void { + protected scheduleOrUpdate(options?: ILocalNotification | Array, eventName: string = 'schedule'): void { options = Array.isArray(options) ? options : [options]; options.forEach((notification) => { @@ -632,8 +633,8 @@ export class LocalNotificationsMock extends LocalNotifications { } // Schedule the notification. - let toTriggerTime = notification.at * 1000 - Date.now(), - trigger = () => { + const toTriggerTime = notification.at * 1000 - Date.now(), + trigger = (): void => { // Trigger the notification. this.triggerNotification(notification); @@ -666,10 +667,11 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {boolean} triggered Whether the notification has been triggered. * @return {Promise} Promise resolved when stored. */ - protected storeNotification(notification: any, triggered: boolean) : Promise { + protected storeNotification(notification: any, triggered: boolean): Promise { notification = Object.assign({}, notification); // Clone the object. notification.triggered = !!triggered; - return this.appDB.insertOrUpdateRecord(this.DESKTOP_NOTIFS_TABLE, notification, {id: notification.id}); + + return this.appDB.insertOrUpdateRecord(this.DESKTOP_NOTIFS_TABLE, notification, { id: notification.id }); } /** @@ -678,7 +680,7 @@ export class LocalNotificationsMock extends LocalNotifications { * @param {string} eventName Event name. * @param {any[]} ...args List of parameters to pass. */ - protected triggerEvent(eventName: string, ...args: any[]) { + protected triggerEvent(eventName: string, ...args: any[]): void { if (this.observers[eventName]) { this.observers[eventName].forEach((callback) => { callback.apply(null, args); @@ -691,13 +693,13 @@ export class LocalNotificationsMock extends LocalNotifications { * * @param {ILocalNotification} notification Notification to trigger. */ - protected triggerNotification(notification: ILocalNotification) : void { + protected triggerNotification(notification: ILocalNotification): void { if (this.winNotif) { // Use Windows notifications. - let notifInstance = new this.winNotif.ToastNotification({ + const notifInstance = new this.winNotif.ToastNotification({ appId: CoreConfigConstants.app_id, template: this.toastTemplate, - strings: [notification.title, notification.text] + strings: [notification.title, notification.text] }); // Listen for click events. @@ -709,16 +711,17 @@ export class LocalNotificationsMock extends LocalNotifications { try { // Show it in Tile too. - let tileNotif = new this.winNotif.TileNotification({ + const tileNotif = new this.winNotif.TileNotification({ tag: notification.id + '', template: this.tileTemplate, - strings: [notification.title, notification.text, notification.title, notification.text, notification.title, - notification.text], + strings: [notification.title, notification.text, notification.title, notification.text, notification.title, + notification.text], expirationTime: new Date(Date.now() + CoreConstants.SECONDS_HOUR * 1000) // Expire in 1 hour. - }) + }); - tileNotif.show() - } catch(ex) { + tileNotif.show(); + } catch (ex) { + // tslint:disable-next-line console.warn('Error showing TileNotification. Please notice they only work with the app installed.', ex); } } else { @@ -728,7 +731,7 @@ export class LocalNotificationsMock extends LocalNotifications { }); // Listen for click events. - notifInstance.onclick = () => { + notifInstance.onclick = (): void => { this.notificationClicked(notification); }; } @@ -758,5 +761,5 @@ export class LocalNotificationsMock extends LocalNotifications { */ update(options?: ILocalNotification): void { return this.scheduleOrUpdate(options, 'update'); - }; + } } diff --git a/src/core/emulator/providers/media-capture.ts b/src/core/emulator/providers/media-capture.ts index 55a8b65ce..568cd4beb 100644 --- a/src/core/emulator/providers/media-capture.ts +++ b/src/core/emulator/providers/media-capture.ts @@ -32,7 +32,7 @@ export class MediaCaptureMock extends MediaCapture { * @param {CaptureAudioOptions} options Options. * @return {Promise} Promise resolved when captured. */ - captureAudio(options: CaptureAudioOptions) : Promise { + captureAudio(options: CaptureAudioOptions): Promise { return this.captureHelper.captureMedia('audio', options); } @@ -42,7 +42,7 @@ export class MediaCaptureMock extends MediaCapture { * @param {CaptureImageOptions} options Options. * @return {Promise} Promise resolved when captured. */ - captureImage(options: CaptureImageOptions) : Promise { + captureImage(options: CaptureImageOptions): Promise { return this.captureHelper.captureMedia('captureimage', options); } @@ -52,7 +52,7 @@ export class MediaCaptureMock extends MediaCapture { * @param {CaptureVideoOptions} options Options. * @return {Promise} Promise resolved when captured. */ - captureVideo(options: CaptureVideoOptions) : Promise { + captureVideo(options: CaptureVideoOptions): Promise { return this.captureHelper.captureMedia('video', options); } } diff --git a/src/core/emulator/providers/network.ts b/src/core/emulator/providers/network.ts index c6dfa36da..39f8b91c0 100644 --- a/src/core/emulator/providers/network.ts +++ b/src/core/emulator/providers/network.ts @@ -26,7 +26,7 @@ export class NetworkMock extends Network { constructor() { super(); - (window).Connection = { + ( window).Connection = { UNKNOWN: 'unknown', ETHERNET: 'ethernet', WIFI: 'wifi', @@ -39,21 +39,21 @@ export class NetworkMock extends Network { } /** - * Returns an observable to watch connection changes. - * - * @return {Observable} Observable. - */ + * Returns an observable to watch connection changes. + * + * @return {Observable} Observable. + */ onchange(): Observable { return Observable.merge(this.onConnect(), this.onDisconnect()); } /** - * Returns an observable to notify when the app is connected. - * - * @return {Observable} Observable. - */ - onConnect() : Observable { - let observable = new Subject(); + * Returns an observable to notify when the app is connected. + * + * @return {Observable} Observable. + */ + onConnect(): Observable { + const observable = new Subject(); window.addEventListener('online', (ev) => { observable.next(ev); @@ -63,12 +63,12 @@ export class NetworkMock extends Network { } /** - * Returns an observable to notify when the app is disconnected. - * - * @return {Observable} Observable. - */ - onDisconnect() : Observable { - let observable = new Subject(); + * Returns an observable to notify when the app is disconnected. + * + * @return {Observable} Observable. + */ + onDisconnect(): Observable { + const observable = new Subject(); window.addEventListener('offline', (ev) => { observable.next(ev); diff --git a/src/core/emulator/providers/zip.ts b/src/core/emulator/providers/zip.ts index f83846b9c..2d3e27b77 100644 --- a/src/core/emulator/providers/zip.ts +++ b/src/core/emulator/providers/zip.ts @@ -36,21 +36,21 @@ export class ZipMock extends Zip { * @param {Function} [onProgress] Optional callback to be called on progress update * @return {Promise} Promise that resolves with a number. 0 is success, -1 is error. */ - unzip(source: string, destination: string, onProgress?: Function) : Promise { + unzip(source: string, destination: string, onProgress?: Function): Promise { // Replace all %20 with spaces. source = source.replace(/%20/g, ' '); destination = destination.replace(/%20/g, ' '); - let sourceDir = source.substring(0, source.lastIndexOf('/')), + const sourceDir = source.substring(0, source.lastIndexOf('/')), sourceName = source.substr(source.lastIndexOf('/') + 1); return this.file.readAsArrayBuffer(sourceDir, sourceName).then((data) => { - let zip = new JSZip(data), + const zip = new JSZip(data), promises = [], - loaded = 0, total = Object.keys(zip.files).length; + let loaded = 0; - if (!zip.files || !zip.files.length) { + if (!zip.files || !zip.files.length) { // Nothing to extract. return 0; } @@ -62,7 +62,7 @@ export class ZipMock extends Zip { if (!file.dir) { // It's a file. Get the mimetype and write the file. type = this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(name)); - promise = this.file.writeFile(destination, name, new Blob([file.asArrayBuffer()], {type: type})); + promise = this.file.writeFile(destination, name, new Blob([file.asArrayBuffer()], { type: type })); } else { // It's a folder, create it if it doesn't exist. promise = this.file.createDir(destination, name, false); @@ -71,14 +71,14 @@ export class ZipMock extends Zip { promises.push(promise.then(() => { // File unzipped, call the progress. loaded++; - onProgress && onProgress({loaded: loaded, total: total}); + onProgress && onProgress({ loaded: loaded, total: total }); })); }); - return Promise.all(promises).then(function() { + return Promise.all(promises).then(() => { return 0; }); - }).catch(function() { + }).catch(() => { // Error. return -1; }); diff --git a/src/core/fileuploader/providers/album-handler.ts b/src/core/fileuploader/providers/album-handler.ts index 3e78e3e2d..b1221ef34 100644 --- a/src/core/fileuploader/providers/album-handler.ts +++ b/src/core/fileuploader/providers/album-handler.ts @@ -26,14 +26,14 @@ export class CoreFileUploaderAlbumHandler implements CoreFileUploaderHandler { priority = 2000; constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, - private uploaderHelper: CoreFileUploaderHelperProvider) {} + private uploaderHelper: CoreFileUploaderHelperProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled(): boolean|Promise { + isEnabled(): boolean | Promise { return this.appProvider.isMobile(); } @@ -43,7 +43,7 @@ export class CoreFileUploaderAlbumHandler implements CoreFileUploaderHandler { * @param {string[]} [mimetypes] List of mimetypes. * @return {string[]} Supported mimetypes. */ - getSupportedMimetypes(mimetypes: string[]) : string[] { + getSupportedMimetypes(mimetypes: string[]): string[] { // Album allows picking images and videos. return this.utils.filterByRegexp(mimetypes, /^(image|video)\//); } @@ -53,12 +53,12 @@ export class CoreFileUploaderAlbumHandler implements CoreFileUploaderHandler { * * @return {CoreFileUploaderHandlerData} Data. */ - getData() : CoreFileUploaderHandlerData { + getData(): CoreFileUploaderHandlerData { return { title: 'core.fileuploader.photoalbums', class: 'core-fileuploader-album-handler', icon: 'images', - action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]) => { + action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]): Promise => { return this.uploaderHelper.uploadImage(true, maxSize, upload, mimetypes).then((result) => { return { treated: true, diff --git a/src/core/fileuploader/providers/audio-handler.ts b/src/core/fileuploader/providers/audio-handler.ts index 332e8d0ee..a3d8a2cd0 100644 --- a/src/core/fileuploader/providers/audio-handler.ts +++ b/src/core/fileuploader/providers/audio-handler.ts @@ -27,15 +27,15 @@ export class CoreFileUploaderAudioHandler implements CoreFileUploaderHandler { priority = 1600; constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private platform: Platform, - private uploaderHelper: CoreFileUploaderHelperProvider) {} + private uploaderHelper: CoreFileUploaderHelperProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled(): boolean|Promise { - return this.appProvider.isMobile() || (this.appProvider.canGetUserMedia() && this.appProvider.canRecordMedia()); + isEnabled(): boolean | Promise { + return this.appProvider.isMobile() || (this.appProvider.canGetUserMedia() && this.appProvider.canRecordMedia()); } /** @@ -44,19 +44,20 @@ export class CoreFileUploaderAudioHandler implements CoreFileUploaderHandler { * @param {string[]} [mimetypes] List of mimetypes. * @return {string[]} Supported mimetypes. */ - getSupportedMimetypes(mimetypes: string[]) : string[] { + getSupportedMimetypes(mimetypes: string[]): string[] { if (this.platform.is('ios')) { - // iOS records as WAV. + // In iOS it's recorded as WAV. return this.utils.filterByRegexp(mimetypes, /^audio\/wav$/); } else if (this.platform.is('android')) { // In Android we don't know the format the audio will be recorded, so accept any audio mimetype. return this.utils.filterByRegexp(mimetypes, /^audio\//); } else { // In desktop, support audio formats that are supported by MediaRecorder. - let mediaRecorder = (window).MediaRecorder; + const mediaRecorder = ( window).MediaRecorder; if (mediaRecorder) { return mimetypes.filter((type) => { - let matches = type.match(/^audio\//); + const matches = type.match(/^audio\//); + return matches && matches.length && mediaRecorder.isTypeSupported(type); }); } @@ -70,12 +71,12 @@ export class CoreFileUploaderAudioHandler implements CoreFileUploaderHandler { * * @return {CoreFileUploaderHandlerData} Data. */ - getData() : CoreFileUploaderHandlerData { + getData(): CoreFileUploaderHandlerData { return { title: 'core.fileuploader.audio', class: 'core-fileuploader-audio-handler', icon: 'microphone', - action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]) => { + action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]): Promise => { return this.uploaderHelper.uploadAudioOrVideo(true, maxSize, upload, mimetypes).then((result) => { return { treated: true, diff --git a/src/core/fileuploader/providers/camera-handler.ts b/src/core/fileuploader/providers/camera-handler.ts index e72adb6f4..9e772ecea 100644 --- a/src/core/fileuploader/providers/camera-handler.ts +++ b/src/core/fileuploader/providers/camera-handler.ts @@ -26,15 +26,15 @@ export class CoreFileUploaderCameraHandler implements CoreFileUploaderHandler { priority = 1800; constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, - private uploaderHelper: CoreFileUploaderHelperProvider) {} + private uploaderHelper: CoreFileUploaderHelperProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled(): boolean|Promise { - return this.appProvider.isMobile() || this.appProvider.canGetUserMedia(); + isEnabled(): boolean | Promise { + return this.appProvider.isMobile() || this.appProvider.canGetUserMedia(); } /** @@ -43,7 +43,7 @@ export class CoreFileUploaderCameraHandler implements CoreFileUploaderHandler { * @param {string[]} [mimetypes] List of mimetypes. * @return {string[]} Supported mimetypes. */ - getSupportedMimetypes(mimetypes: string[]) : string[] { + getSupportedMimetypes(mimetypes: string[]): string[] { // Camera only supports JPEG and PNG. return this.utils.filterByRegexp(mimetypes, /^image\/(jpeg|png)$/); } @@ -53,12 +53,12 @@ export class CoreFileUploaderCameraHandler implements CoreFileUploaderHandler { * * @return {CoreFileUploaderHandlerData} Data. */ - getData() : CoreFileUploaderHandlerData { + getData(): CoreFileUploaderHandlerData { return { title: 'core.fileuploader.camera', class: 'core-fileuploader-camera-handler', icon: 'camera', - action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]) => { + action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]): Promise => { return this.uploaderHelper.uploadImage(false, maxSize, upload, mimetypes).then((result) => { return { treated: true, diff --git a/src/core/fileuploader/providers/delegate.ts b/src/core/fileuploader/providers/delegate.ts index 30f9aa1b2..5187e3c9e 100644 --- a/src/core/fileuploader/providers/delegate.ts +++ b/src/core/fileuploader/providers/delegate.ts @@ -170,11 +170,11 @@ export class CoreFileUploaderDelegate extends CoreDelegate { * @return {CoreFileUploaderHandlerDataToReturn[]} List of handlers data. */ getHandlers(mimetypes: string[]): CoreFileUploaderHandlerDataToReturn[] { - let handlers = []; + const handlers = []; - for (let name in this.enabledHandlers) { - let handler = this.enabledHandlers[name], - supportedMimetypes; + for (const name in this.enabledHandlers) { + const handler = this.enabledHandlers[name]; + let supportedMimetypes; if (mimetypes) { if (!handler.getSupportedMimetypes) { @@ -190,7 +190,7 @@ export class CoreFileUploaderDelegate extends CoreDelegate { } } - let data: CoreFileUploaderHandlerDataToReturn = handler.getData(); + const data: CoreFileUploaderHandlerDataToReturn = handler.getData(); data.priority = handler.priority; data.mimetypes = supportedMimetypes; handlers.push(data); diff --git a/src/core/fileuploader/providers/file-handler.ts b/src/core/fileuploader/providers/file-handler.ts index 8240c991b..e045682f2 100644 --- a/src/core/fileuploader/providers/file-handler.ts +++ b/src/core/fileuploader/providers/file-handler.ts @@ -30,16 +30,16 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { constructor(private appProvider: CoreAppProvider, private platform: Platform, private timeUtils: CoreTimeUtilsProvider, private uploaderHelper: CoreFileUploaderHelperProvider, private uploaderProvider: CoreFileUploaderProvider, - private domUtils: CoreDomUtilsProvider) {} + private domUtils: CoreDomUtilsProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled(): boolean|Promise { + isEnabled(): boolean | Promise { return this.platform.is('android') || !this.appProvider.isMobile() || - (this.platform.is('ios') && this.platform.version().major >= 9); + (this.platform.is('ios') && this.platform.version().major >= 9); } /** @@ -48,7 +48,7 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { * @param {string[]} [mimetypes] List of mimetypes. * @return {string[]} Supported mimetypes. */ - getSupportedMimetypes(mimetypes: string[]) : string[] { + getSupportedMimetypes(mimetypes: string[]): string[] { return mimetypes; } @@ -57,14 +57,14 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { * * @return {CoreFileUploaderHandlerData} Data. */ - getData() : CoreFileUploaderHandlerData { + getData(): CoreFileUploaderHandlerData { const isIOS = this.platform.is('ios'); return { title: isIOS ? 'core.fileuploader.more' : 'core.fileuploader.file', class: 'core-fileuploader-file-handler', icon: isIOS ? 'more' : 'folder', - afterRender: (maxSize: number, upload: boolean, allowOffline: boolean, mimetypes: string[]) => { + afterRender: (maxSize: number, upload: boolean, allowOffline: boolean, mimetypes: string[]): void => { // Add an invisible file input in the file handler. // It needs to be done like this because the action sheet items don't accept inputs. const element = document.querySelector('.core-fileuploader-file-handler'); @@ -77,8 +77,9 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { } input.addEventListener('change', (evt: Event) => { - let file = input.files[0], - fileName; + const file = input.files[0]; + let fileName; + input.value = ''; // Unset input. if (!file) { return; @@ -88,6 +89,7 @@ export class CoreFileUploaderFileHandler implements CoreFileUploaderHandler { const error = this.uploaderProvider.isInvalidMimetype(mimetypes, file.name, file.type); if (error) { this.domUtils.showErrorModal(error); + return; } diff --git a/src/core/fileuploader/providers/fileuploader.ts b/src/core/fileuploader/providers/fileuploader.ts index 0b1ba9093..abc48fbc5 100644 --- a/src/core/fileuploader/providers/fileuploader.ts +++ b/src/core/fileuploader/providers/fileuploader.ts @@ -35,15 +35,15 @@ export interface CoreFileUploaderOptions extends CoreWSFileUploadOptions { * @type {boolean} */ deleteAfterUpload?: boolean; -}; +} /** * Service to upload files. */ @Injectable() export class CoreFileUploaderProvider { - public static LIMITED_SIZE_WARNING = 1048576; // 1 MB. - public static WIFI_SIZE_WARNING = 10485760; // 10 MB. + static LIMITED_SIZE_WARNING = 1048576; // 1 MB. + static WIFI_SIZE_WARNING = 10485760; // 10 MB. protected logger; @@ -60,7 +60,7 @@ export class CoreFileUploaderProvider { * @param {string} extension Extension. * @return {string} Treated extension. */ - protected addDot(extension: string) : string { + protected addDot(extension: string): string { return '.' + extension; } @@ -71,7 +71,7 @@ export class CoreFileUploaderProvider { * @param {any[]} b Second file list. * @return {boolean} Whether both lists are different. */ - areFileListDifferent(a: any[], b: any[]) : boolean { + areFileListDifferent(a: any[], b: any[]): boolean { a = a || []; b = b || []; if (a.length != b.length) { @@ -95,12 +95,14 @@ export class CoreFileUploaderProvider { * * @param {any[]} files List of files. */ - clearTmpFiles(files: any[]) : void { + clearTmpFiles(files: any[]): void { // Delete the local files. files.forEach((file) => { if (!file.offline && file.remove) { // Pass an empty function to prevent missing parameter error. - file.remove(() => {}); + file.remove(() => { + // Nothing to do. + }); } }); } @@ -112,8 +114,8 @@ export class CoreFileUploaderProvider { * @param {boolean} [isFromAlbum] True if the image was taken from album, false if it's a new image taken with camera. * @return {CoreFileUploaderOptions} Options. */ - getCameraUploadOptions(uri: string, isFromAlbum?: boolean) : CoreFileUploaderOptions { - let extension = this.mimeUtils.getExtension(uri), + getCameraUploadOptions(uri: string, isFromAlbum?: boolean): CoreFileUploaderOptions { + const extension = this.mimeUtils.getExtension(uri), mimetype = this.mimeUtils.getMimeType(extension), isIOS = this.platform.is('ios'), options: CoreFileUploaderOptions = { @@ -155,11 +157,11 @@ export class CoreFileUploaderProvider { */ getFileUploadOptions(uri: string, name: string, type: string, deleteAfterUpload?: boolean, fileArea?: string, itemId?: number) : CoreFileUploaderOptions { - let options : CoreFileUploaderOptions = {}; + const options: CoreFileUploaderOptions = {}; options.fileName = name; options.mimeType = type || this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(options.fileName)); options.deleteAfterUpload = !!deleteAfterUpload; - options.itemId = itemId || 0; + options.itemId = itemId || 0; options.fileArea = fileArea; return options; @@ -171,9 +173,9 @@ export class CoreFileUploaderProvider { * @param {MediaFile} mediaFile File object to upload. * @return {CoreFileUploaderOptions} Options. */ - getMediaUploadOptions(mediaFile: MediaFile) : CoreFileUploaderOptions { - let options : CoreFileUploaderOptions = {}, - filename = mediaFile.name, + getMediaUploadOptions(mediaFile: MediaFile): CoreFileUploaderOptions { + const options: CoreFileUploaderOptions = {}; + let filename = mediaFile.name, split; // Add a timestamp to the filename to make it unique. @@ -188,6 +190,7 @@ export class CoreFileUploaderProvider { } else { options.mimeType = this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(options.fileName)); } + return options; } @@ -197,7 +200,7 @@ export class CoreFileUploaderProvider { * @param {string} folderPath Folder where to get the files. * @return {Promise} Promise resolved with the list of files. */ - getStoredFiles(folderPath: string) : Promise { + getStoredFiles(folderPath: string): Promise { return this.fileProvider.getDirectoryContents(folderPath).then((files) => { return this.markOfflineFiles(files); }); @@ -210,7 +213,7 @@ export class CoreFileUploaderProvider { * @param {string} folderPath Folder path to get files from. * @return {Promise} Promise resolved with files. */ - getStoredFilesFromOfflineFilesObject(filesObject: {online: any[], offline: number}, folderPath: string) : Promise { + getStoredFilesFromOfflineFilesObject(filesObject: { online: any[], offline: number }, folderPath: string): Promise { let files = []; if (filesObject) { @@ -227,6 +230,7 @@ export class CoreFileUploaderProvider { }); } } + return Promise.resolve(files); } @@ -239,7 +243,7 @@ export class CoreFileUploaderProvider { * @param {string} [mimetype] File's mimetype. * @return {string} Undefined if file is valid, error message if file is invalid. */ - isInvalidMimetype(mimetypes?: string[], path?: string, mimetype?: string) : string { + isInvalidMimetype(mimetypes?: string[], path?: string, mimetype?: string): string { let extension; if (mimetypes) { @@ -253,7 +257,8 @@ export class CoreFileUploaderProvider { if (mimetype && mimetypes.indexOf(mimetype) == -1) { extension = extension || this.translate.instant('core.unknown'); - return this.translate.instant('core.fileuploader.invalidfiletype', {$a: extension}); + + return this.translate.instant('core.fileuploader.invalidfiletype', { $a: extension }); } } } @@ -264,12 +269,13 @@ export class CoreFileUploaderProvider { * @param {any[]} files Files to mark as offline. * @return {any[]} Files marked as offline. */ - markOfflineFiles(files: any[]) : any[] { + markOfflineFiles(files: any[]): any[] { // Mark the files as pending offline. files.forEach((file) => { file.offline = true; file.filename = file.name; }); + return files; } @@ -279,8 +285,8 @@ export class CoreFileUploaderProvider { * @param {string} filetypeList Formatted string list where the mimetypes can be checked. * @return {{info: any[], mimetypes: string[]}} Mimetypes and the filetypes informations. */ - prepareFiletypeList(filetypeList: string) : {info: any[], mimetypes: string[]} { - let filetypes = filetypeList.split(/[;, ]+/g), + prepareFiletypeList(filetypeList: string): { info: any[], mimetypes: string[] } { + const filetypes = filetypeList.split(/[;, ]+/g), mimetypes = {}, // Use an object to prevent duplicates. typesInfo = []; @@ -298,7 +304,7 @@ export class CoreFileUploaderProvider { mimetypes[filetype] = true; } else if (filetype.indexOf('.') === 0) { // It's an extension. - let mimetype = this.mimeUtils.getMimeType(filetype); + const mimetype = this.mimeUtils.getMimeType(filetype); typesInfo.push({ name: mimetype ? this.mimeUtils.getMimetypeDescription(mimetype) : false, extlist: filetype @@ -309,7 +315,7 @@ export class CoreFileUploaderProvider { } } else { // It's a group. - let groupExtensions = this.mimeUtils.getGroupMimeInfo(filetype, 'extensions'), + const groupExtensions = this.mimeUtils.getGroupMimeInfo(filetype, 'extensions'), groupMimetypes = this.mimeUtils.getGroupMimeInfo(filetype, 'mimetypes'); if (groupExtensions.length > 0) { @@ -326,7 +332,8 @@ export class CoreFileUploaderProvider { } else { // Treat them as extensions. filetype = this.addDot(filetype); - let mimetype = this.mimeUtils.getMimeType(filetype); + + const mimetype = this.mimeUtils.getMimeType(filetype); typesInfo.push({ name: mimetype ? this.mimeUtils.getMimetypeDescription(mimetype) : false, extlist: filetype @@ -354,8 +361,8 @@ export class CoreFileUploaderProvider { * @param {any[]} files List of files. * @return {Promise<{online: any[], offline: number}>} Promise resolved if success. */ - storeFilesToUpload(folderPath: string, files: any[]) : Promise<{online: any[], offline: number}> { - let result = { + storeFilesToUpload(folderPath: string, files: any[]): Promise<{ online: any[], offline: number }> { + const result = { online: [], offline: 0 }; @@ -366,7 +373,7 @@ export class CoreFileUploaderProvider { // Remove unused files from previous saves. return this.fileProvider.removeUnusedFiles(folderPath, files).then(() => { - let promises = []; + const promises = []; files.forEach((file) => { if (file.filename && !file.name) { @@ -382,9 +389,9 @@ export class CoreFileUploaderProvider { // File already in the submission folder. result.offline++; } else { - // Local file, copy it. Use copy instead of move to prevent having a unstable state if - // some copies succeed and others don't. - let destFile = this.textUtils.concatenatePaths(folderPath, file.name); + // Local file, copy it. + // Use copy instead of move to prevent having a unstable state if some copies succeed and others don't. + const destFile = this.textUtils.concatenatePaths(folderPath, file.name); promises.push(this.fileProvider.copyFile(file.toURL(), destFile)); result.offline++; } @@ -406,7 +413,7 @@ export class CoreFileUploaderProvider { * @return {Promise} Promise resolved when done. */ uploadFile(uri: string, options?: CoreFileUploaderOptions, onProgress?: (event: ProgressEvent) => any, - siteId?: string) : Promise { + siteId?: string): Promise { options = options || {}; const deleteAfterUpload = options.deleteAfterUpload, @@ -423,6 +430,7 @@ export class CoreFileUploaderProvider { this.fileProvider.removeExternalFile(uri); }, 500); } + return result; }); } @@ -437,8 +445,8 @@ export class CoreFileUploaderProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the itemId. */ - uploadOrReuploadFile(file: any, itemId?: number, component?: string, componentId?: string|number, - siteId?: string) : Promise { + uploadOrReuploadFile(file: any, itemId?: number, component?: string, componentId?: string | number, + siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); let promise, @@ -448,9 +456,9 @@ export class CoreFileUploaderProvider { // It's an online file. We need to download it and re-upload it. fileName = file.filename; promise = this.filepoolProvider.downloadUrl(siteId, file.url || file.fileurl, false, component, componentId, - file.timemodified, undefined, undefined, file).then((path) => { - return this.fileProvider.getExternalFile(path); - }); + file.timemodified, undefined, undefined, file).then((path) => { + return this.fileProvider.getExternalFile(path); + }); } else { // Local file, we already have the file entry. fileName = file.name; @@ -459,7 +467,8 @@ export class CoreFileUploaderProvider { return promise.then((fileEntry) => { // Now upload the file. - let options = this.getFileUploadOptions(fileEntry.toURL(), fileName, fileEntry.type, true, 'draft', itemId); + const options = this.getFileUploadOptions(fileEntry.toURL(), fileName, fileEntry.type, true, 'draft', itemId); + return this.uploadFile(fileEntry.toURL(), options, undefined, siteId).then((result) => { return result.itemid; }); @@ -477,8 +486,8 @@ export class CoreFileUploaderProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the itemId. */ - uploadOrReuploadFiles(files: any[], component?: string, componentId?: string|number, siteId?: string) : Promise { - siteId = siteId || this.sitesProvider.getCurrentSiteId(); + uploadOrReuploadFiles(files: any[], component?: string, componentId?: string | number, siteId?: string): Promise { + siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (!files || !files.length) { // Return fake draft ID. @@ -487,10 +496,10 @@ export class CoreFileUploaderProvider { // Upload only the first file first to get a draft id. return this.uploadOrReuploadFile(files[0], 0, component, componentId, siteId).then((itemId) => { - let promises = []; + const promises = []; for (let i = 1; i < files.length; i++) { - let file = files[i]; + const file = files[i]; promises.push(this.uploadOrReuploadFile(file, itemId, component, componentId, siteId)); } diff --git a/src/core/fileuploader/providers/helper.ts b/src/core/fileuploader/providers/helper.ts index 3573eba52..7c56f9937 100644 --- a/src/core/fileuploader/providers/helper.ts +++ b/src/core/fileuploader/providers/helper.ts @@ -55,7 +55,7 @@ export class CoreFileUploaderHelperProvider { * @return {Promise} Promise resolved when the user confirms or if there's no need to show a modal. */ confirmUploadFile(size: number, alwaysConfirm?: boolean, allowOffline?: boolean, wifiThreshold?: number, - limitedThreshold?: number) : Promise { + limitedThreshold?: number): Promise { if (size == 0) { return Promise.resolve(); } @@ -70,9 +70,10 @@ export class CoreFileUploaderHelperProvider { if (size < 0) { return this.domUtils.showConfirm(this.translate.instant('core.fileuploader.confirmuploadunknownsize')); - } else if (size >= wifiThreshold || (this.appProvider.isNetworkAccessLimited() && size >= limitedThreshold)) { - let readableSize = this.textUtils.bytesToSize(size, 2); - return this.domUtils.showConfirm(this.translate.instant('core.fileuploader.confirmuploadfile', {size: readableSize})); + } else if (size >= wifiThreshold || (this.appProvider.isNetworkAccessLimited() && size >= limitedThreshold)) { + const readableSize = this.textUtils.bytesToSize(size, 2); + + return this.domUtils.showConfirm(this.translate.instant('core.fileuploader.confirmuploadfile', { size: readableSize })); } else if (alwaysConfirm) { return this.domUtils.showConfirm(this.translate.instant('core.areyousure')); } else { @@ -88,11 +89,11 @@ export class CoreFileUploaderHelperProvider { * @param {string} [name] Name to use when uploading the file. If not defined, use the file's name. * @return {Promise} Promise resolved when the file is uploaded. */ - copyAndUploadFile(file: any, upload?: boolean, name?: string) : Promise { + copyAndUploadFile(file: any, upload?: boolean, name?: string): Promise { name = name || file.name; - let modal = this.domUtils.showModalLoading('core.fileuploader.readingfile', true), - fileData; + const modal = this.domUtils.showModalLoading('core.fileuploader.readingfile', true); + let fileData; // We have the data of the file to be uploaded, but not its URL (needed). Create a copy of the file to upload it. return this.fileProvider.readFileData(file, CoreFileProvider.FORMATARRAYBUFFER).then((data) => { @@ -101,12 +102,13 @@ export class CoreFileUploaderHelperProvider { // Get unique name for the copy. return this.fileProvider.getUniqueNameInFolder(CoreFileProvider.TMPFOLDER, name); }).then((newName) => { - let filePath = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); + const filePath = this.textUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); return this.fileProvider.writeFile(filePath, fileData); }).catch((error) => { this.logger.error('Error reading file to upload.', error); modal.dismiss(); + return Promise.reject(this.translate.instant('core.fileuploader.errorreadingfile')); }).then((fileEntry) => { modal.dismiss(); @@ -129,7 +131,7 @@ export class CoreFileUploaderHelperProvider { * @param {string} [defaultExt] Defaut extension to use if the file doesn't have any. * @return {Promise} Promise resolved with the copied file. */ - protected copyToTmpFolder(path: string, shouldDelete: boolean, maxSize?: number, defaultExt?: string) : Promise { + protected copyToTmpFolder(path: string, shouldDelete: boolean, maxSize?: number, defaultExt?: string): Promise { let fileName = this.fileProvider.getFileAndDirectoryFromPath(path).name, promise, fileTooLarge; @@ -178,20 +180,23 @@ export class CoreFileUploaderHelperProvider { * @param {string} fileName Name of the file. * @return {Promise} Rejected promise. */ - protected errorMaxBytes(maxSize: number, fileName: string) : Promise { - let errorMessage = this.translate.instant('core.fileuploader.maxbytesfile', {$a: { - file: fileName, - size: this.textUtils.bytesToSize(maxSize, 2) - }}); + protected errorMaxBytes(maxSize: number, fileName: string): Promise { + const errorMessage = this.translate.instant('core.fileuploader.maxbytesfile', { + $a: { + file: fileName, + size: this.textUtils.bytesToSize(maxSize, 2) + } + }); this.domUtils.showErrorModal(errorMessage); + return Promise.reject(null); } /** * Function called when the file picker is closed. */ - filePickerClosed() : void { + filePickerClosed(): void { if (this.filePickerDeferred) { this.filePickerDeferred.reject(); this.filePickerDeferred = undefined; @@ -207,7 +212,7 @@ export class CoreFileUploaderHelperProvider { * * @param {any} result Result of the upload process. */ - fileUploaded(result: any) : void { + fileUploaded(result: any): void { if (this.filePickerDeferred) { this.filePickerDeferred.resolve(result); this.filePickerDeferred = undefined; @@ -227,7 +232,7 @@ export class CoreFileUploaderHelperProvider { * @return {Promise} Promise resolved when a file is uploaded, rejected if file picker is closed without a file uploaded. * The resolve value is the response of the upload request. */ - selectAndUploadFile(maxSize?: number, title?: string, mimetypes?: string[]) : Promise { + selectAndUploadFile(maxSize?: number, title?: string, mimetypes?: string[]): Promise { return this.selectFileWithPicker(maxSize, false, title, mimetypes, true); } @@ -257,12 +262,12 @@ export class CoreFileUploaderHelperProvider { * @return {Promise} Promise resolved when a file is selected/uploaded, rejected if file picker is closed. */ protected selectFileWithPicker(maxSize?: number, allowOffline?: boolean, title?: string, mimetypes?: string[], - upload?: boolean) : Promise { + upload?: boolean): Promise { // Create the cancel button and get the handlers to upload the file. - let buttons: any[] = [{ + const buttons: any[] = [{ text: this.translate.instant('core.cancel'), role: 'cancel', - handler: () => { + handler: (): void => { // User cancelled the action sheet. this.filePickerClosed(); } @@ -282,7 +287,7 @@ export class CoreFileUploaderHelperProvider { text: this.translate.instant(handler.title), icon: handler.icon, cssClass: handler.class, - handler: () => { + handler: (): boolean => { if (!handler.action) { // Nothing to do. return false; @@ -291,6 +296,7 @@ export class CoreFileUploaderHelperProvider { if (!allowOffline && !this.appProvider.isOnline()) { // Not allowed, show error. this.domUtils.showErrorModal('core.fileuploader.errormustbeonlinetoupload', true); + return false; } @@ -358,7 +364,7 @@ export class CoreFileUploaderHelperProvider { * @param {string} [siteId] Id of the site to upload the file to. If not defined, use current site. * @return {Promise} Promise resolved when the file is uploaded. */ - showConfirmAndUploadInSite(fileEntry: any, deleteAfterUpload?: boolean, siteId?: string) : Promise { + showConfirmAndUploadInSite(fileEntry: any, deleteAfterUpload?: boolean, siteId?: string): Promise { return this.fileProvider.getFileObjectFromFileEntry(fileEntry).then((file) => { return this.confirmUploadFile(file.size).then(() => { return this.uploadGenericFile(fileEntry.toURL(), file.name, file.type, deleteAfterUpload, siteId).then(() => { @@ -368,10 +374,12 @@ export class CoreFileUploaderHelperProvider { if (err) { this.domUtils.showErrorModal(err); } + return Promise.reject(null); }); }, () => { this.domUtils.showErrorModal('core.fileuploader.errorreadingfile', true); + return Promise.reject(null); }); } @@ -383,7 +391,7 @@ export class CoreFileUploaderHelperProvider { * @param {string} defaultMessage Key of the default message to show. * @return {Promise} Rejected promise. If it doesn't have an error message it means it was cancelled. */ - protected treatCaptureError(error: any, defaultMessage: string) : Promise { + protected treatCaptureError(error: any, defaultMessage: string): Promise { // Cancelled or error. If cancelled, error is an object with code = 3. if (error) { if (typeof error === 'string') { @@ -398,12 +406,14 @@ export class CoreFileUploaderHelperProvider { if (error.code != 3) { // Error, not cancelled. this.logger.error('Error while recording audio/video', error); + return Promise.reject(this.translate.instant(defaultMessage)); } else { this.logger.debug('Cancelled'); } } } + return Promise.reject(null); } @@ -414,12 +424,13 @@ export class CoreFileUploaderHelperProvider { * @param {string} defaultMessage Key of the default message to show. * @return {Promise} Rejected promise. If it doesn't have an error message it means it was cancelled. */ - protected treatImageError(error: string, defaultMessage: string) : Promise { + protected treatImageError(error: string, defaultMessage: string): Promise { // Cancelled or error. if (error) { if (typeof error == 'string') { if (error.toLowerCase().indexOf('error') > -1 || error.toLowerCase().indexOf('unable') > -1) { this.logger.error('Error getting image: ' + error); + return Promise.reject(error); } else { // User cancelled. @@ -429,6 +440,7 @@ export class CoreFileUploaderHelperProvider { return Promise.reject(this.translate.instant(defaultMessage)); } } + return Promise.reject(null); } @@ -441,16 +453,16 @@ export class CoreFileUploaderHelperProvider { * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. * @return {Promise} Promise resolved when done. */ - uploadAudioOrVideo(isAudio: boolean, maxSize: number, upload?: boolean, mimetypes?: string[]) : Promise { + uploadAudioOrVideo(isAudio: boolean, maxSize: number, upload?: boolean, mimetypes?: string[]): Promise { this.logger.debug('Trying to record a video file'); - const options = {limit: 1, mimetypes: mimetypes}, + const options = { limit: 1, mimetypes: mimetypes }, promise = isAudio ? this.mediaCapture.captureAudio(options) : this.mediaCapture.captureVideo(options); // The mimetypes param is only for desktop apps, the Cordova plugin doesn't support it. return promise.then((medias) => { // We used limit 1, we only want 1 media. - let media: MediaFile = medias[0], + const media: MediaFile = medias[0], path = media.fullPath, error = this.fileUploaderProvider.isInvalidMimetype(mimetypes, path); // Verify that the mimetype is supported. @@ -466,6 +478,7 @@ export class CoreFileUploaderHelperProvider { } }, (error) => { const defaultError = isAudio ? 'core.fileuploader.errorcapturingaudio' : 'core.fileuploader.errorcapturingvideo'; + return this.treatCaptureError(error, defaultError); }); } @@ -481,25 +494,25 @@ export class CoreFileUploaderHelperProvider { * @param {string} [siteId] Id of the site to upload the file to. If not defined, use current site. * @return {Promise} Promise resolved when the file is uploaded. */ - uploadGenericFile(uri: string, name: string, type: string, deleteAfterUpload?: boolean, siteId?: string) : Promise { - let options = this.fileUploaderProvider.getFileUploadOptions(uri, name, type, deleteAfterUpload); + uploadGenericFile(uri: string, name: string, type: string, deleteAfterUpload?: boolean, siteId?: string): Promise { + const options = this.fileUploaderProvider.getFileUploadOptions(uri, name, type, deleteAfterUpload); + return this.uploadFile(uri, -1, false, options, siteId); } /** * Convenient helper for the user to upload an image, either from the album or taking it with the camera. * - * @param {Boolean} fromAlbum True if the image should be selected from album, false if it should be taken with camera. - * @param {Number} maxSize Max size of the upload. -1 for no max size. - * @param {Boolean} upload True if the image should be uploaded, false to return the picked file. - * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. - * @return {Promise} The reject contains the error message, if there is no error message - * then we can consider that this is a silent fail. + * @param {boolean} fromAlbum True if the image should be selected from album, false if it should be taken with camera. + * @param {number} maxSize Max size of the upload. -1 for no max size. + * @param {boolean} [upload] True if the file should be uploaded, false to return the picked file. + * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. + * @return {Promise} Promise resolved when done. */ - uploadImage(fromAlbum, maxSize, upload, mimetypes) { + uploadImage(fromAlbum: boolean, maxSize: number, upload?: boolean, mimetypes?: string[]): Promise { this.logger.debug('Trying to capture an image with camera'); - let options: CameraOptions = { + const options: CameraOptions = { quality: 50, destinationType: this.camera.DestinationType.FILE_URI, correctOrientation: true @@ -536,7 +549,7 @@ export class CoreFileUploaderHelperProvider { } return this.camera.getPicture(options).then((path) => { - let error = this.fileUploaderProvider.isInvalidMimetype(mimetypes, path); // Verify that the mimetype is supported. + const error = this.fileUploaderProvider.isInvalidMimetype(mimetypes, path); // Verify that the mimetype is supported. if (error) { return Promise.reject(error); } @@ -548,7 +561,8 @@ export class CoreFileUploaderHelperProvider { return this.copyToTmpFolder(path, !fromAlbum, maxSize, 'jpg'); } }, (error) => { - let defaultError = fromAlbum ? 'core.fileuploader.errorgettingimagealbum' : 'core.fileuploader.errorcapturingimage'; + const defaultError = fromAlbum ? 'core.fileuploader.errorgettingimagealbum' : 'core.fileuploader.errorcapturingimage'; + return this.treatImageError(error, defaultError); }); } @@ -565,13 +579,14 @@ export class CoreFileUploaderHelperProvider { * @return {Promise} Promise resolved when done. */ uploadFileEntry(fileEntry: any, deleteAfter: boolean, maxSize?: number, upload?: boolean, allowOffline?: boolean, - name?: string) : Promise { + name?: string): Promise { return this.fileProvider.getFileObjectFromFileEntry(fileEntry).then((file) => { return this.uploadFileObject(file, maxSize, upload, allowOffline, name).then((result) => { if (deleteAfter) { // We have uploaded and deleted a copy of the file. Now delete the original one. this.fileProvider.removeFileByFileEntry(fileEntry); } + return result; }); }); @@ -587,7 +602,7 @@ export class CoreFileUploaderHelperProvider { * @param {string} [name] Name to use when uploading the file. If not defined, use the file's name. * @return {Promise} Promise resolved when done. */ - uploadFileObject(file: any, maxSize?: number, upload?: boolean, allowOffline?: boolean, name?: string) : Promise { + uploadFileObject(file: any, maxSize?: number, upload?: boolean, allowOffline?: boolean, name?: string): Promise { if (maxSize != -1 && file.size > maxSize) { return this.errorMaxBytes(maxSize, file.name); } @@ -611,12 +626,10 @@ export class CoreFileUploaderHelperProvider { protected uploadFile(path: string, maxSize: number, checkSize: boolean, options: CoreFileUploaderOptions, siteId?: string) : Promise { - let errorStr = this.translate.instant('core.error'), + const errorStr = this.translate.instant('core.error'), retryStr = this.translate.instant('core.retry'), uploadingStr = this.translate.instant('core.fileuploader.uploading'), - promise, - file, - errorUploading = (error) => { + errorUploading = (error): Promise => { // Allow the user to retry. return this.domUtils.showConfirm(error, errorStr, retryStr).then(() => { // Try again. @@ -626,10 +639,14 @@ export class CoreFileUploaderHelperProvider { if (options.deleteAfterUpload) { this.fileProvider.removeExternalFile(path); } + return Promise.reject(null); }); }; + let promise, + file; + if (!this.appProvider.isOnline()) { return errorUploading(this.translate.instant('core.fileuploader.errormustbeonlinetoupload')); } @@ -639,6 +656,7 @@ export class CoreFileUploaderHelperProvider { promise = this.fileProvider.getExternalFile(path).then((fileEntry) => { return this.fileProvider.getFileObjectFromFileEntry(fileEntry).then((f) => { file = f; + return file.size; }); }).catch(() => { @@ -658,14 +676,14 @@ export class CoreFileUploaderHelperProvider { } }).then(() => { // File isn't too large and user confirmed, let's upload. - let modal = this.domUtils.showModalLoading(uploadingStr); + const modal = this.domUtils.showModalLoading(uploadingStr); return this.fileUploaderProvider.uploadFile(path, options, (progress: ProgressEvent) => { // Progress uploading. if (progress && progress.lengthComputable) { - let perc = Math.min((progress.loaded / progress.total) * 100, 100); + const perc = Math.min((progress.loaded / progress.total) * 100, 100); if (perc >= 0) { - modal.setContent(this.translate.instant('core.fileuploader.uploadingperc', {$a: perc.toFixed(1)})); + modal.setContent(this.translate.instant('core.fileuploader.uploadingperc', { $a: perc.toFixed(1) })); if (modal._cmp && modal._cmp.changeDetectorRef) { // Force a change detection, otherwise the content is not updated. modal._cmp.changeDetectorRef.detectChanges(); @@ -679,6 +697,7 @@ export class CoreFileUploaderHelperProvider { if (typeof error != 'string') { error = this.translate.instant('core.fileuploader.errorwhileuploading'); } + return errorUploading(error); }).finally(() => { modal.dismiss(); diff --git a/src/core/fileuploader/providers/video-handler.ts b/src/core/fileuploader/providers/video-handler.ts index d90cca555..8d66019e0 100644 --- a/src/core/fileuploader/providers/video-handler.ts +++ b/src/core/fileuploader/providers/video-handler.ts @@ -27,15 +27,15 @@ export class CoreFileUploaderVideoHandler implements CoreFileUploaderHandler { priority = 1400; constructor(private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, private platform: Platform, - private uploaderHelper: CoreFileUploaderHelperProvider) {} + private uploaderHelper: CoreFileUploaderHelperProvider) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled(): boolean|Promise { - return this.appProvider.isMobile() || (this.appProvider.canGetUserMedia() && this.appProvider.canRecordMedia()); + isEnabled(): boolean | Promise { + return this.appProvider.isMobile() || (this.appProvider.canGetUserMedia() && this.appProvider.canRecordMedia()); } /** @@ -44,19 +44,20 @@ export class CoreFileUploaderVideoHandler implements CoreFileUploaderHandler { * @param {string[]} [mimetypes] List of mimetypes. * @return {string[]} Supported mimetypes. */ - getSupportedMimetypes(mimetypes: string[]) : string[] { + getSupportedMimetypes(mimetypes: string[]): string[] { if (this.platform.is('ios')) { - // iOS records as MOV. + // In iOS it's recorded as MOV. return this.utils.filterByRegexp(mimetypes, /^video\/quicktime$/); } else if (this.platform.is('android')) { // In Android we don't know the format the video will be recorded, so accept any video mimetype. return this.utils.filterByRegexp(mimetypes, /^video\//); } else { // In desktop, support video formats that are supported by MediaRecorder. - let mediaRecorder = (window).MediaRecorder; + const mediaRecorder = ( window).MediaRecorder; if (mediaRecorder) { - return mimetypes.filter(function(type) { - let matches = type.match(/^video\//); + return mimetypes.filter((type) => { + const matches = type.match(/^video\//); + return matches && matches.length && mediaRecorder.isTypeSupported(type); }); } @@ -70,12 +71,12 @@ export class CoreFileUploaderVideoHandler implements CoreFileUploaderHandler { * * @return {CoreFileUploaderHandlerData} Data. */ - getData() : CoreFileUploaderHandlerData { + getData(): CoreFileUploaderHandlerData { return { title: 'core.fileuploader.video', class: 'core-fileuploader-video-handler', icon: 'videocam', - action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]) => { + action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]): Promise => { return this.uploaderHelper.uploadAudioOrVideo(false, maxSize, upload, mimetypes).then((result) => { return { treated: true, diff --git a/src/core/login/pages/credentials/credentials.ts b/src/core/login/pages/credentials/credentials.ts index 9ba824b40..790ea8df5 100644 --- a/src/core/login/pages/credentials/credentials.ts +++ b/src/core/login/pages/credentials/credentials.ts @@ -28,7 +28,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to enter the user credentials. */ -@IonicPage({segment: "core-login-credentials"}) +@IonicPage({ segment: 'core-login-credentials' }) @Component({ selector: 'page-core-login-credentials', templateUrl: 'credentials.html', @@ -62,15 +62,15 @@ export class CoreLoginCredentialsPage { this.urlToOpen = navParams.get('urlToOpen'); this.credForm = fb.group({ - 'username': [navParams.get('username') || '', Validators.required], - 'password': ['', Validators.required] + username: [navParams.get('username') || '', Validators.required], + password: ['', Validators.required] }); } /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.treatSiteConfig(); if (this.loginHelper.isFixedUrlSet()) { @@ -85,9 +85,9 @@ export class CoreLoginCredentialsPage { /** * View left. */ - ionViewDidLeave() { - this.viewLeft = true; - this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_UNCHECKED, {config: this.siteConfig}, this.siteId); + ionViewDidLeave(): void { + this.viewLeft = true; + this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_UNCHECKED, { config: this.siteConfig }, this.siteId); } /** @@ -95,12 +95,14 @@ export class CoreLoginCredentialsPage { * This should be used only if a fixed URL is set, otherwise this check is already performed in CoreLoginSitePage. * * @param {string} siteUrl Site URL to check. + * @return {Promise} Promise resolved when done. */ - protected checkSite(siteUrl: string) { + protected checkSite(siteUrl: string): Promise { this.pageLoaded = false; // If the site is configured with http:// protocol we force that one, otherwise we use default mode. const protocol = siteUrl.indexOf('http://') === 0 ? 'http://' : undefined; + return this.sitesProvider.checkSite(siteUrl, protocol).then((result) => { this.siteChecked = true; @@ -120,7 +122,7 @@ export class CoreLoginCredentialsPage { // Check that there's no SSO authentication ongoing and the view hasn't changed. if (!this.appProvider.isSSOAuthenticationOngoing() && !this.viewLeft) { this.loginHelper.confirmAndOpenBrowserForSSOLogin( - result.siteUrl, result.code, result.service, result.config && result.config.launchurl); + result.siteUrl, result.code, result.service, result.config && result.config.launchurl); } } else { this.isBrowserSSO = false; @@ -136,7 +138,7 @@ export class CoreLoginCredentialsPage { /** * Treat the site configuration (if it exists). */ - protected treatSiteConfig() : void { + protected treatSiteConfig(): void { if (this.siteConfig) { this.siteName = this.siteConfig.sitename; this.logoUrl = this.siteConfig.logourl || this.siteConfig.compactlogourl; @@ -146,7 +148,7 @@ export class CoreLoginCredentialsPage { if (!this.eventThrown && !this.viewLeft) { this.eventThrown = true; - this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, {config: this.siteConfig}); + this.eventsProvider.trigger(CoreEventsProvider.LOGIN_SITE_CHECKED, { config: this.siteConfig }); } } else { this.siteName = null; @@ -160,11 +162,11 @@ export class CoreLoginCredentialsPage { /** * Tries to authenticate the user. */ - login() : void { + login(): void { this.appProvider.closeKeyboard(); // Get input data. - let siteUrl = this.siteUrl, + const siteUrl = this.siteUrl, username = this.credForm.value.username, password = this.credForm.value.password; @@ -176,24 +178,28 @@ export class CoreLoginCredentialsPage { return this.login(); } }); + return; } if (!username) { this.domUtils.showErrorModal('core.login.usernamerequired', true); + return; } if (!password) { this.domUtils.showErrorModal('core.login.passwordrequired', true); + return; } if (!this.appProvider.isOnline()) { this.domUtils.showErrorModal('core.networkerrormsg', true); + return; } - let modal = this.domUtils.showModalLoading(); + const modal = this.domUtils.showModalLoading(); // Start the authentication process. this.sitesProvider.getUserToken(siteUrl, username, password).then((data) => { @@ -229,15 +235,16 @@ export class CoreLoginCredentialsPage { /** * Forgotten password button clicked. */ - forgottenPassword() : void { + forgottenPassword(): void { if (this.siteConfig && this.siteConfig.forgottenpasswordurl) { // URL set, open it. this.utils.openInApp(this.siteConfig.forgottenpasswordurl); + return; } // Check if password reset can be done through the app. - let modal = this.domUtils.showModalLoading(); + const modal = this.domUtils.showModalLoading(); this.loginHelper.canRequestPasswordReset(this.siteUrl).then((canReset) => { if (canReset) { this.navCtrl.push('CoreLoginForgottenPasswordPage', { @@ -256,7 +263,7 @@ export class CoreLoginCredentialsPage { * * @param {any} provider The provider that was clicked. */ - oauthClicked(provider) : void { + oauthClicked(provider: any): void { if (!this.loginHelper.openBrowserForOAuthLogin(this.siteUrl, provider, this.siteConfig.launchurl)) { this.domUtils.showErrorModal('Invalid data.'); } @@ -265,7 +272,7 @@ export class CoreLoginCredentialsPage { /** * Signup button was clicked. */ - signup() : void { - this.navCtrl.push('CoreLoginEmailSignupPage', {siteUrl: this.siteUrl}); + signup(): void { + this.navCtrl.push('CoreLoginEmailSignupPage', { siteUrl: this.siteUrl }); } } diff --git a/src/core/login/pages/email-signup/email-signup.ts b/src/core/login/pages/email-signup/email-signup.ts index e8853c67b..6b3ed9172 100644 --- a/src/core/login/pages/email-signup/email-signup.ts +++ b/src/core/login/pages/email-signup/email-signup.ts @@ -27,7 +27,7 @@ import { CoreUserProfileFieldDelegate } from '../../../user/providers/user-profi /** * Page to signup using email. */ -@IonicPage({segment: "core-login-email-signup"}) +@IonicPage({ segment: 'core-login-email-signup' }) @Component({ selector: 'page-core-login-email-signup', templateUrl: 'email-signup.html', @@ -43,7 +43,7 @@ export class CoreLoginEmailSignupPage { countries: any; countriesKeys: any[]; categories: any[]; - settingsLoaded: boolean = false; + settingsLoaded = false; // Validation errors. usernameErrors: any; @@ -56,16 +56,16 @@ export class CoreLoginEmailSignupPage { constructor(private navCtrl: NavController, navParams: NavParams, private fb: FormBuilder, private wsProvider: CoreWSProvider, private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, private domUtils: CoreDomUtilsProvider, private translate: TranslateService, private utils: CoreUtilsProvider, - private textUtils: CoreTextUtilsProvider, private userProfileFieldDelegate :CoreUserProfileFieldDelegate) { + private textUtils: CoreTextUtilsProvider, private userProfileFieldDelegate: CoreUserProfileFieldDelegate) { this.siteUrl = navParams.get('siteUrl'); // Create the signupForm with the basic controls. More controls will be added later. this.signupForm = this.fb.group({ - 'username': ['', Validators.required], - 'password': ['', Validators.required], - 'email': ['', Validators.compose([Validators.required, Validators.email])], - 'email2': ['', Validators.compose([Validators.required, Validators.email])] + username: ['', Validators.required], + password: ['', Validators.required], + email: ['', Validators.compose([Validators.required, Validators.email])], + email2: ['', Validators.compose([Validators.required, Validators.email])] }); // Setup validation errors. @@ -79,7 +79,7 @@ export class CoreLoginEmailSignupPage { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { // Fetch the data. this.fetchData().finally(() => { this.settingsLoaded = true; @@ -89,12 +89,12 @@ export class CoreLoginEmailSignupPage { /** * Complete the FormGroup using the settings received from server. */ - protected completeFormGroup() { + protected completeFormGroup(): void { this.signupForm.addControl('city', this.fb.control(this.settings.defaultcity || '')); this.signupForm.addControl('country', this.fb.control(this.settings.country || '')); // Add the name fields. - for (let i in this.settings.namefields) { + for (const i in this.settings.namefields) { this.signupForm.addControl(this.settings.namefields[i], this.fb.control('', Validators.required)); } @@ -110,7 +110,7 @@ export class CoreLoginEmailSignupPage { /** * Fetch the required data from the server- */ - protected fetchData() : Promise { + protected fetchData(): Promise { // Get site config. return this.sitesProvider.getSitePublicConfig(this.siteUrl).then((config) => { this.siteConfig = config; @@ -128,8 +128,8 @@ export class CoreLoginEmailSignupPage { /** * Get signup settings from server. */ - protected getSignupSettings() : Promise { - return this.wsProvider.callAjax('auth_email_get_signup_settings', {}, {siteUrl: this.siteUrl}).then((settings) => { + protected getSignupSettings(): Promise { + return this.wsProvider.callAjax('auth_email_get_signup_settings', {}, { siteUrl: this.siteUrl }).then((settings) => { this.settings = settings; this.categories = this.loginHelper.formatProfileFieldsForSignup(settings.profilefields); @@ -155,16 +155,19 @@ export class CoreLoginEmailSignupPage { * Treat the site config, checking if it's valid and extracting the data we're interested in. * * @param {any} siteConfig Site config to treat. + * @return {boolean} True if success. */ - protected treatSiteConfig(siteConfig) { + protected treatSiteConfig(siteConfig: any): boolean { if (siteConfig && siteConfig.registerauth == 'email' && !this.loginHelper.isEmailSignupDisabled(siteConfig)) { this.siteName = siteConfig.sitename; this.authInstructions = siteConfig.authinstructions; + return true; } else { this.domUtils.showErrorModal( - this.translate.instant('core.login.signupplugindisabled', {$a: this.translate.instant('core.login.auth_email')})); + this.translate.instant('core.login.signupplugindisabled', { $a: this.translate.instant('core.login.auth_email') })); this.navCtrl.pop(); + return false; } } @@ -174,7 +177,7 @@ export class CoreLoginEmailSignupPage { * * @param {any} refresher Refresher. */ - refreshSettings(refresher: any) : void { + refreshSettings(refresher: any): void { this.fetchData().finally(() => { refresher.complete(); }); @@ -185,8 +188,8 @@ export class CoreLoginEmailSignupPage { * * @param {boolean} ignoreError Whether to ignore errors. */ - requestCaptcha(ignoreError?: boolean) : void { - let modal = this.domUtils.showModalLoading(); + requestCaptcha(ignoreError?: boolean): void { + const modal = this.domUtils.showModalLoading(); this.getSignupSettings().catch((err) => { if (!ignoreError && err) { this.domUtils.showErrorModal(err); @@ -199,7 +202,7 @@ export class CoreLoginEmailSignupPage { /** * Create account. */ - create() : void { + create(): void { if (!this.signupForm.valid) { // Form not valid. Scroll to the first element with errors. if (!this.domUtils.scrollToInputError(this.content, document.body)) { @@ -207,7 +210,7 @@ export class CoreLoginEmailSignupPage { this.domUtils.showErrorModal('core.errorinvalidform', true); } } else { - let params: any = { + const params: any = { username: this.signupForm.value.username.trim().toLowerCase(), password: this.signupForm.value.password, firstname: this.textUtils.cleanTags(this.signupForm.value.firstname), @@ -219,7 +222,7 @@ export class CoreLoginEmailSignupPage { modal = this.domUtils.showModalLoading('core.sending', true); if (this.siteConfig.launchurl) { - let service = this.sitesProvider.determineService(this.siteUrl); + const service = this.sitesProvider.determineService(this.siteUrl); params.redirect = this.loginHelper.prepareForSSOLogin(this.siteUrl, service, this.siteConfig.launchurl); } @@ -230,37 +233,37 @@ export class CoreLoginEmailSignupPage { // Get the data for the custom profile fields. this.userProfileFieldDelegate.getDataForFields(this.settings.profilefields, true, 'email', this.signupForm.value).then( - (fieldsData) => { - params.customprofilefields = fieldsData; + (fieldsData) => { + params.customprofilefields = fieldsData; - this.wsProvider.callAjax('auth_email_signup_user', params, {siteUrl: this.siteUrl}).then((result) => { - if (result.success) { - // Show alert and ho back. - let message = this.translate.instant('core.login.emailconfirmsent', {$a: params.email}); - this.domUtils.showAlert(this.translate.instant('core.success'), message); - this.navCtrl.pop(); - } else { - this.domUtils.showErrorModalFirstWarning(result.warnings, 'core.login.usernotaddederror', true); + this.wsProvider.callAjax('auth_email_signup_user', params, { siteUrl: this.siteUrl }).then((result) => { + if (result.success) { + // Show alert and ho back. + const message = this.translate.instant('core.login.emailconfirmsent', { $a: params.email }); + this.domUtils.showAlert(this.translate.instant('core.success'), message); + this.navCtrl.pop(); + } else { + this.domUtils.showErrorModalFirstWarning(result.warnings, 'core.login.usernotaddederror', true); - // Error sending, request another capctha since the current one is probably invalid now. - this.requestCaptcha(true); - } + // Error sending, request another capctha since the current one is probably invalid now. + this.requestCaptcha(true); + } + }); + }).catch((error) => { + this.domUtils.showErrorModalDefault(error && error.error, 'core.login.usernotaddederror', true); + + // Error sending, request another capctha since the current one is probably invalid now. + this.requestCaptcha(true); + }).finally(() => { + modal.dismiss(); }); - }).catch((error) => { - this.domUtils.showErrorModalDefault(error && error.error, 'core.login.usernotaddederror', true); - - // Error sending, request another capctha since the current one is probably invalid now. - this.requestCaptcha(true); - }).finally(() => { - modal.dismiss(); - }); } } /** * Show authentication instructions. */ - protected showAuthInstructions() { + protected showAuthInstructions(): void { this.textUtils.expandText(this.translate.instant('core.login.instructions'), this.authInstructions); } } diff --git a/src/core/login/pages/forgotten-password/forgotten-password.ts b/src/core/login/pages/forgotten-password/forgotten-password.ts index 5dc6f1daa..1c37f23ab 100644 --- a/src/core/login/pages/forgotten-password/forgotten-password.ts +++ b/src/core/login/pages/forgotten-password/forgotten-password.ts @@ -22,7 +22,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to recover a forgotten password. */ -@IonicPage({segment: "core-login-forgotten-password"}) +@IonicPage({ segment: 'core-login-forgotten-password' }) @Component({ selector: 'page-core-login-forgotten-password', templateUrl: 'forgotten-password.html', @@ -36,31 +36,32 @@ export class CoreLoginForgottenPasswordPage { this.siteUrl = navParams.get('siteUrl'); this.myForm = fb.group({ - 'field': ['username', Validators.required], - 'value': [navParams.get('username') || '', Validators.required] + field: ['username', Validators.required], + value: [navParams.get('username') || '', Validators.required] }); } /** * Request to reset the password. */ - resetPassword() : void { - let field = this.myForm.value.field, + resetPassword(): void { + const field = this.myForm.value.field, value = this.myForm.value.value; if (!value) { this.domUtils.showErrorModal('core.login.usernameoremail', true); + return; } - let modal = this.domUtils.showModalLoading('core.sending', true), + const modal = this.domUtils.showModalLoading('core.sending', true), isMail = field == 'email'; this.loginHelper.requestPasswordReset(this.siteUrl, isMail ? '' : value, isMail ? value : '').then((response) => { if (response.status == 'dataerror') { // Error in the data sent. this.showError(isMail, response.warnings); - } else if (response.status == 'emailpasswordconfirmnotsent' || response.status == 'emailpasswordconfirmnoemail') { + } else if (response.status == 'emailpasswordconfirmnotsent' || response.status == 'emailpasswordconfirmnoemail') { // Error, not found. this.domUtils.showErrorModal(response.notice); } else { @@ -73,12 +74,12 @@ export class CoreLoginForgottenPasswordPage { }).finally(() => { modal.dismiss(); }); - }; + } // Show an error from the warnings. - protected showError(isMail: boolean, warnings: any[]) : void { + protected showError(isMail: boolean, warnings: any[]): void { for (let i = 0; i < warnings.length; i++) { - let warning = warnings[i]; + const warning = warnings[i]; if ((warning.item == 'email' && isMail) || (warning.item == 'username' && !isMail)) { this.domUtils.showErrorModal(warning.message); break; diff --git a/src/core/login/pages/init/init.ts b/src/core/login/pages/init/init.ts index 7926f43f1..c5ccb35aa 100644 --- a/src/core/login/pages/init/init.ts +++ b/src/core/login/pages/init/init.ts @@ -23,7 +23,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper'; /** * Page that displays a "splash screen" while the app is being initialized. */ -@IonicPage({segment: "core-login-init"}) +@IonicPage({ segment: 'core-login-init' }) @Component({ selector: 'page-core-login-init', templateUrl: 'init.html', @@ -31,12 +31,12 @@ import { CoreLoginHelperProvider } from '../../providers/helper'; export class CoreLoginInitPage { constructor(private navCtrl: NavController, private appProvider: CoreAppProvider, private initDelegate: CoreInitDelegate, - private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider) {} + private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider) { } /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { // Wait for the app to be ready. this.initDelegate.ready().then(() => { // Check if there was a pending redirect. @@ -51,7 +51,7 @@ export class CoreLoginInitPage { // The redirect is pointing to a site, load it. return this.sitesProvider.loadSite(redirectData.siteId).then(() => { if (!this.loginHelper.isSiteLoggedOut(redirectData.page, redirectData.params)) { - this.navCtrl.setRoot(redirectData.page, redirectData.params, {animate: false}); + this.navCtrl.setRoot(redirectData.page, redirectData.params, { animate: false }); } }).catch(() => { // Site doesn't exist. @@ -59,7 +59,7 @@ export class CoreLoginInitPage { }); } else { // No site to load, just open the state. - return this.navCtrl.setRoot(redirectData.page, redirectData.params, {animate: false}); + return this.navCtrl.setRoot(redirectData.page, redirectData.params, { animate: false }); } } } @@ -71,7 +71,7 @@ export class CoreLoginInitPage { /** * Load the right page. */ - protected loadPage() : void { + protected loadPage(): void { if (this.sitesProvider.isLoggedIn()) { if (!this.loginHelper.isSiteLoggedOut()) { this.loginHelper.goToSiteInitialPage(); diff --git a/src/core/login/pages/reconnect/reconnect.ts b/src/core/login/pages/reconnect/reconnect.ts index 4873c923c..cbbabea19 100644 --- a/src/core/login/pages/reconnect/reconnect.ts +++ b/src/core/login/pages/reconnect/reconnect.ts @@ -23,7 +23,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to enter the user password to reconnect to a site. */ -@IonicPage({segment: "core-login-reconnect"}) +@IonicPage({ segment: 'core-login-reconnect' }) @Component({ selector: 'page-core-login-reconnect', templateUrl: 'reconnect.html', @@ -45,10 +45,10 @@ export class CoreLoginReconnectPage { protected siteId: string; constructor(private navCtrl: NavController, navParams: NavParams, fb: FormBuilder, private appProvider: CoreAppProvider, - private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, - private domUtils: CoreDomUtilsProvider) { + private sitesProvider: CoreSitesProvider, private loginHelper: CoreLoginHelperProvider, + private domUtils: CoreDomUtilsProvider) { - let currentSite = this.sitesProvider.getCurrentSite(); + const currentSite = this.sitesProvider.getCurrentSite(); this.infoSiteUrl = navParams.get('infoSiteUrl'); this.pageName = navParams.get('pageName'); @@ -59,14 +59,14 @@ export class CoreLoginReconnectPage { this.isLoggedOut = currentSite && currentSite.isLoggedOut(); this.credForm = fb.group({ - 'password': ['', Validators.required] + password: ['', Validators.required] }); } /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { if (this.siteConfig) { this.identityProviders = this.loginHelper.getValidIdentityProviders(this.siteConfig); } @@ -85,6 +85,7 @@ export class CoreLoginReconnectPage { // Check logoURL if user avatar is not set. if (this.site.avatar.startsWith(site.infos.siteurl + '/theme/image.php')) { this.site.avatar = false; + return site.getPublicConfig().then((config) => { this.logoUrl = config.logourl || config.compactlogourl; }); @@ -99,7 +100,7 @@ export class CoreLoginReconnectPage { /** * Cancel reconnect. */ - cancel() { + cancel(): void { this.sitesProvider.logout().finally(() => { this.navCtrl.setRoot('CoreLoginSitesPage'); }); @@ -108,25 +109,27 @@ export class CoreLoginReconnectPage { /** * Tries to authenticate the user. */ - login() : void { + login(): void { this.appProvider.closeKeyboard(); // Get input data. - let siteUrl = this.siteUrl, + const siteUrl = this.siteUrl, username = this.username, password = this.credForm.value.password; if (!password) { this.domUtils.showErrorModal('core.login.passwordrequired', true); + return; } if (!this.appProvider.isOnline()) { this.domUtils.showErrorModal('core.networkerrormsg', true); + return; } - let modal = this.domUtils.showModalLoading(); + const modal = this.domUtils.showModalLoading(); // Start the authentication process. this.sitesProvider.getUserToken(siteUrl, username, password).then((data) => { @@ -160,7 +163,7 @@ export class CoreLoginReconnectPage { * * @param {any} provider The provider that was clicked. */ - oauthClicked(provider) : void { + oauthClicked(provider: any): void { if (!this.loginHelper.openBrowserForOAuthLogin(this.siteUrl, provider, this.siteConfig.launchurl)) { this.domUtils.showErrorModal('Invalid data.'); } diff --git a/src/core/login/pages/site-error/site-error.ts b/src/core/login/pages/site-error/site-error.ts index f68e085ea..c9db8ccb5 100644 --- a/src/core/login/pages/site-error/site-error.ts +++ b/src/core/login/pages/site-error/site-error.ts @@ -18,7 +18,7 @@ import { IonicPage, ViewController, NavParams } from 'ionic-angular'; /** * Component that displays an error when trying to connect to a site. */ -@IonicPage({segment: "core-login-site-error"}) +@IonicPage({ segment: 'core-login-site-error' }) @Component({ selector: 'page-core-login-site-error', templateUrl: 'site-error.html', @@ -35,7 +35,7 @@ export class CoreLoginSiteErrorPage { /** * Close modal. */ - closeModal() : void { + closeModal(): void { this.viewCtrl.dismiss(); } -} \ No newline at end of file +} diff --git a/src/core/login/pages/site-help/site-help.ts b/src/core/login/pages/site-help/site-help.ts index a7d8132d2..ff71e891a 100644 --- a/src/core/login/pages/site-help/site-help.ts +++ b/src/core/login/pages/site-help/site-help.ts @@ -18,18 +18,18 @@ import { IonicPage, ViewController } from 'ionic-angular'; /** * Component that displays some help regarding the CoreLoginSitePage. */ -@IonicPage({segment: "core-login-site-help"}) +@IonicPage({ segment: 'core-login-site-help' }) @Component({ selector: 'page-core-login-site-help', templateUrl: 'site-help.html', }) export class CoreLoginSiteHelpPage { - constructor(private viewCtrl: ViewController) {} + constructor(private viewCtrl: ViewController) { } /** * Close help modal. */ - closeHelp() : void { + closeHelp(): void { this.viewCtrl.dismiss(); } -} \ No newline at end of file +} diff --git a/src/core/login/pages/site-policy/site-policy.ts b/src/core/login/pages/site-policy/site-policy.ts index c3c582358..c9fc76eed 100644 --- a/src/core/login/pages/site-policy/site-policy.ts +++ b/src/core/login/pages/site-policy/site-policy.ts @@ -23,7 +23,7 @@ import { CoreSite } from '../../../../classes/site'; /** * Page to accept a site policy. */ -@IonicPage({segment: "core-login-site-policy"}) +@IonicPage({ segment: 'core-login-site-policy' }) @Component({ selector: 'page-core-login-site-policy', templateUrl: 'site-policy.html', @@ -44,21 +44,23 @@ export class CoreLoginSitePolicyPage { /** * View laoded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.currentSite = this.sitesProvider.getCurrentSite(); if (!this.currentSite) { // Not logged in, stop. this.cancel(); + return; } - let currentSiteId = this.currentSite.id; + const currentSiteId = this.currentSite.id; this.siteId = this.siteId || currentSiteId; if (this.siteId != currentSiteId || !this.currentSite.wsAvailable('core_user_agree_site_policy')) { // Not current site or WS not available, stop. this.cancel(); + return; } @@ -67,8 +69,10 @@ export class CoreLoginSitePolicyPage { /** * Fetch the site policy URL. + * + * @return {Promise} Promise resolved when done. */ - protected fetchSitePolicy() { + protected fetchSitePolicy(): Promise { return this.loginHelper.getSitePolicy(this.siteId).then((sitePolicy) => { this.sitePolicy = sitePolicy; @@ -91,7 +95,7 @@ export class CoreLoginSitePolicyPage { /** * Cancel. */ - cancel() : void { + cancel(): void { this.sitesProvider.logout().catch(() => { // Ignore errors, shouldn't happen. }).then(() => { @@ -102,8 +106,8 @@ export class CoreLoginSitePolicyPage { /** * Accept the site policy. */ - accept() : void { - let modal = this.domUtils.showModalLoading('core.sending', true); + accept(): void { + const modal = this.domUtils.showModalLoading('core.sending', true); this.loginHelper.acceptSitePolicy(this.siteId).then(() => { // Success accepting, go to site initial page. // Invalidate cache since some WS don't return error if site policy is not accepted. diff --git a/src/core/login/pages/site/site.ts b/src/core/login/pages/site/site.ts index 6615edace..fd7799502 100644 --- a/src/core/login/pages/site/site.ts +++ b/src/core/login/pages/site/site.ts @@ -24,7 +24,7 @@ import { FormBuilder, FormGroup, Validators } from '@angular/forms'; /** * Page to enter or select the site URL to connect to. */ -@IonicPage({segment: "core-login-site"}) +@IonicPage({ segment: 'core-login-site' }) @Component({ selector: 'page-core-login-site', templateUrl: 'site.html', @@ -47,27 +47,29 @@ export class CoreLoginSitePage { } this.siteForm = fb.group({ - 'siteUrl': [url, Validators.required] + siteUrl: [url, Validators.required] }); } /** * Try to connect to a site. */ - connect(url: string) : void { + connect(url: string): void { this.appProvider.closeKeyboard(); if (!url) { this.domUtils.showErrorModal('core.login.siteurlrequired', true); + return; } if (!this.appProvider.isOnline()) { this.domUtils.showErrorModal('core.networkerrormsg', true); + return; } - let modal = this.domUtils.showModalLoading(), + const modal = this.domUtils.showModalLoading(), siteData = this.sitesProvider.getDemoSiteData(url); if (siteData) { @@ -95,9 +97,9 @@ export class CoreLoginSitePage { if (this.loginHelper.isSSOLoginNeeded(result.code)) { // SSO. User needs to authenticate in a browser. this.loginHelper.confirmAndOpenBrowserForSSOLogin( - result.siteUrl, result.code, result.service, result.config && result.config.launchurl); + result.siteUrl, result.code, result.service, result.config && result.config.launchurl); } else { - this.navCtrl.push('CoreLoginCredentialsPage', {siteUrl: result.siteUrl, siteConfig: result.config}); + this.navCtrl.push('CoreLoginCredentialsPage', { siteUrl: result.siteUrl, siteConfig: result.config }); } }, (error) => { this.showLoginIssue(url, error); @@ -110,8 +112,8 @@ export class CoreLoginSitePage { /** * Show a help modal. */ - showHelp() : void { - let modal = this.modalCtrl.create('CoreLoginSiteHelpPage'); + showHelp(): void { + const modal = this.modalCtrl.create('CoreLoginSiteHelpPage'); modal.present(); } @@ -121,8 +123,8 @@ export class CoreLoginSitePage { * @param {string} url The URL the user was trying to connect to. * @param {string} error Error to display. */ - protected showLoginIssue(url: string, error: string) : void { - let modal = this.modalCtrl.create('CoreLoginSiteErrorPage', {siteUrl: url, issue: error}); + protected showLoginIssue(url: string, error: string): void { + const modal = this.modalCtrl.create('CoreLoginSiteErrorPage', { siteUrl: url, issue: error }); modal.present(); } } diff --git a/src/core/login/pages/sites/sites.ts b/src/core/login/pages/sites/sites.ts index 3ae4d56cd..bbc36bb8c 100644 --- a/src/core/login/pages/sites/sites.ts +++ b/src/core/login/pages/sites/sites.ts @@ -24,7 +24,7 @@ import { CoreLoginHelperProvider } from '../../providers/helper'; /** * Page that displays the list of stored sites. */ -@IonicPage({segment: "core-login-sites"}) +@IonicPage({ segment: 'core-login-sites' }) @Component({ selector: 'page-core-login-sites', templateUrl: 'sites.html', @@ -43,18 +43,14 @@ export class CoreLoginSitesPage { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.sitesProvider.getSites().then((sites) => { // Remove protocol from the url to show more url text. sites = sites.map((site) => { site.siteUrl = site.siteUrl.replace(/^https?:\/\//, ''); site.badge = 10; - // @todo: Implement it once push notifications addon is implemented. - // if ($mmaPushNotifications) { - // $mmaPushNotifications.getSiteCounter(site.id).then(function(number) { - // site.badge = number; - // }); - // } + // @todo: Implement it once push notifications addon is implemented: $mmaPushNotifications.getSiteCounter(site.id) + return site; }); @@ -62,8 +58,8 @@ export class CoreLoginSitesPage { this.sites = sites.sort((a, b) => { // First compare by site url without the protocol. let compareA = a.siteUrl.toLowerCase(), - compareB = b.siteUrl.toLowerCase(), - compare = compareA.localeCompare(compareB); + compareB = b.siteUrl.toLowerCase(); + const compare = compareA.localeCompare(compareB); if (compare !== 0) { return compare; @@ -72,6 +68,7 @@ export class CoreLoginSitesPage { // If site url is the same, use fullname instead. compareA = a.fullName.toLowerCase().trim(); compareB = b.fullName.toLowerCase().trim(); + return compareA.localeCompare(compareB); }); @@ -84,7 +81,7 @@ export class CoreLoginSitesPage { /** * Go to the page to add a site. */ - add() : void { + add(): void { this.loginHelper.goToAddSite(false); } @@ -94,14 +91,14 @@ export class CoreLoginSitesPage { * @param {Event} e Click event. * @param {number} index Position of the site. */ - deleteSite(e: Event, index: number) : void { + deleteSite(e: Event, index: number): void { e.stopPropagation(); - let site = this.sites[index], + const site = this.sites[index], siteName = site.siteName; this.textUtils.formatText(siteName).then((siteName) => { - this.domUtils.showConfirm(this.translate.instant('core.login.confirmdeletesite', {sitename: siteName})).then(() => { + this.domUtils.showConfirm(this.translate.instant('core.login.confirmdeletesite', { sitename: siteName })).then(() => { this.sitesProvider.deleteSite(site.id).then(() => { this.sites.splice(index, 1); this.showDelete = false; @@ -126,8 +123,8 @@ export class CoreLoginSitesPage { * * @param {string} siteId The site ID. */ - login(siteId: string) : void { - let modal = this.domUtils.showModalLoading(); + login(siteId: string): void { + const modal = this.domUtils.showModalLoading(); this.sitesProvider.loadSite(siteId).then(() => { if (!this.loginHelper.isSiteLoggedOut()) { @@ -144,7 +141,7 @@ export class CoreLoginSitesPage { /** * Toggle delete. */ - toggleDelete() : void { + toggleDelete(): void { this.showDelete = !this.showDelete; } } diff --git a/src/core/login/providers/helper.ts b/src/core/login/providers/helper.ts index 1706f28cc..6068586ad 100644 --- a/src/core/login/providers/helper.ts +++ b/src/core/login/providers/helper.ts @@ -62,8 +62,8 @@ export interface CoreLoginSSOData { * Params to page to the page. * @type {string} */ - pageParams?: any -}; + pageParams?: any; +} /** * Helper provider that provides some common features regarding authentication. @@ -79,7 +79,7 @@ export class CoreLoginHelperProvider { constructor(logger: CoreLoggerProvider, private sitesProvider: CoreSitesProvider, private domUtils: CoreDomUtilsProvider, private wsProvider: CoreWSProvider, private translate: TranslateService, private textUtils: CoreTextUtilsProvider, private eventsProvider: CoreEventsProvider, private appProvider: CoreAppProvider, private utils: CoreUtilsProvider, - private urlUtils: CoreUrlUtilsProvider,private configProvider: CoreConfigProvider, private platform: Platform, + private urlUtils: CoreUrlUtilsProvider, private configProvider: CoreConfigProvider, private platform: Platform, private initDelegate: CoreInitDelegate) { this.logger = logger.getInstance('CoreLoginHelper'); } @@ -90,15 +90,15 @@ export class CoreLoginHelperProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved if success, rejected if failure. */ - acceptSitePolicy(siteId?: string) : Promise { + acceptSitePolicy(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.write('core_user_agree_site_policy', {}).then((result) => { if (!result.status) { // Error. if (result.warnings && result.warnings.length) { // Check if there is a warning 'alreadyagreed'. - for (let i in result.warnings) { - let warning = result.warnings[i]; + for (const i in result.warnings) { + const warning = result.warnings[i]; if (warning.warningcode == 'alreadyagreed') { // Policy already agreed, treat it as a success. return; @@ -121,8 +121,8 @@ export class CoreLoginHelperProvider { * @param {string} url URL received. * @return {boolean} True if it's a SSO URL, false otherwise. */ - appLaunchedByURL(url: string) : boolean { - let ssoScheme = CoreConfigConstants.customurlscheme + '://token='; + appLaunchedByURL(url: string): boolean { + const ssoScheme = CoreConfigConstants.customurlscheme + '://token='; if (url.indexOf(ssoScheme) == -1) { return false; } @@ -150,20 +150,22 @@ export class CoreLoginHelperProvider { // Decode from base64. try { url = atob(url); - } catch(err) { + } catch (err) { // Error decoding the parameter. this.logger.error('Error decoding parameter received for login SSO'); + return false; } - let modal = this.domUtils.showModalLoading('core.login.authenticating', true), - siteData: CoreLoginSSOData; + const modal = this.domUtils.showModalLoading('core.login.authenticating', true); + let siteData: CoreLoginSSOData; // Wait for app to be ready. this.initDelegate.ready().then(() => { return this.validateBrowserSSOLogin(url); }).then((data) => { siteData = data; + return this.handleSSOLoginAuthentication(siteData.siteUrl, siteData.token, siteData.privateToken); }).then(() => { if (siteData.pageName) { @@ -190,21 +192,21 @@ export class CoreLoginHelperProvider { * @param {string} siteUrl URL of the site. * @return {Promise} Promise resolved with boolean: whether can be done through the app. */ - canRequestPasswordReset(siteUrl: string) : Promise { + canRequestPasswordReset(siteUrl: string): Promise { return this.requestPasswordReset(siteUrl).then(() => { return true; }).catch((error) => { - return error.available == 1 || error.errorcode != 'invalidrecord'; + return error.available == 1 || error.errorcode != 'invalidrecord'; }); } /** * Function called when an SSO InAppBrowser is closed or the app is resumed. Check if user needs to be logged out. */ - checkLogout() { - let navCtrl = this.appProvider.getRootNavController(); + checkLogout(): void { + const navCtrl = this.appProvider.getRootNavController(); if (!this.appProvider.isSSOAuthenticationOngoing() && this.sitesProvider.isLoggedIn() && - this.sitesProvider.getCurrentSite().isLoggedOut() && navCtrl.getActive().name == 'CoreLoginReconnectPage') { + this.sitesProvider.getCurrentSite().isLoggedOut() && navCtrl.getActive().name == 'CoreLoginReconnectPage') { // User must reauthenticate but he closed the InAppBrowser without doing so, logout him. this.sitesProvider.logout(); } @@ -219,10 +221,10 @@ export class CoreLoginHelperProvider { * @param {string} [launchUrl] The URL to open for SSO. If not defined, local_mobile launch URL will be used. * @return {Void} */ - confirmAndOpenBrowserForSSOLogin(siteUrl: string, typeOfLogin: number, service?: string, launchUrl?: string) : void { + confirmAndOpenBrowserForSSOLogin(siteUrl: string, typeOfLogin: number, service?: string, launchUrl?: string): void { // Show confirm only if it's needed. Treat "false" (string) as false to prevent typing errors. - let showConfirmation = this.shouldShowSSOConfirm(typeOfLogin), - promise; + const showConfirmation = this.shouldShowSSOConfirm(typeOfLogin); + let promise; if (showConfirmation) { promise = this.domUtils.showConfirm(this.translate.instant('core.login.logininsiterequired')); @@ -243,12 +245,12 @@ export class CoreLoginHelperProvider { * @param {any[]} profileFields Profile fields to format. * @return {any} Categories with the fields to show in each one. */ - formatProfileFieldsForSignup(profileFields: any[]) : any { + formatProfileFieldsForSignup(profileFields: any[]): any { if (!profileFields) { return []; } - let categories = {}; + const categories = {}; profileFields.forEach((field) => { if (!field.signup) { @@ -261,7 +263,7 @@ export class CoreLoginHelperProvider { id: field.categoryid, name: field.categoryname, fields: [] - } + }; } categories[field.categoryid].fields.push(field); @@ -287,8 +289,8 @@ export class CoreLoginHelperProvider { * @return {any} Object with the errors. */ getErrorMessages(requiredMsg?: string, emailMsg?: string, patternMsg?: string, urlMsg?: string, minlengthMsg?: string, - maxlengthMsg?: string, minMsg?: string, maxMsg?: string) : any { - var errors: any = {}; + maxlengthMsg?: string, minMsg?: string, maxMsg?: string): any { + const errors: any = {}; if (requiredMsg) { errors.required = errors.requiredTrue = this.translate.instant(requiredMsg); @@ -324,16 +326,16 @@ export class CoreLoginHelperProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the site policy. */ - getSitePolicy(siteId?: string) : Promise { + getSitePolicy(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { // Check if it's stored in the site config. - let sitePolicy = site.getStoredConfig('sitepolicy'); + const sitePolicy = site.getStoredConfig('sitepolicy'); if (typeof sitePolicy != 'undefined') { return sitePolicy ? sitePolicy : Promise.reject(null); } // Not in the config, try to get it using auth_email_get_signup_settings. - return this.wsProvider.callAjax('auth_email_get_signup_settings', {}, {siteUrl: site.getURL()}).then((settings) => { + return this.wsProvider.callAjax('auth_email_get_signup_settings', {}, { siteUrl: site.getURL() }).then((settings) => { return settings.sitepolicy ? settings.sitepolicy : Promise.reject(null); }); }); @@ -344,7 +346,7 @@ export class CoreLoginHelperProvider { * * @return {string|any[]} Fixed site or list of fixed sites. */ - getFixedSites() : string|any[] { + getFixedSites(): string | any[] { return CoreConfigConstants.siteurl; } @@ -354,8 +356,8 @@ export class CoreLoginHelperProvider { * @param {any} siteConfig Site's public config. * @return {any[]} Valid identity providers. */ - getValidIdentityProviders(siteConfig: any) : any[] { - let validProviders = [], + getValidIdentityProviders(siteConfig: any): any[] { + const validProviders = [], httpUrl = this.textUtils.concatenatePaths(siteConfig.wwwroot, 'auth/oauth2/'), httpsUrl = this.textUtils.concatenatePaths(siteConfig.httpswwwroot, 'auth/oauth2/'); @@ -377,23 +379,23 @@ export class CoreLoginHelperProvider { * @param {boolean} [setRoot] True to set the new page as root, false to add it to the stack. * @return {Promise} Promise resolved when done. */ - goToAddSite(setRoot?: boolean) : Promise { + goToAddSite(setRoot?: boolean): Promise { let pageName, params; if (this.isFixedUrlSet()) { // Fixed URL is set, go to credentials page. - let url = typeof CoreConfigConstants.siteurl == 'string' ? - CoreConfigConstants.siteurl : CoreConfigConstants.siteurl[0].url; + const url = typeof CoreConfigConstants.siteurl == 'string' ? + CoreConfigConstants.siteurl : CoreConfigConstants.siteurl[0].url; pageName = 'CoreLoginCredentialsPage'; - params = {siteUrl: url}; + params = { siteUrl: url }; } else { pageName = 'CoreLoginSitePage'; } if (setRoot) { - return this.appProvider.getRootNavController().setRoot(pageName, params, {animate: false}); + return this.appProvider.getRootNavController().setRoot(pageName, params, { animate: false }); } else { return this.appProvider.getRootNavController().push(pageName, params); } @@ -404,7 +406,7 @@ export class CoreLoginHelperProvider { * * @return {Promise} Promise resolved when done. */ - goToSiteInitialPage() : Promise { + goToSiteInitialPage(): Promise { return this.appProvider.getRootNavController().setRoot('CoreMainMenuPage'); } @@ -417,10 +419,10 @@ export class CoreLoginHelperProvider { * @param {string} [privateToken] User's private token. * @return {Promise} Promise resolved when the user is authenticated with the token. */ - handleSSOLoginAuthentication(siteUrl: string, token: string, privateToken?: string) : Promise { + handleSSOLoginAuthentication(siteUrl: string, token: string, privateToken?: string): Promise { if (this.sitesProvider.isLoggedIn()) { // User logged in, he is reconnecting. Retrieve username. - var info = this.sitesProvider.getCurrentSite().getInfo(); + const info = this.sitesProvider.getCurrentSite().getInfo(); if (typeof info != 'undefined' && typeof info.username != 'undefined') { return this.sitesProvider.updateSiteToken(info.siteurl, info.username, token, privateToken).then(() => { this.sitesProvider.updateSiteInfoByUrl(info.siteurl, info.username); @@ -429,6 +431,7 @@ export class CoreLoginHelperProvider { return Promise.reject(this.translate.instant('core.login.errorupdatesite')); }); } + return Promise.reject(this.translate.instant('core.login.errorupdatesite')); } else { return this.sitesProvider.newSite(siteUrl, token, privateToken); @@ -440,9 +443,9 @@ export class CoreLoginHelperProvider { * * @return {boolean} Whether there are several fixed URLs. */ - hasSeveralFixedSites() : boolean { + hasSeveralFixedSites(): boolean { return CoreConfigConstants.siteurl && Array.isArray(CoreConfigConstants.siteurl) && - CoreConfigConstants.siteurl.length > 1; + CoreConfigConstants.siteurl.length > 1; } /** @@ -450,7 +453,7 @@ export class CoreLoginHelperProvider { * * @param {string} url Loaded url. */ - inAppBrowserLoadStart(url: string) : void { + inAppBrowserLoadStart(url: string): void { // URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this. url = url.replace(/^https?:\/\//, ''); @@ -484,13 +487,14 @@ export class CoreLoginHelperProvider { * @param {any} config Site public config. * @return {boolean} Whether email signup is disabled. */ - isEmailSignupDisabled(config: any) : boolean { - let disabledFeatures = config && config.tool_mobile_disabledfeatures; + isEmailSignupDisabled(config: any): boolean { + const disabledFeatures = config && config.tool_mobile_disabledfeatures; if (!disabledFeatures) { return false; } - let regEx = new RegExp('(,|^)\\$mmLoginEmailSignup(,|$)', 'g'); + const regEx = new RegExp('(,|^)\\$mmLoginEmailSignup(,|$)', 'g'); + return !!disabledFeatures.match(regEx); } @@ -499,10 +503,11 @@ export class CoreLoginHelperProvider { * * @return {boolean} Whether there is 1 fixed URL. */ - isFixedUrlSet() : boolean { + isFixedUrlSet(): boolean { if (Array.isArray(CoreConfigConstants.siteurl)) { return CoreConfigConstants.siteurl.length == 1; } + return !!CoreConfigConstants.siteurl; } @@ -513,8 +518,8 @@ export class CoreLoginHelperProvider { * @param {any} [params] Params of the page to go once authenticated if logged out. * @return {boolean} True if user is logged out, false otherwise. */ - isSiteLoggedOut(pageName?: string, params?: any) : boolean { - let site = this.sitesProvider.getCurrentSite(); + isSiteLoggedOut(pageName?: string, params?: any): boolean { + const site = this.sitesProvider.getCurrentSite(); if (!site) { return false; } @@ -524,8 +529,10 @@ export class CoreLoginHelperProvider { pageName: pageName, params: params }, site.getId()); + return true; } + return false; } @@ -535,7 +542,7 @@ export class CoreLoginHelperProvider { * @param {number} code Code to check. * @return {boolean} True if embedded browser, false othwerise. */ - isSSOEmbeddedBrowser(code: number) : boolean { + isSSOEmbeddedBrowser(code: number): boolean { if (this.appProvider.isLinux()) { // In Linux desktop apps, always use embedded browser. return true; @@ -550,7 +557,7 @@ export class CoreLoginHelperProvider { * @param {number} code Code to check. * @return {boolean} True if SSO login is needed, false othwerise. */ - isSSOLoginNeeded(code: number) : boolean { + isSSOLoginNeeded(code: number): boolean { return code == CoreConstants.LOGIN_SSO_CODE || code == CoreConstants.LOGIN_SSO_INAPP_CODE; } @@ -561,19 +568,19 @@ export class CoreLoginHelperProvider { * @param {any} params Params to pass to the page. * @param {string} siteId Site to load. */ - protected loadSiteAndPage(page: string, params: any, siteId: string) : void { + protected loadSiteAndPage(page: string, params: any, siteId: string): void { if (siteId == CoreConstants.NO_SITE_ID) { // Page doesn't belong to a site, just load the page. this.appProvider.getRootNavController().setRoot(page, params); } else { - let modal = this.domUtils.showModalLoading(); + const modal = this.domUtils.showModalLoading(); this.sitesProvider.loadSite(siteId).then(() => { if (!this.isSiteLoggedOut(page, params)) { this.loadPageInMainMenu(page, params); } }).catch(() => { // Site doesn't exist. - this.appProvider.getRootNavController().setRoot('CoreLoginSitesPage') + this.appProvider.getRootNavController().setRoot('CoreLoginSitesPage'); }).finally(() => { modal.dismiss(); }); @@ -586,8 +593,8 @@ export class CoreLoginHelperProvider { * @param {string} page Name of the page to load. * @param {any} params Params to pass to the page. */ - protected loadPageInMainMenu(page: string, params: any) : void { - this.appProvider.getRootNavController().setRoot('CoreMainMenuPage', {redirectPage: page, redirectParams: params}) + protected loadPageInMainMenu(page: string, params: any): void { + this.appProvider.getRootNavController().setRoot('CoreMainMenuPage', { redirectPage: page, redirectParams: params }); } /** @@ -600,15 +607,15 @@ export class CoreLoginHelperProvider { * @param {any} [pageParams] Params of the state to go once authenticated. * @return {boolean} True if success, false if error. */ - openBrowserForOAuthLogin(siteUrl: string, provider: any, launchUrl?: string, pageName?: string, pageParams?: any) : boolean { + openBrowserForOAuthLogin(siteUrl: string, provider: any, launchUrl?: string, pageName?: string, pageParams?: any): boolean { launchUrl = launchUrl || siteUrl + '/admin/tool/mobile/launch.php'; - if (!provider || !provider.url) { + if (!provider || !provider.url) { return false; } - let service = this.sitesProvider.determineService(siteUrl), - loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams), + const service = this.sitesProvider.determineService(siteUrl), params = this.urlUtils.extractUrlParams(provider.url); + let loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams); if (!params.id) { return false; @@ -622,8 +629,8 @@ export class CoreLoginHelperProvider { } else { // Always open it in browser because the user might have the session stored in there. this.utils.openInBrowser(loginUrl); - if ((navigator).app) { - (navigator).app.exitApp(); + if (( navigator).app) { + ( navigator).app.exitApp(); } } @@ -641,19 +648,19 @@ export class CoreLoginHelperProvider { * @param {any} [pageParams] Params of the state to go once authenticated. */ openBrowserForSSOLogin(siteUrl: string, typeOfLogin: number, service?: string, launchUrl?: string, pageName?: string, - pageParams?: any) : void { - let loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams); + pageParams?: any): void { + const loginUrl = this.prepareForSSOLogin(siteUrl, service, launchUrl, pageName, pageParams); if (this.isSSOEmbeddedBrowser(typeOfLogin)) { - let options = { + const options = { clearsessioncache: 'yes', // Clear the session cache to allow for multiple logins. closebuttoncaption: this.translate.instant('core.login.cancel'), - } + }; this.utils.openInApp(loginUrl, options); } else { this.utils.openInBrowser(loginUrl); - if ((navigator).app) { - (navigator).app.exitApp(); + if (( navigator).app) { + ( navigator).app.exitApp(); } } } @@ -664,8 +671,8 @@ export class CoreLoginHelperProvider { * @param {string} siteUrl Site URL to construct change password URL. * @param {string} error Error message. */ - openChangePassword(siteUrl: string, error: string) : void { - let alert = this.domUtils.showAlert(this.translate.instant('core.notice'), error, undefined, 3000); + openChangePassword(siteUrl: string, error: string): void { + const alert = this.domUtils.showAlert(this.translate.instant('core.notice'), error, undefined, 3000); alert.onDidDismiss(() => { this.utils.openInApp(siteUrl + '/login/change_password.php'); }); @@ -676,7 +683,7 @@ export class CoreLoginHelperProvider { * * @param {string} siteUrl URL of the site. */ - openForgottenPassword(siteUrl: string) : void { + openForgottenPassword(siteUrl: string): void { this.utils.openInApp(siteUrl + '/login/forgot_password.php'); } @@ -688,13 +695,13 @@ export class CoreLoginHelperProvider { * @param {string} alertMessage The key of the message to display before opening the in app browser. * @param {boolean} [invalidateCache] Whether to invalidate site's cache (e.g. when the user is forced to change password). */ - openInAppForEdit(siteId: string, path: string, alertMessage: string, invalidateCache?: boolean) : void { - if (!siteId || siteId !== this.sitesProvider.getCurrentSiteId()) { + openInAppForEdit(siteId: string, path: string, alertMessage: string, invalidateCache?: boolean): void { + if (!siteId || siteId !== this.sitesProvider.getCurrentSiteId()) { // Site that triggered the event is not current site, nothing to do. return; } - let currentSite = this.sitesProvider.getCurrentSite(), + const currentSite = this.sitesProvider.getCurrentSite(), siteUrl = currentSite && currentSite.getURL(); if (!currentSite || !siteUrl) { return; @@ -726,18 +733,18 @@ export class CoreLoginHelperProvider { * @param {string} [pageName] Name of the page to go once authenticated. If not defined, site initial page. * @param {any} [pageParams] Params of the state to go once authenticated. */ - prepareForSSOLogin(siteUrl: string, service?: string, launchUrl?: string, pageName?: string, pageParams?: any) : string { + prepareForSSOLogin(siteUrl: string, service?: string, launchUrl?: string, pageName?: string, pageParams?: any): string { service = service || CoreConfigConstants.wsextservice; launchUrl = launchUrl || siteUrl + '/local/mobile/launch.php'; - let passport = Math.random() * 1000, - loginUrl = launchUrl + '?service=' + service; + const passport = Math.random() * 1000; + let loginUrl = launchUrl + '?service=' + service; - loginUrl += "&passport=" + passport; - loginUrl += "&urlscheme=" + CoreConfigConstants.customurlscheme; + loginUrl += '&passport=' + passport; + loginUrl += '&urlscheme=' + CoreConfigConstants.customurlscheme; - // Store the siteurl and passport in $mmConfig for persistence. We are "configuring" - // the app to wait for an SSO. $mmConfig shouldn't be used as a temporary storage. + // Store the siteurl and passport in $mmConfig for persistence. + // We are "configuring" the app to wait for an SSO. $mmConfig shouldn't be used as a temporary storage. this.configProvider.set(CoreConstants.LOGIN_LAUNCH_DATA, JSON.stringify({ siteUrl: siteUrl, passport: passport, @@ -755,22 +762,16 @@ export class CoreLoginHelperProvider { * @param {any} params Params to pass to the page. * @param {string} [siteId] Site to load. If not defined, current site. */ - redirect(page: string, params?: any, siteId?: string) : void { + redirect(page: string, params?: any, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (this.sitesProvider.isLoggedIn()) { if (siteId && siteId != this.sitesProvider.getCurrentSiteId()) { // Target page belongs to a different site. Change site. - // @todo Once we have addon manager. - // if ($mmAddonManager.hasRemoteAddonsLoaded()) { - // // The site has remote addons so the app will be restarted. Store the data and logout. - // this.appProvider.storeRedirect(siteId, page, params); - // this.sitesProvider.logout(); - // } else { - this.sitesProvider.logout().then(() => { - this.loadSiteAndPage(page, params, siteId); - }); - // } + // @todo Store redirect once we have addon manager. + this.sitesProvider.logout().then(() => { + this.loadSiteAndPage(page, params, siteId); + }); } else { this.loadPageInMainMenu(page, params); } @@ -791,8 +792,8 @@ export class CoreLoginHelperProvider { * @param {string} [email] Email to search. * @return {Promise} Promise resolved when done. */ - requestPasswordReset(siteUrl: string, username?: string, email?: string) : Promise { - var params: any = {}; + requestPasswordReset(siteUrl: string, username?: string, email?: string): Promise { + const params: any = {}; if (username) { params.username = username; @@ -802,7 +803,7 @@ export class CoreLoginHelperProvider { params.email = email; } - return this.wsProvider.callAjax('core_auth_request_password_reset', params, {siteUrl: siteUrl}); + return this.wsProvider.callAjax('core_auth_request_password_reset', params, { siteUrl: siteUrl }); } /** @@ -810,11 +811,11 @@ export class CoreLoginHelperProvider { * * @param {any} data Data received by the SESSION_EXPIRED event. */ - sessionExpired(data: any) : void { - let siteId = data && data.siteId, + sessionExpired(data: any): void { + const siteId = data && data.siteId, currentSite = this.sitesProvider.getCurrentSite(), - siteUrl = currentSite && currentSite.getURL(), - promise; + siteUrl = currentSite && currentSite.getURL(); + let promise; if (!currentSite || !siteUrl) { return; @@ -832,14 +833,13 @@ export class CoreLoginHelperProvider { } if (this.isSSOLoginNeeded(result.code)) { - // SSO. User needs to authenticate in a browser. Prevent showing the message several times - // or show it again if the user is already authenticating using SSO. + // SSO. User needs to authenticate in a browser. Check if we need to display a message. if (!this.appProvider.isSSOAuthenticationOngoing() && !this.isSSOConfirmShown && !this.waitingForBrowser) { this.isSSOConfirmShown = true; if (this.shouldShowSSOConfirm(result.code)) { promise = this.domUtils.showConfirm(this.translate.instant('core.login.' + - (currentSite.isLoggedOut() ? 'loggedoutssodescription' : 'reconnectssodescription'))); + (currentSite.isLoggedOut() ? 'loggedoutssodescription' : 'reconnectssodescription'))); } else { promise = Promise.resolve(); } @@ -847,7 +847,7 @@ export class CoreLoginHelperProvider { promise.then(() => { this.waitingForBrowser = true; this.openBrowserForSSOLogin(result.siteUrl, result.code, result.service, - result.config && result.config.launchurl, data.pageName, data.params); + result.config && result.config.launchurl, data.pageName, data.params); }).catch(() => { // User cancelled, logout him. this.sitesProvider.logout(); @@ -856,7 +856,7 @@ export class CoreLoginHelperProvider { }); } } else { - let info = currentSite.getInfo(); + const info = currentSite.getInfo(); if (typeof info != 'undefined' && typeof info.username != 'undefined') { this.appProvider.getRootNavController().setRoot('CoreLoginReconnectPage', { infoSiteUrl: info.siteurl, @@ -884,9 +884,9 @@ export class CoreLoginHelperProvider { * @param {number} typeOfLogin CoreConstants.LOGIN_SSO_CODE or CoreConstants.LOGIN_SSO_INAPP_CODE. * @return {boolean} True if confirm modal should be shown, false otherwise. */ - shouldShowSSOConfirm(typeOfLogin: number) : boolean { + shouldShowSSOConfirm(typeOfLogin: number): boolean { return !this.isSSOEmbeddedBrowser(typeOfLogin) && - (!CoreConfigConstants.skipssoconfirmation || String(CoreConfigConstants.skipssoconfirmation) === 'false'); + (!CoreConfigConstants.skipssoconfirmation || String(CoreConfigConstants.skipssoconfirmation) === 'false'); } /** @@ -894,7 +894,7 @@ export class CoreLoginHelperProvider { * * @param {string} [siteId] Site ID. If not defined, current site. */ - sitePolicyNotAgreed(siteId?: string) : void { + sitePolicyNotAgreed(siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (!siteId || siteId != this.sitesProvider.getCurrentSiteId()) { // Only current site allowed. @@ -906,7 +906,7 @@ export class CoreLoginHelperProvider { return; } - this.appProvider.getRootNavController().setRoot('CoreLoginSitePolicyPage', {siteId: siteId}); + this.appProvider.getRootNavController().setRoot('CoreLoginSitePolicyPage', { siteId: siteId }); } /** @@ -915,7 +915,7 @@ export class CoreLoginHelperProvider { * @param {string} siteUrl Site URL to construct change password URL. * @param {any} error Error object containing errorcode and error message. */ - treatUserTokenError(siteUrl: string, error: any) : void { + treatUserTokenError(siteUrl: string, error: any): void { if (typeof error == 'string') { this.domUtils.showErrorModal(error); } else if (error.errorcode == 'forcepasswordchangenotice') { @@ -931,47 +931,49 @@ export class CoreLoginHelperProvider { * @param {string} url URL received, to be validated. * @return {Promise} Promise resolved on success. */ - validateBrowserSSOLogin(url: string) : Promise { + validateBrowserSSOLogin(url: string): Promise { // Split signature:::token - const params = url.split(":::"); + const params = url.split(':::'); return this.configProvider.get(CoreConstants.LOGIN_LAUNCH_DATA).then((data): any => { try { data = JSON.parse(data); - } catch(ex) { + } catch (ex) { return Promise.reject(null); } - let launchSiteURL = data.siteUrl, - passport = data.passport; + const passport = data.passport; + let launchSiteURL = data.siteUrl; // Reset temporary values. this.configProvider.delete(CoreConstants.LOGIN_LAUNCH_DATA); // Validate the signature. // We need to check both http and https. - let signature = Md5.hashAsciiStr(launchSiteURL + passport); + let signature = Md5.hashAsciiStr(launchSiteURL + passport); if (signature != params[0]) { - if (launchSiteURL.indexOf("https://") != -1) { - launchSiteURL = launchSiteURL.replace("https://", "http://"); + if (launchSiteURL.indexOf('https://') != -1) { + launchSiteURL = launchSiteURL.replace('https://', 'http://'); } else { - launchSiteURL = launchSiteURL.replace("http://", "https://"); + launchSiteURL = launchSiteURL.replace('http://', 'https://'); } - signature = Md5.hashAsciiStr(launchSiteURL + passport); + signature = Md5.hashAsciiStr(launchSiteURL + passport); } if (signature == params[0]) { this.logger.debug('Signature validated'); + return { siteUrl: launchSiteURL, token: params[1], privateToken: params[2], pageName: data.pageName, pageParams: data.pageParams - } + }; } else { this.logger.debug('Invalid signature in the URL request yours: ' + params[0] + ' mine: ' - + signature + ' for passport ' + passport); + + signature + ' for passport ' + passport); + return Promise.reject(this.translate.instant('core.unexpectederror')); } }); diff --git a/src/core/mainmenu/pages/menu/menu.ts b/src/core/mainmenu/pages/menu/menu.ts index 372b7dd5f..016dfe398 100644 --- a/src/core/mainmenu/pages/menu/menu.ts +++ b/src/core/mainmenu/pages/menu/menu.ts @@ -22,7 +22,7 @@ import { CoreMainMenuDelegate, CoreMainMenuHandlerData } from '../../providers/d /** * Page that displays the main menu of the app. */ -@IonicPage({segment: "core-mainmenu"}) +@IonicPage({segment: 'core-mainmenu'}) @Component({ selector: 'page-core-mainmenu', templateUrl: 'menu.html', @@ -48,7 +48,7 @@ export class CoreMainMenuPage implements OnDestroy { ionTabs.select(indexToSelect); }); } - }; + } tabs: CoreMainMenuHandlerData[] = []; loaded: boolean; @@ -74,13 +74,14 @@ export class CoreMainMenuPage implements OnDestroy { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { if (!this.sitesProvider.isLoggedIn()) { this.navCtrl.setRoot('CoreLoginSitesPage'); + return; } - let site = this.sitesProvider.getCurrentSite(), + const site = this.sitesProvider.getCurrentSite(), displaySiteHome = site.getInfo() && site.getInfo().userhomepage === 0; this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => { @@ -89,13 +90,13 @@ export class CoreMainMenuPage implements OnDestroy { // Check if handlers are already in tabs. Add the ones that aren't. // @todo: https://github.com/ionic-team/ionic/issues/13633 for (let i = 0; i < handlers.length; i++) { - let handler = handlers[i], - found = false, + const handler = handlers[i], shouldSelect = (displaySiteHome && handler.name == 'CoreSiteHome') || (!displaySiteHome && handler.name == 'CoreCourses'); + let found = false; for (let j = 0; j < this.tabs.length; j++) { - let tab = this.tabs[j]; + const tab = this.tabs[j]; if (tab.title == handler.title && tab.icon == handler.icon) { found = true; if (shouldSelect) { @@ -125,7 +126,7 @@ export class CoreMainMenuPage implements OnDestroy { /** * Page destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.subscription && this.subscription.unsubscribe(); } } diff --git a/src/core/mainmenu/pages/more/more.ts b/src/core/mainmenu/pages/more/more.ts index 6ae8863e3..084b061d9 100644 --- a/src/core/mainmenu/pages/more/more.ts +++ b/src/core/mainmenu/pages/more/more.ts @@ -22,7 +22,7 @@ import { CoreMainMenuProvider, CoreMainMenuCustomItem } from '../../providers/ma /** * Page that displays the list of main menu options that aren't in the tabs. */ -@IonicPage({segment: "core-mainmenu-more"}) +@IonicPage({segment: 'core-mainmenu-more'}) @Component({ selector: 'page-core-mainmenu-more', templateUrl: 'more.html', @@ -53,7 +53,7 @@ export class CoreMainMenuMorePage implements OnDestroy { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { // Load the handlers. this.subscription = this.menuDelegate.getHandlers().subscribe((handlers) => { this.handlers = handlers.slice(CoreMainMenuProvider.NUM_MAIN_HANDLERS); // Remove the main handlers. @@ -64,7 +64,7 @@ export class CoreMainMenuMorePage implements OnDestroy { /** * Page destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { if (this.subscription) { this.subscription.unsubscribe(); } @@ -73,12 +73,12 @@ export class CoreMainMenuMorePage implements OnDestroy { /** * Load the site info required by the view. */ - protected loadSiteInfo() { + protected loadSiteInfo(): void { const currentSite = this.sitesProvider.getCurrentSite(), config = currentSite.getStoredConfig(); this.siteInfo = currentSite.getInfo(); - this.logoutLabel = 'core.mainmenu.' + (config && config.tool_mobile_forcelogout == '1' ? 'logout': 'changesite'); + this.logoutLabel = 'core.mainmenu.' + (config && config.tool_mobile_forcelogout == '1' ? 'logout' : 'changesite'); this.showWeb = !currentSite.isFeatureDisabled('$mmSideMenuDelegate_website'); this.showHelp = !currentSite.isFeatureDisabled('$mmSideMenuDelegate_help'); @@ -96,7 +96,7 @@ export class CoreMainMenuMorePage implements OnDestroy { * * @param {CoreMainMenuHandlerData} handler Handler to open. */ - openHandler(handler: CoreMainMenuHandlerData) { + openHandler(handler: CoreMainMenuHandlerData): void { // @todo. } @@ -105,21 +105,21 @@ export class CoreMainMenuMorePage implements OnDestroy { * * @param {CoreMainMenuCustomItem} item Item to open. */ - openItem(item: CoreMainMenuCustomItem) { + openItem(item: CoreMainMenuCustomItem): void { this.navCtrl.push('CoreViewerIframePage', {title: item.label, url: item.url}); } /** * Open settings page. */ - openSettings() { + openSettings(): void { this.navCtrl.push('CoreSettingsListPage'); } /** * Logout the user. */ - logout() { + logout(): void { this.sitesProvider.logout(); } } diff --git a/src/core/mainmenu/providers/delegate.ts b/src/core/mainmenu/providers/delegate.ts index c2a4e3959..191cafbdf 100644 --- a/src/core/mainmenu/providers/delegate.ts +++ b/src/core/mainmenu/providers/delegate.ts @@ -36,7 +36,7 @@ export interface CoreMainMenuHandler extends CoreDelegateHandler { * @return {CoreMainMenuHandlerData} Data. */ getDisplayData(): CoreMainMenuHandlerData; -}; +} /** * Data needed to render a main menu handler. It's returned by the handler. @@ -65,7 +65,7 @@ export interface CoreMainMenuHandlerData { * @type {string} */ class?: string; -}; +} /** * Data returned by the delegate for each handler. @@ -77,16 +77,16 @@ export interface CoreMainMenuHandlerToDisplay extends CoreMainMenuHandlerData { * @type {string} */ name?: string; -}; +} /** * Service to interact with plugins to be shown in the main menu. Provides functions to register a plugin * and notify an update in the data. */ @Injectable() -export class CoreMainMenuDelegate extends CoreDelegate { - protected handlers: {[s: string]: CoreMainMenuHandler} = {}; - protected enabledHandlers: {[s: string]: CoreMainMenuHandler} = {}; +export class CoreMainMenuDelegate extends CoreDelegate { + protected handlers: { [s: string]: CoreMainMenuHandler } = {}; + protected enabledHandlers: { [s: string]: CoreMainMenuHandler } = {}; protected loaded = false; protected siteHandlers: Subject = new BehaviorSubject([]); protected featurePrefix = '$mmSideMenuDelegate_'; @@ -103,14 +103,14 @@ export class CoreMainMenuDelegate extends CoreDelegate { * * @return {boolean} True if handlers are loaded, false otherwise. */ - areHandlersLoaded() : boolean { + areHandlersLoaded(): boolean { return this.loaded; } /** * Clear current site handlers. Reserved for core use. */ - protected clearSiteHandlers() { + protected clearSiteHandlers(): void { this.loaded = false; this.siteHandlers.next([]); } @@ -120,18 +120,18 @@ export class CoreMainMenuDelegate extends CoreDelegate { * * @return {Subject} An observable that will receive the handlers. */ - getHandlers() : Subject { + getHandlers(): Subject { return this.siteHandlers; } /** * Update handlers Data. */ - updateData() { - let handlersData: any[] = []; + updateData(): void { + const handlersData: any[] = []; - for (let name in this.enabledHandlers) { - let handler = this.enabledHandlers[name], + for (const name in this.enabledHandlers) { + const handler = this.enabledHandlers[name], data = handler.getDisplayData(); handlersData.push({ @@ -146,7 +146,7 @@ export class CoreMainMenuDelegate extends CoreDelegate { }); // Return only the display data. - let displayData = handlersData.map((item) => { + const displayData = handlersData.map((item) => { return item.data; }); diff --git a/src/core/mainmenu/providers/mainmenu.ts b/src/core/mainmenu/providers/mainmenu.ts index 0f3bd8e71..679445442 100644 --- a/src/core/mainmenu/providers/mainmenu.ts +++ b/src/core/mainmenu/providers/mainmenu.ts @@ -44,16 +44,16 @@ export interface CoreMainMenuCustomItem { * @type {string} */ icon: string; -}; +} /** * Service that provides some features regarding Main Menu. */ @Injectable() export class CoreMainMenuProvider { - public static NUM_MAIN_HANDLERS = 4; + static NUM_MAIN_HANDLERS = 4; - constructor(private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) {} + constructor(private langProvider: CoreLangProvider, private sitesProvider: CoreSitesProvider) { } /** * Get a list of custom menu items for a certain site. @@ -61,14 +61,15 @@ export class CoreMainMenuProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} List of custom menu items. */ - getCustomMenuItems(siteId?: string) : Promise { + getCustomMenuItems(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let itemsString = site.getStoredConfig('tool_mobile_custommenuitems'), - items, - position = 0, // Position of each item, to keep the same order as it's configured. + const itemsString = site.getStoredConfig('tool_mobile_custommenuitems'), map = {}, result = []; + let items, + position = 0; // Position of each item, to keep the same order as it's configured. + if (!itemsString || typeof itemsString != 'string') { // Setting not valid. return result; @@ -77,12 +78,12 @@ export class CoreMainMenuProvider { // Add items to the map. items = itemsString.split(/(?:\r\n|\r|\n)/); items.forEach((item) => { - let values = item.split('|'), - id, + const values = item.split('|'), label = values[0] ? values[0].trim() : values[0], url = values[1] ? values[1].trim() : values[1], type = values[2] ? values[2].trim() : values[2], - lang = (values[3] ? values[3].trim() : values[3]) || 'none', + lang = (values[3] ? values[3].trim() : values[3]) || 'none'; + let id, icon = values[4] ? values[4].trim() : values[4]; if (!label || !url || !type) { @@ -119,17 +120,17 @@ export class CoreMainMenuProvider { } return this.langProvider.getCurrentLanguage().then((currentLang) => { - const fallbackLang = CoreConfigConstants.default_lang || 'en'; + const fallbackLang = CoreConfigConstants.default_lang || 'en'; // Get the right label for each entry and add it to the result. - for (let id in map) { - let entry = map[id], - data = entry.labels[currentLang] || entry.labels[currentLang + '_only'] || - entry.labels.none || entry.labels[fallbackLang]; + for (const id in map) { + const entry = map[id]; + let data = entry.labels[currentLang] || entry.labels[currentLang + '_only'] || + entry.labels.none || entry.labels[fallbackLang]; if (!data) { // No valid label found, get the first one that is not "_only". - for (let lang in entry.labels) { + for (const lang in entry.labels) { if (lang.indexOf('_only') == -1) { data = entry.labels[lang]; break; diff --git a/src/core/sharedfiles/pages/choose-site/choose-site.ts b/src/core/sharedfiles/pages/choose-site/choose-site.ts index 20702dc4c..86ca16621 100644 --- a/src/core/sharedfiles/pages/choose-site/choose-site.ts +++ b/src/core/sharedfiles/pages/choose-site/choose-site.ts @@ -22,7 +22,7 @@ import { CoreSharedFilesHelperProvider } from '../../providers/helper'; /** * Modal to display the list of sites to choose one to store a shared file. */ -@IonicPage({segment: "core-shared-files-choose-site"}) +@IonicPage({ segment: 'core-shared-files-choose-site' }) @Component({ selector: 'page-core-shared-files-choose-site', templateUrl: 'choose-site.html', @@ -45,14 +45,15 @@ export class CoreSharedFilesChooseSitePage implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { if (!this.filePath) { this.domUtils.showErrorModal('Error reading file.'); this.navCtrl.pop(); + return; } - let fileAndDir = this.fileProvider.getFileAndDirectoryFromPath(this.filePath); + const fileAndDir = this.fileProvider.getFileAndDirectoryFromPath(this.filePath); this.fileName = fileAndDir.name; // Get the file. @@ -77,13 +78,12 @@ export class CoreSharedFilesChooseSitePage implements OnInit { * * @param {string} siteId Site ID. */ - storeInSite(siteId: string) : void { + storeInSite(siteId: string): void { this.loaded = false; this.sharedFilesHelper.storeSharedFileInSite(this.fileEntry, siteId).then(() => { this.navCtrl.pop(); }).finally(() => { this.loaded = true; }); - }; - -} \ No newline at end of file + } +} diff --git a/src/core/sharedfiles/pages/list/list.ts b/src/core/sharedfiles/pages/list/list.ts index 92a1d7f34..c018d4175 100644 --- a/src/core/sharedfiles/pages/list/list.ts +++ b/src/core/sharedfiles/pages/list/list.ts @@ -24,7 +24,7 @@ import { CoreSharedFilesProvider } from '../../providers/sharedfiles'; /** * Modal to display the list of shared files. */ -@IonicPage({segment: "core-shared-files-list"}) +@IonicPage({ segment: 'core-shared-files-list' }) @Component({ selector: 'page-core-shared-files-list', templateUrl: 'list.html', @@ -35,7 +35,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { isModal: boolean; manage: boolean; pick: boolean; // To pick a file you MUST use a modal. - path: string = ''; + path = ''; title: string; filesLoaded: boolean; files: any[]; @@ -57,7 +57,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.loadFiles(); // Listen for new files shared with the app. @@ -74,8 +74,10 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Load the files. + * + * @return {Promise} Promise resolved when done. */ - protected loadFiles() { + protected loadFiles(): Promise { if (this.path) { this.title = this.fileProvider.getFileAndDirectoryFromPath(this.path).name; } else { @@ -91,7 +93,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { /** * Close modal. */ - closeModal() : void { + closeModal(): void { this.viewCtrl.dismiss(); } @@ -100,7 +102,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { * * @param {any} refresher Refresher. */ - refreshFiles(refresher: any) : void { + refreshFiles(refresher: any): void { this.loadFiles().finally(() => { refresher.complete(); }); @@ -111,7 +113,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { * * @param {number} index Position of the file. */ - fileDeleted(index: number) : void { + fileDeleted(index: number): void { this.files.splice(index, 1); } @@ -121,7 +123,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { * @param {number} index Position of the file. * @param {any} file New FileEntry. */ - fileRenamed(index: number, file: any) : void { + fileRenamed(index: number, file: any): void { this.files[index] = file; } @@ -130,8 +132,8 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { * * @param {any} folder The folder to open. */ - openFolder(folder: any) : void { - let path = this.textUtils.concatenatePaths(this.path, folder.name); + openFolder(folder: any): void { + const path = this.textUtils.concatenatePaths(this.path, folder.name); if (this.isModal) { // In Modal we don't want to open a new page because we cannot dismiss the modal from the new page. this.path = path; @@ -154,7 +156,7 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { * * @param {string} id Site to load. */ - changeSite(id: string) : void { + changeSite(id: string): void { this.siteId = id; this.path = ''; this.filesLoaded = false; @@ -166,16 +168,16 @@ export class CoreSharedFilesListPage implements OnInit, OnDestroy { * * @param {any} file Picked file. */ - filePicked(file: any) : void { + filePicked(file: any): void { this.viewCtrl.dismiss(file); } /** * Component destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { if (this.shareObserver) { this.shareObserver.off(); } } -} \ No newline at end of file +} diff --git a/src/core/sharedfiles/providers/helper.ts b/src/core/sharedfiles/providers/helper.ts index 22f76e6b7..b7307bdd7 100644 --- a/src/core/sharedfiles/providers/helper.ts +++ b/src/core/sharedfiles/providers/helper.ts @@ -47,21 +47,21 @@ export class CoreSharedFilesHelperProvider { * @param {string} newName New name. * @return {Promise} Promise resolved with the name to use when the user chooses. Rejected if user cancels. */ - askRenameReplace(originalName: string, newName: string) : Promise { + askRenameReplace(originalName: string, newName: string): Promise { const deferred = this.utils.promiseDefer(), alert = this.alertCtrl.create({ title: this.translate.instant('core.sharedfiles.sharedfiles'), - message: this.translate.instant('core.sharedfiles.chooseactionrepeatedfile', {$a: newName}), + message: this.translate.instant('core.sharedfiles.chooseactionrepeatedfile', { $a: newName }), buttons: [ { text: this.translate.instant('core.sharedfiles.rename'), - handler: () => { + handler: (): void => { deferred.resolve(newName); } }, { text: this.translate.instant('core.sharedfiles.replace'), - handler: () => { + handler: (): void => { deferred.resolve(originalName); } } @@ -69,6 +69,7 @@ export class CoreSharedFilesHelperProvider { }); alert.present(); + return deferred.promise; } @@ -77,9 +78,9 @@ export class CoreSharedFilesHelperProvider { * * @param {string} filePath File path to send to the view. */ - goToChooseSite(filePath: string) : void { - let navCtrl = this.appProvider.getRootNavController(); - navCtrl.push('CoreSharedFilesChooseSitePage', {filePath: filePath}); + goToChooseSite(filePath: string): void { + const navCtrl = this.appProvider.getRootNavController(); + navCtrl.push('CoreSharedFilesChooseSitePage', { filePath: filePath }); } /** @@ -88,15 +89,16 @@ export class CoreSharedFilesHelperProvider { * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. * @return {Promise} Promise resolved when a file is picked, rejected if file picker is closed without selecting a file. */ - pickSharedFile(mimetypes?: string[]) : Promise { - return new Promise((resolve, reject) => { - let modal = this.modalCtrl.create('CoreSharedFilesListPage', {mimetypes: mimetypes, isModal: true, pick: true}); + pickSharedFile(mimetypes?: string[]): Promise { + return new Promise((resolve, reject): void => { + const modal = this.modalCtrl.create('CoreSharedFilesListPage', { mimetypes: mimetypes, isModal: true, pick: true }); modal.present(); modal.onDidDismiss((file: any) => { if (!file) { // User cancelled. reject(); + return; } @@ -109,7 +111,7 @@ export class CoreSharedFilesHelperProvider { treated: false }); } - }) + }); }); } @@ -120,9 +122,9 @@ export class CoreSharedFilesHelperProvider { * * @return {Promise} Promise resolved when done. */ - searchIOSNewSharedFiles() : Promise { + searchIOSNewSharedFiles(): Promise { return this.initDelegate.ready().then(() => { - let navCtrl = this.appProvider.getRootNavController(); + const navCtrl = this.appProvider.getRootNavController(); if (navCtrl && navCtrl.getActive().id == 'CoreSharedFilesChooseSite') { // We're already treating a shared file. Abort. return Promise.reject(null); @@ -133,6 +135,7 @@ export class CoreSharedFilesHelperProvider { if (!siteIds.length) { // No sites stored, show error and delete the file. this.domUtils.showErrorModal('core.sharedfiles.errorreceivefilenosites', true); + return this.sharedFilesProvider.deleteInboxFile(fileEntry); } else if (siteIds.length == 1) { return this.storeSharedFileInSite(fileEntry, siteIds[0]); @@ -151,11 +154,12 @@ export class CoreSharedFilesHelperProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when done. */ - storeSharedFileInSite(fileEntry: any, siteId?: string) : Promise { + storeSharedFileInSite(fileEntry: any, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // First of all check if there's already a file with the same name in the shared files folder. const sharedFilesDirPath = this.sharedFilesProvider.getSiteSharedFilesDirPath(siteId); + return this.fileProvider.getUniqueNameInFolder(sharedFilesDirPath, fileEntry.name).then((newName) => { if (newName == fileEntry.name) { // No file with the same name. Use the original file name. @@ -165,7 +169,7 @@ export class CoreSharedFilesHelperProvider { return this.askRenameReplace(fileEntry.name, newName); } }).then((name) => { - return this.sharedFilesProvider.storeFileInSite(fileEntry, name, siteId).catch(function(err) { + return this.sharedFilesProvider.storeFileInSite(fileEntry, name, siteId).catch((err) => { this.domUtils.showErrorModal(err || 'Error moving file.'); }).finally(() => { this.sharedFilesProvider.deleteInboxFile(fileEntry); diff --git a/src/core/sharedfiles/providers/sharedfiles.ts b/src/core/sharedfiles/providers/sharedfiles.ts index 5a653f442..c34daf4d6 100644 --- a/src/core/sharedfiles/providers/sharedfiles.ts +++ b/src/core/sharedfiles/providers/sharedfiles.ts @@ -28,7 +28,7 @@ import { SQLiteDB } from '../../../classes/sqlitedb'; */ @Injectable() export class CoreSharedFilesProvider { - public static SHARED_FILES_FOLDER = 'sharedfiles'; + static SHARED_FILES_FOLDER = 'sharedfiles'; // Variables for the database. protected SHARED_FILES_TABLE = 'wscache'; @@ -41,14 +41,14 @@ export class CoreSharedFilesProvider { primaryKey: true } ] - } + }; protected logger; protected appDB: SQLiteDB; constructor(logger: CoreLoggerProvider, private fileProvider: CoreFileProvider, appProvider: CoreAppProvider, - private textUtils: CoreTextUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, - private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider) { + private textUtils: CoreTextUtilsProvider, private mimeUtils: CoreMimetypeUtilsProvider, + private sitesProvider: CoreSitesProvider, private eventsProvider: CoreEventsProvider) { this.logger = logger.getInstance('CoreSharedFilesProvider'); this.appDB = appProvider.getDB(); @@ -61,12 +61,13 @@ export class CoreSharedFilesProvider { * * @return {Promise} Promise resolved with a new file to be treated. If no new files found, promise is rejected. */ - checkIOSNewFiles() : Promise { + checkIOSNewFiles(): Promise { this.logger.debug('Search for new files on iOS'); + return this.fileProvider.getDirectoryContents('Inbox').then((entries) => { if (entries.length > 0) { - let promises = [], - fileToReturn; + const promises = []; + let fileToReturn; entries.forEach((entry) => { const fileId = this.getFileId(entry); @@ -90,8 +91,10 @@ export class CoreSharedFilesProvider { if (fileToReturn) { // Mark it as "treated". fileId = this.getFileId(fileToReturn); + return this.markAsTreated(fileId).then(() => { this.logger.debug('File marked as "treated": ' + fileToReturn.name); + return fileToReturn; }); } else { @@ -110,7 +113,7 @@ export class CoreSharedFilesProvider { * @param {any} entry FileEntry. * @return {Promise} Promise resolved when done, rejected otherwise. */ - deleteInboxFile(entry: any) : Promise { + deleteInboxFile(entry: any): Promise { this.logger.debug('Delete inbox file: ' + entry.name); return this.fileProvider.removeFileByFileEntry(entry).catch(() => { @@ -120,6 +123,7 @@ export class CoreSharedFilesProvider { this.logger.debug('"Treated" mark removed from file: ' + entry.name); }).catch((error) => { this.logger.debug('Error deleting "treated" mark from file: ' + entry.name, error); + return Promise.reject(error); }); }); @@ -131,7 +135,7 @@ export class CoreSharedFilesProvider { * @param {any} entry FileEntry. * @return {string} File ID. */ - protected getFileId(entry: any) : string { + protected getFileId(entry: any): string { return Md5.hashAsciiStr(entry.name); } @@ -143,7 +147,7 @@ export class CoreSharedFilesProvider { * @param {string[]} [mimetypes] List of supported mimetypes. If undefined, all mimetypes supported. * @return {Promise} Promise resolved with the files. */ - getSiteSharedFiles(siteId?: string, path?: string, mimetypes?: string[]) : Promise { + getSiteSharedFiles(siteId?: string, path?: string, mimetypes?: string[]): Promise { let pathToGet = this.getSiteSharedFilesDirPath(siteId); if (path) { pathToGet = this.textUtils.concatenatePaths(pathToGet, path); @@ -173,8 +177,9 @@ export class CoreSharedFilesProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {string} Path. */ - getSiteSharedFilesDirPath(siteId?: string) : string { + getSiteSharedFilesDirPath(siteId?: string): string { siteId = siteId || this.sitesProvider.getCurrentSiteId(); + return this.fileProvider.getSiteFolder(siteId) + '/' + CoreSharedFilesProvider.SHARED_FILES_FOLDER; } @@ -184,8 +189,8 @@ export class CoreSharedFilesProvider { * @param {string} fileId File ID. * @return {Promise} Resolved if treated, rejected otherwise. */ - protected isFileTreated(fileId: string) : Promise { - return this.appDB.getRecord(this.SHARED_FILES_TABLE, {id: fileId}); + protected isFileTreated(fileId: string): Promise { + return this.appDB.getRecord(this.SHARED_FILES_TABLE, { id: fileId }); } /** @@ -194,11 +199,11 @@ export class CoreSharedFilesProvider { * @param {string} fileId File ID. * @return {Promise} Promise resolved when marked. */ - protected markAsTreated(fileId: string) : Promise { + protected markAsTreated(fileId: string): Promise { // Check if it's already marked. return this.isFileTreated(fileId).catch(() => { // Doesn't exist, insert it. - return this.appDB.insertRecord(this.SHARED_FILES_TABLE, {id: fileId}); + return this.appDB.insertRecord(this.SHARED_FILES_TABLE, { id: fileId }); }); } @@ -210,10 +215,10 @@ export class CoreSharedFilesProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise}Promise resolved when done. */ - storeFileInSite(entry: any, newName?: string, siteId?: string) : Promise { + storeFileInSite(entry: any, newName?: string, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); - if (!entry || !siteId) { + if (!entry || !siteId) { return Promise.reject(null); } @@ -225,7 +230,8 @@ export class CoreSharedFilesProvider { // Create dir if it doesn't exist already. return this.fileProvider.createDir(sharedFilesFolder).then(() => { return this.fileProvider.moveFile(entry.fullPath, newPath).then((newFile) => { - this.eventsProvider.trigger(CoreEventsProvider.FILE_SHARED, {siteId: siteId, name: newName}); + this.eventsProvider.trigger(CoreEventsProvider.FILE_SHARED, { siteId: siteId, name: newName }); + return newFile; }); }); @@ -237,7 +243,7 @@ export class CoreSharedFilesProvider { * @param {string} fileId File ID. * @return {Promise} Resolved when unmarked. */ - protected unmarkAsTreated(fileId: string) : Promise { - return this.appDB.deleteRecords(this.SHARED_FILES_TABLE, {id: fileId}); + protected unmarkAsTreated(fileId: string): Promise { + return this.appDB.deleteRecords(this.SHARED_FILES_TABLE, { id: fileId }); } } diff --git a/src/core/sharedfiles/providers/upload-handler.ts b/src/core/sharedfiles/providers/upload-handler.ts index 1337dd4f2..abb4331de 100644 --- a/src/core/sharedfiles/providers/upload-handler.ts +++ b/src/core/sharedfiles/providers/upload-handler.ts @@ -24,14 +24,14 @@ export class CoreSharedFilesUploadHandler implements CoreFileUploaderHandler { name = 'CoreSharedFilesUpload'; priority = 1300; - constructor(private sharedFilesHelper: CoreSharedFilesHelperProvider, private platform: Platform) {} + constructor(private sharedFilesHelper: CoreSharedFilesHelperProvider, private platform: Platform) { } /** * Whether or not the handler is enabled on a site level. * * @return {boolean|Promise} True or promise resolved with true if enabled. */ - isEnabled(): boolean|Promise { + isEnabled(): boolean | Promise { return this.platform.is('ios'); } @@ -41,7 +41,7 @@ export class CoreSharedFilesUploadHandler implements CoreFileUploaderHandler { * @param {string[]} [mimetypes] List of mimetypes. * @return {string[]} Supported mimetypes. */ - getSupportedMimetypes(mimetypes: string[]) : string[] { + getSupportedMimetypes(mimetypes: string[]): string[] { return mimetypes; } @@ -50,12 +50,12 @@ export class CoreSharedFilesUploadHandler implements CoreFileUploaderHandler { * * @return {CoreFileUploaderHandlerData} Data. */ - getData() : CoreFileUploaderHandlerData { + getData(): CoreFileUploaderHandlerData { return { title: 'core.sharedfiles.sharedfiles', class: 'core-sharedfiles-fileuploader-handler', icon: 'folder', - action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]) => { + action: (maxSize?: number, upload?: boolean, allowOffline?: boolean, mimetypes?: string[]): Promise => { // Don't use the params because the file won't be uploaded, it is returned to the fileuploader. return this.sharedFilesHelper.pickSharedFile(mimetypes); } diff --git a/src/core/sitehome/components/enrolled-course-list/enrolled-course-list.ts b/src/core/sitehome/components/enrolled-course-list/enrolled-course-list.ts index 5c3cf6ae2..4c53ab4ff 100644 --- a/src/core/sitehome/components/enrolled-course-list/enrolled-course-list.ts +++ b/src/core/sitehome/components/enrolled-course-list/enrolled-course-list.ts @@ -25,16 +25,16 @@ import { CoreCoursesProvider } from '../../../courses/providers/courses'; export class CoreSiteHomeEnrolledCourseListComponent implements OnInit { show: boolean; - constructor(private coursesProvider: CoreCoursesProvider) {} + constructor(private coursesProvider: CoreCoursesProvider) { } /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { if (this.coursesProvider.isMyCoursesDisabledInSite()) { this.show = false; } else { - return this.coursesProvider.getUserCourses().then((courses) => { + this.coursesProvider.getUserCourses().then((courses) => { this.show = courses.length > 0; }); } diff --git a/src/core/sitehome/components/index/index.ts b/src/core/sitehome/components/index/index.ts index f883ab7dc..f2dadf33a 100644 --- a/src/core/sitehome/components/index/index.ts +++ b/src/core/sitehome/components/index/index.ts @@ -45,7 +45,7 @@ export class CoreSiteHomeIndexComponent implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.loadContent().finally(() => { this.dataLoaded = true; }); @@ -56,7 +56,7 @@ export class CoreSiteHomeIndexComponent implements OnInit { * * @param {any} refresher Refresher. */ - doRefresh(refresher: any) { + doRefresh(refresher: any): void { const promises = [], currentSite = this.sitesProvider.getCurrentSite(); @@ -83,15 +83,17 @@ export class CoreSiteHomeIndexComponent implements OnInit { /** * Convenience function to fetch the data. + * + * @return {Promise} Promise resolved when done. */ - protected loadContent() { + protected loadContent(): Promise { this.hasContent = false; - let config = this.sitesProvider.getCurrentSite().getStoredConfig() || {numsections: 1}; + const config = this.sitesProvider.getCurrentSite().getStoredConfig() || { numsections: 1 }; if (config.frontpageloggedin) { // Items with index 1 and 3 were removed on 2.5 and not being supported in the app. - let frontpageItems = [ + const frontpageItems = [ 'news', // News items. false, 'categories', // List of categories. diff --git a/src/core/sitehome/components/news/news.ts b/src/core/sitehome/components/news/news.ts index a9b6ec75f..2e3381ac1 100644 --- a/src/core/sitehome/components/news/news.ts +++ b/src/core/sitehome/components/news/news.ts @@ -34,33 +34,13 @@ export class CoreSiteHomeNewsComponent implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { // Get number of news items to show. - const newsItems = this.sitesProvider.getCurrentSite().getStoredConfig('newsitems') || 0; + const newsItems = this.sitesProvider.getCurrentSite().getStoredConfig('newsitems') || 0; if (!newsItems) { return; } // @todo: Implement it once forum is supported. - // $mmaModForum = $mmAddonManager.get('$mmaModForum'); - // if ($mmaModForum) { - // return $mmaModForum.getCourseForums(courseId).then(function(forums) { - // for (var x in forums) { - // if (forums[x].type == 'news') { - // return forums[x]; - // } - // } - // }).then(function(forum) { - // if (forum) { - // return $mmCourse.getModuleBasicInfo(forum.cmid).then(function(module) { - // scope.show = true; - // scope.module = module; - // scope.module._controller = - // $mmCourseDelegate.getContentHandlerControllerFor(module.modname, module, courseId, - // module.section); - // }); - // } - // }); - // } } } diff --git a/src/core/sitehome/pages/index/index.ts b/src/core/sitehome/pages/index/index.ts index 1208b8a77..b2b7cea63 100644 --- a/src/core/sitehome/pages/index/index.ts +++ b/src/core/sitehome/pages/index/index.ts @@ -20,7 +20,7 @@ import { CoreCourseHelperProvider } from '../../../course/providers/helper'; /** * Page that displays site home index. */ -@IonicPage({segment: 'core-sitehome-index'}) +@IonicPage({ segment: 'core-sitehome-index' }) @Component({ selector: 'page-core-sitehome-index', templateUrl: 'index.html', @@ -29,7 +29,7 @@ export class CoreSiteHomeIndexPage { constructor(navParams: NavParams, navCtrl: NavController, courseHelper: CoreCourseHelperProvider, sitesProvider: CoreSitesProvider) { - let module = navParams.get('module'); + const module = navParams.get('module'); if (module) { courseHelper.openModule(navCtrl, module, sitesProvider.getCurrentSite().getSiteHomeId()); } diff --git a/src/core/sitehome/providers/index-link-handler.ts b/src/core/sitehome/providers/index-link-handler.ts index daacc1542..06eeb9cd6 100644 --- a/src/core/sitehome/providers/index-link-handler.ts +++ b/src/core/sitehome/providers/index-link-handler.ts @@ -42,10 +42,10 @@ export class CoreSiteHomeIndexLinkHandler extends CoreContentLinksHandlerBase { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise { + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { return [{ - action: (siteId, navCtrl?) => { + action: (siteId, navCtrl?): void => { // Always use redirect to make it the new history root (to avoid "loops" in history). this.loginHelper.redirect('CoreSiteHomeIndexPage', undefined, siteId); } @@ -62,7 +62,7 @@ export class CoreSiteHomeIndexLinkHandler extends CoreContentLinksHandlerBase { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {boolean|Promise} Whether the handler is enabled for the URL and site. */ - isEnabled(siteId: string, url: string, params: any, courseId?: number) : boolean|Promise { + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { courseId = parseInt(params.id, 10); if (!courseId) { return false; diff --git a/src/core/sitehome/providers/mainmenu-handler.ts b/src/core/sitehome/providers/mainmenu-handler.ts index 88d8c28a3..c2cb016b2 100644 --- a/src/core/sitehome/providers/mainmenu-handler.ts +++ b/src/core/sitehome/providers/mainmenu-handler.ts @@ -26,14 +26,14 @@ export class CoreSiteHomeMainMenuHandler implements CoreMainMenuHandler { priority = 1000; isOverviewEnabled: boolean; - constructor(private siteHomeProvider: CoreSiteHomeProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider) {} + constructor(private siteHomeProvider: CoreSiteHomeProvider, private myOverviewProvider: CoreCoursesMyOverviewProvider) { } /** * 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|Promise { + isEnabled(): boolean | Promise { // Check if my overview is enabled. return this.myOverviewProvider.isEnabled().then((enabled) => { if (enabled) { diff --git a/src/core/sitehome/providers/sitehome.ts b/src/core/sitehome/providers/sitehome.ts index 4fffeba3b..3f9e131be 100644 --- a/src/core/sitehome/providers/sitehome.ts +++ b/src/core/sitehome/providers/sitehome.ts @@ -35,7 +35,7 @@ export class CoreSiteHomeProvider { * @param {string} [siteId] The site ID. If not defined, current site. * @return {Promise} Promise resolved with boolean: whether it's available. */ - isAvailable(siteId?: string) : Promise { + isAvailable(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { // First check if it's disabled. if (this.isDisabledInSite(site)) { @@ -44,17 +44,17 @@ export class CoreSiteHomeProvider { // Use a WS call to check if there's content in the site home. const siteHomeId = site.getSiteHomeId(), - preSets = {emergencyCache: false}; + preSets = { emergencyCache: false }; this.logger.debug('Using WS call to check if site home is available.'); - return this.courseProvider.getSections(siteHomeId, false, true, preSets, site.id).then((sections) : any => { + return this.courseProvider.getSections(siteHomeId, false, true, preSets, site.id).then((sections): any => { if (!sections || !sections.length) { return Promise.reject(null); } for (let i = 0; i < sections.length; i++) { - let section = sections[i]; + const section = sections[i]; if (section.summary || (section.modules && section.modules.length)) { // It has content, return true. return true; @@ -85,7 +85,7 @@ export class CoreSiteHomeProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved with true if disabled, rejected or resolved with false otherwise. */ - isDisabled(siteId?: string) : Promise { + isDisabled(siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return this.isDisabledInSite(site); }); @@ -97,8 +97,9 @@ export class CoreSiteHomeProvider { * @param {CoreSite} [site] Site. If not defined, use current site. * @return {boolean} Whether it's disabled. */ - isDisabledInSite(site: CoreSite) : boolean { + isDisabledInSite(site: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); + return site.isFeatureDisabled('$mmSideMenuDelegate_mmaFrontpage'); } } diff --git a/src/core/user/components/user-profile-field/user-profile-field.ts b/src/core/user/components/user-profile-field/user-profile-field.ts index f10494844..0058e9a9a 100644 --- a/src/core/user/components/user-profile-field/user-profile-field.ts +++ b/src/core/user/components/user-profile-field/user-profile-field.ts @@ -26,20 +26,20 @@ import { CoreUtilsProvider } from '../../../../providers/utils/utils'; }) export class CoreUserProfileFieldComponent implements OnInit { @Input() field: any; // The profile field to be rendered. - @Input() signup?: boolean = false; // True if editing the field in signup. Defaults to false. - @Input() edit?: boolean = false; // True if editing the field. Defaults to false. + @Input() signup = false; // True if editing the field in signup. Defaults to false. + @Input() edit = false; // True if editing the field. Defaults to false. @Input() form?: any; // Form where to add the form control. Required if edit=true or signup=true. @Input() registerAuth?: string; // Register auth method. E.g. 'email'. // Get the containers where to inject dynamic components. We use a setter because they might be inside a *ngIf. - @ViewChild('userProfileField', { read: ViewContainerRef }) set userProfileField (el: ViewContainerRef) { + @ViewChild('userProfileField', { read: ViewContainerRef }) set userProfileField(el: ViewContainerRef) { if (this.field) { this.createComponent(this.ufDelegate.getComponent(this.field, this.signup), el); } else { // The component hasn't been initialized yet. Store the container. this.fieldContainer = el; } - }; + } protected logger; @@ -48,14 +48,14 @@ export class CoreUserProfileFieldComponent implements OnInit { protected fieldInstance: any; constructor(logger: CoreLoggerProvider, private factoryResolver: ComponentFactoryResolver, - private ufDelegate: CoreUserProfileFieldDelegate, private utilsProvider: CoreUtilsProvider) { + private ufDelegate: CoreUserProfileFieldDelegate, private utilsProvider: CoreUtilsProvider) { this.logger = logger.getInstance('CoreUserProfileFieldComponent'); } /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { this.createComponent(this.ufDelegate.getComponent(this.field, this.signup), this.fieldContainer); } @@ -66,7 +66,7 @@ export class CoreUserProfileFieldComponent implements OnInit { * @param {ViewContainerRef} container The container to add the component to. * @return {boolean} Whether the component was successfully created. */ - protected createComponent(componentClass: any, container: ViewContainerRef) : boolean { + protected createComponent(componentClass: any, container: ViewContainerRef): boolean { if (!componentClass || !container) { // No component to instantiate or container doesn't exist right now. return false; @@ -95,8 +95,9 @@ export class CoreUserProfileFieldComponent implements OnInit { } return true; - } catch(ex) { + } catch (ex) { this.logger.error('Error creating user field component', ex, componentClass); + return false; } } diff --git a/src/core/user/pages/about/about.ts b/src/core/user/pages/about/about.ts index 05c05926d..856760407 100644 --- a/src/core/user/pages/about/about.ts +++ b/src/core/user/pages/about/about.ts @@ -23,7 +23,7 @@ import { CoreSitesProvider } from '../../../../providers/sites'; /** * Page that displays an user about page. */ -@IonicPage({segment: "core-user-about"}) +@IonicPage({ segment: 'core-user-about' }) @Component({ selector: 'page-core-user-about', templateUrl: 'about.html', @@ -33,10 +33,10 @@ export class CoreUserAboutPage { protected userId: number; protected siteId; - userLoaded: boolean = false; - hasContact: boolean = false; - hasDetails: boolean = false; - isAndroid: boolean = false; + userLoaded = false; + hasContact = false; + hasDetails = false; + isAndroid = false; user: any = {}; title: string; @@ -54,7 +54,7 @@ export class CoreUserAboutPage { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.fetchUser().finally(() => { this.userLoaded = true; }); @@ -63,7 +63,7 @@ export class CoreUserAboutPage { /** * Fetches the user and updates the view. */ - fetchUser() : Promise { + fetchUser(): Promise { return this.userProvider.getProfile(this.userId, this.courseId).then((user) => { if (user.address) { @@ -86,14 +86,15 @@ export class CoreUserAboutPage { * * @param {any} refresher Refresher. */ - refreshUser(refresher?: any) { + refreshUser(refresher?: any): void { this.userProvider.invalidateUserCache(this.userId).finally(() => { this.fetchUser().finally(() => { - this.eventsProvider.trigger(CoreUserProvider.PROFILE_REFRESHED, {courseId: this.courseId, userId: this.userId, - user: this.user}, this.siteId); + this.eventsProvider.trigger(CoreUserProvider.PROFILE_REFRESHED, { + courseId: this.courseId, userId: this.userId, + user: this.user + }, this.siteId); refresher && refresher.complete(); }); }); } - -} \ No newline at end of file +} diff --git a/src/core/user/pages/profile/profile.ts b/src/core/user/pages/profile/profile.ts index f8615b215..63b88ba5a 100644 --- a/src/core/user/pages/profile/profile.ts +++ b/src/core/user/pages/profile/profile.ts @@ -28,7 +28,7 @@ import { CoreUserDelegate } from '../../providers/user-delegate'; /** * Page that displays an user profile page. */ -@IonicPage({segment: "core-user-profile"}) +@IonicPage({ segment: 'core-user-profile' }) @Component({ selector: 'page-core-user-profile', templateUrl: 'profile.html', @@ -39,12 +39,12 @@ export class CoreUserProfilePage { protected site; protected obsProfileRefreshed: any; - userLoaded: boolean = false; - isLoadingHandlers: boolean = false; + userLoaded = false; + isLoadingHandlers = false; user: any = {}; title: string; - isDeleted: boolean = false; - canChangeProfilePicture: boolean = false; + isDeleted = false; + canChangeProfilePicture = false; actionHandlers = []; newPageHandlers = []; communicationHandlers = []; @@ -68,9 +68,9 @@ export class CoreUserProfilePage { !this.userProvider.isUpdatePictureDisabledInSite(this.site); this.obsProfileRefreshed = eventsProvider.on(CoreUserProvider.PROFILE_REFRESHED, (data) => { - if (typeof data.user != "undefined") { + if (typeof data.user != 'undefined') { this.user.email = data.user.email; - this.user.address = this.userHelper.formatAddress("", data.user.city, data.user.country); + this.user.address = this.userHelper.formatAddress('', data.user.city, data.user.country); } }, sitesProvider.getCurrentSiteId()); } @@ -78,7 +78,7 @@ export class CoreUserProfilePage { /** * View loaded. */ - ionViewDidLoad() { + ionViewDidLoad(): void { this.fetchUser().then(() => { return this.userProvider.logView(this.userId, this.courseId).catch((error) => { this.isDeleted = error.errorcode === 'userdeleted'; @@ -91,10 +91,10 @@ export class CoreUserProfilePage { /** * Fetches the user and updates the view. */ - fetchUser() : Promise { - return this.userProvider.getProfile(this.userId, this.courseId).then((user) => { + fetchUser(): Promise { + return this.userProvider.getProfile(this.userId, this.courseId).then((user) => { - user.address = this.userHelper.formatAddress("", user.city, user.country); + user.address = this.userHelper.formatAddress('', user.city, user.country); user.roles = this.userHelper.formatRoleList(user.roles); this.user = user; @@ -129,21 +129,22 @@ export class CoreUserProfilePage { }); } - /** * Opens dialog to change profile picture. */ - changeProfilePicture(){ - let maxSize = -1, + changeProfilePicture(): Promise { + const maxSize = -1, title = this.translate.instant('core.user.newpicture'), mimetypes = this.mimetypeUtils.getGroupMimeInfo('image', 'mimetypes'); return this.fileUploaderHelper.selectAndUploadFile(maxSize, title, mimetypes).then((result) => { - let modal = this.domUtils.showModalLoading('core.sending', true); + const modal = this.domUtils.showModalLoading('core.sending', true); return this.userProvider.changeProfilePicture(result.itemid, this.userId).then((profileImageURL) => { - this.eventsProvider.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, {userId: this.userId, - picture: profileImageURL}); + this.eventsProvider.trigger(CoreUserProvider.PROFILE_PICTURE_UPDATED, { + userId: this.userId, + picture: profileImageURL + }); this.sitesProvider.updateSiteInfo(this.site.getId()); this.refreshUser(); }).finally(() => { @@ -161,8 +162,8 @@ export class CoreUserProfilePage { * * @param {any} refresher Refresher. */ - refreshUser(refresher?: any) { - let promises = []; + refreshUser(refresher?: any): void { + const promises = []; promises.push(this.userProvider.invalidateUserCache(this.userId)); promises.push(this.coursesProvider.invalidateUserNavigationOptions()); @@ -170,8 +171,11 @@ export class CoreUserProfilePage { Promise.all(promises).finally(() => { this.fetchUser().finally(() => { - this.eventsProvider.trigger(CoreUserProvider.PROFILE_REFRESHED, {courseId: this.courseId, userId: this.userId, - user: this.user}, this.site.getId()); + this.eventsProvider.trigger(CoreUserProvider.PROFILE_REFRESHED, { + courseId: this.courseId, + userId: this.userId, + user: this.user + }, this.site.getId()); refresher && refresher.complete(); }); }); @@ -180,7 +184,7 @@ export class CoreUserProfilePage { /** * Page destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { this.obsProfileRefreshed && this.obsProfileRefreshed.off(); } -} \ No newline at end of file +} diff --git a/src/core/user/providers/helper.ts b/src/core/user/providers/helper.ts index ba62a3f00..044c85bd6 100644 --- a/src/core/user/providers/helper.ts +++ b/src/core/user/providers/helper.ts @@ -35,15 +35,15 @@ export class CoreUserHelperProvider { * @param {string} country Country. * @return {string} Formatted address. */ - formatAddress(address: string, city: string, country: string) : string { - let separator = this.translate.instant('core.listsep'), - values = [address, city, country]; + formatAddress(address: string, city: string, country: string): string { + const separator = this.translate.instant('core.listsep'); + let values = [address, city, country]; values = values.filter((value) => { return value && value.length > 0; }); - return values.join(separator + " "); + return values.join(separator + ' '); } /** @@ -52,16 +52,17 @@ export class CoreUserHelperProvider { * @param {any[]} [roles] List of user roles. * @return {string} The formatted roles. */ - formatRoleList(roles?: any[]) : string { + formatRoleList(roles?: any[]): string { if (!roles || roles.length <= 0) { - return ""; + return ''; } - let separator = this.translate.instant('core.listsep'); + const separator = this.translate.instant('core.listsep'); return roles.map((value) => { - let translation = this.translate.instant('core.user.' + value.shortname); + const translation = this.translate.instant('core.user.' + value.shortname); + return translation.indexOf('core.user.') < 0 ? translation : value.shortname; - }).join(separator + " "); + }).join(separator + ' '); } } diff --git a/src/core/user/providers/user-delegate.ts b/src/core/user/providers/user-delegate.ts index d32ca5caa..0787ff32b 100644 --- a/src/core/user/providers/user-delegate.ts +++ b/src/core/user/providers/user-delegate.ts @@ -106,25 +106,25 @@ export class CoreUserDelegate extends CoreDelegate { * User profile handler type for communication. * @type {string} */ - public static TYPE_COMMUNICATION = 'communication'; + static TYPE_COMMUNICATION = 'communication'; /** * User profile handler type for new page. * @type {string} */ - public static TYPE_NEW_PAGE = 'newpage'; + static TYPE_NEW_PAGE = 'newpage'; /** * User profile handler type for actions. * @type {string} */ - public static TYPE_ACTION = 'action'; + static TYPE_ACTION = 'action'; protected handlers: { [s: string]: CoreUserProfileHandler } = {}; protected enabledHandlers: { [s: string]: CoreUserProfileHandler } = {}; protected featurePrefix = '$mmUserDelegate_'; constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, - private coursesProvider: CoreCoursesProvider, protected eventsProvider: CoreEventsProvider) { + private coursesProvider: CoreCoursesProvider, protected eventsProvider: CoreEventsProvider) { super('CoreUserDelegate', loggerProvider, sitesProvider, eventsProvider); } @@ -136,24 +136,24 @@ export class CoreUserDelegate extends CoreDelegate { * @return {Promise} Resolved with an array of objects containing 'priority', 'data' and 'type'. */ getProfileHandlersFor(user: any, courseId: number): Promise { - let handlers = [], + const handlers = [], promises = []; // Retrieve course options forcing cache. return this.coursesProvider.getUserCourses(true).then((courses) => { - let courseIds = courses.map((course) => { + const courseIds = courses.map((course) => { return course.id; }); return this.coursesProvider.getCoursesAdminAndNavOptions(courseIds).then((options) => { // For backwards compatibility we don't modify the courseId. - let courseIdForOptions = courseId || this.sitesProvider.getCurrentSiteHomeId(), + const courseIdForOptions = courseId || this.sitesProvider.getCurrentSiteHomeId(), navOptions = options.navOptions[courseIdForOptions], admOptions = options.admOptions[courseIdForOptions]; - for (let name in this.enabledHandlers) { + for (const name in this.enabledHandlers) { // Checks if the handler is enabled for the user. - let handler = this.handlers[name], + const handler = this.handlers[name], isEnabledForUser = handler.isEnabledForUser(user, courseId, navOptions, admOptions), promise = Promise.resolve(isEnabledForUser).then((enabled) => { if (enabled) { diff --git a/src/core/user/providers/user-handler.ts b/src/core/user/providers/user-handler.ts index 971851e48..0c50827a1 100644 --- a/src/core/user/providers/user-handler.ts +++ b/src/core/user/providers/user-handler.ts @@ -25,7 +25,7 @@ export class CoreUserProfileMailHandler implements CoreUserProfileHandler { priority = 700; type = CoreUserDelegate.TYPE_COMMUNICATION; - constructor(protected sitesProvider: CoreSitesProvider) {} + constructor(protected sitesProvider: CoreSitesProvider) { } /** * Check if handler is enabled. @@ -45,10 +45,10 @@ export class CoreUserProfileMailHandler implements CoreUserProfileHandler { * @param {any} [admOptions] Course admin options for current user. See $mmCourses#getUserAdministrationOptions. * @return {boolean|Promise} Promise resolved with true if enabled, resolved with false otherwise. */ - isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean|Promise { + isEnabledForUser(user: any, courseId: number, navOptions?: any, admOptions?: any): boolean | Promise { // Not current user required. return user.id != this.sitesProvider.getCurrentSite().getUserId() && user.email; - }; + } /** * Returns the data needed to render the handler. @@ -60,10 +60,10 @@ export class CoreUserProfileMailHandler implements CoreUserProfileHandler { icon: 'mail', title: 'core.user.sendemail', class: 'core-user-profile-mail', - action: ($event, user, courseId) => { + action: ($event, user, courseId): void => { $event.preventDefault(); $event.stopPropagation(); - window.open("mailto:" + user.email, '_blank'); + window.open('mailto:' + user.email, '_blank'); } }; } diff --git a/src/core/user/providers/user-link-handler.ts b/src/core/user/providers/user-link-handler.ts index 6d9020fe4..5495105d9 100644 --- a/src/core/user/providers/user-link-handler.ts +++ b/src/core/user/providers/user-link-handler.ts @@ -39,11 +39,11 @@ export class CoreUserProfileLinkHandler extends CoreContentLinksHandlerBase { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {CoreContentLinksAction[]|Promise} List of (or promise resolved with list of) actions. */ - getActions(siteIds: string[], url: string, params: any, courseId?: number) : - CoreContentLinksAction[]|Promise { + getActions(siteIds: string[], url: string, params: any, courseId?: number): + CoreContentLinksAction[] | Promise { return [{ - action: (siteId, navCtrl?) => { - let pageParams = { + action: (siteId, navCtrl?): void => { + const pageParams = { courseId: params.course, userId: parseInt(params.id, 10) }; @@ -63,7 +63,7 @@ export class CoreUserProfileLinkHandler extends CoreContentLinksHandlerBase { * @param {number} [courseId] Course ID related to the URL. Optional but recommended. * @return {boolean|Promise} Whether the handler is enabled for the URL and site. */ - isEnabled(siteId: string, url: string, params: any, courseId?: number) : boolean|Promise { + isEnabled(siteId: string, url: string, params: any, courseId?: number): boolean | Promise { return url.indexOf('/grade/report/') == -1; } } diff --git a/src/core/user/providers/user-profile-field-delegate.ts b/src/core/user/providers/user-profile-field-delegate.ts index 72cbc2b5a..5f924896d 100644 --- a/src/core/user/providers/user-profile-field-delegate.ts +++ b/src/core/user/providers/user-profile-field-delegate.ts @@ -18,7 +18,7 @@ import { CoreLoggerProvider } from '../../../providers/logger'; import { CoreSitesProvider } from '../../../providers/sites'; import { CoreEventsProvider } from '../../../providers/events'; -export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { +export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { /** * Return the Component to use to display the user profile field. @@ -37,7 +37,7 @@ export interface CoreUserProfileFieldHandler extends CoreDelegateHandler { */ getData?(field: any, signup: boolean, registerAuth: string, formValues: any): Promise | CoreUserProfileFieldHandlerData; -}; +} export interface CoreUserProfileFieldHandlerData { /** @@ -57,15 +57,15 @@ export interface CoreUserProfileFieldHandlerData { * @type {any} */ value: any; -}; +} /** * Service to interact with user profile fields. Provides functions to register a plugin. */ @Injectable() export class CoreUserProfileFieldDelegate extends CoreDelegate { - protected handlers: {[s: string]: CoreUserProfileFieldHandler} = {}; - protected enabledHandlers: {[s: string]: CoreUserProfileFieldHandler} = {}; + protected handlers: { [s: string]: CoreUserProfileFieldHandler } = {}; + protected enabledHandlers: { [s: string]: CoreUserProfileFieldHandler } = {}; constructor(protected loggerProvider: CoreLoggerProvider, protected sitesProvider: CoreSitesProvider, protected eventsProvider: CoreEventsProvider) { @@ -79,8 +79,8 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { * @param {boolean} signup True if user is in signup page. * @return {any} The component to use, undefined if not found. */ - getComponent(field: any, signup: boolean) : any { - let type = field.type || field.datatype; + getComponent(field: any, signup: boolean): any { + const type = field.type || field.datatype; if (signup) { return this.executeFunction(type, 'getComponent'); } else { @@ -98,10 +98,10 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { * @return {Promise} Data to send for the field. */ getDataForField(field: any, signup: boolean, registerAuth: string, formValues: any): Promise { - let type = field.type || field.datatype, + const type = field.type || field.datatype, handler = this.getHandler(type, !signup); if (handler) { - let name = 'profile_field_' + field.shortname; + const name = 'profile_field_' + field.shortname; if (handler.getData) { return Promise.resolve(handler.getData(field, signup, registerAuth, formValues)); } else if (field.shortname && typeof formValues[name] != 'undefined') { @@ -113,6 +113,7 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { }); } } + return Promise.reject(null); } @@ -125,8 +126,8 @@ export class CoreUserProfileFieldDelegate extends CoreDelegate { * @param {any} formValues Form values. * @return {Promise} Data to send. */ - getDataForFields(fields: any[], signup = false, registerAuth = "", formValues: any): Promise { - let result = [], + getDataForFields(fields: any[], signup: boolean = false, registerAuth: string = '', formValues: any): Promise { + const result = [], promises = []; fields.forEach((field) => { diff --git a/src/core/user/providers/user.ts b/src/core/user/providers/user.ts index 97dcc35fc..b2502f788 100644 --- a/src/core/user/providers/user.ts +++ b/src/core/user/providers/user.ts @@ -23,8 +23,8 @@ import { CoreUtilsProvider } from '../../../providers/utils/utils'; */ @Injectable() export class CoreUserProvider { - public static PROFILE_REFRESHED = 'CoreUserProfileRefreshed'; - public static PROFILE_PICTURE_UPDATED = 'CoreUserProfilePictureUpdated'; + static PROFILE_REFRESHED = 'CoreUserProfileRefreshed'; + static PROFILE_PICTURE_UPDATED = 'CoreUserProfilePictureUpdated'; protected ROOT_CACHE_KEY = 'mmUser:'; // Variables for database. @@ -65,16 +65,17 @@ export class CoreUserProvider { * @return {Promise} Promise resolve with the new profileimageurl */ changeProfilePicture(draftItemId: number, userId: number): Promise { - var data = { - 'draftitemid': draftItemId, - 'delete': 0, - 'userid': userId + const data = { + draftitemid: draftItemId, + delete: 0, + userid: userId }; return this.sitesProvider.getCurrentSite().write('core_user_update_picture', data).then((result) => { if (!result.success) { return Promise.reject(null); } + return result.profileimageurl; }); } @@ -91,7 +92,7 @@ export class CoreUserProvider { return Promise.reject(null); } - let promises = []; + const promises = []; siteId = siteId || this.sitesProvider.getCurrentSiteId(); @@ -114,7 +115,7 @@ export class CoreUserProvider { * @param {string} [siteId] ID of the site. If not defined, use current site. * @return {Promise} Promise resolved with the user data. */ - getProfile(userId: number, courseId?: number, forceLocal = false, siteId?: string): Promise { + getProfile(userId: number, courseId?: number, forceLocal: boolean = false, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (forceLocal) { @@ -122,6 +123,7 @@ export class CoreUserProvider { return this.getUserFromWS(userId, courseId, siteId); }); } + return this.getUserFromWS(userId, courseId, siteId).catch(() => { return this.getUserFromLocalDb(userId, siteId); }); @@ -133,7 +135,7 @@ export class CoreUserProvider { * @param {number} userId User ID. * @return {string} Cache key. */ - protected getUserCacheKey(userId): string { + protected getUserCacheKey(userId: number): string { return this.ROOT_CACHE_KEY + 'data:' + userId; } @@ -146,7 +148,7 @@ export class CoreUserProvider { */ protected getUserFromLocalDb(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - return site.getDb().getRecord(this.USERS_TABLE, {id: userId}); + return site.getDb().getRecord(this.USERS_TABLE, { id: userId }); }); } @@ -160,18 +162,22 @@ export class CoreUserProvider { */ protected getUserFromWS(userId: number, courseId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let presets = { + const presets = { cacheKey: this.getUserCacheKey(userId) - }, - wsName, data; + }; + let wsName, data; // Determine WS and data to use. if (courseId && courseId != site.getSiteHomeId()) { this.logger.debug(`Get participant with ID '${userId}' in course '${courseId}`); wsName = 'core_user_get_course_user_profiles'; data = { - "userlist[0][userid]": userId, - "userlist[0][courseid]": courseId + userlist: [ + { + userid: userId, + courseid: courseId + } + ] }; } else { this.logger.debug(`Get user with ID '${userId}'`); @@ -187,11 +193,12 @@ export class CoreUserProvider { return Promise.reject('Cannot retrieve user info.'); } - var user = users.shift(); + const user = users.shift(); if (user.country) { user.country = this.utils.getCountryName(user.country); } this.storeUser(user.id, user.fullname, user.profileimageurl); + return user; }); @@ -205,11 +212,11 @@ export class CoreUserProvider { * @param {string} [siteId] Site Id. If not defined, use current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateUserCache(userId: number, siteId?: string) : Promise { + invalidateUserCache(userId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getUserCacheKey(userId)); }); - }; + } /** * Check if update profile picture is disabled in a certain site. @@ -217,11 +224,11 @@ export class CoreUserProvider { * @param {CoreSite} [site] Site. If not defined, use current site. * @return {boolean} True if disabled, false otherwise. */ - isUpdatePictureDisabledInSite(site?: CoreSite) : boolean { + isUpdatePictureDisabledInSite(site?: CoreSite): boolean { site = site || this.sitesProvider.getCurrentSite(); - return site.isFeatureDisabled('$mmUserDelegate_picture'); - }; + return site.isFeatureDisabled('$mmUserDelegate_picture'); + } /** * Log User Profile View in Moodle. @@ -229,14 +236,15 @@ export class CoreUserProvider { * @param {number} [courseId] Course ID. * @return {Promise} Promise resolved when done. */ - logView(userId: number, courseId?: number) : Promise { - let params = { + logView(userId: number, courseId?: number): Promise { + const params = { userid: userId }; if (courseId) { params['courseid'] = courseId; } + return this.sitesProvider.getCurrentSite().write('core_user_view_user_profile', params); } @@ -251,13 +259,13 @@ export class CoreUserProvider { */ protected storeUser(userId: number, fullname: string, avatar: string, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let userRecord = { + const userRecord = { id: userId, fullname: fullname, profileimageurl: avatar }; - return site.getDb().insertOrUpdateRecord(this.USERS_TABLE, userRecord, {id: userId}); + return site.getDb().insertOrUpdateRecord(this.USERS_TABLE, userRecord, { id: userId }); }); } } diff --git a/src/core/user/user.module.ts b/src/core/user/user.module.ts index 62fb1af70..4f86bb899 100644 --- a/src/core/user/user.module.ts +++ b/src/core/user/user.module.ts @@ -46,8 +46,9 @@ export class CoreUserModule { eventsProvider.on(CoreEventsProvider.USER_DELETED, (data) => { // Search for userid in params. - let params = data.params, - userId = 0; + const params = data.params; + let userId = 0; + if (params.userid) { userId = params.userid; } else if (params.userids) { diff --git a/src/core/viewer/pages/iframe/iframe.ts b/src/core/viewer/pages/iframe/iframe.ts index ee598859a..63fda7f3f 100644 --- a/src/core/viewer/pages/iframe/iframe.ts +++ b/src/core/viewer/pages/iframe/iframe.ts @@ -18,7 +18,7 @@ import { IonicPage, NavParams } from 'ionic-angular'; /** * Page to display a URL in an iframe. */ -@IonicPage({segment: "core-viewer-iframe"}) +@IonicPage({ segment: 'core-viewer-iframe' }) @Component({ selector: 'page-core-viewer-iframe', templateUrl: 'iframe.html', @@ -31,4 +31,4 @@ export class CoreViewerIframePage { this.title = params.get('title'); this.url = params.get('url'); } -} \ No newline at end of file +} diff --git a/src/core/viewer/pages/image/image.ts b/src/core/viewer/pages/image/image.ts index 97eab737e..b1e7ced87 100644 --- a/src/core/viewer/pages/image/image.ts +++ b/src/core/viewer/pages/image/image.ts @@ -19,7 +19,7 @@ import { IonicPage, ViewController, NavParams } from 'ionic-angular'; /** * Page to view an image. If opened as a modal, it will have a button to close the modal. */ -@IonicPage({segment: 'core-viewer-image'}) +@IonicPage({ segment: 'core-viewer-image' }) @Component({ selector: 'page-core-viewer-image', templateUrl: 'image.html', @@ -28,7 +28,7 @@ export class CoreViewerImagePage { title: string; // Page title. image: string; // Image URL. component: string; // Component to use in external-content. - componentId: string|number; // Component ID to use in external-content. + componentId: string | number; // Component ID to use in external-content. constructor(private viewCtrl: ViewController, params: NavParams, translate: TranslateService) { this.title = params.get('title') || translate.instant('core.imageviewer'); @@ -40,7 +40,7 @@ export class CoreViewerImagePage { /** * Close modal. */ - closeModal() : void { + closeModal(): void { this.viewCtrl.dismiss(); } -} \ No newline at end of file +} diff --git a/src/core/viewer/pages/text/text.module.ts b/src/core/viewer/pages/text/text.module.ts index fa7f8b168..01fc3cf39 100644 --- a/src/core/viewer/pages/text/text.module.ts +++ b/src/core/viewer/pages/text/text.module.ts @@ -18,6 +18,9 @@ import { TranslateModule } from '@ngx-translate/core'; import { CoreViewerTextPage } from './text'; import { CoreDirectivesModule } from '../../../../directives/directives.module'; +/** + * Module to lazy load the page. + */ @NgModule({ declarations: [ CoreViewerTextPage diff --git a/src/core/viewer/pages/text/text.ts b/src/core/viewer/pages/text/text.ts index e0647bf30..64da6c45f 100644 --- a/src/core/viewer/pages/text/text.ts +++ b/src/core/viewer/pages/text/text.ts @@ -19,7 +19,7 @@ import { CoreTextUtilsProvider } from '../../../../providers/utils/text'; /** * Page to render a certain text. If opened as a modal, it will have a button to close the modal. */ -@IonicPage({segment: "core-viewer-text"}) +@IonicPage({ segment: 'core-viewer-text' }) @Component({ selector: 'page-core-viewer-text', templateUrl: 'text.html', @@ -28,7 +28,7 @@ export class CoreViewerTextPage { title: string; // Page title. content: string; // Page content. component: string; // Component to use in format-text. - componentId: string|number; // Component ID to use in format-text. + componentId: string | number; // Component ID to use in format-text. constructor(private viewCtrl: ViewController, params: NavParams, textUtils: CoreTextUtilsProvider) { this.title = params.get('title'); @@ -40,7 +40,7 @@ export class CoreViewerTextPage { /** * Close modal. */ - closeModal() : void { + closeModal(): void { this.viewCtrl.dismiss(); } -} \ No newline at end of file +} diff --git a/src/directives/auto-focus.ts b/src/directives/auto-focus.ts index 57081c3b7..98988335a 100644 --- a/src/directives/auto-focus.ts +++ b/src/directives/auto-focus.ts @@ -26,7 +26,7 @@ import { CoreUtilsProvider } from '../providers/utils/utils'; selector: '[core-auto-focus]' }) export class CoreAutoFocusDirective implements OnInit { - @Input('core-auto-focus') coreAutoFocus: boolean|string = true; + @Input('core-auto-focus') coreAutoFocus: boolean | string = true; protected element: HTMLElement; @@ -38,10 +38,10 @@ export class CoreAutoFocusDirective implements OnInit { /** * Component being initialized. */ - ngOnInit() { + ngOnInit(): void { if (this.navCtrl.isTransitioning()) { // Navigating to a new page. Wait for the transition to be over. - let subscription = this.navCtrl.viewDidEnter.subscribe(() => { + const subscription = this.navCtrl.viewDidEnter.subscribe(() => { this.autoFocus(); subscription.unsubscribe(); }); @@ -53,7 +53,7 @@ export class CoreAutoFocusDirective implements OnInit { /** * Function after the view is initialized. */ - protected autoFocus() { + protected autoFocus(): void { const autoFocus = this.utils.isTrueOrOne(this.coreAutoFocus); if (autoFocus) { // If it's a ion-input or ion-textarea, search the right input to use. diff --git a/src/directives/external-content.ts b/src/directives/external-content.ts index a91ff93ce..b6e89722b 100644 --- a/src/directives/external-content.ts +++ b/src/directives/external-content.ts @@ -35,7 +35,7 @@ import { CoreUrlUtilsProvider } from '../providers/utils/url'; export class CoreExternalContentDirective implements AfterViewInit { @Input() siteId?: string; // Site ID to use. @Input() component?: string; // Component to link the file to. - @Input() componentId?: string|number; // Component ID to use in conjunction with the component. + @Input() componentId?: string | number; // Component ID to use in conjunction with the component. protected element: HTMLElement; protected logger; @@ -51,12 +51,12 @@ export class CoreExternalContentDirective implements AfterViewInit { /** * View has been initialized */ - ngAfterViewInit() { - let currentSite = this.sitesProvider.getCurrentSite(), + ngAfterViewInit(): void { + const currentSite = this.sitesProvider.getCurrentSite(), siteId = this.siteId || (currentSite && currentSite.getId()), - targetAttr, - sourceAttr, tagName = this.element.tagName; + let targetAttr, + sourceAttr; if (tagName === 'A') { targetAttr = 'href'; @@ -66,12 +66,12 @@ export class CoreExternalContentDirective implements AfterViewInit { targetAttr = 'src'; sourceAttr = 'src'; - } else if (tagName === 'AUDIO' || tagName === 'VIDEO' || tagName === 'SOURCE' || tagName === 'TRACK') { + } else if (tagName === 'AUDIO' || tagName === 'VIDEO' || tagName === 'SOURCE' || tagName === 'TRACK') { targetAttr = 'src'; sourceAttr = 'targetSrc'; if (tagName === 'VIDEO') { - let poster = (this.element).poster; + const poster = ( this.element).poster; if (poster) { // Handle poster. this.handleExternalContent('poster', poster, siteId).catch(() => { @@ -83,10 +83,11 @@ export class CoreExternalContentDirective implements AfterViewInit { } else { // Unsupported tag. this.logger.warn('Directive attached to non-supported tag: ' + tagName); + return; } - let url = this.element.getAttribute(sourceAttr) || this.element.getAttribute(targetAttr); + const url = this.element.getAttribute(sourceAttr) || this.element.getAttribute(targetAttr); this.handleExternalContent(targetAttr, url, siteId).catch(() => { // Ignore errors. }); @@ -97,12 +98,12 @@ export class CoreExternalContentDirective implements AfterViewInit { * * @param {string} url URL to use in the source. */ - protected addSource(url: string) : void { + protected addSource(url: string): void { if (this.element.tagName !== 'SOURCE') { return; } - let newSource = document.createElement('source'), + const newSource = document.createElement('source'), type = this.element.getAttribute('type'); newSource.setAttribute('src', url); @@ -126,19 +127,19 @@ export class CoreExternalContentDirective implements AfterViewInit { * @param {string} [siteId] Site ID. * @return {Promise} Promise resolved if the element is successfully treated. */ - protected handleExternalContent(targetAttr: string, url: string, siteId?: string) : Promise { + protected handleExternalContent(targetAttr: string, url: string, siteId?: string): Promise { const tagName = this.element.tagName; if (tagName == 'VIDEO' && targetAttr != 'poster') { - let video = this.element; + const video = this.element; if (video.textTracks) { // It's a video with subtitles. In iOS, subtitles position is wrong so it needs to be fixed. - video.textTracks.onaddtrack = (event) => { - let track = event.track; + video.textTracks.onaddtrack = (event): void => { + const track = event.track; if (track) { - track.oncuechange = () => { - var line = this.platform.is('tablet') || this.platform.is('android') ? 90 : 80; + track.oncuechange = (): void => { + const line = this.platform.is('tablet') || this.platform.is('android') ? 90 : 80; // Position all subtitles to a percentage of video height. Array.from(track.cues).forEach((cue: any) => { cue.snapToLines = false; @@ -160,6 +161,7 @@ export class CoreExternalContentDirective implements AfterViewInit { // Restoring original src. this.addSource(url); } + return Promise.reject(null); } @@ -167,12 +169,13 @@ export class CoreExternalContentDirective implements AfterViewInit { 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. + return Promise.reject(null); } // Download images, tracks and posters if size is unknown. - let promise, - dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster'; + const dwnUnknown = tagName == 'IMG' || tagName == 'TRACK' || targetAttr == 'poster'; + let promise; if (targetAttr === 'src' && tagName !== 'SOURCE' && tagName !== 'TRACK') { promise = this.filepoolProvider.getSrcByUrl(siteId, url, this.component, this.componentId, 0, true, dwnUnknown); @@ -192,9 +195,9 @@ export class CoreExternalContentDirective implements AfterViewInit { // Set events to download big files (not downloaded automatically). if (finalUrl.indexOf('http') === 0 && targetAttr != 'poster' && - (tagName == 'VIDEO' || tagName == 'AUDIO' || tagName == 'A' || tagName == 'SOURCE')) { - let eventName = tagName == 'A' ? 'click' : 'play', - clickableEl = this.element; + (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'); diff --git a/src/directives/format-text.ts b/src/directives/format-text.ts index af8a04d4b..cf492bd2d 100644 --- a/src/directives/format-text.ts +++ b/src/directives/format-text.ts @@ -43,16 +43,15 @@ export class CoreFormatTextDirective implements OnChanges { @Input() text: string; // The text to format. @Input() siteId?: string; // Site ID to use. @Input() component?: string; // Component for CoreExternalContentDirective. - @Input() componentId?: string|number; // Component ID to use in conjunction with the component. - @Input() adaptImg?: boolean|string = true; // Whether to adapt images to screen width. - @Input() clean?: boolean|string; // Whether all the HTML tags should be removed. - @Input() singleLine?: boolean|string; // Whether new lines should be removed (all text in single line). Only valid if - // clean=true. + @Input() componentId?: string | number; // Component ID to use in conjunction with the component. + @Input() adaptImg?: boolean | string = true; // Whether to adapt images to screen width. + @Input() clean?: boolean | string; // Whether all the HTML tags should be removed. + @Input() singleLine?: boolean | string; // Whether new lines should be removed (all text in single line). Only if clean=true. @Input() maxHeight?: number; // Max height in pixels to render the content box. It should be 50 at least to make sense. - // Using this parameter will force display: block to calculate height better. If you want to - // avoid this use class="inline" at the same time to use display: inline-block. - @Input() fullOnClick?: boolean|string; // Whether it should open a new page with the full contents on click. Only if - // "max-height" is set and the content has been collapsed. + // Using this parameter will force display: block to calculate height better. + // If you want to avoid this use class="inline" at the same time to use display: inline-block. + @Input() fullOnClick?: boolean | string; // Whether it should open a new page with the full contents on click. + // Only if "max-height" is set and the content has been collapsed. @Input() fullTitle?: string; // Title to use in full view. Defaults to "Description". @Output() afterRender?: EventEmitter; // Called when the data is rendered. @@ -72,7 +71,7 @@ export class CoreFormatTextDirective implements OnChanges { /** * Detect changes on input properties. */ - ngOnChanges(changes: {[name: string]: SimpleChange}) { + ngOnChanges(changes: { [name: string]: SimpleChange }): void { if (changes.text) { this.formatAndRenderContents(); } @@ -83,10 +82,10 @@ export class CoreFormatTextDirective implements OnChanges { * * @param {HTMLElement} element Element to add the attributes to. */ - protected addExternalContent(element: HTMLElement) : void { + protected addExternalContent(element: HTMLElement): void { // Angular 2 doesn't let adding directives dynamically. Create the CoreExternalContentDirective manually. - let extContent = new CoreExternalContentDirective(element, this.loggerProvider, this.filepoolProvider, this.platform, - this.sitesProvider, this.domUtils, this.urlUtils, this.appProvider); + const extContent = new CoreExternalContentDirective( element, this.loggerProvider, this.filepoolProvider, + this.platform, this.sitesProvider, this.domUtils, this.urlUtils, this.appProvider); extContent.component = this.component; extContent.componentId = this.componentId; @@ -100,7 +99,7 @@ export class CoreFormatTextDirective implements OnChanges { * * @param {HTMLElement} element Element to add the class to. */ - protected addMediaAdaptClass(element: HTMLElement) : void { + protected addMediaAdaptClass(element: HTMLElement): void { element.classList.add('core-media-adapt-width'); } @@ -110,8 +109,8 @@ export class CoreFormatTextDirective implements OnChanges { * @param {number} elWidth Width of the directive's element. * @param {HTMLElement} img Image to adapt. */ - protected adaptImage(elWidth: number, img: HTMLElement) : void { - let imgWidth = this.getElementWidth(img), + protected adaptImage(elWidth: number, img: HTMLElement): void { + const imgWidth = this.getElementWidth(img), // Element to wrap the image. container = document.createElement('span'); @@ -137,8 +136,8 @@ export class CoreFormatTextDirective implements OnChanges { * @param {HTMLElement} container The container of the image. * @param {HTMLElement} img The image. */ - addMagnifyingGlass(container: HTMLElement, img: HTMLElement) : void { - let imgSrc = this.textUtils.escapeHTML(img.getAttribute('src')), + addMagnifyingGlass(container: HTMLElement, img: HTMLElement): void { + const imgSrc = this.textUtils.escapeHTML(img.getAttribute('src')), label = this.textUtils.escapeHTML(this.translate.instant('core.openfullimage')), anchor = document.createElement('a'); @@ -159,7 +158,7 @@ export class CoreFormatTextDirective implements OnChanges { /** * Finish the rendering, displaying the element again and calling afterRender. */ - protected finishRender() : void { + protected finishRender(): void { // Show the element again. this.element.classList.remove('opacity-hide'); // Emit the afterRender output. @@ -169,10 +168,11 @@ export class CoreFormatTextDirective implements OnChanges { /** * Format contents and render. */ - protected formatAndRenderContents() : void { + protected formatAndRenderContents(): void { if (!this.text) { this.element.innerHTML = ''; // Remove current contents. this.finishRender(); + return; } @@ -183,7 +183,7 @@ export class CoreFormatTextDirective implements OnChanges { this.element.classList.add('core-disable-media-adapt'); this.element.innerHTML = ''; // Remove current contents. - if (this.maxHeight && div.innerHTML != "") { + if (this.maxHeight && div.innerHTML != '') { // Move the children to the current element to be able to calculate the height. // @todo: Display the element? this.domUtils.moveChildren(div, this.element); @@ -191,11 +191,11 @@ export class CoreFormatTextDirective implements OnChanges { // Height cannot be calculated if the element is not shown while calculating. // Force shorten if it was previously shortened. // @todo: Work on calculate this height better. - let height = this.element.style.maxHeight ? 0 : this.getElementHeight(this.element); + const height = this.element.style.maxHeight ? 0 : this.getElementHeight(this.element); // If cannot calculate height, shorten always. if (!height || height > this.maxHeight) { - let expandInFullview = this.utils.isTrueOrOne(this.fullOnClick) || false, + const expandInFullview = this.utils.isTrueOrOne(this.fullOnClick) || false, showMoreDiv = document.createElement('div'); showMoreDiv.classList.add('core-show-more'); @@ -213,13 +213,14 @@ export class CoreFormatTextDirective implements OnChanges { e.preventDefault(); e.stopPropagation(); - let target = e.target; + const target = e.target; if (this.tagsToIgnore.indexOf(target.tagName) === -1 || (target.tagName === 'A' && - !target.getAttribute('href'))) { + !target.getAttribute('href'))) { if (!expandInFullview) { // Change class. this.element.classList.toggle('core-shortened'); + return; } } @@ -243,7 +244,7 @@ export class CoreFormatTextDirective implements OnChanges { * * @return {Promise} Promise resolved with a div element containing the code. */ - protected formatContents() : Promise { + protected formatContents(): Promise { let site: CoreSite; @@ -257,9 +258,9 @@ export class CoreFormatTextDirective implements OnChanges { return this.textUtils.formatText(this.text, this.utils.isTrueOrOne(this.clean), this.utils.isTrueOrOne(this.singleLine)); }).then((formatted) => { - let div = document.createElement('div'), - canTreatVimeo = site && site.isVersionGreaterEqualThan(['3.3.4', '3.4']), - images, + const div = document.createElement('div'), + canTreatVimeo = site && site.isVersionGreaterEqualThan(['3.3.4', '3.4']); + let images, anchors, audios, videos, @@ -278,8 +279,8 @@ export class CoreFormatTextDirective implements OnChanges { // Important: We need to look for links first because in 'img' we add new links without core-link. anchors.forEach((anchor) => { // Angular 2 doesn't let adding directives dynamically. Create the CoreLinkDirective manually. - let linkDir = new CoreLinkDirective(anchor, this.domUtils, this.utils, this.sitesProvider, this.urlUtils, - this.contentLinksHelper, this.navCtrl); + const linkDir = new CoreLinkDirective(anchor, this.domUtils, this.utils, this.sitesProvider, this.urlUtils, + this.contentLinksHelper, this.navCtrl); linkDir.capture = true; linkDir.ngOnInit(); @@ -288,7 +289,7 @@ export class CoreFormatTextDirective implements OnChanges { if (images && images.length > 0) { // If cannot calculate element's width, use a medium number to avoid false adapt image icons appearing. - let elWidth = this.getElementWidth(this.element) || 100; + const elWidth = this.getElementWidth(this.element) || 100; // Walk through the content to find images, and add our directive. images.forEach((img: HTMLElement) => { @@ -337,12 +338,12 @@ export class CoreFormatTextDirective implements OnChanges { * @param {HTMLElement} element Element to get width from. * @return {number} The width of the element in pixels. When 0 is returned it means the element is not visible. */ - protected getElementWidth(element: HTMLElement) : number { + protected getElementWidth(element: HTMLElement): number { let width = this.domUtils.getElementWidth(element); if (!width) { // All elements inside are floating or inline. Change display mode to allow calculate the width. - let parentWidth = this.domUtils.getElementWidth(element.parentNode, true, false, false, true), + const parentWidth = this.domUtils.getElementWidth(element.parentNode, true, false, false, true), previousDisplay = getComputedStyle(element, null).display; element.style.display = 'inline-block'; @@ -366,7 +367,7 @@ export class CoreFormatTextDirective implements OnChanges { * @param {HTMLElement} elementAng Element to get height from. * @return {number} The height of the element in pixels. When 0 is returned it means the element is not visible. */ - protected getElementHeight(element: HTMLElement) : number { + protected getElementHeight(element: HTMLElement): number { return this.domUtils.getElementHeight(element) || 0; } @@ -375,13 +376,13 @@ export class CoreFormatTextDirective implements OnChanges { * * @param {HTMLElement} el Video element. */ - protected treatVideoFilters(video: HTMLElement) : void { + protected treatVideoFilters(video: HTMLElement): void { // Treat Video JS Youtube video links and translate them to iframes. if (!video.classList.contains('video-js')) { return; } - let data = JSON.parse(video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'), + const data = JSON.parse(video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}'), youtubeId = data.techOrder && data.techOrder[0] && data.techOrder[0] == 'youtube' && data.sources && data.sources[0] && data.sources[0].src && this.youtubeGetId(data.sources[0].src); @@ -389,7 +390,7 @@ export class CoreFormatTextDirective implements OnChanges { return; } - let iframe = document.createElement('iframe'); + const iframe = document.createElement('iframe'); iframe.id = video.id; iframe.src = 'https://www.youtube.com/embed/' + youtubeId; iframe.setAttribute('frameborder', '0'); @@ -405,11 +406,11 @@ export class CoreFormatTextDirective implements OnChanges { * * @param {HTMLElement} element Video or audio to treat. */ - protected treatMedia(element: HTMLElement) : void { + protected treatMedia(element: HTMLElement): void { this.addMediaAdaptClass(element); this.addExternalContent(element); - let sources = Array.from(element.querySelectorAll('source')), + const sources = Array.from(element.querySelectorAll('source')), tracks = Array.from(element.querySelectorAll('track')); sources.forEach((source) => { @@ -430,15 +431,15 @@ export class CoreFormatTextDirective implements OnChanges { * @param {CoreSite} site Site instance. * @param {Boolean} canTreatVimeo Whether Vimeo videos can be treated in the site. */ - protected treatIframe(iframe: HTMLIFrameElement, site: CoreSite, canTreatVimeo: boolean) : void { + protected treatIframe(iframe: HTMLIFrameElement, site: CoreSite, canTreatVimeo: boolean): void { this.addMediaAdaptClass(iframe); if (iframe.src && canTreatVimeo) { // Check if it's a Vimeo video. If it is, use the wsplayer script instead to make restricted videos work. - let matches = iframe.src.match(/https?:\/\/player\.vimeo\.com\/video\/([^\/]*)/); + const matches = iframe.src.match(/https?:\/\/player\.vimeo\.com\/video\/([^\/]*)/); if (matches && matches[1]) { let newUrl = this.textUtils.concatenatePaths(site.getURL(), '/media/player/vimeo/wsplayer.php?video=') + - matches[1] + '&token=' + site.getToken(); + matches[1] + '&token=' + site.getToken(); if (iframe.width) { newUrl = newUrl + '&width=' + iframe.width; } @@ -457,9 +458,10 @@ export class CoreFormatTextDirective implements OnChanges { * * @param {string} url URL of the video. */ - protected youtubeGetId(url: string) : string { - let regExp = /^.*(?:(?:youtu.be\/)|(?:v\/)|(?:\/u\/\w\/)|(?:embed\/)|(?:watch\?))\??v?=?([^#\&\?]*).*/, + protected youtubeGetId(url: string): string { + const regExp = /^.*(?:(?:youtu.be\/)|(?:v\/)|(?:\/u\/\w\/)|(?:embed\/)|(?:watch\?))\??v?=?([^#\&\?]*).*/, match = url.match(regExp); + return (match && match[1].length == 11) ? match[1] : ''; } } diff --git a/src/directives/keep-keyboard.ts b/src/directives/keep-keyboard.ts index ed22cfc7e..f953020a7 100644 --- a/src/directives/keep-keyboard.ts +++ b/src/directives/keep-keyboard.ts @@ -45,15 +45,15 @@ import { CoreUtilsProvider } from '../providers/utils/utils'; }) export class CoreKeepKeyboardDirective implements AfterViewInit, OnDestroy { @Input('core-keep-keyboard') selector: string; // Selector to identify the button or input. - @Input() inButton?: boolean|string; // Whether this directive is applied to the button (true) or to the input (false). + @Input() inButton?: boolean | string; // Whether this directive is applied to the button (true) or to the input (false). protected element: HTMLElement; // Current element. protected button: HTMLElement; // Button element. protected input: HTMLElement; // Input element. protected lastFocusOut = 0; // Last time the input was focused out. - protected clickListener : any; // Listener for clicks in the button. - protected focusOutListener : any; // Listener for focusout in the input. - protected focusAgainListener : any; // Another listener for focusout, with the purpose to focus again. + protected clickListener: any; // Listener for clicks in the button. + protected focusOutListener: any; // Listener for focusout in the input. + protected focusAgainListener: any; // Another listener for focusout, with the purpose to focus again. protected stopFocusAgainTimeout: any; // Timeout to stop focus again listener. constructor(element: ElementRef, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider) { @@ -63,12 +63,11 @@ export class CoreKeepKeyboardDirective implements AfterViewInit, OnDestroy { /** * View has been initialized. */ - ngAfterViewInit() { - // Use a setTimeout because, if this directive is applied to a button, then the ion-input that it affects - // maybe it hasn't been treated yet. + ngAfterViewInit(): void { + // Use a setTimeout because to make sure that child components have been treated. setTimeout(() => { - let inButton = this.utils.isTrueOrOne(this.inButton), - candidateEls, + const inButton = this.utils.isTrueOrOne(this.inButton); + let candidateEls, selectedEl; if (typeof this.selector != 'string' || !this.selector) { @@ -121,7 +120,7 @@ export class CoreKeepKeyboardDirective implements AfterViewInit, OnDestroy { /** * Component destroyed. */ - ngOnDestroy() { + ngOnDestroy(): void { if (this.button && this.clickListener) { this.button.removeEventListener('click', this.clickListener); } @@ -133,19 +132,11 @@ export class CoreKeepKeyboardDirective implements AfterViewInit, OnDestroy { /** * The button we're interested in was clicked. */ - protected buttonClicked() : void { + protected buttonClicked(): void { if (document.activeElement == this.input) { // Directive's element is focused at the time the button is clicked. Listen for focusout to focus it again. this.focusAgainListener = this.focusElementAgain.bind(this); this.input.addEventListener('focusout', this.focusAgainListener); - - // Focus it after a timeout just in case the focusout event isn't triggered. - // @todo: This doesn't seem to be needed in iOS. We should test it in Android. - // setTimeout(() => { - // if (this.focusAgainListener) { - // this.focusElementAgain(); - // } - // }, 1000); } else if (document.activeElement == this.button && Date.now() - this.lastFocusOut < 200) { // Last focused element was the directive's element, focus it again. setTimeout(this.focusElementAgain.bind(this), 0); @@ -155,13 +146,13 @@ export class CoreKeepKeyboardDirective implements AfterViewInit, OnDestroy { /** * If keyboard is open, focus the input again and stop listening focusout to focus again if needed. */ - protected focusElementAgain() : void { + protected focusElementAgain(): void { this.domUtils.focusElement(this.input); if (this.focusAgainListener) { - // Sometimes we can receive more than 1 focus out event. If we spend 1 second without receiving any, - // stop listening for them. - let listener = this.focusAgainListener; // Store it in a local variable, in case it changes. + // Sometimes we can receive more than 1 focus out event. + // If we spend 1 second without receiving any, stop listening for them. + const listener = this.focusAgainListener; // Store it in a local variable, in case it changes. clearTimeout(this.stopFocusAgainTimeout); this.stopFocusAgainTimeout = setTimeout(() => { this.input.removeEventListener('focusout', listener); @@ -175,7 +166,7 @@ export class CoreKeepKeyboardDirective implements AfterViewInit, OnDestroy { /** * Input was focused out, save the time it was done. */ - protected focusOut() : void { + protected focusOut(): void { this.lastFocusOut = Date.now(); } } diff --git a/src/directives/link.ts b/src/directives/link.ts index 7a50b20d8..2a54f67b7 100644 --- a/src/directives/link.ts +++ b/src/directives/link.ts @@ -28,18 +28,18 @@ import { CoreConfigConstants } from '../configconstants'; selector: '[core-link]' }) export class CoreLinkDirective implements OnInit { - @Input() capture?: boolean|string; // If the link needs to be captured by the app. - @Input() inApp?: boolean|string; // True to open in embedded browser, false to open in system browser. - @Input() autoLogin? = 'check'; // If the link should be open with auto-login. Accepts the following values: - // "yes" -> Always auto-login. - // "no" -> Never auto-login. - // "check" -> Auto-login only if it points to the current site. Default value. + @Input() capture?: boolean | string; // If the link needs to be captured by the app. + @Input() inApp?: boolean | string; // True to open in embedded browser, false to open in system browser. + @Input() autoLogin?= 'check'; // If the link should be open with auto-login. Accepts the following values: + // "yes" -> Always auto-login. + // "no" -> Never auto-login. + // "check" -> Auto-login only if it points to the current site. Default value. protected element: HTMLElement; constructor(element: ElementRef, private domUtils: CoreDomUtilsProvider, private utils: CoreUtilsProvider, - private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider, - private contentLinksHelper: CoreContentLinksHelperProvider, private navCtrl: NavController) { + private sitesProvider: CoreSitesProvider, private urlUtils: CoreUrlUtilsProvider, + private contentLinksHelper: CoreContentLinksHelperProvider, private navCtrl: NavController) { // This directive can be added dynamically. In that case, the first param is the anchor HTMLElement. this.element = element.nativeElement || element; } @@ -47,7 +47,7 @@ export class CoreLinkDirective implements OnInit { /** * Function executed when the component is initialized. */ - ngOnInit() { + ngOnInit(): void { this.inApp = this.utils.isTrueOrOne(this.inApp); this.element.addEventListener('click', (event) => { @@ -61,7 +61,7 @@ export class CoreLinkDirective implements OnInit { if (this.utils.isTrueOrOne(this.capture)) { this.contentLinksHelper.handleLink(href, undefined, this.navCtrl).then((treated) => { if (!treated) { - this.navigate(href); + this.navigate(href); } }); } else { @@ -77,7 +77,7 @@ export class CoreLinkDirective implements OnInit { * * @param {string} href HREF to be opened. */ - protected navigate(href: string) : void { + protected navigate(href: string): void { const contentLinksScheme = CoreConfigConstants.customurlscheme + '://link='; if (href.indexOf('cdvfile://') === 0 || href.indexOf('file://') === 0) { @@ -93,8 +93,8 @@ export class CoreLinkDirective implements OnInit { // $location.url(href); } else { // Look for id or name. - let scrollEl = this.domUtils.closest(this.element, 'scroll-content'); - this.domUtils.scrollToElement(scrollEl, document.body, "#" + href + ", [name='" + href + "']"); + const scrollEl = this.domUtils.closest(this.element, 'scroll-content'); + this.domUtils.scrollToElement(scrollEl, document.body, '#' + href + ', [name=\'' + href + '\']'); } } else if (href.indexOf(contentLinksScheme) === 0) { // Link should be treated by Custom URL Scheme. Encode the right part, otherwise ':' is removed in iOS. diff --git a/src/directives/user-link.ts b/src/directives/user-link.ts index 6c74a0c7e..c24adb892 100644 --- a/src/directives/user-link.ts +++ b/src/directives/user-link.ts @@ -14,6 +14,7 @@ import { Directive, Input, OnInit, ElementRef } from '@angular/core'; import { NavController } from 'ionic-angular'; + /** * Directive to go to user profile on click. */ @@ -34,14 +35,14 @@ export class CoreUserLinkDirective implements OnInit { /** * Function executed when the component is initialized. */ - ngOnInit() { + ngOnInit(): void { this.element.addEventListener('click', (event) => { // If the event prevented default action, do nothing. if (!event.defaultPrevented) { event.preventDefault(); event.stopPropagation(); - this.navCtrl.push('CoreUserProfilePage', {userId: this.userId, courseId: this.courseId}); + this.navCtrl.push('CoreUserProfilePage', { userId: this.userId, courseId: this.courseId }); } }); } -} \ No newline at end of file +} diff --git a/src/pipes/bytes-to-size.ts b/src/pipes/bytes-to-size.ts index a05ef7e4b..de73241fe 100644 --- a/src/pipes/bytes-to-size.ts +++ b/src/pipes/bytes-to-size.ts @@ -35,12 +35,13 @@ export class CoreBytesToSizePipe implements PipeTransform { * @param {number|string} value The bytes to convert. * @return {string} Readable bytes. */ - transform(value: number|string) : string { + transform(value: number | string): string { if (typeof value == 'string') { // Convert the value to a number. const numberValue = parseInt(value, 10); if (isNaN(numberValue)) { this.logger.error('Invalid value received', value); + return value; } value = numberValue; diff --git a/src/pipes/create-links.ts b/src/pipes/create-links.ts index 0cc66c27a..35bb659d5 100644 --- a/src/pipes/create-links.ts +++ b/src/pipes/create-links.ts @@ -29,7 +29,7 @@ export class CoreCreateLinksPipe implements PipeTransform { * @param {string} text Text to treat. * @return {string} Treated text. */ - transform(text: string) { + transform(text: string): string { return text.replace(this.replacePattern, '$1'); } } diff --git a/src/pipes/date-day-or-time.ts b/src/pipes/date-day-or-time.ts index 0ebfd1e88..11d741285 100644 --- a/src/pipes/date-day-or-time.ts +++ b/src/pipes/date-day-or-time.ts @@ -46,12 +46,13 @@ export class CoreDateDayOrTimePipe implements PipeTransform { * @param {number|string} timestamp The UNIX timestamp (without milliseconds). * @return {string} Formatted time. */ - transform(timestamp: string|number) : string { + transform(timestamp: string | number): string { if (typeof timestamp == 'string') { // Convert the value to a number. const numberTimestamp = parseInt(timestamp, 10); if (isNaN(numberTimestamp)) { this.logger.error('Invalid value received', timestamp); + return timestamp; } timestamp = numberTimestamp; diff --git a/src/pipes/duration.ts b/src/pipes/duration.ts index c5bde0e2a..71ce2d1ce 100644 --- a/src/pipes/duration.ts +++ b/src/pipes/duration.ts @@ -35,12 +35,13 @@ export class CoreDurationPipe implements PipeTransform { * @param {number|string} seconds The number of seconds. * @return {string} Formatted duration. */ - transform(seconds: string|number) { + transform(seconds: string | number): string { if (typeof seconds == 'string') { // Convert the value to a number. const numberSeconds = parseInt(seconds, 10); if (isNaN(numberSeconds)) { this.logger.error('Invalid value received', seconds); + return seconds; } seconds = numberSeconds; diff --git a/src/pipes/format-date.ts b/src/pipes/format-date.ts index 387fc017e..acac49f9e 100644 --- a/src/pipes/format-date.ts +++ b/src/pipes/format-date.ts @@ -36,9 +36,9 @@ export class CoreFormatDatePipe implements PipeTransform { * @param {string|number} timestamp Timestamp to format (in milliseconds). If not defined, use current time. * @param {string} format Format to use. It should be a string code to handle i18n (e.g. core.dftimedate). If the code * doesn't have a prefix, 'core' will be used by default. E.g. 'dftimedate' -> 'core.dftimedate'. - * @return {String} Formatted date. + * @return {string} Formatted date. */ - transform(timestamp: string|number, format: string) { + transform(timestamp: string | number, format: string): string { timestamp = timestamp || Date.now(); if (typeof timestamp == 'string') { @@ -46,6 +46,7 @@ export class CoreFormatDatePipe implements PipeTransform { const numberTimestamp = parseInt(timestamp, 10); if (isNaN(numberTimestamp)) { this.logger.error('Invalid value received', timestamp); + return timestamp; } timestamp = numberTimestamp; @@ -54,6 +55,7 @@ export class CoreFormatDatePipe implements PipeTransform { if (format.indexOf('.') == -1) { format = 'core.' + format; } + return moment(timestamp).format(this.translate.instant(format)); } } diff --git a/src/pipes/no-tags.ts b/src/pipes/no-tags.ts index d43e40794..921241ac4 100644 --- a/src/pipes/no-tags.ts +++ b/src/pipes/no-tags.ts @@ -28,7 +28,7 @@ export class CoreNoTagsPipe implements PipeTransform { * @param {string} text The text to treat. * @return {string} Treated text. */ - transform(text: string) : string { + transform(text: string): string { return text.replace(/(<([^>]+)>)/ig, ''); } } diff --git a/src/pipes/pipes.module.ts b/src/pipes/pipes.module.ts index b91eccfc6..7667b224b 100644 --- a/src/pipes/pipes.module.ts +++ b/src/pipes/pipes.module.ts @@ -24,7 +24,7 @@ import { CoreTimeAgoPipe } from './time-ago'; import { CoreToLocaleStringPipe } from './to-locale-string'; @NgModule({ - declarations: [ + declarations: [ CoreBytesToSizePipe, CoreCreateLinksPipe, CoreDateDayOrTimePipe, diff --git a/src/pipes/seconds-to-hms.ts b/src/pipes/seconds-to-hms.ts index 34bd4d3cf..05f7825f7 100644 --- a/src/pipes/seconds-to-hms.ts +++ b/src/pipes/seconds-to-hms.ts @@ -38,7 +38,7 @@ export class CoreSecondsToHMSPipe implements PipeTransform { * @param {number|string} seconds Number of seconds. * @return {string} Formatted seconds. */ - transform(seconds: string|number) : string { + transform(seconds: string | number): string { let hours, minutes; @@ -49,6 +49,7 @@ export class CoreSecondsToHMSPipe implements PipeTransform { const numberSeconds = parseInt(seconds, 10); if (isNaN(numberSeconds)) { this.logger.error('Invalid value received', seconds); + return seconds; } seconds = numberSeconds; diff --git a/src/pipes/time-ago.ts b/src/pipes/time-ago.ts index b1c7ad1c0..d0283e9ec 100644 --- a/src/pipes/time-ago.ts +++ b/src/pipes/time-ago.ts @@ -35,12 +35,13 @@ export class CoreTimeAgoPipe implements PipeTransform { * @param {number|string} timestamp The UNIX timestamp (without milliseconds). * @return {string} Formatted time. */ - transform(timestamp: string|number) : string { + transform(timestamp: string | number): string { if (typeof timestamp == 'string') { // Convert the value to a number. const numberTimestamp = parseInt(timestamp, 10); if (isNaN(numberTimestamp)) { this.logger.error('Invalid value received', timestamp); + return timestamp; } timestamp = numberTimestamp; diff --git a/src/pipes/to-locale-string.ts b/src/pipes/to-locale-string.ts index eb7aec685..84f880054 100644 --- a/src/pipes/to-locale-string.ts +++ b/src/pipes/to-locale-string.ts @@ -34,12 +34,13 @@ export class CoreToLocaleStringPipe implements PipeTransform { * @param {number|string} timestamp The timestamp (can be in seconds or milliseconds). * @return {string} Formatted time. */ - transform(timestamp: number|string) : string { + transform(timestamp: number | string): string { if (typeof timestamp == 'string') { // Convert the value to a number. const numberTimestamp = parseInt(timestamp, 10); if (isNaN(numberTimestamp)) { this.logger.error('Invalid value received', timestamp); + return timestamp; } timestamp = numberTimestamp; @@ -53,6 +54,7 @@ export class CoreToLocaleStringPipe implements PipeTransform { // Timestamp is in seconds, convert it to milliseconds. timestamp = timestamp * 1000; } + return new Date(timestamp).toLocaleString(); } } diff --git a/src/providers/app.ts b/src/providers/app.ts index 3b74ca164..86df6ce5d 100644 --- a/src/providers/app.ts +++ b/src/providers/app.ts @@ -48,7 +48,7 @@ export interface CoreRedirectData { * @type {number} */ timemodified?: number; -}; +} /** * Factory to provide some global functionalities, like access to the global app database. @@ -65,8 +65,8 @@ export class CoreAppProvider { protected DBNAME = 'MoodleMobile'; protected db: SQLiteDB; protected logger; - protected ssoAuthenticationPromise : Promise; - protected isKeyboardShown: boolean = false; + protected ssoAuthenticationPromise: Promise; + protected isKeyboardShown = false; constructor(dbProvider: CoreDbProvider, private platform: Platform, private keyboard: Keyboard, private appCtrl: App, private network: Network, logger: CoreLoggerProvider) { @@ -87,7 +87,7 @@ export class CoreAppProvider { * * @return {boolean} Whether the function is supported. */ - canGetUserMedia() : boolean { + canGetUserMedia(): boolean { return !!(navigator && navigator.mediaDevices && navigator.mediaDevices.getUserMedia); } @@ -96,14 +96,14 @@ export class CoreAppProvider { * * @return {boolean} Whether the function is supported. */ - canRecordMedia() : boolean { - return !!(window).MediaRecorder; + canRecordMedia(): boolean { + return !!( window).MediaRecorder; } /** * Closes the keyboard. */ - closeKeyboard() : void { + closeKeyboard(): void { if (this.isMobile()) { this.keyboard.close(); } @@ -114,7 +114,7 @@ export class CoreAppProvider { * * @return {SQLiteDB} App's DB. */ - getDB() : SQLiteDB { + getDB(): SQLiteDB { return this.db; } @@ -123,8 +123,8 @@ export class CoreAppProvider { * * @return {NavController} Root NavController. */ - getRootNavController() : NavController { - // getRootNav is deprecated. Get the first root nav, there should always be one. + getRootNavController(): NavController { + // Function getRootNav is deprecated. Get the first root nav, there should always be one. return this.appCtrl.getRootNavs()[0]; } @@ -133,8 +133,9 @@ export class CoreAppProvider { * * @return {boolean} Whether the app is running in a desktop environment (not browser). */ - isDesktop() : boolean { - let process = (window).process; + isDesktop(): boolean { + const process = ( window).process; + return !!(process && process.versions && typeof process.versions.electron != 'undefined'); } @@ -143,7 +144,7 @@ export class CoreAppProvider { * * @return {boolean} Whether keyboard is visible. */ - isKeyboardVisible() : boolean { + isKeyboardVisible(): boolean { return this.isKeyboardShown; } @@ -152,15 +153,14 @@ export class CoreAppProvider { * * @return {boolean} Whether it's running in a Linux environment. */ - isLinux() : boolean { + isLinux(): boolean { if (!this.isDesktop()) { return false; } try { - var os = require('os'); - return os.platform().indexOf('linux') === 0; - } catch(ex) { + return require('os').platform().indexOf('linux') === 0; + } catch (ex) { return false; } } @@ -170,15 +170,14 @@ export class CoreAppProvider { * * @return {boolean} Whether it's running in a Mac OS environment. */ - isMac() : boolean { + isMac(): boolean { if (!this.isDesktop()) { return false; } try { - var os = require('os'); - return os.platform().indexOf('darwin') === 0; - } catch(ex) { + return require('os').platform().indexOf('darwin') === 0; + } catch (ex) { return false; } } @@ -188,7 +187,7 @@ export class CoreAppProvider { * * @return {boolean} Whether the app is running in a mobile or tablet device. */ - isMobile() : boolean { + isMobile(): boolean { return this.platform.is('cordova'); } @@ -197,12 +196,13 @@ export class CoreAppProvider { * * @return {boolean} Whether the app is online. */ - isOnline() : boolean { + isOnline(): boolean { let online = this.network.type !== null && this.network.type != Connection.NONE && this.network.type != Connection.UNKNOWN; // Double check we are not online because we cannot rely 100% in Cordova APIs. Also, check it in browser. if (!online && navigator.onLine) { online = true; } + return online; } @@ -211,14 +211,15 @@ export class CoreAppProvider { * * @return {boolean} Whether the device uses a limited connection. */ - isNetworkAccessLimited() : boolean { - let type = this.network.type; + isNetworkAccessLimited(): boolean { + const type = this.network.type; if (type === null) { // Plugin not defined, probably in browser. return false; } - let limited = [Connection.CELL_2G, Connection.CELL_3G, Connection.CELL_4G, Connection.CELL]; + const limited = [Connection.CELL_2G, Connection.CELL_3G, Connection.CELL_4G, Connection.CELL]; + return limited.indexOf(type) > -1; } @@ -227,15 +228,14 @@ export class CoreAppProvider { * * @return {boolean} Whether it's running in a Windows environment. */ - isWindows() : boolean { + isWindows(): boolean { if (!this.isDesktop()) { return false; } try { - var os = require('os'); - return os.platform().indexOf('win') === 0; - } catch(ex) { + return require('os').platform().indexOf('win') === 0; + } catch (ex) { return false; } } @@ -243,7 +243,7 @@ export class CoreAppProvider { /** * Open the keyboard. */ - openKeyboard() : void { + openKeyboard(): void { // Open keyboard is not supported in desktop and in iOS. if (this.isMobile() && !this.platform.is('ios')) { this.keyboard.show(); @@ -255,11 +255,11 @@ export class CoreAppProvider { * Please notice that this function should be called when the app receives the new token from the browser, * NOT when the browser is opened. */ - startSSOAuthentication() : void { + startSSOAuthentication(): void { let cancelTimeout, resolvePromise; - this.ssoAuthenticationPromise = new Promise((resolve, reject) => { + this.ssoAuthenticationPromise = new Promise((resolve, reject): void => { resolvePromise = resolve; // Resolve it automatically after 10 seconds (it should never take that long). @@ -269,7 +269,7 @@ export class CoreAppProvider { }); // Store the resolve function in the promise itself. - (this.ssoAuthenticationPromise).resolve = resolvePromise; + ( this.ssoAuthenticationPromise).resolve = resolvePromise; // If the promise is resolved because finishSSOAuthentication is called, stop the cancel promise. this.ssoAuthenticationPromise.then(() => { @@ -280,9 +280,9 @@ export class CoreAppProvider { /** * Finish an SSO authentication process. */ - finishSSOAuthentication() : void { + finishSSOAuthentication(): void { if (this.ssoAuthenticationPromise) { - (this.ssoAuthenticationPromise).resolve && (this.ssoAuthenticationPromise).resolve(); + ( this.ssoAuthenticationPromise).resolve && ( this.ssoAuthenticationPromise).resolve(); this.ssoAuthenticationPromise = undefined; } } @@ -292,7 +292,7 @@ export class CoreAppProvider { * * @return {boolean} Whether there's a SSO authentication ongoing. */ - isSSOAuthenticationOngoing() : boolean { + isSSOAuthenticationOngoing(): boolean { return !!this.ssoAuthenticationPromise; } @@ -301,7 +301,7 @@ export class CoreAppProvider { * * @return {Promise} Promise resolved once SSO authentication finishes. */ - waitForSSOAuthentication() : Promise { + waitForSSOAuthentication(): Promise { return this.ssoAuthenticationPromise || Promise.resolve(); } @@ -310,10 +310,10 @@ export class CoreAppProvider { * * @return {CoreRedirectData} Object with siteid, state, params and timemodified. */ - getRedirect() : CoreRedirectData { + getRedirect(): CoreRedirectData { if (localStorage && localStorage.getItem) { try { - let data: CoreRedirectData = { + const data: CoreRedirectData = { siteId: localStorage.getItem('mmCoreRedirectSiteId'), page: localStorage.getItem('mmCoreRedirectState'), params: localStorage.getItem('mmCoreRedirectParams'), @@ -325,7 +325,7 @@ export class CoreAppProvider { } return data; - } catch(ex) { + } catch (ex) { this.logger.error('Error loading redirect data:', ex); } } @@ -340,14 +340,16 @@ export class CoreAppProvider { * @param {string} page Page to go. * @param {any} params Page params. */ - storeRedirect(siteId: string, page: string, params: any) : void { + storeRedirect(siteId: string, page: string, params: any): void { if (localStorage && localStorage.setItem) { try { localStorage.setItem('mmCoreRedirectSiteId', siteId); localStorage.setItem('mmCoreRedirectState', page); localStorage.setItem('mmCoreRedirectParams', JSON.stringify(params)); localStorage.setItem('mmCoreRedirectTime', String(Date.now())); - } catch(ex) {} + } catch (ex) { + // Ignore errors. + } } } } diff --git a/src/providers/config.ts b/src/providers/config.ts index 7b9f4970e..1898d189d 100644 --- a/src/providers/config.ts +++ b/src/providers/config.ts @@ -50,8 +50,8 @@ export class CoreConfigProvider { * @param {string} name The config name. * @return {Promise} Promise resolved when done. */ - delete(name: string) : Promise { - return this.appDB.deleteRecords(this.TABLE_NAME, {name: name}); + delete(name: string): Promise { + return this.appDB.deleteRecords(this.TABLE_NAME, { name: name }); } /** @@ -61,8 +61,8 @@ export class CoreConfigProvider { * @param {any} [defaultValue] Default value to use if the entry is not found. * @return {Promise} Resolves upon success along with the config data. Reject on failure. */ - get(name: string, defaultValue?: any) : Promise { - return this.appDB.getRecord(this.TABLE_NAME, {name: name}).then((entry) => { + get(name: string, defaultValue?: any): Promise { + return this.appDB.getRecord(this.TABLE_NAME, { name: name }).then((entry) => { return entry.value; }).catch((error) => { if (typeof defaultValue != 'undefined') { @@ -80,7 +80,7 @@ export class CoreConfigProvider { * @param {boolean|number|string} value The config value. Can only store primitive values, not objects. * @return {Promise} Promise resolved when done. */ - set(name: string, value: boolean|number|string) : Promise { - return this.appDB.insertOrUpdateRecord(this.TABLE_NAME, {name: name, value: value}, {name: name}); + set(name: string, value: boolean | number | string): Promise { + return this.appDB.insertOrUpdateRecord(this.TABLE_NAME, { name: name, value: value }, { name: name }); } } diff --git a/src/providers/cron.ts b/src/providers/cron.ts index 53cb47273..a33af21b2 100644 --- a/src/providers/cron.ts +++ b/src/providers/cron.ts @@ -79,7 +79,7 @@ export interface CoreCronHandler { * @type {number} */ timeout: number; -}; +} /* * Service to handle cron processes. The registered processes will be executed every certain time. @@ -87,10 +87,10 @@ export interface CoreCronHandler { @Injectable() export class CoreCronDelegate { // Constants. - public static DEFAULT_INTERVAL = 3600000; // Default interval is 1 hour. - public static MIN_INTERVAL = 300000; // Minimum interval is 5 minutes. - public static DESKTOP_MIN_INTERVAL = 60000; // Minimum interval in desktop is 1 minute. - public static MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes. + static DEFAULT_INTERVAL = 3600000; // Default interval is 1 hour. + static MIN_INTERVAL = 300000; // Minimum interval is 5 minutes. + static DESKTOP_MIN_INTERVAL = 60000; // Minimum interval in desktop is 1 minute. + static MAX_TIME_PROCESS = 120000; // Max time a process can block the queue. Defaults to 2 minutes. // Variables for database. protected CRON_TABLE = 'cron'; @@ -111,7 +111,7 @@ export class CoreCronDelegate { protected logger; protected appDB: SQLiteDB; - protected handlers: {[s: string]: CoreCronHandler} = {}; + protected handlers: { [s: string]: CoreCronHandler } = {}; protected queuePromise = Promise.resolve(); constructor(logger: CoreLoggerProvider, private appProvider: CoreAppProvider, private configProvider: CoreConfigProvider, @@ -136,21 +136,23 @@ export class CoreCronDelegate { * @param {string} [siteId] Site ID. If not defined, all sites. * @return {Promise} Promise resolved if handler is executed successfully, rejected otherwise. */ - protected checkAndExecuteHandler(name: string, force?: boolean, siteId?: string) : Promise { - if (!this.handlers[name] || !this.handlers[name].execute) { + protected checkAndExecuteHandler(name: string, force?: boolean, siteId?: string): Promise { + if (!this.handlers[name] || !this.handlers[name].execute) { // Invalid handler. this.logger.debug('Cannot execute handler because is invalid: ' + name); + return Promise.reject(null); } - let usesNetwork = this.handlerUsesNetwork(name), - isSync = !force && this.isHandlerSync(name), - promise; + const usesNetwork = this.handlerUsesNetwork(name), + isSync = !force && this.isHandlerSync(name); + let promise; if (usesNetwork && !this.appProvider.isOnline()) { // Offline, stop executing. this.logger.debug('Cannot execute handler because device is offline: ' + name); this.stopHandler(name); + return Promise.reject(null); } @@ -168,6 +170,7 @@ export class CoreCronDelegate { // Cannot execute in this network connection, retry soon. this.logger.debug('Cannot execute handler because device is using limited connection: ' + name); this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL); + return Promise.reject(null); } @@ -177,6 +180,7 @@ export class CoreCronDelegate { }).then(() => { return this.executeHandler(name, siteId).then(() => { this.logger.debug(`Execution of handler '${name}' was a success.`); + return this.setHandlerLastExecutionTime(name, Date.now()).then(() => { this.scheduleNextExecution(name); }); @@ -184,6 +188,7 @@ export class CoreCronDelegate { // Handler call failed. Retry soon. this.logger.debug(`Execution of handler '${name}' failed.`); this.scheduleNextExecution(name, CoreCronDelegate.MIN_INTERVAL); + return Promise.reject(null); }); }); @@ -199,8 +204,8 @@ export class CoreCronDelegate { * @param {string} [siteId] Site ID. If not defined, all sites. * @return {Promise} Promise resolved when the handler finishes or reaches max time, rejected if it fails. */ - protected executeHandler(name: string, siteId?: string) : Promise { - return new Promise((resolve, reject) => { + protected executeHandler(name: string, siteId?: string): Promise { + return new Promise((resolve, reject): void => { let cancelTimeout; this.logger.debug('Executing handler: ' + name); @@ -224,11 +229,11 @@ export class CoreCronDelegate { * @param {string} [siteId] Site ID. If not defined, all sites. * @return {Promise} Promise resolved if all handlers are executed successfully, rejected otherwise. */ - forceSyncExecution(siteId?: string) : Promise { - let promises = []; + forceSyncExecution(siteId?: string): Promise { + const promises = []; - for (let name in this.handlers) { - let handler = this.handlers[name]; + for (const name in this.handlers) { + const handler = this.handlers[name]; if (this.isHandlerManualSync(name)) { // Mark the handler as running (it might be running already). handler.running = true; @@ -251,8 +256,8 @@ export class CoreCronDelegate { * @param {string} name Handler's name. * @return {number} Handler's interval. */ - protected getHandlerInterval(name) : number { - if (!this.handlers[name] || !this.handlers[name].getInterval) { + protected getHandlerInterval(name: string): number { + if (!this.handlers[name] || !this.handlers[name].getInterval) { // Invalid, return default. return CoreCronDelegate.DEFAULT_INTERVAL; } @@ -274,7 +279,7 @@ export class CoreCronDelegate { * @param {string} name Handler's name. * @return {string} Handler's last execution ID. */ - protected getHandlerLastExecutionId(name: string) : string { + protected getHandlerLastExecutionId(name: string): string { return 'last_execution_' + name; } @@ -284,10 +289,12 @@ export class CoreCronDelegate { * @param {string} name Handler's name. * @return {Promise} Promise resolved with the handler's last execution time. */ - protected getHandlerLastExecutionTime(name: string) : Promise { + protected getHandlerLastExecutionTime(name: string): Promise { const id = this.getHandlerLastExecutionId(name); - return this.appDB.getRecord(this.CRON_TABLE, {id: id}).then((entry) => { + + return this.appDB.getRecord(this.CRON_TABLE, { id: id }).then((entry) => { const time = parseInt(entry.value, 10); + return isNaN(time) ? 0 : time; }).catch(() => { return 0; // Not set, return 0. @@ -300,8 +307,8 @@ export class CoreCronDelegate { * @param {string} name Handler's name. * @return {boolean} True if handler uses network or not defined, false otherwise. */ - protected handlerUsesNetwork(name: string) : boolean { - if (!this.handlers[name] || !this.handlers[name].usesNetwork) { + protected handlerUsesNetwork(name: string): boolean { + if (!this.handlers[name] || !this.handlers[name].usesNetwork) { // Invalid, return default. return true; } @@ -314,12 +321,13 @@ export class CoreCronDelegate { * * @return {boolean} Whether it has at least 1 manual sync handler. */ - hasManualSyncHandlers() : boolean { - for (let name in this.handlers) { + hasManualSyncHandlers(): boolean { + for (const name in this.handlers) { if (this.isHandlerManualSync(name)) { return true; } } + return false; } @@ -328,12 +336,13 @@ export class CoreCronDelegate { * * @return {boolean} Whether it has at least 1 sync handler. */ - hasSyncHandlers() : boolean { - for (let name in this.handlers) { + hasSyncHandlers(): boolean { + for (const name in this.handlers) { if (this.isHandlerSync(name)) { return true; } } + return false; } @@ -343,8 +352,8 @@ export class CoreCronDelegate { * @param {string} name Handler's name. * @return {boolean} True if handler is a sync process and can be manually executed or not defined, false otherwise. */ - protected isHandlerManualSync(name: string) : boolean { - if (!this.handlers[name] || !this.handlers[name].canManualSync) { + protected isHandlerManualSync(name: string): boolean { + if (!this.handlers[name] || !this.handlers[name].canManualSync) { // Invalid, return default. return this.isHandlerSync(name); } @@ -358,7 +367,7 @@ export class CoreCronDelegate { * @param {string} name Handler's name. * @return {boolean} True if handler is a sync process or not defined, false otherwise. */ - protected isHandlerSync(name) : boolean { + protected isHandlerSync(name: string): boolean { if (!this.handlers[name] || !this.handlers[name].isSync) { // Invalid, return default. return true; @@ -372,13 +381,14 @@ export class CoreCronDelegate { * * @param {CoreCronHandler} handler The handler to register. */ - register(handler: CoreCronHandler) : void { + register(handler: CoreCronHandler): void { if (!handler || !handler.name) { // Invalid handler. return; } if (typeof this.handlers[handler.name] != 'undefined') { this.logger.debug(`The cron handler '${name}' is already registered.`); + return; } @@ -398,7 +408,7 @@ export class CoreCronDelegate { * @param {number} [time] Time to the next execution. If not supplied it will be calculated using the last execution and * the handler's interval. This param should be used only if it's really necessary. */ - protected scheduleNextExecution(name: string, time?: number) : void { + protected scheduleNextExecution(name: string, time?: number): void { if (!this.handlers[name]) { // Invalid handler. return; @@ -442,14 +452,14 @@ export class CoreCronDelegate { * @param {number} time Time to set. * @return {Promise} Promise resolved when the execution time is saved. */ - protected setHandlerLastExecutionTime(name: string, time: number) : Promise { + protected setHandlerLastExecutionTime(name: string, time: number): Promise { const id = this.getHandlerLastExecutionId(name), entry = { id: id, value: time - } + }; - return this.appDB.insertOrUpdateRecord(this.CRON_TABLE, entry, {id: id}); + return this.appDB.insertOrUpdateRecord(this.CRON_TABLE, entry, { id: id }); } /** @@ -457,15 +467,17 @@ export class CoreCronDelegate { * * @param {string} name Name of the handler. */ - protected startHandler(name) : void { + protected startHandler(name: string): void { if (!this.handlers[name]) { // Invalid handler. this.logger.debug(`Cannot start handler '${name}', is invalid.`); + return; } if (this.handlers[name].running) { this.logger.debug(`Handler '${name}', is already running.`); + return; } @@ -477,8 +489,8 @@ export class CoreCronDelegate { /** * Start running periodically the handlers that use network. */ - startNetworkHandlers() : void { - for (let name in this.handlers) { + startNetworkHandlers(): void { + for (const name in this.handlers) { if (this.handlerUsesNetwork(name)) { this.startHandler(name); } @@ -490,15 +502,17 @@ export class CoreCronDelegate { * * @param {string} name Name of the handler. */ - protected stopHandler(name) { + protected stopHandler(name: string): void { if (!this.handlers[name]) { // Invalid handler. this.logger.debug(`Cannot stop handler '${name}', is invalid.`); + return; } if (!this.handlers[name].running) { this.logger.debug(`Cannot stop handler '${name}', it's not running.`); + return; } @@ -506,5 +520,4 @@ export class CoreCronDelegate { clearTimeout(this.handlers[name].timeout); delete this.handlers[name].timeout; } - } diff --git a/src/providers/db.ts b/src/providers/db.ts index a3c1f4aab..fa4fd6192 100644 --- a/src/providers/db.ts +++ b/src/providers/db.ts @@ -26,7 +26,7 @@ export class CoreDbProvider { protected dbInstances = {}; - constructor(private sqlite: SQLite, private platform: Platform) {} + constructor(private sqlite: SQLite, private platform: Platform) { } /** * Get or create a database object. @@ -37,14 +37,15 @@ export class CoreDbProvider { * @param {boolean} forceNew True if it should always create a new instance. * @return {SQLiteDB} DB. */ - getDB(name: string, forceNew?: boolean) : SQLiteDB { - if (typeof this.dbInstances[name] === 'undefined' || forceNew) { + getDB(name: string, forceNew?: boolean): SQLiteDB { + if (typeof this.dbInstances[name] === 'undefined' || forceNew) { if (this.platform.is('cordova')) { this.dbInstances[name] = new SQLiteDB(name, this.sqlite, this.platform); } else { this.dbInstances[name] = new SQLiteDBMock(name); } } + return this.dbInstances[name]; } @@ -54,7 +55,7 @@ export class CoreDbProvider { * @param {string} name DB name. * @return {Promise} Promise resolved when the DB is deleted. */ - deleteDB(name: string) : Promise { + deleteDB(name: string): Promise { let promise; if (typeof this.dbInstances[name] != 'undefined') { @@ -65,7 +66,7 @@ export class CoreDbProvider { } return promise.then(() => { - let db = this.dbInstances[name]; + const db = this.dbInstances[name]; delete this.dbInstances[name]; if (this.platform.is('cordova')) { diff --git a/src/providers/events.ts b/src/providers/events.ts index a9d538a25..5ca5c2ab1 100644 --- a/src/providers/events.ts +++ b/src/providers/events.ts @@ -24,39 +24,39 @@ export interface CoreEventObserver { * Stop the observer. */ off: () => void; -}; +} /* * Service to send and listen to events. */ @Injectable() export class CoreEventsProvider { - public static SESSION_EXPIRED = 'session_expired'; - public static PASSWORD_CHANGE_FORCED = 'password_change_forced'; - public static USER_NOT_FULLY_SETUP = 'user_not_fully_setup'; - public static SITE_POLICY_NOT_AGREED = 'site_policy_not_agreed'; - public static LOGIN = 'login'; - public static LOGOUT = 'logout'; - public static LANGUAGE_CHANGED = 'language_changed'; - public static NOTIFICATION_SOUND_CHANGED = 'notification_sound_changed'; - public static SITE_ADDED = 'site_added'; - public static SITE_UPDATED = 'site_updated'; - public static SITE_DELETED = 'site_deleted'; - public static COMPLETION_MODULE_VIEWED = 'completion_module_viewed'; - public static USER_DELETED = 'user_deleted'; - public static PACKAGE_STATUS_CHANGED = 'package_status_changed'; - public static COURSE_STATUS_CHANGED = 'course_status_changed'; - public static SECTION_STATUS_CHANGED = 'section_status_changed'; - public static REMOTE_ADDONS_LOADED = 'remote_addons_loaded'; - public static LOGIN_SITE_CHECKED = 'login_site_checked'; - public static LOGIN_SITE_UNCHECKED = 'login_site_unchecked'; - public static IAB_LOAD_START = 'inappbrowser_load_start'; - public static IAB_EXIT = 'inappbrowser_exit'; - public static APP_LAUNCHED_URL = 'app_launched_url'; // App opened with a certain URL (custom URL scheme). - public static FILE_SHARED = 'file_shared'; + static SESSION_EXPIRED = 'session_expired'; + static PASSWORD_CHANGE_FORCED = 'password_change_forced'; + static USER_NOT_FULLY_SETUP = 'user_not_fully_setup'; + static SITE_POLICY_NOT_AGREED = 'site_policy_not_agreed'; + static LOGIN = 'login'; + static LOGOUT = 'logout'; + static LANGUAGE_CHANGED = 'language_changed'; + static NOTIFICATION_SOUND_CHANGED = 'notification_sound_changed'; + static SITE_ADDED = 'site_added'; + static SITE_UPDATED = 'site_updated'; + static SITE_DELETED = 'site_deleted'; + static COMPLETION_MODULE_VIEWED = 'completion_module_viewed'; + static USER_DELETED = 'user_deleted'; + static PACKAGE_STATUS_CHANGED = 'package_status_changed'; + static COURSE_STATUS_CHANGED = 'course_status_changed'; + static SECTION_STATUS_CHANGED = 'section_status_changed'; + static REMOTE_ADDONS_LOADED = 'remote_addons_loaded'; + static LOGIN_SITE_CHECKED = 'login_site_checked'; + static LOGIN_SITE_UNCHECKED = 'login_site_unchecked'; + static IAB_LOAD_START = 'inappbrowser_load_start'; + static IAB_EXIT = 'inappbrowser_exit'; + static APP_LAUNCHED_URL = 'app_launched_url'; // App opened with a certain URL (custom URL scheme). + static FILE_SHARED = 'file_shared'; protected logger; - protected observables: {[s: string] : Subject} = {}; + protected observables: { [s: string]: Subject } = {}; protected uniqueEvents = {}; constructor(logger: CoreLoggerProvider) { @@ -74,14 +74,17 @@ export class CoreEventsProvider { * @param {string} [siteId] Site where to trigger the event. Undefined won't check the site. * @return {CoreEventObserver} Observer to stop listening. */ - on(eventName: string, callBack: (value: any) => void, siteId?: string) : CoreEventObserver { + on(eventName: string, callBack: (value: any) => void, siteId?: string): CoreEventObserver { // If it's a unique event and has been triggered already, call the callBack. // We don't need to create an observer because the event won't be triggered again. if (this.uniqueEvents[eventName]) { callBack(this.uniqueEvents[eventName].data); + // Return a fake observer to prevent errors. return { - off: () => {} + off: (): void => { + // Nothing to do. + } }; } @@ -92,7 +95,7 @@ export class CoreEventsProvider { this.observables[eventName] = new Subject(); } - let subscription = this.observables[eventName].subscribe((value: any) => { + const subscription = this.observables[eventName].subscribe((value: any) => { if (!siteId || value.siteId == siteId) { callBack(value); } @@ -100,7 +103,7 @@ export class CoreEventsProvider { // Create and return a CoreEventObserver. return { - off: () => { + off: (): void => { this.logger.debug(`Stop listening to event '${eventName}'`); subscription.unsubscribe(); } @@ -114,7 +117,7 @@ export class CoreEventsProvider { * @param {any} [data] Data to pass to the observers. * @param {string} [siteId] Site where to trigger the event. Undefined means no Site. */ - trigger(eventName: string, data?: any, siteId?: string) : void { + trigger(eventName: string, data?: any, siteId?: string): void { this.logger.debug(`Event '${eventName}' triggered.`); if (this.observables[eventName]) { if (siteId) { @@ -134,7 +137,7 @@ export class CoreEventsProvider { * @param {any} data Data to pass to the observers. * @param {string} [siteId] Site where to trigger the event. Undefined means no Site. */ - triggerUnique(eventName: string, data: any, siteId?: string) : void { + triggerUnique(eventName: string, data: any, siteId?: string): void { if (this.uniqueEvents[eventName]) { this.logger.debug(`Unique event '${eventName}' ignored because it was already triggered.`); } else { diff --git a/src/providers/file-session.ts b/src/providers/file-session.ts index bf9ca18fd..8d35df55e 100644 --- a/src/providers/file-session.ts +++ b/src/providers/file-session.ts @@ -26,7 +26,7 @@ import { CoreSitesProvider } from './sites'; export class CoreFileSessionProvider { protected files = {}; - constructor(private sitesProvider: CoreSitesProvider) {} + constructor(private sitesProvider: CoreSitesProvider) { } /** * Add a file to the session. @@ -36,7 +36,7 @@ export class CoreFileSessionProvider { * @param {any} file File to add. * @param {string} [siteId] Site ID. If not defined, current site. */ - addFile(component: string, id: string|number, file: any, siteId?: string) : void { + addFile(component: string, id: string | number, file: any, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); this.initFileArea(component, id, siteId); @@ -51,7 +51,7 @@ export class CoreFileSessionProvider { * @param {string|number} id File area identifier. * @param {string} [siteId] Site ID. If not defined, current site. */ - clearFiles(component: string, id: string|number, siteId?: string) : void { + clearFiles(component: string, id: string | number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id]) { this.files[siteId][component][id] = []; @@ -66,11 +66,12 @@ export class CoreFileSessionProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {any[]} Array of files in session. */ - getFiles(component: string, id: string|number, siteId?: string) : any[] { + getFiles(component: string, id: string | number, siteId?: string): any[] { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id]) { return this.files[siteId][component][id]; } + return []; } @@ -81,7 +82,7 @@ export class CoreFileSessionProvider { * @param {string|number} id File area identifier. * @param {string} [siteId] Site ID. If not defined, current site. */ - protected initFileArea(component: string, id: string|number, siteId?: string) : void { + protected initFileArea(component: string, id: string | number, siteId?: string): void { if (!this.files[siteId]) { this.files[siteId] = {}; } @@ -103,7 +104,7 @@ export class CoreFileSessionProvider { * @param {any} file File to remove. The instance should be exactly the same as the one stored in session. * @param {string} [siteId] Site ID. If not defined, current site. */ - removeFile(component: string, id: string|number, file: any, siteId?: string) : void { + removeFile(component: string, id: string | number, file: any, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id]) { const position = this.files[siteId][component][id].indexOf(file); @@ -121,10 +122,10 @@ export class CoreFileSessionProvider { * @param {number} index Position of the file to remove. * @param {string} [siteId] Site ID. If not defined, current site. */ - removeFileByIndex(component: string, id: string|number, index: number, siteId?: string) : void { + removeFileByIndex(component: string, id: string | number, index: number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (this.files[siteId] && this.files[siteId][component] && this.files[siteId][component][id] && index >= 0 && - index < this.files[siteId][component][id].length) { + index < this.files[siteId][component][id].length) { this.files[siteId][component][id].splice(index, 1); } } @@ -137,7 +138,7 @@ export class CoreFileSessionProvider { * @param {any[]} newFiles Files to set. * @param {string} [siteId] Site ID. If not defined, current site. */ - setFiles(component: string, id: string|number, newFiles: any[], siteId?: string) : void { + setFiles(component: string, id: string | number, newFiles: any[], siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); this.initFileArea(component, id, siteId); diff --git a/src/providers/file.ts b/src/providers/file.ts index 53d8d0d0a..3d625d8f3 100644 --- a/src/providers/file.ts +++ b/src/providers/file.ts @@ -27,21 +27,21 @@ import { Zip } from '@ionic-native/zip'; */ @Injectable() export class CoreFileProvider { + // Formats to read a file. + static FORMATTEXT = 0; + static FORMATDATAURL = 1; + static FORMATBINARYSTRING = 2; + static FORMATARRAYBUFFER = 3; + + // Folders. + static SITESFOLDER = 'sites'; + static TMPFOLDER = 'tmp'; + protected logger; protected initialized = false; protected basePath = ''; protected isHTMLAPI = false; - // Formats to read a file. - public static FORMATTEXT = 0; - public static FORMATDATAURL = 1; - public static FORMATBINARYSTRING = 2; - public static FORMATARRAYBUFFER = 3; - - // Folders. - public static SITESFOLDER = 'sites'; - public static TMPFOLDER = 'tmp'; - constructor(logger: CoreLoggerProvider, private platform: Platform, private file: File, private appProvider: CoreAppProvider, private textUtils: CoreTextUtilsProvider, private zip: Zip, private mimeUtils: CoreMimetypeUtilsProvider) { this.logger = logger.getInstance('CoreFileProvider'); @@ -52,7 +52,7 @@ export class CoreFileProvider { * * @param {string} path Base path to use. */ - setHTMLBasePath(path: string) { + setHTMLBasePath(path: string): void { this.isHTMLAPI = true; this.basePath = path; } @@ -62,7 +62,7 @@ export class CoreFileProvider { * * @return {boolean} True if uses HTML API, false otherwise. */ - usesHTMLAPI() : boolean { + usesHTMLAPI(): boolean { return this.isHTMLAPI; } @@ -71,7 +71,7 @@ export class CoreFileProvider { * * @return {Promise} Promise to be resolved when the initialization is finished. */ - init() : Promise { + init(): Promise { if (this.initialized) { return Promise.resolve(); } @@ -84,6 +84,7 @@ export class CoreFileProvider { this.basePath = this.file.documentsDirectory; } else if (!this.isAvailable() || this.basePath === '') { this.logger.error('Error getting device OS.'); + return Promise.reject(null); } @@ -97,7 +98,7 @@ export class CoreFileProvider { * * @return {boolean} Whether the plugin is available. */ - isAvailable() : boolean { + isAvailable(): boolean { return typeof window.resolveLocalFileSystemURL !== 'undefined'; } @@ -107,9 +108,10 @@ export class CoreFileProvider { * @param {string} path Relative path to the file. * @return {Promise} Promise resolved when the file is retrieved. */ - getFile(path: string) : Promise { + getFile(path: string): Promise { return this.init().then(() => { this.logger.debug('Get file: ' + path); + return this.file.resolveLocalFilesystemUrl(this.addBasePathIfNeeded(path)); }).then((entry) => { return entry; @@ -122,9 +124,10 @@ export class CoreFileProvider { * @param {string} path Relative path to the directory. * @return {Promise} Promise resolved when the directory is retrieved. */ - getDir(path: string) : Promise { + getDir(path: string): Promise { return this.init().then(() => { this.logger.debug('Get directory: ' + path); + return this.file.resolveDirectoryUrl(this.addBasePathIfNeeded(path)); }); } @@ -135,7 +138,7 @@ export class CoreFileProvider { * @param {string} siteId Site ID. * @return {string} Site folder path. */ - getSiteFolder(siteId: string) : string { + getSiteFolder(siteId: string): string { return CoreFileProvider.SITESFOLDER + '/' + siteId; } @@ -148,7 +151,7 @@ export class CoreFileProvider { * @param {string} [base] Base path to create the dir/file in. If not set, use basePath. * @return {Promise} Promise to be resolved when the dir/file is created. */ - protected create(isDirectory: boolean, path: string, failIfExists?: boolean, base?: string) : Promise { + protected create(isDirectory: boolean, path: string, failIfExists?: boolean, base?: string): Promise { return this.init().then(() => { // Remove basePath if it's in the path. path = this.removeStartingSlash(path.replace(this.basePath, '')); @@ -157,15 +160,17 @@ export class CoreFileProvider { if (path.indexOf('/') == -1) { if (isDirectory) { this.logger.debug('Create dir ' + path + ' in ' + base); + return this.file.createDir(base, path, !failIfExists); } else { this.logger.debug('Create file ' + path + ' in ' + base); + return this.file.createFile(base, path, !failIfExists); } } else { - // this.file doesn't allow creating more than 1 level at a time (e.g. tmp/folder). + // The file plugin doesn't allow creating more than 1 level at a time (e.g. tmp/folder). // We need to create them 1 by 1. - let firstDir = path.substr(0, path.indexOf('/')), + const firstDir = path.substr(0, path.indexOf('/')), restOfPath = path.substr(path.indexOf('/') + 1); this.logger.debug('Create dir ' + firstDir + ' in ' + base); @@ -174,6 +179,7 @@ export class CoreFileProvider { return this.create(isDirectory, restOfPath, failIfExists, newDirEntry.toURL()); }).catch((error) => { this.logger.error('Error creating directory ' + firstDir + ' in ' + base); + return Promise.reject(error); }); } @@ -187,7 +193,7 @@ export class CoreFileProvider { * @param {boolean} [failIfExists] True if it should fail if the directory exists, false otherwise. * @return {Promise} Promise to be resolved when the directory is created. */ - createDir(path: string, failIfExists?: boolean) : Promise { + createDir(path: string, failIfExists?: boolean): Promise { return this.create(true, path, failIfExists); } @@ -198,7 +204,7 @@ export class CoreFileProvider { * @param {boolean} [failIfExists] True if it should fail if the file exists, false otherwise.. * @return {Promise} Promise to be resolved when the file is created. */ - createFile(path: string, failIfExists?: boolean) : Promise { + createFile(path: string, failIfExists?: boolean): Promise { return this.create(false, path, failIfExists); } @@ -208,11 +214,12 @@ export class CoreFileProvider { * @param {string} path Relative path to the directory. * @return {Promise} Promise to be resolved when the directory is deleted. */ - removeDir(path: string) : Promise { + removeDir(path: string): Promise { return this.init().then(() => { // Remove basePath if it's in the path. path = this.removeStartingSlash(path.replace(this.basePath, '')); this.logger.debug('Remove directory: ' + path); + return this.file.removeRecursively(this.basePath, path); }); } @@ -223,11 +230,12 @@ export class CoreFileProvider { * @param {string} path Relative path to the file. * @return {Promise} Promise to be resolved when the file is deleted. */ - removeFile(path: string) : Promise { + removeFile(path: string): Promise { return this.init().then(() => { // Remove basePath if it's in the path. path = this.removeStartingSlash(path.replace(this.basePath, '')); this.logger.debug('Remove file: ' + path); + return this.file.removeFile(this.basePath, path); }); } @@ -238,8 +246,8 @@ export class CoreFileProvider { * @param {FileEntry} fileEntry File Entry. * @return {Promise} Promise resolved when the file is deleted. */ - removeFileByFileEntry(fileEntry: any) : Promise { - return new Promise((resolve, reject) => { + removeFileByFileEntry(fileEntry: any): Promise { + return new Promise((resolve, reject): void => { fileEntry.remove(resolve, reject); }); } @@ -250,7 +258,7 @@ export class CoreFileProvider { * @param {string} path Relative path to the directory. * @return {Promise} Promise to be resolved when the contents are retrieved. */ - getDirectoryContents(path: string) : Promise { + getDirectoryContents(path: string): Promise { return this.init().then(() => { // Remove basePath if it's in the path. path = this.removeStartingSlash(path.replace(this.basePath, '')); @@ -266,13 +274,13 @@ export class CoreFileProvider { * @param {any} entry Directory or file. * @return {Promise} Promise to be resolved when the size is calculated. */ - protected getSize(entry: any) : Promise { - return new Promise((resolve, reject) => { + protected getSize(entry: any): Promise { + return new Promise((resolve, reject): void => { if (entry.isDirectory) { - let directoryReader = entry.createReader(); + const directoryReader = entry.createReader(); directoryReader.readEntries((entries) => { - let promises = []; + const promises = []; for (let i = 0; i < entries.length; i++) { promises.push(this.getSize(entries[i])); } @@ -281,9 +289,10 @@ export class CoreFileProvider { let directorySize = 0; for (let i = 0; i < sizes.length; i++) { - let fileSize = parseInt(sizes[i]); + const fileSize = parseInt(sizes[i]); if (isNaN(fileSize)) { reject(); + return; } directorySize += fileSize; @@ -308,13 +317,14 @@ export class CoreFileProvider { * @param {string} path Relative path to the directory. * @return {Promise} Promise to be resolved when the size is calculated. */ - getDirectorySize(path: string) : Promise { + getDirectorySize(path: string): Promise { // Remove basePath if it's in the path. path = this.removeStartingSlash(path.replace(this.basePath, '')); this.logger.debug('Get size of dir: ' + path); + return this.getDir(path).then((dirEntry) => { - return this.getSize(dirEntry); + return this.getSize(dirEntry); }); } @@ -324,13 +334,14 @@ export class CoreFileProvider { * @param {string} path Relative path to the file. * @return {Promise} Promise to be resolved when the size is calculated. */ - getFileSize(path: string) : Promise { + getFileSize(path: string): Promise { // Remove basePath if it's in the path. path = this.removeStartingSlash(path.replace(this.basePath, '')); this.logger.debug('Get size of file: ' + path); + return this.getFile(path).then((fileEntry) => { - return this.getSize(fileEntry); + return this.getSize(fileEntry); }); } @@ -340,8 +351,8 @@ export class CoreFileProvider { * @param {FileEntry} path Relative path to the file. * @return {Promise} Promise to be resolved when the file is retrieved. */ - getFileObjectFromFileEntry(entry: FileEntry) : Promise { - return new Promise((resolve, reject) => { + getFileObjectFromFileEntry(entry: FileEntry): Promise { + return new Promise((resolve, reject): void => { this.logger.debug('Get file object of: ' + entry.fullPath); entry.file(resolve, reject); }); @@ -352,7 +363,7 @@ export class CoreFileProvider { * * @return {Promise} Promise resolved with the estimated free space in bytes. */ - calculateFreeSpace() : Promise { + calculateFreeSpace(): Promise { return this.file.getFreeDiskSpace().then((size) => { return size; // GetFreeDiskSpace returns KB. }); @@ -364,8 +375,9 @@ export class CoreFileProvider { * @param {string} filename The file name. * @return {string} The file name normalized. */ - normalizeFileName(filename: string) : string { + normalizeFileName(filename: string): string { filename = this.textUtils.decodeURIComponent(filename); + return filename; } @@ -380,7 +392,7 @@ export class CoreFileProvider { * FORMATARRAYBUFFER * @return {Promise} Promise to be resolved when the file is read. */ - readFile(path: string, format = CoreFileProvider.FORMATTEXT) : Promise { + readFile(path: string, format: number = CoreFileProvider.FORMATTEXT): Promise { // Remove basePath if it's in the path. path = this.removeStartingSlash(path.replace(this.basePath, '')); this.logger.debug('Read file ' + path + ' with format ' + format); @@ -408,22 +420,22 @@ export class CoreFileProvider { * FORMATARRAYBUFFER * @return {Promise} Promise to be resolved when the file is read. */ - readFileData(fileData: any, format = CoreFileProvider.FORMATTEXT) : Promise { + readFileData(fileData: any, format: number = CoreFileProvider.FORMATTEXT): Promise { format = format || CoreFileProvider.FORMATTEXT; this.logger.debug('Read file from file data with format ' + format); - return new Promise((resolve, reject) => { - let reader = new FileReader(); - reader.onloadend = (evt) => { - let target = evt.target; // Convert to to be able to use non-standard properties. + return new Promise((resolve, reject): void => { + const reader = new FileReader(); + reader.onloadend = (evt): void => { + const target = evt.target; // Convert to to be able to use non-standard properties. if (target.result !== undefined || target.result !== null) { resolve(target.result); } else if (target.error !== undefined || target.error !== null) { reject(target.error); } else { - reject({code: null, message: 'READER_ONLOADEND_ERR'}); + reject({ code: null, message: 'READER_ONLOADEND_ERR' }); } - } + }; switch (format) { case CoreFileProvider.FORMATDATAURL: @@ -449,7 +461,7 @@ export class CoreFileProvider { * @param {any} data Data to write. * @return {Promise} Promise to be resolved when the file is written. */ - writeFile(path: string, data: any) : Promise { + writeFile(path: string, data: any): Promise { return this.init().then(() => { // Remove basePath if it's in the path. path = this.removeStartingSlash(path.replace(this.basePath, '')); @@ -458,12 +470,13 @@ export class CoreFileProvider { // Create file (and parent folders) to prevent errors. return this.createFile(path).then((fileEntry) => { if (this.isHTMLAPI && !this.appProvider.isDesktop() && - (typeof data == 'string' || data.toString() == '[object ArrayBuffer]')) { + (typeof data == 'string' || data.toString() == '[object ArrayBuffer]')) { // We need to write Blobs. - let type = this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(path)); - data = new Blob([data], {type: type || 'text/plain'}); + const type = this.mimeUtils.getMimeType(this.mimeUtils.getFileExtension(path)); + data = new Blob([data], { type: type || 'text/plain' }); } - return this.file.writeFile(this.basePath, path, data, {replace: true}).then(() => { + + return this.file.writeFile(this.basePath, path, data, { replace: true }).then(() => { return fileEntry; }); }); @@ -476,7 +489,7 @@ export class CoreFileProvider { * @param {string} fullPath Absolute path to the file. * @return {Promise} Promise to be resolved when the file is retrieved. */ - getExternalFile(fullPath: string) : Promise { + getExternalFile(fullPath: string): Promise { return this.file.resolveLocalFilesystemUrl(fullPath).then((entry) => { return entry; }); @@ -488,10 +501,10 @@ export class CoreFileProvider { * @param {string} fullPath Absolute path to the file. * @return {Promise} Promise to be resolved when the file is removed. */ - removeExternalFile(fullPath: string) : Promise { - // removeFile(fullPath, '') does not work, we need to pass two valid parameters. - let directory = fullPath.substring(0, fullPath.lastIndexOf('/') ), + removeExternalFile(fullPath: string): Promise { + const directory = fullPath.substring(0, fullPath.lastIndexOf('/')), filename = fullPath.substr(fullPath.lastIndexOf('/') + 1); + return this.file.removeFile(directory, filename); } @@ -500,7 +513,7 @@ export class CoreFileProvider { * * @return {Promise} Promise to be resolved when the base path is retrieved. */ - getBasePath() : Promise { + getBasePath(): Promise { return this.init().then(() => { if (this.basePath.slice(-1) == '/') { return this.basePath; @@ -517,7 +530,7 @@ export class CoreFileProvider { * * @return {Promise} Promise to be resolved when the base path is retrieved. */ - getBasePathToDownload() : Promise { + getBasePathToDownload(): Promise { return this.init().then(() => { if (this.platform.is('ios')) { // In iOS we want the internal URL (cdvfile://localhost/persistent/...). @@ -536,7 +549,7 @@ export class CoreFileProvider { * * @return {string} Base path. If the service hasn't been initialized it will return an invalid value. */ - getBasePathInstant() : string { + getBasePathInstant(): string { if (!this.basePath) { return this.basePath; } else if (this.basePath.slice(-1) == '/') { @@ -553,7 +566,7 @@ export class CoreFileProvider { * @param {string} [newPath] New path of the file. * @return {Promise} Promise resolved when the entry is moved. */ - moveFile(originalPath: string, newPath: string) : Promise { + moveFile(originalPath: string, newPath: string): Promise { return this.init().then(() => { // Remove basePath if it's in the paths. originalPath = this.removeStartingSlash(originalPath.replace(this.basePath, '')); @@ -561,11 +574,11 @@ export class CoreFileProvider { if (this.isHTMLAPI) { // In Cordova API we need to calculate the longest matching path to make it work. - // this.file.moveFile('a/', 'b/c.ext', 'a/', 'b/d.ext') doesn't work. - // cordovaFile.moveFile('a/b/', 'c.ext', 'a/b/', 'd.ext') works. - let commonPath = this.basePath, - dirsA = originalPath.split('/'), + // The function this.file.moveFile('a/', 'b/c.ext', 'a/', 'b/d.ext') doesn't work. + // The function this.file.moveFile('a/b/', 'c.ext', 'a/b/', 'd.ext') works. + const dirsA = originalPath.split('/'), dirsB = newPath.split('/'); + let commonPath = this.basePath; for (let i = 0; i < dirsA.length; i++) { let dir = dirsA[i]; @@ -595,7 +608,7 @@ export class CoreFileProvider { * @param {string} to New path of the file. * @return {Promise} Promise resolved when the entry is copied. */ - copyFile(from: string, to: string) : Promise { + copyFile(from: string, to: string): Promise { let fromFileAndDir, toFileAndDir; @@ -614,7 +627,7 @@ export class CoreFileProvider { }).then(() => { if (this.isHTMLAPI) { // In HTML API, the file name cannot include a directory, otherwise it fails. - let fromDir = this.textUtils.concatenatePaths(this.basePath, fromFileAndDir.directory), + const fromDir = this.textUtils.concatenatePaths(this.basePath, fromFileAndDir.directory), toDir = this.textUtils.concatenatePaths(this.basePath, toFileAndDir.directory); return this.file.copyFile(fromDir, fromFileAndDir.name, toDir, toFileAndDir.name); @@ -636,13 +649,13 @@ export class CoreFileProvider { * path/ -> directory: 'path', name: '' * path -> directory: '', name: 'path' */ - getFileAndDirectoryFromPath(path: string) : any { - let file = { + getFileAndDirectoryFromPath(path: string): any { + const file = { directory: '', name: '' - } + }; - file.directory = path.substring(0, path.lastIndexOf('/') ); + file.directory = path.substring(0, path.lastIndexOf('/')); file.name = path.substr(path.lastIndexOf('/') + 1); return file; @@ -654,11 +667,12 @@ export class CoreFileProvider { * @param {FileEntry} fileEntry File Entry. * @return {string} Internal URL. */ - getInternalURL(fileEntry: FileEntry) : string { + getInternalURL(fileEntry: FileEntry): string { if (!fileEntry.toInternalURL) { // File doesn't implement toInternalURL, use toURL. return fileEntry.toURL(); } + return fileEntry.toInternalURL(); } @@ -668,7 +682,7 @@ export class CoreFileProvider { * @param {string} path Path to treat. * @return {string} Path with basePath added. */ - addBasePathIfNeeded(path: string) : string { + addBasePathIfNeeded(path: string): string { if (path.indexOf(this.basePath) > -1) { return path; } else { @@ -682,7 +696,7 @@ export class CoreFileProvider { * @param {string} path Path to treat. * @return {string} Path without basePath if basePath was found, undefined otherwise. */ - removeBasePath(path: string) : string { + removeBasePath(path: string): string { if (path.indexOf(this.basePath) > -1) { return path.replace(this.basePath, ''); } @@ -697,11 +711,12 @@ export class CoreFileProvider { * @param {Function} [onProgress] Function to call on progress. * @return {Promise} Promise resolved when the file is unzipped. */ - unzipFile(path: string, destFolder?: string, onProgress?: Function) : Promise { + unzipFile(path: string, destFolder?: string, onProgress?: Function): Promise { // Get the source file. return this.getFile(path).then((fileEntry) => { // If destFolder is not set, use same location as ZIP file. We need to use absolute paths (including basePath). destFolder = this.addBasePathIfNeeded(destFolder || this.mimeUtils.removeExtension(path)); + return this.zip.unzip(fileEntry.toURL(), destFolder, onProgress); }); } @@ -714,7 +729,7 @@ export class CoreFileProvider { * @param {string} newValue New value. * @return {Promise} Promise resolved in success. */ - replaceInFile(path: string, search: string|RegExp, newValue: string) : Promise { + replaceInFile(path: string, search: string | RegExp, newValue: string): Promise { return this.readFile(path).then((content) => { if (typeof content == 'undefined' || content === null || !content.replace) { return Promise.reject(null); @@ -722,6 +737,7 @@ export class CoreFileProvider { if (content.match(search)) { content = content.replace(search, newValue); + return this.writeFile(path, content); } }); @@ -733,12 +749,12 @@ export class CoreFileProvider { * @param {Entry} fileEntry FileEntry retrieved from getFile or similar. * @return {Promise} Promise resolved with metadata. */ - getMetadata(fileEntry: Entry) : Promise { + getMetadata(fileEntry: Entry): Promise { if (!fileEntry || !fileEntry.getMetadata) { return Promise.reject(null); } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { fileEntry.getMetadata(resolve, reject); }); } @@ -750,7 +766,7 @@ export class CoreFileProvider { * @param {boolean} [isDir] True if directory, false if file. * @return {Promise} Promise resolved with metadata. */ - getMetadataFromPath(path: string, isDir?: boolean) : Promise { + getMetadataFromPath(path: string, isDir?: boolean): Promise { let promise; if (isDir) { promise = this.getDir(path); @@ -769,10 +785,11 @@ export class CoreFileProvider { * @param {string} path Path. * @return {string} Path without a slash in the first position. */ - removeStartingSlash(path: string) : string { + removeStartingSlash(path: string): string { if (path[0] == '/') { return path.substr(1); } + return path; } @@ -784,14 +801,15 @@ export class CoreFileProvider { * @param {boolean} copy True to copy, false to move. * @return {Promise} Promise resolved when the entry is copied/moved. */ - protected copyOrMoveExternalFile(from: string, to: string, copy?: boolean) : Promise { + protected copyOrMoveExternalFile(from: string, to: string, copy?: boolean): Promise { // Get the file to copy/move. return this.getExternalFile(from).then((fileEntry) => { // Create the destination dir if it doesn't exist. - let dirAndFile = this.getFileAndDirectoryFromPath(to); + const dirAndFile = this.getFileAndDirectoryFromPath(to); + return this.createDir(dirAndFile.directory).then((dirEntry) => { // Now copy/move the file. - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { if (copy) { fileEntry.copyTo(dirEntry, dirAndFile.name, resolve, reject); } else { @@ -809,7 +827,7 @@ export class CoreFileProvider { * @param {string} to Relative new path of the file (inside the app folder). * @return {Promise} Promise resolved when the entry is copied. */ - copyExternalFile(from: string, to: string) : Promise { + copyExternalFile(from: string, to: string): Promise { return this.copyOrMoveExternalFile(from, to, true); } @@ -820,7 +838,7 @@ export class CoreFileProvider { * @param {string} to Relative new path of the file (inside the app folder). * @return {Promise} Promise resolved when the entry is moved. */ - moveExternalFile(from: string, to: string) : Promise { + moveExternalFile(from: string, to: string): Promise { return this.copyOrMoveExternalFile(from, to, false); } @@ -832,18 +850,18 @@ export class CoreFileProvider { * @param {string} [defaultExt] Default extension to use if no extension found in the file. * @return {Promise} Promise resolved with the unique file name. */ - getUniqueNameInFolder(dirPath: string, fileName: string, defaultExt?: string) : Promise { + getUniqueNameInFolder(dirPath: string, fileName: string, defaultExt?: string): Promise { // Get existing files in the folder. return this.getDirectoryContents(dirPath).then((entries) => { - let files = {}, + const files = {}; + let num = 1, fileNameWithoutExtension = this.mimeUtils.removeExtension(fileName), extension = this.mimeUtils.getFileExtension(fileName) || defaultExt, - newName, - number = 1; + newName; // Clean the file name. fileNameWithoutExtension = this.textUtils.removeSpecialCharactersForFiles( - this.textUtils.decodeURIComponent(fileNameWithoutExtension)); + this.textUtils.decodeURIComponent(fileNameWithoutExtension)); // Index the files by name. entries.forEach((entry) => { @@ -864,8 +882,8 @@ export class CoreFileProvider { } else { // Repeated name. Add a number until we find a free name. do { - newName = fileNameWithoutExtension + '(' + number + ')' + extension; - number++; + newName = fileNameWithoutExtension + '(' + num + ')' + extension; + num++; } while (typeof files[newName] != 'undefined'); // Ask the user what he wants to do. @@ -882,7 +900,7 @@ export class CoreFileProvider { * * @return {Promise} Promise resolved when done. */ - clearTmpFolder() : Promise { + clearTmpFolder(): Promise { return this.removeDir(CoreFileProvider.TMPFOLDER); } @@ -893,14 +911,14 @@ export class CoreFileProvider { * @param {any[]} files List of used files. * @return {Promise} Promise resolved when done, rejected if failure. */ - removeUnusedFiles(dirPath: string, files: any[]) : Promise { + removeUnusedFiles(dirPath: string, files: any[]): Promise { // Get the directory contents. return this.getDirectoryContents(dirPath).then((contents) => { if (!contents.length) { return; } - let filesMap = {}, + const filesMap = {}, promises = []; // Index the received files by fullPath and ignore the invalid ones. @@ -930,7 +948,7 @@ export class CoreFileProvider { * @param {string} path The absolute path of the file to check. * @return {boolean} Whether the file is in the app's folder. */ - isFileInAppFolder(path: string) : boolean { + isFileInAppFolder(path: string): boolean { return path.indexOf(this.basePath) != -1; } } diff --git a/src/providers/filepool.ts b/src/providers/filepool.ts index 38f2ecb49..80388199e 100644 --- a/src/providers/filepool.ts +++ b/src/providers/filepool.ts @@ -94,7 +94,7 @@ export interface CoreFilepoolFileEntry { * @type {string} */ extension?: string; -}; +} /** * Entry from the file's queue. @@ -165,7 +165,7 @@ export interface CoreFilepoolQueueEntry { * @type {any[]} */ links?: any[]; -}; +} /** * Entry from packages table. @@ -187,7 +187,7 @@ export interface CoreFilepoolPackageEntry { * An ID to use in conjunction with the component. * @type {string|number} */ - componentId?: string|number; + componentId?: string | number; /** * Package status. @@ -224,7 +224,7 @@ export interface CoreFilepoolPackageEntry { * @type {string} */ extra?: string; -}; +} /* * Factory for handling downloading files and retrieve downloaded files. @@ -429,7 +429,7 @@ export class CoreFilepoolProvider { protected sizeCache = {}; // A "cache" to store file sizes to prevent performing too many HEAD requests. // Variables to prevent downloading packages/files twice at the same time. protected packagesPromises = {}; - protected filePromises: {[s: string]: {[s: string]: Promise}} = {}; + 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, @@ -463,18 +463,20 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved on success. */ - protected addFileLink(siteId: string, fileId: string, component: string, componentId?: string|number) : Promise { + protected addFileLink(siteId: string, fileId: string, component: string, componentId?: string | number): Promise { if (!component) { return Promise.reject(null); } componentId = this.fixComponentId(componentId); + return this.sitesProvider.getSiteDb(siteId).then((db) => { - let newEntry = { + const newEntry = { fileId: fileId, component: component, componentId: componentId || '' }; + return db.insertOrUpdateRecord(this.LINKS_TABLE, newEntry, undefined); }); } @@ -491,9 +493,10 @@ export class CoreFilepoolProvider { * Use this method to create a link between a URL and a component. You usually do not need to call this manually since * downloading a file automatically does this. Note that this method does not check if the file exists in the pool. */ - addFileLinkByUrl(siteId: string, fileUrl: string, component: string, componentId?: string|number) : Promise { + addFileLinkByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number): Promise { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { const fileId = this.getFileIdByUrl(fileUrl); + return this.addFileLink(siteId, fileId, component, componentId); }); } @@ -506,11 +509,12 @@ export class CoreFilepoolProvider { * @param {any[]} links Array of objects containing the component and optionally componentId. * @return {Promise} Promise resolved on success. */ - protected addFileLinks(siteId: string, fileId: string, links: any[]) : Promise { - let promises = []; + protected addFileLinks(siteId: string, fileId: string, links: any[]): Promise { + const promises = []; links.forEach((link) => { promises.push(this.addFileLink(siteId, fileId, link.component, link.componentId)); }); + return Promise.all(promises); } @@ -523,7 +527,7 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component (optional). * @return {Promise} Resolved on success. */ - addFilesToQueue(siteId: string, files: any[], component?: string, componentId?: string|number) : Promise { + addFilesToQueue(siteId: string, files: any[], component?: string, componentId?: string | number): Promise { return this.downloadOrPrefetchFiles(siteId, files, true, false, component, componentId); } @@ -535,11 +539,12 @@ export class CoreFilepoolProvider { * @param {any} data Additional information to store about the file (timemodified, url, ...). See FILES_TABLE schema. * @return {Promise} Promise resolved on success. */ - protected addFileToPool(siteId: string, fileId: string, data: any) : Promise { - let values = Object.assign({}, data); + protected addFileToPool(siteId: string, fileId: string, data: any): Promise { + const values = Object.assign({}, data); values.fileId = fileId; + return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.insertOrUpdateRecord(this.FILES_TABLE, values, {fileId: fileId}); + return db.insertOrUpdateRecord(this.FILES_TABLE, values, { fileId: fileId }); }); } @@ -558,7 +563,7 @@ export class CoreFilepoolProvider { * @return {Promise} Promise resolved when the file is downloaded. */ protected addToQueue(siteId: string, fileId: string, url: string, priority: number, revision: number, timemodified: number, - filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any) : Promise { + filePath: string, onProgress?: (event: any) => any, options: any = {}, link?: any): Promise { this.logger.debug(`Adding ${fileId} to the queue`); return this.appDB.insertRecord(this.QUEUE_TABLE, { @@ -577,6 +582,7 @@ export class CoreFilepoolProvider { // Check if the queue is running. this.checkQueueProcessing(); this.notifyFileDownloading(siteId, fileId); + return this.getQueuePromise(siteId, fileId, true, onProgress); }); } @@ -595,8 +601,8 @@ export class CoreFilepoolProvider { * @param {any} [options] Extra options (isexternalfile, repositorytype). * @return {Promise} Resolved on success. */ - addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string|number, timemodified = 0, - filePath?: string, onProgress?: (event: any) => any, priority = 0, options: any = {}) : Promise { + addToQueueByUrl(siteId: string, fileUrl: string, component?: string, componentId?: string | number, timemodified: number = 0, + filePath?: string, onProgress?: (event: any) => any, priority: number = 0, options: any = {}): Promise { let fileId, link, revision, @@ -612,7 +618,7 @@ export class CoreFilepoolProvider { } return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - let primaryKey = {siteId: siteId, fileId: fileId}; + const primaryKey = { siteId: siteId, fileId: fileId }; revision = this.getRevisionFromUrl(fileUrl); fileId = this.getFileIdByUrl(fileUrl); @@ -622,16 +628,16 @@ export class CoreFilepoolProvider { link = { component: component, componentId: this.fixComponentId(componentId) - } + }; } - // Retrieve the queue deferred now if it exists to prevent errors if file is removed from queue - // while we're checking if the file is in queue. + // Retrieve the queue deferred now if it exists. + // This is to prevent errors if file is removed from queue while we're checking if the file is in queue. queueDeferred = this.getQueueDeferred(siteId, fileId, false, onProgress); return this.hasFileInQueue(siteId, fileId).then((entry: CoreFilepoolQueueEntry) => { - let foundLink = false, - newData: any = {}; + const newData: any = {}; + let foundLink = false; if (entry) { // We already have the file in queue, we update the priority and links. @@ -657,8 +663,8 @@ export class CoreFilepoolProvider { if (link) { // We need to add the new link if it does not exist yet. if (entry.links && entry.links.length) { - for (let i in entry.links) { - let fileLink = entry.links[i]; + for (const i in entry.links) { + const fileLink = entry.links[i]; if (fileLink.component == link.component && fileLink.componentId == link.componentId) { foundLink = true; break; @@ -676,6 +682,7 @@ export class CoreFilepoolProvider { if (Object.keys(newData).length) { // Update only when required. this.logger.debug(`Updating file ${fileId} which is already in queue`); + return this.appDB.updateRecords(this.QUEUE_TABLE, newData, primaryKey).then(() => { return this.getQueuePromise(siteId, fileId, true, onProgress); }); @@ -683,8 +690,7 @@ export class CoreFilepoolProvider { this.logger.debug(`File ${fileId} already in queue and does not require update`); if (queueDeferred) { - // If we were able to retrieve the queue deferred before we use that one, since the file download - // might have finished now and the deferred wouldn't be in the array anymore. + // If we were able to retrieve the queue deferred before, we use that one. return queueDeferred.promise; } else { // Create a new deferred and return its promise. @@ -717,8 +723,8 @@ export class CoreFilepoolProvider { * @param {any} [options] Extra options (isexternalfile, repositorytype). * @return {Promise} Promise resolved when the file is downloaded. */ - protected addToQueueIfNeeded(siteId: string, fileUrl: string, component: string, componentId?: string|number, timemodified = 0, - checkSize = true, downloadUnknown?: boolean, options: any = {}) : Promise { + protected addToQueueIfNeeded(siteId: string, fileUrl: string, component: string, componentId?: string | number, + timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise { let promise; if (checkSize) { @@ -735,7 +741,7 @@ export class CoreFilepoolProvider { // Calculate the size of the file. return promise.then((size) => { - let isWifi = !this.appProvider.isNetworkAccessLimited(), + const isWifi = !this.appProvider.isNetworkAccessLimited(), sizeUnknown = size <= 0; if (!sizeUnknown) { @@ -768,9 +774,10 @@ export class CoreFilepoolProvider { * Though, this will disable the queue if we are missing network or if the file system * is not accessible. Also, this will have no effect if the queue is already running. */ - protected checkQueueProcessing() : void { + protected checkQueueProcessing(): void { if (!this.fileProvider.isAvailable() || !this.appProvider.isOnline()) { this.queueState = this.QUEUE_PAUSED; + return; } else if (this.queueState === this.QUEUE_RUNNING) { return; @@ -786,7 +793,7 @@ export class CoreFilepoolProvider { * @param {string} siteId Site ID. * @return {Promise} Promise resolved when all status are cleared. */ - clearAllPackagesStatus(siteId: string) : Promise { + clearAllPackagesStatus(siteId: string): Promise { this.logger.debug('Clear all packages status for site ' + siteId); return this.sitesProvider.getSite(siteId).then((site) => { @@ -809,7 +816,7 @@ export class CoreFilepoolProvider { * @param {string} siteId ID of the site to clear. * @return {Promise} Promise resolved when the filepool is cleared. */ - clearFilepool(siteId: string) : Promise { + clearFilepool(siteId: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { return db.deleteRecords(this.FILES_TABLE); }); @@ -823,9 +830,9 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Resolved means yes, rejected means no. */ - componentHasFiles(siteId: string, component: string, componentId?: string|number) : Promise { + componentHasFiles(siteId: string, component: string, componentId?: string | number): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - let conditions = { + const conditions = { component: component, componentId: componentId || '' }; @@ -853,7 +860,7 @@ export class CoreFilepoolProvider { * @param {string} packagestatus Status of one of the packages. * @return {string} New status for the list of packages; */ - determinePackagesStatus(current: string, packageStatus: string) : string { + determinePackagesStatus(current: string, packageStatus: string): string { if (!current) { current = CoreConstants.NOT_DOWNLOADABLE; } @@ -865,7 +872,7 @@ export class CoreFilepoolProvider { // If all packages are downloaded or not downloadable with at least 1 downloaded, status will be downloaded. return CoreConstants.DOWNLOADED; } else if (packageStatus === CoreConstants.DOWNLOADING && - (current === CoreConstants.NOT_DOWNLOADABLE || current === CoreConstants.DOWNLOADED)) { + (current === CoreConstants.NOT_DOWNLOADABLE || current === CoreConstants.DOWNLOADED)) { // If all packages are downloading/downloaded/notdownloadable with at least 1 downloading, status will be downloading. return CoreConstants.DOWNLOADING; } else if (packageStatus === CoreConstants.OUTDATED && current !== CoreConstants.NOT_DOWNLOADED) { @@ -891,20 +898,21 @@ export class CoreFilepoolProvider { * @return {Promise} Resolved with internal URL on success, rejected otherwise. */ protected downloadForPoolByUrl(siteId: string, fileUrl: string, options: any = {}, filePath?: string, - onProgress?: (event: any) => any, poolFileObject?: CoreFilepoolFileEntry) : Promise { + onProgress?: (event: any) => any, poolFileObject?: CoreFilepoolFileEntry): Promise { - let fileId = this.getFileIdByUrl(fileUrl), + const fileId = this.getFileIdByUrl(fileUrl), extension = this.mimeUtils.guessExtensionFromUrl(fileUrl), - addExtension = typeof filePath == "undefined", + addExtension = typeof filePath == 'undefined', pathPromise = filePath ? filePath : this.getFilePath(siteId, fileId, extension); return Promise.resolve(pathPromise).then((filePath) => { if (poolFileObject && poolFileObject.fileId !== fileId) { this.logger.error('Invalid object to update passed'); + return Promise.reject(null); } - let downloadId = this.getFileDownloadId(fileUrl, filePath); + const downloadId = this.getFileDownloadId(fileUrl, filePath); if (this.filePromises[siteId] && this.filePromises[siteId][downloadId]) { // There's already a download ongoing for this file in this location, return the promise. @@ -919,7 +927,7 @@ export class CoreFilepoolProvider { } return this.wsProvider.downloadFile(fileUrl, filePath, addExtension, onProgress).then((fileEntry) => { - let data: CoreFilepoolFileEntry = poolFileObject || {}; + const data: CoreFilepoolFileEntry = poolFileObject || {}; data.downloadTime = Date.now(); data.stale = 0; @@ -956,8 +964,8 @@ export class CoreFilepoolProvider { * @return {Promise} Resolved on success. */ downloadOrPrefetchFiles(siteId: string, files: any[], prefetch: boolean, ignoreStale?: boolean, component?: string, - componentId?: string|number) : Promise { - let promises = []; + componentId?: string | number): Promise { + const promises = []; // Download files. files.forEach((file) => { @@ -966,7 +974,7 @@ export class CoreFilepoolProvider { options = { isexternalfile: file.isexternalfile, repositorytype: file.repositorytype - } + }; if (prefetch) { promises.push(this.addToQueueByUrl( @@ -995,10 +1003,10 @@ export class CoreFilepoolProvider { * @return {Promise} Promise resolved when the package is downloaded. */ protected downloadOrPrefetchPackage(siteId: string, fileList: any[], prefetch?: boolean, component?: string, - componentId?: string|number, extra?: string, dirPath?: string, onProgress?: (event: any) => any) : Promise { + componentId?: string | number, extra?: string, dirPath?: string, onProgress?: (event: any) => any): Promise { - let packageId = this.getPackageId(component, componentId), - promise; + const packageId = this.getPackageId(component, componentId); + let promise; if (this.packagesPromises[siteId] && this.packagesPromises[siteId][packageId]) { // There's already a download ongoing for this package, return the promise. @@ -1009,23 +1017,23 @@ export class CoreFilepoolProvider { // Set package as downloading. promise = this.storePackageStatus(siteId, CoreConstants.DOWNLOADING, component, componentId).then(() => { - let promises = [], - packageLoaded = 0; + const promises = []; + let packageLoaded = 0; fileList.forEach((file) => { - let path, - promise, - fileLoaded = 0, - fileUrl = file.url || file.fileurl, + const fileUrl = file.url || file.fileurl, options = { isexternalfile: file.isexternalfile, repositorytype: file.repositorytype - }, + }; + let path, + promise, + fileLoaded = 0, onFileProgress; if (onProgress) { // There's a onProgress event, create a function to receive file download progress events. - onFileProgress = (progress: any) => { + onFileProgress = (progress: any): void => { if (progress && progress.loaded) { // Add the new size loaded to the package loaded. packageLoaded = packageLoaded + (progress.loaded - fileLoaded); @@ -1050,10 +1058,10 @@ export class CoreFilepoolProvider { if (prefetch) { promise = this.addToQueueByUrl( - siteId, fileUrl, component, componentId, file.timemodified, path, undefined, 0, options); + siteId, fileUrl, component, componentId, file.timemodified, path, undefined, 0, options); } else { promise = this.downloadUrl( - siteId, fileUrl, false, component, componentId, file.timemodified, onFileProgress, path, options); + siteId, fileUrl, false, component, componentId, file.timemodified, onFileProgress, path, options); } // Using undefined for success & fail will pass the success/failure to the parent promise. @@ -1076,6 +1084,7 @@ export class CoreFilepoolProvider { }); this.packagesPromises[siteId][packageId] = promise; + return promise; } @@ -1092,8 +1101,8 @@ export class CoreFilepoolProvider { * @param {Function} [onProgress] Function to call on progress. * @return {Promise} Promise resolved when all files are downloaded. */ - downloadPackage(siteId: string, fileList: any[], component: string, componentId?: string|number, extra?: string, - dirPath?: string, onProgress?: (event: any) => any) : Promise { + downloadPackage(siteId: string, fileList: any[], component: string, componentId?: string | number, extra?: string, + dirPath?: string, onProgress?: (event: any) => any): Promise { return this.downloadOrPrefetchPackage(siteId, fileList, false, component, componentId, extra, dirPath, onProgress); } @@ -1116,8 +1125,8 @@ export class CoreFilepoolProvider { * not force a file to be re-downloaded if it is already part of the pool. You should mark a file as stale using * invalidateFileByUrl to trigger a download. */ - downloadUrl(siteId: string, fileUrl: string, ignoreStale?: boolean, component?: string, componentId?: string|number, - timemodified = 0, onProgress?: (event: any) => any, filePath?: string, options: any = {}) : Promise { + downloadUrl(siteId: string, fileUrl: string, ignoreStale?: boolean, component?: string, componentId?: string | number, + timemodified: number = 0, onProgress?: (event: any) => any, filePath?: string, options: any = {}): Promise { let fileId, promise; @@ -1135,12 +1144,14 @@ export class CoreFilepoolProvider { if (typeof fileObject === 'undefined') { // We do not have the file, download and add to pool. this.notifyFileDownloading(siteId, fileId); + return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress); } else if (this.isFileOutdated(fileObject, options.revision, options.timemodified) && - this.appProvider.isOnline() && !ignoreStale) { + this.appProvider.isOnline() && !ignoreStale) { // The file is outdated, force the download and update it. this.notifyFileDownloading(siteId, fileId); + return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress, fileObject); } @@ -1156,21 +1167,25 @@ export class CoreFilepoolProvider { }, () => { // The file was not found in the pool, weird. this.notifyFileDownloading(siteId, fileId); + return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress, fileObject); }); }, () => { // The file is not in the pool just yet. this.notifyFileDownloading(siteId, fileId); + return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress); }).then((response) => { if (typeof component != 'undefined') { this.addFileLink(siteId, fileId, component, componentId); } this.notifyFileDownloaded(siteId, fileId); + return response; }, (err) => { this.notifyFileDownloadError(siteId, fileId); + return Promise.reject(err); }); }); @@ -1187,35 +1202,37 @@ export class CoreFilepoolProvider { * @param {string} siteId SiteID to get migrated. * @return {Promise} Promise resolved when done. */ - protected fillExtensionInFile(entry: CoreFilepoolFileEntry, siteId: string) : Promise { + protected fillExtensionInFile(entry: CoreFilepoolFileEntry, siteId: string): Promise { if (typeof entry.extension != 'undefined') { // Already filled. return Promise.resolve(); } return this.sitesProvider.getSiteDb(siteId).then((db) => { - let extension = this.mimeUtils.getFileExtension(entry.path); + const extension = this.mimeUtils.getFileExtension(entry.path); if (!extension) { // Files does not have extension. Invalidate file (stale = true). // Minor problem: file will remain in the filesystem once downloaded again. this.logger.debug('Staled file with no extension ' + entry.fileId); - return db.updateRecords(this.FILES_TABLE, {stale: 1}, {fileId: entry.fileId}); + + return db.updateRecords(this.FILES_TABLE, { stale: 1 }, { fileId: entry.fileId }); } // File has extension. Save extension, and add extension to path. - let fileId = entry.fileId; + const fileId = entry.fileId; entry.fileId = this.mimeUtils.removeExtension(fileId); entry.extension = extension; - return db.updateRecords(this.FILES_TABLE, entry, {fileId: fileId}).then(() =>{ + return db.updateRecords(this.FILES_TABLE, entry, { fileId: fileId }).then(() => { if (entry.fileId == fileId) { // File ID hasn't changed, we're done. this.logger.debug('Removed extesion ' + extension + ' from file ' + entry.fileId); + return; } // Now update the links. - return db.updateRecords(this.LINKS_TABLE, {fileId: entry.fileId}, {fileId: fileId}); + return db.updateRecords(this.LINKS_TABLE, { fileId: entry.fileId }, { fileId: fileId }); }); }); } @@ -1227,14 +1244,16 @@ export class CoreFilepoolProvider { * @param {string} siteId SiteID to get migrated * @return {Promise} Promise resolved when done. */ - fillMissingExtensionInFiles(siteId: string) : Promise { + fillMissingExtensionInFiles(siteId: string): Promise { this.logger.debug('Fill missing extensions in files of ' + siteId); + return this.sitesProvider.getSiteDb(siteId).then((db) => { return db.getAllRecords(this.FILES_TABLE).then((entries) => { - let promises = []; + const promises = []; entries.forEach((entry) => { promises.push(this.fillExtensionInFile(entry, siteId)); }); + return Promise.all(promises); }); }); @@ -1246,7 +1265,7 @@ export class CoreFilepoolProvider { * @param {string|number} componentId The component ID. * @return {string|number} The normalised component ID. -1 when undefined was passed. */ - protected fixComponentId(componentId: string|number) : string|number { + protected fixComponentId(componentId: string | number): string | number { if (typeof componentId == 'number') { return componentId; } @@ -1261,6 +1280,7 @@ export class CoreFilepoolProvider { return componentId; } } + return id; } @@ -1271,7 +1291,7 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The file URL. * @return {Promise} Resolved with fixed URL on success, rejected otherwise. */ - protected fixPluginfileURL(siteId: string, fileUrl: string) : Promise { + protected fixPluginfileURL(siteId: string, fileUrl: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.fixPluginfileURL(fileUrl); }); @@ -1285,8 +1305,8 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved with the files. */ - protected getComponentFiles(db: SQLiteDB, component: string, componentId?: string|number) : Promise { - let conditions = { + protected getComponentFiles(db: SQLiteDB, component: string, componentId?: string | number): Promise { + const conditions = { component: component, componentId: componentId || '' }; @@ -1301,16 +1321,18 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The file URL. * @return {Promise} Resolved with the URL. Rejected otherwise. */ - getDirectoryUrlByUrl(siteId: string, fileUrl: string) : Promise { + getDirectoryUrlByUrl(siteId: string, fileUrl: string): Promise { if (this.fileProvider.isAvailable()) { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { const fileId = this.getFileIdByUrl(fileUrl), filePath = this.getFilePath(siteId, fileId, ''); // No extension, the function will return a string. + return this.fileProvider.getDir(filePath).then((dirEntry) => { return dirEntry.toURL(); }); }); } + return Promise.reject(null); } @@ -1321,7 +1343,7 @@ export class CoreFilepoolProvider { * @param {string} filePath The file destination path. * @return {string} File download ID. */ - protected getFileDownloadId(fileUrl: string, filePath: string) : string { + protected getFileDownloadId(fileUrl: string, filePath: string): string { return Md5.hashAsciiStr(fileUrl + '###' + filePath); } @@ -1332,7 +1354,7 @@ export class CoreFilepoolProvider { * @param {string} fileId The file ID. * @return {string} Event name. */ - protected getFileEventName(siteId: string, fileId: string) : string { + protected getFileEventName(siteId: string, fileId: string): string { return 'mmFilepoolFile:' + siteId + ':' + fileId; } @@ -1343,9 +1365,10 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The absolute URL to the file. * @return {Promise} Promise resolved with event name. */ - getFileEventNameByUrl(siteId: string, fileUrl: string) : Promise { + getFileEventNameByUrl(siteId: string, fileUrl: string): Promise { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { const fileId = this.getFileIdByUrl(fileUrl); + return this.getFileEventName(siteId, fileId); }); } @@ -1359,7 +1382,7 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The absolute URL to the file. * @return {string} The file ID. */ - protected getFileIdByUrl(fileUrl: string) : string { + protected getFileIdByUrl(fileUrl: string): string { let url = this.removeRevisionFromUrl(fileUrl), filename; @@ -1373,8 +1396,8 @@ export class CoreFilepoolProvider { }); } - // Try to guess the filename the target file should have. We want to keep the original file name so - // people can easily identify the files after the download. + // Try to guess the filename the target file should have. + // We want to keep the original file name so people can easily identify the files after the download. filename = this.guessFilenameFromUrl(url); return filename + '_' + Md5.hashAsciiStr('url:' + url); @@ -1387,9 +1410,9 @@ export class CoreFilepoolProvider { * @param {string} fileId The file ID. * @return {Promise} Promise resolved with the links. */ - protected getFileLinks(siteId: string, fileId: string) : Promise { + protected getFileLinks(siteId: string, fileId: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getRecords(this.LINKS_TABLE, {fileId: fileId}); + return db.getRecords(this.LINKS_TABLE, { fileId: fileId }); }); } @@ -1401,7 +1424,7 @@ export class CoreFilepoolProvider { * @param {string} [extension] Previously calculated extension. Empty to not add any. Undefined to calculate it. * @return {string|Promise} The path to the file relative to storage root. */ - protected getFilePath(siteId: string, fileId: string, extension?: string) : string|Promise { + protected getFilePath(siteId: string, fileId: string, extension?: string): string | Promise { let path = this.getFilepoolFolderPath(siteId) + '/' + fileId; if (typeof extension == 'undefined') { // We need the extension to be able to open files properly. @@ -1409,6 +1432,7 @@ export class CoreFilepoolProvider { if (entry.extension) { path += '.' + entry.extension; } + return path; }).catch(() => { // If file not found, use the path without extension. @@ -1418,6 +1442,7 @@ export class CoreFilepoolProvider { if (extension) { path += '.' + extension; } + return path; } } @@ -1429,9 +1454,10 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The file URL. * @return {Promise} Promise resolved with the path to the file relative to storage root. */ - getFilePathByUrl(siteId: string, fileUrl: string) : Promise { + getFilePathByUrl(siteId: string, fileUrl: string): Promise { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { const fileId = this.getFileIdByUrl(fileUrl); + return this.getFilePath(siteId, fileId); }); } @@ -1442,7 +1468,7 @@ export class CoreFilepoolProvider { * @param {string} siteId The site ID. * @return {string} The root path to the filepool of the site. */ - getFilepoolFolderPath(siteId: string) : string { + getFilepoolFolderPath(siteId: string): string { return this.fileProvider.getSiteFolder(siteId) + '/' + this.FOLDER; } @@ -1454,14 +1480,14 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved with the files on success. */ - getFilesByComponent(siteId: string, component: string, componentId?: string|number) : Promise { + getFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { return this.getComponentFiles(db, component, componentId).then((items) => { - let promises = [], + const promises = [], files = []; items.forEach((item) => { - promises.push(db.getRecord(this.FILES_TABLE, {fileId: item.fileId}).then((fileEntry) => { + promises.push(db.getRecord(this.FILES_TABLE, { fileId: item.fileId }).then((fileEntry) => { if (!fileEntry) { return; } @@ -1493,10 +1519,10 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved with the size on success. */ - getFilesSizeByComponent(siteId: string, component: string, componentId?: string|number) : Promise { + getFilesSizeByComponent(siteId: string, component: string, componentId?: string | number): Promise { return this.getFilesByComponent(siteId, component, componentId).then((files) => { - let promises = [], - size = 0; + const promises = []; + let size = 0; files.forEach((file) => { promises.push(this.fileProvider.getFileSize(file.path).then((fs) => { @@ -1521,7 +1547,7 @@ export class CoreFilepoolProvider { * @param {string} [filePath] Filepath to download the file to. If defined, no extension will be added. * @return {Promise} Promise resolved with the file state. */ - getFileStateByUrl(siteId: string, fileUrl: string, timemodified = 0, filePath?: string) : Promise { + getFileStateByUrl(siteId: string, fileUrl: string, timemodified: number = 0, filePath?: string): Promise { let fileId, revision; @@ -1535,7 +1561,7 @@ export class CoreFilepoolProvider { return CoreConstants.DOWNLOADING; }).catch(() => { // Check if the file is being downloaded right now. - let extension = this.mimeUtils.guessExtensionFromUrl(fileUrl), + const extension = this.mimeUtils.guessExtensionFromUrl(fileUrl), path = filePath ? filePath : this.getFilePath(siteId, fileId, extension); return Promise.resolve(path).then((filePath) => { @@ -1579,16 +1605,18 @@ export class CoreFilepoolProvider { * This handles the queue and validity of the file. If there is a local file and it's valid, return the local URL. * If the file isn't downloaded or it's outdated, return the online URL and add it to the queue to be downloaded later. */ - protected getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string|number, mode = 'url', - timemodified = 0, checkSize = true, downloadUnknown?: boolean, options: any = {}) : Promise { + protected getFileUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, + mode: string = 'url', timemodified: number = 0, checkSize: boolean = true, downloadUnknown?: boolean, + options: any = {}): Promise { + let fileId, - revision, - addToQueue = (fileUrl) => { + revision; + const addToQueue = (fileUrl): void => { // Add the file to queue if needed and ignore errors. this.addToQueueIfNeeded(siteId, fileUrl, component, componentId, timemodified, checkSize, - downloadUnknown, options).catch(() => { - // Ignore errors. - }); + downloadUnknown, options).catch(() => { + // Ignore errors. + }); }; return this.fixPluginfileURL(siteId, fileUrl).then((fixedUrl) => { @@ -1638,6 +1666,7 @@ export class CoreFilepoolProvider { }, () => { // We do not have the file in store yet. Add to queue and return the fixed URL. addToQueue(fileUrl); + return fileUrl; }); }); @@ -1652,16 +1681,16 @@ export class CoreFilepoolProvider { * @param {string} fileId The file ID. * @return {Promise} Resolved with the internal URL. Rejected otherwise. */ - protected getInternalSrcById(siteId: string, fileId: string) : Promise { + protected getInternalSrcById(siteId: string, fileId: string): Promise { 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, - // with toURL the OS is unable to find the image files. + // We use toInternalURL so images are loaded in iOS8 using img HTML tags. return this.fileProvider.getInternalURL(fileEntry); }); }); } + return Promise.reject(null); } @@ -1672,7 +1701,7 @@ export class CoreFilepoolProvider { * @param {string} fileId The file ID. * @return {Promise} Resolved with the URL. Rejected otherwise. */ - protected getInternalUrlById(siteId: string, fileId: string) : Promise { + protected getInternalUrlById(siteId: string, fileId: string): Promise { if (this.fileProvider.isAvailable()) { return Promise.resolve(this.getFilePath(siteId, fileId)).then((path) => { return this.fileProvider.getFile(path).then((fileEntry) => { @@ -1680,6 +1709,7 @@ export class CoreFilepoolProvider { }); }); } + return Promise.reject(null); } @@ -1689,12 +1719,13 @@ export class CoreFilepoolProvider { * @param {string} filePath The file path. * @return {Promise} Resolved with the URL. */ - protected getInternalUrlByPath(filePath: string) : Promise { + protected getInternalUrlByPath(filePath: string): Promise { if (this.fileProvider.isAvailable()) { return this.fileProvider.getFile(filePath).then((fileEntry) => { return fileEntry.toURL(); }); } + return Promise.reject(null); } @@ -1705,13 +1736,15 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The file URL. * @return {Promise} Resolved with the URL. Rejected otherwise. */ - getInternalUrlByUrl(siteId: string, fileUrl: string) : Promise { + getInternalUrlByUrl(siteId: string, fileUrl: string): Promise { if (this.fileProvider.isAvailable()) { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { const fileId = this.getFileIdByUrl(fileUrl); + return this.getInternalUrlById(siteId, fileId); }); } + return Promise.reject(null); } @@ -1723,13 +1756,13 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved with the data. */ - getPackageData(siteId: string, component: string, componentId?: string|number) : Promise { + getPackageData(siteId: string, component: string, componentId?: string | number): Promise { componentId = this.fixComponentId(componentId); return this.sitesProvider.getSite(siteId).then((site) => { const packageId = this.getPackageId(component, componentId); - return site.getDb().getRecord(this.PACKAGES_TABLE, {id: packageId}); + return site.getDb().getRecord(this.PACKAGES_TABLE, { id: packageId }); }); } @@ -1739,7 +1772,7 @@ export class CoreFilepoolProvider { * @param {string} url An URL to identify the package. * @return {string} The directory name. */ - protected getPackageDirNameByUrl(url: string) : string { + protected getPackageDirNameByUrl(url: string): string { let candidate, extension = ''; @@ -1757,6 +1790,7 @@ export class CoreFilepoolProvider { extension = '.' + candidate; } } + return Md5.hashAsciiStr('url:' + url) + extension; } @@ -1767,9 +1801,10 @@ export class CoreFilepoolProvider { * @param {string} url An URL to identify the package. * @return {Promise} Promise resolved with the path of the package. */ - getPackageDirPathByUrl(siteId: string, url: string) : Promise { + getPackageDirPathByUrl(siteId: string, url: string): Promise { return this.fixPluginfileURL(siteId, url).then((fixedUrl) => { const dirName = this.getPackageDirNameByUrl(fixedUrl); + return this.getFilePath(siteId, dirName, ''); }); } @@ -1781,16 +1816,18 @@ export class CoreFilepoolProvider { * @param {string} url An URL to identify the package. * @return {Promise} Resolved with the URL. */ - getPackageDirUrlByUrl(siteId: string, url: string) : Promise { + getPackageDirUrlByUrl(siteId: string, url: string): Promise { if (this.fileProvider.isAvailable()) { return this.fixPluginfileURL(siteId, url).then((fixedUrl) => { const dirName = this.getPackageDirNameByUrl(fixedUrl), dirPath = this.getFilePath(siteId, dirName, ''); // No extension, the function will return a string. + return this.fileProvider.getDir(dirPath).then((dirEntry) => { return dirEntry.toURL(); }); }); } + return Promise.reject(null); } @@ -1802,7 +1839,7 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Download promise or undefined. */ - getPackageDownloadPromise(siteId: string, component: string, componentId?: string|number) : Promise { + getPackageDownloadPromise(siteId: string, component: string, componentId?: string | number): Promise { const packageId = this.getPackageId(component, componentId); if (this.packagesPromises[siteId] && this.packagesPromises[siteId][packageId]) { return this.packagesPromises[siteId][packageId]; @@ -1816,7 +1853,7 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved with the extra data. */ - getPackageExtra(siteId: string, component: string, componentId?: string|number) : Promise { + getPackageExtra(siteId: string, component: string, componentId?: string | number): Promise { return this.getPackageData(siteId, component, componentId).then((entry) => { return entry.extra; }); @@ -1829,7 +1866,7 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {string} Package ID. */ - getPackageId(component: string, componentId?: string|number) : string { + getPackageId(component: string, componentId?: string | number): string { return Md5.hashAsciiStr(component + '#' + this.fixComponentId(componentId)); } @@ -1841,7 +1878,7 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved with the status. */ - getPackagePreviousStatus(siteId: string, component: string, componentId?: string|number) : Promise { + getPackagePreviousStatus(siteId: string, component: string, componentId?: string | number): Promise { return this.getPackageData(siteId, component, componentId).then((entry) => { return entry.previous || CoreConstants.NOT_DOWNLOADED; }).catch(() => { @@ -1857,7 +1894,7 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved with the status. */ - getPackageStatus(siteId: string, component: string, componentId?: string|number) : Promise { + getPackageStatus(siteId: string, component: string, componentId?: string | number): Promise { return this.getPackageData(siteId, component, componentId).then((entry) => { return entry.status || CoreConstants.NOT_DOWNLOADED; }).catch(() => { @@ -1871,13 +1908,13 @@ export class CoreFilepoolProvider { * @param {string} url URL to get the args. * @return {string[]} The args found, undefined if not a pluginfile. */ - protected getPluginFileArgs(url: string) : string[] { + protected getPluginFileArgs(url: string): string[] { if (!this.urlUtils.isPluginFileUrl(url)) { // Not pluginfile, return. return; } - let relativePath = url.substr(url.indexOf('/pluginfile.php') + 16), + const relativePath = url.substr(url.indexOf('/pluginfile.php') + 16), args = relativePath.split('/'); if (args.length < 3) { @@ -1897,7 +1934,7 @@ export class CoreFilepoolProvider { * @param {Function} [onProgress] Function to call on progress. * @return {any} Deferred. */ - protected getQueueDeferred(siteId: string, fileId: string, create = true, onProgress?: (event: any) => any): any { + protected getQueueDeferred(siteId: string, fileId: string, create: boolean = true, onProgress?: (event: any) => any): any { if (!this.queueDeferreds[siteId]) { if (!create) { return; @@ -1941,7 +1978,8 @@ export class CoreFilepoolProvider { * @param {Function} [onProgress] Function to call on progress. * @return {Promise} Promise. */ - protected getQueuePromise(siteId: string, fileId: string, create = true, onProgress?: (event: any) => any) : Promise { + protected getQueuePromise(siteId: string, fileId: string, create: boolean = true, onProgress?: (event: any) => any) + : Promise { return this.getQueueDeferred(siteId, fileId, create, onProgress).promise; } @@ -1951,7 +1989,7 @@ export class CoreFilepoolProvider { * @param {any[]} files Package files. * @return {number} Highest revision. */ - getRevisionFromFileList(files: any[]) : number { + getRevisionFromFileList(files: any[]): number { let revision = 0; files.forEach((file) => { @@ -1972,22 +2010,23 @@ export class CoreFilepoolProvider { * @param {string} url URL to get the revision number. * @return {number} Revision number. */ - protected getRevisionFromUrl(url: string) : number { - let args = this.getPluginFileArgs(url); + protected getRevisionFromUrl(url: string): number { + const args = this.getPluginFileArgs(url); if (!args) { // Not a pluginfile, no revision will be found. return 0; } - let revisionRegex = this.pluginFileDelegate.getComponentRevisionRegExp(args); + const revisionRegex = this.pluginFileDelegate.getComponentRevisionRegExp(args); if (!revisionRegex) { return 0; } - let matches = url.match(revisionRegex); + const matches = url.match(revisionRegex); if (matches && typeof matches[1] != 'undefined') { return parseInt(matches[1], 10); } + return 0; } @@ -2009,10 +2048,10 @@ export class CoreFilepoolProvider { * This will return a URL pointing to the content of the requested URL. * The URL returned is compatible to use with IMG tags. */ - getSrcByUrl(siteId: string, fileUrl: string, component: string, componentId?: string|number, timemodified = 0, - checkSize = true, downloadUnknown?: boolean, options: any = {}) : Promise { + getSrcByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0, + checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise { return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'src', - timemodified, checkSize, downloadUnknown, options); + timemodified, checkSize, downloadUnknown, options); } /** @@ -2021,7 +2060,7 @@ export class CoreFilepoolProvider { * @param {any[]} files List of files. * @return {number} Time modified. */ - getTimemodifiedFromFileList(files: any[]) : number { + getTimemodifiedFromFileList(files: any[]): number { let timemodified = 0; files.forEach((file) => { @@ -2051,10 +2090,10 @@ export class CoreFilepoolProvider { * This will return a URL pointing to the content of the requested URL. * The URL returned is compatible to use with a local browser. */ - getUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string|number, timemodified = 0, - checkSize = true, downloadUnknown?: boolean, options: any = {}) : Promise { + getUrlByUrl(siteId: string, fileUrl: string, component: string, componentId?: string | number, timemodified: number = 0, + checkSize: boolean = true, downloadUnknown?: boolean, options: any = {}): Promise { return this.getFileUrlByUrl(siteId, fileUrl, component, componentId, 'url', - timemodified, checkSize, downloadUnknown, options); + timemodified, checkSize, downloadUnknown, options); } /** @@ -2063,12 +2102,12 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The file URL. * @return {string} The filename treated so it doesn't have any special character. */ - protected guessFilenameFromUrl(fileUrl: string) : string { + protected guessFilenameFromUrl(fileUrl: string): string { let filename = ''; if (fileUrl.indexOf('/webservice/pluginfile') !== -1) { // It's a pluginfile URL. Search for the 'file' param to extract the name. - let params = this.urlUtils.extractUrlParams(fileUrl); + const params = this.urlUtils.extractUrlParams(fileUrl); if (params.file) { filename = params.file.substr(params.file.lastIndexOf('/') + 1); } else { @@ -2105,12 +2144,13 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The file URL. * @return {Promise} Resolved with file object from DB on success, rejected otherwise. */ - protected hasFileInPool(siteId: string, fileId: string) : Promise { + protected hasFileInPool(siteId: string, fileId: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.getRecord(this.FILES_TABLE, {fileId: fileId}).then((entry) => { + return db.getRecord(this.FILES_TABLE, { fileId: fileId }).then((entry) => { if (typeof entry === 'undefined') { return Promise.reject(null); } + return entry; }); }); @@ -2123,13 +2163,14 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The file URL. * @return {Promise} Resolved with file object from DB on success, rejected otherwise. */ - protected hasFileInQueue(siteId: string, fileId: string) : Promise { - return this.appDB.getRecord(this.QUEUE_TABLE, {siteId: siteId, fileId: fileId}).then((entry) => { + protected hasFileInQueue(siteId: string, fileId: string): Promise { + return this.appDB.getRecord(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId }).then((entry) => { if (typeof entry === 'undefined') { return Promise.reject(null); } // Convert the links to an object. entry.links = JSON.parse(entry.links); + return entry; }); } @@ -2142,7 +2183,7 @@ export class CoreFilepoolProvider { * It is advised to set it to true to reduce the performance and data usage of the app. * @return {Promise} Resolved on success. */ - invalidateAllFiles(siteId: string, onlyUnknown = true) : Promise { + invalidateAllFiles(siteId: string, onlyUnknown: boolean = true): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { let where, whereParams; @@ -2151,7 +2192,7 @@ export class CoreFilepoolProvider { whereParams = [0, 1, 0]; } - return db.updateRecordsWhere(this.FILES_TABLE, {stale: 1}, where, whereParams); + return db.updateRecordsWhere(this.FILES_TABLE, { stale: 1 }, where, whereParams); }); } @@ -2167,11 +2208,12 @@ export class CoreFilepoolProvider { * You can manully call addToQueueByUrl to add this file to the queue immediately. * Please note that, if a file is stale, the user will be presented the stale file if there is no network access. */ - invalidateFileByUrl(siteId: string, fileUrl: string) : Promise { + invalidateFileByUrl(siteId: string, fileUrl: string): Promise { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { const fileId = this.getFileIdByUrl(fileUrl); + return this.sitesProvider.getSiteDb(siteId).then((db) => { - return db.updateRecords(this.FILES_TABLE, {stale: 1}, {fileId: fileId}); + return db.updateRecords(this.FILES_TABLE, { stale: 1 }, { fileId: fileId }); }); }); } @@ -2184,12 +2226,13 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @param {boolean} [onlyUnknown=true] True to only invalidate files from external repos or without revision/timemodified. * It is advised to set it to true to reduce the performance and data usage of the app. - * @return {Promise} Resolved on success. + * @return {Promise} Resolved when done. */ - invalidateFilesByComponent(siteId, component, componentId?: string|number, onlyUnknown = true) { + invalidateFilesByComponent(siteId: string, component: string, componentId?: string | number, onlyUnknown: boolean = true) + : Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { return this.getComponentFiles(db, component, componentId).then((items) => { - let fileIds = items.map((item) => { + const fileIds = items.map((item) => { return item.fileId; }), whereAndParams = db.getInOrEqual(fileIds); @@ -2199,7 +2242,7 @@ export class CoreFilepoolProvider { whereAndParams[1] = whereAndParams[1].params.concat([0, 1, 0]); } - return db.updateRecordsWhere(this.FILES_TABLE, {stale: 1}, whereAndParams[0], whereAndParams[1]); + return db.updateRecordsWhere(this.FILES_TABLE, { stale: 1 }, whereAndParams[0], whereAndParams[1]); }); }); } @@ -2211,9 +2254,10 @@ export class CoreFilepoolProvider { * @param {string} fileUrl File URL. * @param {Promise} Promise resolved if file is downloading, rejected otherwise. */ - isFileDownloadingByUrl(siteId: string, fileUrl: string) : Promise { + isFileDownloadingByUrl(siteId: string, fileUrl: string): Promise { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { - let fileId = this.getFileIdByUrl(fileUrl); + const fileId = this.getFileIdByUrl(fileUrl); + return this.hasFileInQueue(siteId, fileId); }); } @@ -2226,7 +2270,7 @@ export class CoreFilepoolProvider { * @param {number} [timemodified] The time this file was modified. * @param {boolean} Whether the file is outdated. */ - protected isFileOutdated(entry: CoreFilepoolFileEntry, revision?: number, timemodified?: number) : boolean { + protected isFileOutdated(entry: CoreFilepoolFileEntry, revision?: number, timemodified?: number): boolean { return !!entry.stale || revision > entry.revision || timemodified > entry.timemodified; } @@ -2236,7 +2280,7 @@ export class CoreFilepoolProvider { * @param {CoreFilepoolFileEntry} entry Filepool entry. * @return {boolean} Whether it cannot determine updates. */ - protected isFileUpdateUnknown(entry: CoreFilepoolFileEntry) : boolean { + protected isFileUpdateUnknown(entry: CoreFilepoolFileEntry): boolean { return !!entry.isexternalfile || (entry.revision < 1 && !entry.timemodified); } @@ -2246,8 +2290,8 @@ export class CoreFilepoolProvider { * @param {string} siteId The site ID. * @param {string} fileId The file ID. */ - protected notifyFileDeleted(siteId: string, fileId: string) : void { - this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), {action: 'deleted'}); + protected notifyFileDeleted(siteId: string, fileId: string): void { + this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'deleted' }); } /** @@ -2256,8 +2300,8 @@ export class CoreFilepoolProvider { * @param {string} siteId The site ID. * @param {string} fileId The file ID. */ - protected notifyFileDownloaded(siteId: string, fileId: string) : void { - this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), {action: 'download', success: true}); + protected notifyFileDownloaded(siteId: string, fileId: string): void { + this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'download', success: true }); } /** @@ -2266,8 +2310,8 @@ export class CoreFilepoolProvider { * @param {string} siteId The site ID. * @param {string} fileId The file ID. */ - protected notifyFileDownloadError(siteId: string, fileId: string) : void { - this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), {action: 'download', success: false}); + protected notifyFileDownloadError(siteId: string, fileId: string): void { + this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'download', success: false }); } /** @@ -2276,8 +2320,8 @@ export class CoreFilepoolProvider { * @param {string} siteId The site ID. * @param {string} fileId The file ID. */ - protected notifyFileDownloading(siteId: string, fileId: string) : void { - this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), {action: 'downloading'}); + protected notifyFileDownloading(siteId: string, fileId: string): void { + this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'downloading' }); } /** @@ -2286,8 +2330,8 @@ export class CoreFilepoolProvider { * @param {string} siteId The site ID. * @param {string} fileId The file ID. */ - protected notifyFileOutdated(siteId: string, fileId: string) : void { - this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), {action: 'outdated'}); + protected notifyFileOutdated(siteId: string, fileId: string): void { + this.eventsProvider.trigger(this.getFileEventName(siteId, fileId), { action: 'outdated' }); } /** @@ -2303,8 +2347,8 @@ export class CoreFilepoolProvider { * @param {Function} [onProgress] Function to call on progress. * @return {Promise} Promise resolved when all files are downloaded. */ - prefetchPackage(siteId: string, fileList: any[], component: string, componentId?: string|number, extra?: string, - dirPath?: string, onProgress?: (event: any) => any) : Promise { + prefetchPackage(siteId: string, fileList: any[], component: string, componentId?: string | number, extra?: string, + dirPath?: string, onProgress?: (event: any) => any): Promise { return this.downloadOrPrefetchPackage(siteId, fileList, true, component, componentId, extra, dirPath, onProgress); } @@ -2315,7 +2359,7 @@ export class CoreFilepoolProvider { * This loops over itself to keep on processing the queue in the background. * The queue process is site agnostic. */ - protected processQueue() : void { + protected processQueue(): void { let promise; if (this.queueState !== this.QUEUE_RUNNING) { @@ -2354,14 +2398,15 @@ export class CoreFilepoolProvider { * * @return {Promise} Resolved on success. Rejected on failure. */ - protected processImportantQueueItem() : Promise { + protected processImportantQueueItem(): Promise { return this.appDB.getRecords(this.QUEUE_TABLE, undefined, 'priority DESC, added ASC', undefined, 0, 1).then((items) => { - let item = items.pop(); + const item = items.pop(); if (!item) { return Promise.reject(this.ERR_QUEUE_IS_EMPTY); } // Convert the links to an object. item.links = JSON.parse(item.links); + return this.processQueueItem(item); }, () => { return Promise.reject(this.ERR_QUEUE_IS_EMPTY); @@ -2374,9 +2419,9 @@ export class CoreFilepoolProvider { * @param {CoreFilepoolQueueEntry} item The object from the queue store. * @return {Promise} Resolved on success. Rejected on failure. */ - protected processQueueItem(item: CoreFilepoolQueueEntry) : Promise { + protected processQueueItem(item: CoreFilepoolQueueEntry): Promise { // Cast optional fields to undefined instead of null. - let siteId = item.siteId, + const siteId = item.siteId, fileId = item.fileId, fileUrl = item.url, options = { @@ -2389,6 +2434,7 @@ export class CoreFilepoolProvider { links = item.links || []; this.logger.debug('Processing queue item: ' + siteId + ', ' + fileId); + // Check if the file is already in pool. return this.hasFileInPool(siteId, fileId).catch(() => { // File not in pool. @@ -2402,11 +2448,13 @@ export class CoreFilepoolProvider { this.treatQueueDeferred(siteId, fileId, true); }); this.notifyFileDownloaded(siteId, fileId); + return; } // The file does not exist, or is stale, ... download it. - let onProgress = this.getQueueOnProgress(siteId, fileId); + const onProgress = this.getQueueOnProgress(siteId, fileId); + return this.downloadForPoolByUrl(siteId, fileUrl, options, filePath, onProgress, entry).then(() => { // Success, we add links and remove from queue. this.addFileLinks(siteId, fileId, links); @@ -2416,7 +2464,9 @@ export class CoreFilepoolProvider { // Wait for the item to be removed from queue before resolving the promise. // If the item could not be removed from queue we still resolve the promise. - return this.removeFromQueue(siteId, fileId).catch(() => {}); + return this.removeFromQueue(siteId, fileId).catch(() => { + // Ignore errors. + }); }, (errorObject) => { // Whoops, we have an error... let dropFromQueue = false; @@ -2438,8 +2488,7 @@ export class CoreFilepoolProvider { // We have the latest version of the file, HTTP 304 status. dropFromQueue = true; } else { - // Unknown error, let's remove the file from the queue to avoid - // locking down the queue because of one file. + // Unknown error, let's remove the file from the queue to avoi locking down the queue. dropFromQueue = true; } } else { @@ -2449,8 +2498,9 @@ export class CoreFilepoolProvider { if (dropFromQueue) { this.logger.debug('Item dropped from queue due to error: ' + fileUrl, errorObject); - // Consider this as a silent error, never reject the promise here. - return this.removeFromQueue(siteId, fileId).catch(() => {}).then(() => { + return this.removeFromQueue(siteId, fileId).catch(() => { + // Consider this as a silent error, never reject the promise here. + }).then(() => { this.treatQueueDeferred(siteId, fileId, false); this.notifyFileDownloadError(siteId, fileId); }); @@ -2458,6 +2508,7 @@ export class CoreFilepoolProvider { // We considered the file as legit but did not get it, failure. this.treatQueueDeferred(siteId, fileId, false); this.notifyFileDownloadError(siteId, fileId); + return Promise.reject(errorObject); } @@ -2470,10 +2521,10 @@ export class CoreFilepoolProvider { * * @param {string} siteId The site ID. * @param {string} fileId The file ID. - * @return {Promise} Resolved on success. Rejected on failure. It is advised to silently ignore failures. + * @return {Promise} Resolved on success. Rejected on failure. It is advised to silently ignore failures. */ - protected removeFromQueue(siteId: string, fileId: string) { - return this.appDB.deleteRecords(this.QUEUE_TABLE, {siteId: siteId, fileId: fileId}); + protected removeFromQueue(siteId: string, fileId: string): Promise { + return this.appDB.deleteRecords(this.QUEUE_TABLE, { siteId: siteId, fileId: fileId }); } /** @@ -2483,17 +2534,17 @@ export class CoreFilepoolProvider { * @param {string} fileId The file ID. * @return {Promise} Resolved on success. */ - protected removeFileById(siteId: string, fileId: string) : Promise { + protected removeFileById(siteId: string, fileId: string): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { // Get the path to the file first since it relies on the file object stored in the pool. return Promise.resolve(this.getFilePath(siteId, fileId)).then((path) => { - let promises = []; + const promises = []; // Remove entry from filepool store. - promises.push(db.deleteRecords(this.FILES_TABLE, {fileId: fileId})); + promises.push(db.deleteRecords(this.FILES_TABLE, { fileId: fileId })); // Remove links. - promises.push(db.deleteRecords(this.LINKS_TABLE, {fileId: fileId})); + promises.push(db.deleteRecords(this.LINKS_TABLE, { fileId: fileId })); // Remove the file. if (this.fileProvider.isAvailable()) { @@ -2521,7 +2572,7 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Resolved on success. */ - removeFilesByComponent(siteId: string, component: string, componentId?: string|number) : Promise { + removeFilesByComponent(siteId: string, component: string, componentId?: string | number): Promise { return this.sitesProvider.getSiteDb(siteId).then((db) => { return this.getComponentFiles(db, component, componentId); }).then((items) => { @@ -2538,9 +2589,10 @@ export class CoreFilepoolProvider { * @param {string} fileUrl The file URL. * @return {Promise} Resolved on success, rejected on failure. */ - removeFileByUrl(siteId: string, fileUrl: string) : Promise { + removeFileByUrl(siteId: string, fileUrl: string): Promise { return this.fixPluginfileURL(siteId, fileUrl).then((fileUrl) => { const fileId = this.getFileIdByUrl(fileUrl); + return this.removeFileById(siteId, fileId); }); } @@ -2553,8 +2605,8 @@ export class CoreFilepoolProvider { * @description * The revision is used to know if a file has changed. We remove it from the URL to prevent storing a file per revision. */ - protected removeRevisionFromUrl(url: string) : string { - let args = this.getPluginFileArgs(url); + protected removeRevisionFromUrl(url: string): string { + const args = this.getPluginFileArgs(url); if (!args) { // Not a pluginfile, no revision will be found. return url; @@ -2571,7 +2623,7 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved when the status is changed. Resolve param: new status. */ - setPackagePreviousStatus(siteId: string, component: string, componentId?: string|number) : Promise { + setPackagePreviousStatus(siteId: string, component: string, componentId?: string | number): Promise { componentId = this.fixComponentId(componentId); this.logger.debug(`Set previous status for package ${component} ${componentId}`); @@ -2579,8 +2631,8 @@ export class CoreFilepoolProvider { const packageId = this.getPackageId(component, componentId); // Get current stored data, we'll only update 'status' and 'updated' fields. - return site.getDb().getRecord(this.PACKAGES_TABLE, {id: packageId}).then((entry: CoreFilepoolPackageEntry) => { - let newData: CoreFilepoolPackageEntry = {}; + return site.getDb().getRecord(this.PACKAGES_TABLE, { id: packageId }).then((entry: CoreFilepoolPackageEntry) => { + const newData: CoreFilepoolPackageEntry = {}; if (entry.status == CoreConstants.DOWNLOADING) { // Going back from downloading to previous status, restore previous download time. newData.downloadTime = entry.previousDownloadTime; @@ -2589,9 +2641,10 @@ export class CoreFilepoolProvider { newData.updated = Date.now(); this.logger.debug(`Set previous status '${entry.status}' for package ${component} ${componentId}`); - return site.getDb().updateRecords(this.PACKAGES_TABLE, newData, {id: packageId}).then(() => { + return site.getDb().updateRecords(this.PACKAGES_TABLE, newData, { id: packageId }).then(() => { // Success updating, trigger event. this.triggerPackageStatusChanged(site.id, newData.status, component, componentId); + return newData.status; }); }); @@ -2612,7 +2665,7 @@ export class CoreFilepoolProvider { * - The file cannot be streamed. * If the file is big and can be streamed, the promise returned by this function will be rejected. */ - shouldDownloadBeforeOpen(url: string, size: number) : Promise { + shouldDownloadBeforeOpen(url: string, size: number): Promise { if (size >= 0 && size <= this.DOWNLOAD_THRESHOLD) { // The file is small, download it. return Promise.resolve(); @@ -2641,14 +2694,14 @@ export class CoreFilepoolProvider { * @param {string} [extra] Extra data to store for the package. If you want to store more than 1 value, use JSON.stringify. * @return {Promise} Promise resolved when status is stored. */ - storePackageStatus(siteId: string, status: string, component: string, componentId?: string|number, extra?: string) + storePackageStatus(siteId: string, status: string, component: string, componentId?: string | number, extra?: string) : Promise { this.logger.debug(`Set status '${status}'' for package ${component} ${componentId}`); componentId = this.fixComponentId(componentId); return this.sitesProvider.getSite(siteId).then((site) => { - let packageId = this.getPackageId(component, componentId), - downloadTime, + const packageId = this.getPackageId(component, componentId); + let downloadTime, previousDownloadTime; if (status == CoreConstants.DOWNLOADING) { @@ -2657,7 +2710,7 @@ export class CoreFilepoolProvider { } // Search current status to set it as previous status. - return site.getDb().getRecord(this.PACKAGES_TABLE, {id: packageId}).then((entry: CoreFilepoolPackageEntry) => { + return site.getDb().getRecord(this.PACKAGES_TABLE, { id: packageId }).then((entry: CoreFilepoolPackageEntry) => { if (typeof extra == 'undefined' || extra === null) { extra = entry.extra; } @@ -2666,7 +2719,7 @@ export class CoreFilepoolProvider { downloadTime = entry.downloadTime; previousDownloadTime = entry.previousDownloadTime; } else { - // downloadTime will be updated, store current time as previous. + // The downloadTime will be updated, store current time as previous. previousDownloadTime = entry.downloadTime; } @@ -2674,7 +2727,7 @@ export class CoreFilepoolProvider { }).catch(() => { // No previous status. }).then((previousStatus: string) => { - let packageEntry: CoreFilepoolPackageEntry = { + const packageEntry: CoreFilepoolPackageEntry = { id: packageId, component: component, componentId: componentId, @@ -2684,14 +2737,14 @@ export class CoreFilepoolProvider { downloadTime: downloadTime, previousDownloadTime: previousDownloadTime, extra: extra - }, - promise; + }; + let promise; if (previousStatus === status) { // The package already has this status, no need to change it. promise = Promise.resolve(); } else { - promise = site.getDb().insertOrUpdateRecord(this.PACKAGES_TABLE, packageEntry, {id: packageId}); + promise = site.getDb().insertOrUpdateRecord(this.PACKAGES_TABLE, packageEntry, { id: packageId }); } return promise.then(() => { @@ -2707,11 +2760,11 @@ export class CoreFilepoolProvider { * * @return {Promise} Promise resolved when done. */ - treatExtensionInQueue() : Promise { + treatExtensionInQueue(): Promise { this.logger.debug('Treat extensions in queue'); return this.appDB.getAllRecords(this.QUEUE_TABLE).then((entries) => { - let promises = []; + const promises = []; entries.forEach((entry) => { // For files in the queue, we only need to remove the extension from the fileId. @@ -2723,8 +2776,9 @@ export class CoreFilepoolProvider { return; } - promises.push(this.appDB.updateRecords(this.QUEUE_TABLE, {fileId: entry.fileId}, {fileId: fileId})); + promises.push(this.appDB.updateRecords(this.QUEUE_TABLE, { fileId: entry.fileId }, { fileId: fileId })); }); + return Promise.all(promises); }); } @@ -2736,7 +2790,7 @@ export class CoreFilepoolProvider { * @param {string} fileId The file ID. * @param {boolean} resolve True if promise should be resolved, false if it should be rejected. */ - protected treatQueueDeferred(siteId: string, fileId: string, resolve: boolean) : void { + protected treatQueueDeferred(siteId: string, fileId: string, resolve: boolean): void { if (this.queueDeferreds[siteId] && this.queueDeferreds[siteId][fileId]) { if (resolve) { this.queueDeferreds[siteId][fileId].resolve(); @@ -2755,12 +2809,12 @@ export class CoreFilepoolProvider { * @param {string} component Package's component. * @param {string|number} [componentId] An ID to use in conjunction with the component. */ - protected triggerPackageStatusChanged(siteId: string, status: string, component: string, componentId?: string|number) : void { + protected triggerPackageStatusChanged(siteId: string, status: string, component: string, componentId?: string | number): void { const data = { component: component, componentId: this.fixComponentId(componentId), status: status - } + }; this.eventsProvider.trigger(CoreEventsProvider.PACKAGE_STATUS_CHANGED, data, siteId); } @@ -2774,13 +2828,13 @@ export class CoreFilepoolProvider { * @param {string|number} [componentId] An ID to use in conjunction with the component. * @return {Promise} Promise resolved when status is stored. */ - updatePackageDownloadTime(siteId: string, component: string, componentId?: string|number) : Promise { + updatePackageDownloadTime(siteId: string, component: string, componentId?: string | number): Promise { componentId = this.fixComponentId(componentId); return this.sitesProvider.getSite(siteId).then((site) => { const packageId = this.getPackageId(component, componentId); - return site.getDb().updateRecords(this.PACKAGES_TABLE, {downloadTime: this.timeUtils.timestamp()}, {id: packageId}); + return site.getDb().updateRecords(this.PACKAGES_TABLE, { downloadTime: this.timeUtils.timestamp() }, { id: packageId }); }); } } diff --git a/src/providers/groups.ts b/src/providers/groups.ts index 72c2ba57e..5395a4071 100644 --- a/src/providers/groups.ts +++ b/src/providers/groups.ts @@ -37,7 +37,7 @@ export interface CoreGroupInfo { * @type {boolean} */ visibleGroups?: boolean; -}; +} /* * Service to handle groups. @@ -45,12 +45,12 @@ export interface CoreGroupInfo { @Injectable() export class CoreGroupsProvider { // Group mode constants. - public static NOGROUPS = 0; - public static SEPARATEGROUPS = 1; - public static VISIBLEGROUPS = 2; + static NOGROUPS = 0; + static SEPARATEGROUPS = 1; + static VISIBLEGROUPS = 2; protected ROOT_CACHE_KEY = 'mmGroups:'; - constructor(private sitesProvider: CoreSitesProvider, private translate: TranslateService) {} + constructor(private sitesProvider: CoreSitesProvider, private translate: TranslateService) { } /** * Check if group mode of an activity is enabled. @@ -59,7 +59,7 @@ export class CoreGroupsProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with true if the activity has groups, resolved with false otherwise. */ - activityHasGroups(cmId: number, siteId?: string) : Promise { + activityHasGroups(cmId: number, siteId?: string): Promise { return this.getActivityGroupMode(cmId, siteId).then((groupmode) => { return groupmode === CoreGroupsProvider.SEPARATEGROUPS || groupmode === CoreGroupsProvider.VISIBLEGROUPS; }).catch(() => { @@ -75,7 +75,7 @@ export class CoreGroupsProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the groups are retrieved. */ - getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string) : Promise { + getActivityAllowedGroups(cmId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { userId = userId || site.getUserId(); @@ -85,12 +85,13 @@ export class CoreGroupsProvider { }, preSets = { cacheKey: this.getActivityAllowedGroupsCacheKey(cmId, userId) - } + }; return site.read('core_group_get_activity_allowed_groups', params, preSets).then((response) => { if (!response || !response.groups) { return Promise.reject(null); } + return response.groups; }); }); @@ -103,7 +104,7 @@ export class CoreGroupsProvider { * @param {number} userId User ID. * @return {string} Cache key. */ - protected getActivityAllowedGroupsCacheKey(cmId: number, userId: number) : string { + protected getActivityAllowedGroupsCacheKey(cmId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'allowedgroups:' + cmId + ':' + userId; } @@ -115,7 +116,7 @@ export class CoreGroupsProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the groups are retrieved. If not allowed, empty array will be returned. */ - getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string) : Promise { + getActivityAllowedGroupsIfEnabled(cmId: number, userId?: number, siteId?: string): Promise { siteId = siteId || this.sitesProvider.getCurrentSiteId(); // Get real groupmode, in case it's forced by the course. @@ -124,6 +125,7 @@ export class CoreGroupsProvider { // Get the groups available for the user. return this.getActivityAllowedGroups(cmId, userId, siteId); } + return []; }); } @@ -137,10 +139,10 @@ export class CoreGroupsProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved with the group info. */ - getActivityGroupInfo(cmId: number, addAllParts = true, userId?: number, siteId?: string) : Promise { - let groupInfo: CoreGroupInfo = { + getActivityGroupInfo(cmId: number, addAllParts: boolean = true, userId?: number, siteId?: string): Promise { + const groupInfo: CoreGroupInfo = { groups: [] - } + }; return this.getActivityGroupMode(cmId, siteId).then((groupMode) => { groupInfo.separateGroups = groupMode === CoreGroupsProvider.SEPARATEGROUPS; @@ -149,6 +151,7 @@ export class CoreGroupsProvider { if (groupInfo.separateGroups || groupInfo.visibleGroups) { return this.getActivityAllowedGroups(cmId, userId, siteId); } + return []; }).then((groups) => { if (groups.length <= 0) { @@ -156,10 +159,11 @@ export class CoreGroupsProvider { groupInfo.visibleGroups = false; } else { if (addAllParts || groupInfo.visibleGroups) { - groupInfo.groups.push({id: 0, name: this.translate.instant('core.allparticipants')}); + groupInfo.groups.push({ id: 0, name: this.translate.instant('core.allparticipants') }); } groupInfo.groups = groupInfo.groups.concat(groups); } + return groupInfo; }); } @@ -171,19 +175,20 @@ export class CoreGroupsProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the group mode is retrieved. */ - getActivityGroupMode(cmId: number, siteId?: string) : Promise { + getActivityGroupMode(cmId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { const params = { cmid: cmId }, preSets = { cacheKey: this.getActivityGroupModeCacheKey(cmId) - } + }; return site.read('core_group_get_activity_groupmode', params, preSets).then((response) => { if (!response || typeof response.groupmode == 'undefined') { return Promise.reject(null); } + return response.groupmode; }); }); @@ -195,7 +200,7 @@ export class CoreGroupsProvider { * @param {number} cmId Course module ID. * @return {string} Cache key. */ - protected getActivityGroupModeCacheKey(cmId: number) : string { + protected getActivityGroupModeCacheKey(cmId: number): string { return this.ROOT_CACHE_KEY + 'groupmode:' + cmId; } @@ -207,9 +212,9 @@ export class CoreGroupsProvider { * @param {number} [userId] ID of the user. If not defined, use the userId related to siteId. * @return {Promise} Promise resolved when the groups are retrieved. */ - getUserGroups(courses: any[], siteId?: string, userId?: number) : Promise { - let promises = [], - groups = []; + getUserGroups(courses: any[], siteId?: string, userId?: number): Promise { + const promises = []; + let groups = []; courses.forEach((course) => { const courseId = typeof course == 'object' ? course.id : course; @@ -231,12 +236,13 @@ export class CoreGroupsProvider { * @param {number} [userId] ID of the user. If not defined, use ID related to siteid. * @return {Promise} Promise resolved when the groups are retrieved. */ - getUserGroupsInCourse(courseId: number, siteId?: string, userId?: number) : Promise { + getUserGroupsInCourse(courseId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let data = { + const data = { userid: userId || site.getUserId(), courseid: courseId - }, preSets = { + }, + preSets = { cacheKey: this.getUserGroupsInCourseCacheKey(courseId, userId) }; @@ -257,7 +263,7 @@ export class CoreGroupsProvider { * @param {number} userId User ID. * @return {string} Cache key. */ - protected getUserGroupsInCourseCacheKey(courseId: number, userId: number) : string { + protected getUserGroupsInCourseCacheKey(courseId: number, userId: number): string { return this.ROOT_CACHE_KEY + 'courseGroups:' + courseId + ':' + userId; } @@ -269,9 +275,10 @@ export class CoreGroupsProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateActivityAllowedGroups(cmId: number, userId?: number, siteId?: string) : Promise { + invalidateActivityAllowedGroups(cmId: number, userId?: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - userId = userId || site.getUserId(); + userId = userId || site.getUserId(); + return site.invalidateWsCacheForKey(this.getActivityAllowedGroupsCacheKey(cmId, userId)); }); } @@ -283,7 +290,7 @@ export class CoreGroupsProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateActivityGroupMode(cmId: number, siteId?: string) : Promise { + invalidateActivityGroupMode(cmId: number, siteId?: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { return site.invalidateWsCacheForKey(this.getActivityGroupModeCacheKey(cmId)); }); @@ -297,10 +304,11 @@ export class CoreGroupsProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateActivityGroupInfo(cmId: number, userId?: number, siteId?: string) : Promise { - let promises = []; + invalidateActivityGroupInfo(cmId: number, userId?: number, siteId?: string): Promise { + const promises = []; promises.push(this.invalidateActivityAllowedGroups(cmId, userId, siteId)); promises.push(this.invalidateActivityGroupMode(cmId, siteId)); + return Promise.all(promises); } @@ -312,11 +320,11 @@ export class CoreGroupsProvider { * @param {number} [userId] User ID. If not defined, use current user. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateUserGroups(courses: any[], siteId?: string, userId?: number) : Promise { + invalidateUserGroups(courses: any[], siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - let promises = []; + const promises = []; - userId = userId || site.getUserId(); + userId = userId || site.getUserId(); courses.forEach((course) => { const courseId = typeof course == 'object' ? course.id : course; @@ -335,9 +343,10 @@ export class CoreGroupsProvider { * @param {number} [userId] User ID. If not defined, use current user. * @return {Promise} Promise resolved when the data is invalidated. */ - invalidateUserGroupsInCourse(courseId: number, siteId?: string, userId?: number) : Promise { + invalidateUserGroupsInCourse(courseId: number, siteId?: string, userId?: number): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - userId = userId || site.getUserId(); + userId = userId || site.getUserId(); + return site.invalidateWsCacheForKey(this.getUserGroupsInCourseCacheKey(courseId, userId)); }); } diff --git a/src/providers/init.ts b/src/providers/init.ts index 2b9d6c535..ddb55faf1 100644 --- a/src/providers/init.ts +++ b/src/providers/init.ts @@ -45,15 +45,15 @@ export interface CoreInitHandler { * @type {boolean} */ blocking?: boolean; -}; +} /* * Provider for initialisation mechanisms. */ @Injectable() export class CoreInitDelegate { - public static DEFAULT_PRIORITY = 100; // Default priority for init processes. - public static MAX_RECOMMENDED_PRIORITY = 600; + static DEFAULT_PRIORITY = 100; // Default priority for init processes. + static MAX_RECOMMENDED_PRIORITY = 600; protected initProcesses = {}; protected logger; @@ -68,7 +68,7 @@ export class CoreInitDelegate { * * Reserved for core use, do not call directly. */ - executeInitProcesses() : void { + executeInitProcesses(): void { let ordered = []; if (typeof this.readiness == 'undefined') { @@ -76,7 +76,7 @@ export class CoreInitDelegate { } // Re-ordering by priority. - for (let name in this.initProcesses) { + for (const name in this.initProcesses) { ordered.push(this.initProcesses[name]); } ordered.sort((a, b) => { @@ -99,7 +99,7 @@ export class CoreInitDelegate { /** * Init the readiness promise. */ - protected initReadiness() : void { + protected initReadiness(): void { this.readiness = this.utils.promiseDefer(); this.readiness.promise.then(() => this.readiness.resolved = true); } @@ -113,7 +113,7 @@ export class CoreInitDelegate { */ isReady(): boolean { return this.readiness.resolved; - }; + } /** * Convenience function to return a function that executes the process. @@ -121,7 +121,7 @@ export class CoreInitDelegate { * @param {CoreInitHandler} data The data of the process. * @return {Promise} Promise of the process. */ - protected prepareProcess(data: CoreInitHandler) : Promise { + protected prepareProcess(data: CoreInitHandler): Promise { let promise; this.logger.debug(`Executing init process '${data.name}'`); @@ -130,6 +130,7 @@ export class CoreInitDelegate { promise = data.load(); } catch (e) { this.logger.error('Error while calling the init process \'' + data.name + '\'. ' + e); + return; } @@ -141,7 +142,7 @@ export class CoreInitDelegate { * * @return {Promise} Resolved when the app is initialised. Never rejected. */ - ready() : Promise { + ready(): Promise { if (typeof this.readiness === 'undefined') { // Prevent race conditions if this is called before executeInitProcesses. this.initReadiness(); @@ -164,13 +165,14 @@ export class CoreInitDelegate { * * @param {CoreInitHandler} instance The instance of the handler. */ - registerProcess(handler: CoreInitHandler) : void { + registerProcess(handler: CoreInitHandler): void { if (typeof handler.priority == 'undefined') { handler.priority = CoreInitDelegate.DEFAULT_PRIORITY; } if (typeof this.initProcesses[handler.name] != 'undefined') { this.logger.log(`Process '${handler.name}' already registered.`); + return; } diff --git a/src/providers/lang.ts b/src/providers/lang.ts index af62d6268..f8b62fae2 100644 --- a/src/providers/lang.ts +++ b/src/providers/lang.ts @@ -17,16 +17,16 @@ import { TranslateService } from '@ngx-translate/core'; import * as moment from 'moment'; import { Globalization } from '@ionic-native/globalization'; import { Platform } from 'ionic-angular'; - import { CoreConfigProvider } from './config'; import { CoreConfigConstants } from '../configconstants'; +import { Observable } from 'rxjs'; /* * Service to handle language features, like changing the current language. */ @Injectable() export class CoreLangProvider { - protected fallbackLanguage:string = 'en'; // mmCoreConfigConstants.default_lang || 'en', + protected fallbackLanguage = CoreConfigConstants.default_lang || 'en'; protected currentLanguage: string; // Save current language in a variable to speed up the get function. protected customStrings = {}; protected customStringsRaw: string; @@ -53,60 +53,64 @@ export class CoreLangProvider { * @param {string} language New language to use. * @return {Promise} Promise resolved when the change is finished. */ - changeCurrentLanguage(language: string) : Promise { - let promises = []; + changeCurrentLanguage(language: string): Promise { + const promises = []; promises.push(this.translate.use(language)); promises.push(this.configProvider.set('current_language', language)); moment.locale(language); this.currentLanguage = language; + return Promise.all(promises); - }; + } /** * Clear current custom strings. */ - clearCustomStrings() : void { + clearCustomStrings(): void { this.customStrings = {}; this.customStringsRaw = ''; - }; + } /** * Function to "decorate" the TranslateService. * Basically, it extends the translate functions to use the custom lang strings. */ - decorateTranslate() : void { - let originalGet = this.translate.get, + decorateTranslate(): void { + const originalGet = this.translate.get, originalInstant = this.translate.instant; // Redefine translate.get. - this.translate.get = (key: string|string[], interpolateParams?: object) => { + this.translate.get = (key: string | string[], interpolateParams?: object): Observable => { // Always call the original get function to avoid having to create our own Observables. if (typeof key == 'string') { - let value = this.getCustomString(key); + const value = this.getCustomString(key); if (typeof value != 'undefined') { key = value; } } else { key = this.getCustomStrings(key).translations; } + return originalGet.apply(this.translate, [key, interpolateParams]); }; // Redefine translate.instant. - this.translate.instant = (key: string|string[], interpolateParams?: object) => { + this.translate.instant = (key: string | string[], interpolateParams?: object): any => { if (typeof key == 'string') { - let value = this.getCustomString(key); + const value = this.getCustomString(key); if (typeof value != 'undefined') { return value; } + return originalInstant.apply(this.translate, [key, interpolateParams]); } else { - let result = this.getCustomStrings(key); + const result = this.getCustomStrings(key); if (result.allFound) { return result.translations; } + return originalInstant.apply(this.translate, [result.translations]); } }; @@ -117,16 +121,16 @@ export class CoreLangProvider { * * @return {any} Custom strings. */ - getAllCustomStrings() : any { + getAllCustomStrings(): any { return this.customStrings; - }; + } /** * Get current language. * * @return {Promise} Promise resolved with the current language. */ - getCurrentLanguage() : Promise { + getCurrentLanguage(): Promise { if (typeof this.currentLanguage != 'undefined') { return Promise.resolve(this.currentLanguage); @@ -153,20 +157,22 @@ export class CoreLangProvider { } } + return language; }).catch(() => { // Error getting locale. Use default language. return this.fallbackLanguage; }); - } catch(err) { + } catch (err) { // Error getting locale. Use default language. return Promise.resolve(this.fallbackLanguage); } }).then((language) => { this.currentLanguage = language; // Save it for later. + return language; }); - }; + } /** * Get a custom string for a certain key. @@ -174,8 +180,8 @@ export class CoreLangProvider { * @param {string} key The key of the translation to get. * @return {string} Translation, undefined if not found. */ - getCustomString(key: string) : string { - let customStrings = this.getCustomStringsForLanguage(); + getCustomString(key: string): string { + const customStrings = this.getCustomStringsForLanguage(); if (customStrings && typeof customStrings[key] != 'undefined') { return customStrings[key]; } @@ -187,12 +193,12 @@ export class CoreLangProvider { * @param {string[]} keys The keys of the translations to get. * @return {any} Object with translations and a boolean indicating if all translations were found in custom strings. */ - getCustomStrings(keys: string[]) : any { - let customStrings = this.getCustomStringsForLanguage(), - translations = [], - allFound = true; + getCustomStrings(keys: string[]): any { + const customStrings = this.getCustomStringsForLanguage(), + translations = []; + let allFound = true; - keys.forEach((key : string) => { + keys.forEach((key: string) => { if (customStrings && typeof customStrings[key] != 'undefined') { translations.push(customStrings[key]); } else { @@ -213,17 +219,18 @@ export class CoreLangProvider { * @param {string} [lang] The language to get. If not defined, return current language. * @return {any} Custom strings. */ - getCustomStringsForLanguage(lang?: string) : any { + getCustomStringsForLanguage(lang?: string): any { lang = lang || this.currentLanguage; + return this.customStrings[lang]; - }; + } /** * Load certain custom strings. * * @param {string} strings Custom strings to load (tool_mobile_customlangstrings). */ - loadCustomStrings(strings: string) : void { + loadCustomStrings(strings: string): void { if (strings == this.customStringsRaw) { // Strings haven't changed, stop. return; @@ -236,10 +243,10 @@ export class CoreLangProvider { return; } - let list: string[] = strings.split(/(?:\r\n|\r|\n)/); + const list: string[] = strings.split(/(?:\r\n|\r|\n)/); list.forEach((entry: string) => { - let values: string[] = entry.split('|'), - lang: string; + const values: string[] = entry.split('|'); + let lang: string; if (values.length < 3) { // Not enough data, ignore the entry. @@ -254,5 +261,5 @@ export class CoreLangProvider { this.customStrings[lang][values[0]] = values[1]; }); - }; + } } diff --git a/src/providers/local-notifications.ts b/src/providers/local-notifications.ts index 37c9f7bce..894d228e3 100644 --- a/src/providers/local-notifications.ts +++ b/src/providers/local-notifications.ts @@ -39,7 +39,7 @@ export interface CoreILocalNotification extends ILocalNotification { * @type {number} */ ledOffTime?: number; -}; +} /* Generated class for the LocalNotificationsProvider provider. @@ -103,7 +103,7 @@ export class CoreLocalNotificationsProvider { protected logger; protected appDB: SQLiteDB; - protected codes: {[s: string]: number} = {}; + protected codes: { [s: string]: number } = {}; protected codeRequestsQueue = {}; protected observables = {}; @@ -123,7 +123,7 @@ export class CoreLocalNotificationsProvider { * @param {string} siteId Site ID. * @return {Promise} Promise resolved when the notification is cancelled. */ - cancel(id, component, siteId) : Promise { + cancel(id: number, component: string, siteId: string): Promise { return this.getUniqueNotificationId(id, component, siteId).then((uniqueId) => { return this.localNotifications.cancel(uniqueId); }); @@ -135,7 +135,7 @@ export class CoreLocalNotificationsProvider { * @param {string} siteId Site ID. * @return {Promise} Promise resolved when the notifications are cancelled. */ - cancelSiteNotifications(siteId) : Promise { + cancelSiteNotifications(siteId: string): Promise { if (!this.isAvailable()) { return Promise.resolve(); @@ -144,7 +144,7 @@ export class CoreLocalNotificationsProvider { } return this.localNotifications.getAllScheduled().then((scheduled) => { - let ids = []; + const ids = []; scheduled.forEach((notif) => { if (typeof notif.data == 'string') { @@ -167,7 +167,7 @@ export class CoreLocalNotificationsProvider { * @param {string} id ID of the element to get its code. * @return {Promise} Promise resolved when the code is retrieved. */ - protected getCode(table, id) : Promise { + protected getCode(table: string, id: string): Promise { const key = table + '#' + id; // Check if the code is already in memory. @@ -176,8 +176,9 @@ export class CoreLocalNotificationsProvider { } // Check if we already have a code stored for that ID. - return this.appDB.getRecord(table, {id: id}).then((entry) => { + return this.appDB.getRecord(table, { id: id }).then((entry) => { this.codes[key] = entry.code; + return entry.code; }).catch(() => { // No code stored for that ID. Create a new code for it. @@ -186,8 +187,10 @@ export class CoreLocalNotificationsProvider { if (entries.length > 0) { newCode = entries[0].code + 1; } - return this.appDB.insertRecord(table, {id: id, code: newCode}).then(() => { + + return this.appDB.insertRecord(table, { id: id, code: newCode }).then(() => { this.codes[key] = newCode; + return newCode; }); }); @@ -201,7 +204,7 @@ export class CoreLocalNotificationsProvider { * @param {string} component Component name. * @return {Promise} Promise resolved when the component code is retrieved. */ - protected getComponentCode(component: string) : Promise { + protected getComponentCode(component: string): Promise { return this.requestCode(this.COMPONENTS_TABLE, component); } @@ -212,7 +215,7 @@ export class CoreLocalNotificationsProvider { * @param {string} siteId Site ID. * @return {Promise} Promise resolved when the site code is retrieved. */ - protected getSiteCode(siteId: string) : Promise { + protected getSiteCode(siteId: string): Promise { return this.requestCode(this.SITES_TABLE, siteId); } @@ -229,7 +232,7 @@ export class CoreLocalNotificationsProvider { * @param {string} siteId Site ID. * @return {Promise} Promise resolved when the notification ID is generated. */ - protected getUniqueNotificationId(notificationId: number, component: string, siteId: string) : Promise { + protected getUniqueNotificationId(notificationId: number, component: string, siteId: string): Promise { if (!siteId || !component) { return Promise.reject(null); } @@ -247,19 +250,20 @@ export class CoreLocalNotificationsProvider { * * @return {boolean} Whether local notifications plugin is installed. */ - isAvailable() : boolean { - let win = window; - return this.appProvider.isDesktop() || !!(win.plugin && win.plugin.notification && win.plugin.notification.local); + isAvailable(): boolean { + const win = window; + + return this.appProvider.isDesktop() || !!(win.plugin && win.plugin.notification && win.plugin.notification.local); } /** * Check if a notification has been triggered with the same trigger time. * * @param {CoreILocalNotification} notification Notification to check. - * @return {Promise} Promise resolved with a boolean indicating if promise is triggered (true) or not. + * @return {Promise} Promise resolved with a boolean indicating if promise is triggered (true) or not. */ - isTriggered(notification: CoreILocalNotification) { - return this.appDB.getRecord(this.TRIGGERED_TABLE, {id: notification.id}).then((stored) => { + isTriggered(notification: CoreILocalNotification): Promise { + return this.appDB.getRecord(this.TRIGGERED_TABLE, { id: notification.id }).then((stored) => { return stored.at === notification.at.getTime() / 1000; }).catch(() => { return false; @@ -271,7 +275,7 @@ export class CoreLocalNotificationsProvider { * * @param {any} data Data received by the notification. */ - notifyClick(data: any) : void { + notifyClick(data: any): void { const component = data.component; if (component) { if (this.observables[component]) { @@ -283,9 +287,9 @@ export class CoreLocalNotificationsProvider { /** * Process the next request in queue. */ - protected processNextRequest() : void { - let nextKey = Object.keys(this.codeRequestsQueue)[0], - request, + protected processNextRequest(): void { + const nextKey = Object.keys(this.codeRequestsQueue)[0]; + let request, promise; if (typeof nextKey == 'undefined') { @@ -325,7 +329,7 @@ export class CoreLocalNotificationsProvider { * @param {Function} callback Function to call with the data received by the notification. * @return {any} Object with an "off" property to stop listening for clicks. */ - registerClick(component: string, callback: Function) : any { + registerClick(component: string, callback: Function): any { this.logger.debug(`Register observer '${component}' for notification click.`); if (typeof this.observables[component] == 'undefined') { @@ -336,7 +340,7 @@ export class CoreLocalNotificationsProvider { this.observables[component].subscribe(callback); return { - off: () => { + off: (): void => { this.observables[component].unsubscribe(callback); } }; @@ -348,8 +352,8 @@ export class CoreLocalNotificationsProvider { * @param {number} id Notification ID. * @return {Promise} Promise resolved when it is removed. */ - removeTriggered(id: number) : Promise { - return this.appDB.deleteRecords(this.TRIGGERED_TABLE, {id: id}); + removeTriggered(id: number): Promise { + return this.appDB.deleteRecords(this.TRIGGERED_TABLE, { id: id }); } /** @@ -359,8 +363,8 @@ export class CoreLocalNotificationsProvider { * @param {string} id ID of the element to get its code. * @return {Promise} Promise resolved when the code is retrieved. */ - protected requestCode(table: string, id: string) : Promise { - let deferred = this.utils.promiseDefer(), + protected requestCode(table: string, id: string): Promise { + const deferred = this.utils.promiseDefer(), key = table + '#' + id, isQueueEmpty = Object.keys(this.codeRequestsQueue).length == 0; @@ -373,7 +377,7 @@ export class CoreLocalNotificationsProvider { table: table, id: id, promises: [deferred] - } + }; } if (isQueueEmpty) { @@ -388,10 +392,10 @@ export class CoreLocalNotificationsProvider { * * @return {Promise} Promise resolved when all notifications have been rescheduled. */ - rescheduleAll() : Promise { + rescheduleAll(): Promise { // Get all the scheduled notifications. return this.localNotifications.getAllScheduled().then((notifications) => { - let promises = []; + const promises = []; notifications.forEach((notification) => { // Convert some properties to the needed types. @@ -414,7 +418,7 @@ export class CoreLocalNotificationsProvider { * @param {string} siteId Site ID. * @return {Promise} Promise resolved when the notification is scheduled. */ - schedule(notification: CoreILocalNotification, component: string, siteId: string) : Promise { + schedule(notification: CoreILocalNotification, component: string, siteId: string): Promise { return this.getUniqueNotificationId(notification.id, component, siteId).then((uniqueId) => { notification.id = uniqueId; notification.data = notification.data || {}; @@ -439,7 +443,7 @@ export class CoreLocalNotificationsProvider { * @param {CoreILocalNotification} notification Notification to schedule. * @return {Promise} Promise resolved when scheduled. */ - protected scheduleNotification(notification: CoreILocalNotification) : Promise { + protected scheduleNotification(notification: CoreILocalNotification): Promise { // Check if the notification has been triggered already. return this.isTriggered(notification).then((triggered) => { if (!triggered) { @@ -464,9 +468,9 @@ export class CoreLocalNotificationsProvider { * * @param {CoreILocalNotification} notification Notification. */ - showNotificationPopover(notification: CoreILocalNotification) : void { + showNotificationPopover(notification: CoreILocalNotification): void { // @todo Improve it. For now, show Toast. - if (!notification || !notification.title || !notification.text) { + if (!notification || !notification.title || !notification.text) { // Invalid data. return; } @@ -481,16 +485,17 @@ export class CoreLocalNotificationsProvider { * @param {CoreILocalNotification} notification Triggered notification. * @return {Promise} Promise resolved when stored, rejected otherwise. */ - trigger(notification: CoreILocalNotification) : Promise { + trigger(notification: CoreILocalNotification): Promise { if (this.platform.is('ios') && this.platform.version().num >= 10) { // In iOS10 show in app notification. this.showNotificationPopover(notification); } - let entry = { + const entry = { id: notification.id, at: parseInt(notification.at, 10) }; - return this.appDB.insertOrUpdateRecord(this.TRIGGERED_TABLE, entry, {id: notification.id}); + + return this.appDB.insertOrUpdateRecord(this.TRIGGERED_TABLE, entry, { id: notification.id }); } } diff --git a/src/providers/logger.ts b/src/providers/logger.ts index d196d24b6..c33a86cc5 100644 --- a/src/providers/logger.ts +++ b/src/providers/logger.ts @@ -29,9 +29,11 @@ import * as moment from 'moment'; @Injectable() export class CoreLoggerProvider { /** Whether the logging is enabled. */ - public enabled: boolean = true; + enabled = true; - constructor() {} + constructor() { + // Nothing to do. + } /** * Get a logger instance for a certain class, service or component. @@ -39,15 +41,15 @@ export class CoreLoggerProvider { * @param {string} className Name to use in the messages. * @return {ant} Instance. */ - getInstance(className: string) : any { + getInstance(className: string): any { className = className || ''; return { - log : this.prepareLogFn(console.log.bind(console), className), - info : this.prepareLogFn(console.info.bind(console), className), - warn : this.prepareLogFn(console.warn.bind(console), className), - debug : this.prepareLogFn(console.debug.bind(console), className), - error : this.prepareLogFn(console.error.bind(console), className) + log: this.prepareLogFn(console.log.bind(console), className), + info: this.prepareLogFn(console.info.bind(console), className), + warn: this.prepareLogFn(console.warn.bind(console), className), + debug: this.prepareLogFn(console.debug.bind(console), className), + error: this.prepareLogFn(console.error.bind(console), className) }; } @@ -58,12 +60,11 @@ export class CoreLoggerProvider { * @param {string} className Name to use in the messages. * @return {Function} Prepared function. */ - private prepareLogFn(logFn: Function, className: string) : Function { + private prepareLogFn(logFn: Function, className: string): Function { // Return our own function that will call the logging function with the treated message. - return (...args) => { + return (...args): void => { if (this.enabled) { - let now = moment().format('l LTS'); - + const now = moment().format('l LTS'); args[0] = now + ' ' + className + ': ' + args[0]; // Prepend timestamp and className to the original message. logFn.apply(null, args); } diff --git a/src/providers/plugin-file-delegate.ts b/src/providers/plugin-file-delegate.ts index 3c6af75ec..b504a0ee8 100644 --- a/src/providers/plugin-file-delegate.ts +++ b/src/providers/plugin-file-delegate.ts @@ -40,7 +40,7 @@ export interface CorePluginFileHandler { * @return {string} String to remove the revision on pluginfile url. */ getComponentRevisionReplace?(args: string[]): string; -}; +} /** * Delegate to register pluginfile information handlers. @@ -48,7 +48,7 @@ export interface CorePluginFileHandler { @Injectable() export class CorePluginFileDelegate { protected logger; - protected handlers: {[s: string]: CorePluginFileHandler} = {}; + protected handlers: { [s: string]: CorePluginFileHandler } = {}; constructor(logger: CoreLoggerProvider) { this.logger = logger.getInstance('CorePluginFileDelegate'); @@ -60,7 +60,7 @@ export class CorePluginFileDelegate { * @param {string} pluginType Type of the plugin. * @return {CorePluginFileHandler} Handler. Undefined if no handler found for the plugin. */ - protected getPluginHandler(pluginType: string) : CorePluginFileHandler { + protected getPluginHandler(pluginType: string): CorePluginFileHandler { if (typeof this.handlers[pluginType] != 'undefined') { return this.handlers[pluginType]; } @@ -72,7 +72,7 @@ export class CorePluginFileDelegate { * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. * @return {RegExp} RegExp to match the revision or undefined if not found. */ - getComponentRevisionRegExp(args: string[]) : RegExp { + getComponentRevisionRegExp(args: string[]): RegExp { // Get handler based on component (args[1]). const handler = this.getPluginHandler(args[1]); @@ -87,13 +87,16 @@ export class CorePluginFileDelegate { * @param {CorePluginFileHandler} handler The handler to register. * @return {boolean} True if registered successfully, false otherwise. */ - registerHandler(handler: CorePluginFileHandler) : boolean { + registerHandler(handler: CorePluginFileHandler): boolean { if (typeof this.handlers[handler.name] !== 'undefined') { this.logger.log(`Addon '${handler.name}' already registered`); + return false; } + this.logger.log(`Registered addon '${handler.name}'`); this.handlers[handler.name] = handler; + return true; } @@ -104,7 +107,7 @@ export class CorePluginFileDelegate { * @param {string[]} args Arguments of the pluginfile URL defining component and filearea at least. * @return {string} Replaced URL without revision. */ - removeRevisionFromUrl(url: string, args: string[]) : string { + removeRevisionFromUrl(url: string, args: string[]): string { // Get handler based on component (args[1]). const handler = this.getPluginHandler(args[1]); @@ -117,4 +120,4 @@ export class CorePluginFileDelegate { return url; } -} \ No newline at end of file +} diff --git a/src/providers/sites-factory.ts b/src/providers/sites-factory.ts index 4b5e45da3..e28519e1e 100644 --- a/src/providers/sites-factory.ts +++ b/src/providers/sites-factory.ts @@ -21,7 +21,7 @@ import { CoreSite } from '../classes/site'; @Injectable() export class CoreSitesFactoryProvider { - constructor(private injector: Injector) {} + constructor(private injector: Injector) { } /** * Make a site object. @@ -38,7 +38,7 @@ export class CoreSitesFactoryProvider { * This returns a site object. */ makeSite(id: string, siteUrl: string, token?: string, info?: any, privateToken?: string, - config?: any, loggedOut?: boolean) : CoreSite { + config?: any, loggedOut?: boolean): CoreSite { return new CoreSite(this.injector, id, siteUrl, token, info, privateToken, config, loggedOut); } @@ -48,10 +48,11 @@ export class CoreSitesFactoryProvider { * @return {string[]} List of methods. */ getSiteMethods(): string[] { - let methods = []; - for (let name in CoreSite.prototype) { + const methods = []; + for (const name in CoreSite.prototype) { methods.push(name); } + return methods; } } diff --git a/src/providers/sites.ts b/src/providers/sites.ts index ca46c1113..ef7441bd2 100644 --- a/src/providers/sites.ts +++ b/src/providers/sites.ts @@ -60,7 +60,7 @@ export interface CoreSiteCheckResponse { * @type {any} */ config?: any; -}; +} /** * Response of getting user token. @@ -83,7 +83,7 @@ export interface CoreSiteUserTokenResponse { * @type {string} */ privateToken?: string; -}; +} /** * Site's basic info. @@ -124,7 +124,7 @@ export interface CoreSiteBasicInfo { * @type {number} */ badge?: number; -}; +} /* * Service to manage and interact with sites. @@ -205,7 +205,7 @@ export class CoreSitesProvider { protected services = {}; protected sessionRestored = false; protected currentSite: CoreSite; - protected sites: {[s: string]: CoreSite} = {}; + protected sites: { [s: string]: CoreSite } = {}; protected appDB: SQLiteDB; protected siteTablesSchemas = []; // Schemas for site tables. Other providers can add schemas in here. @@ -224,7 +224,7 @@ export class CoreSitesProvider { * @param {string} name Name of the site to check. * @return {any} Site data if it's a demo site, undefined otherwise. */ - getDemoSiteData(name: string) { + getDemoSiteData(name: string): any { const demoSites = CoreConfigConstants.demo_sites; if (typeof demoSites != 'undefined' && typeof demoSites[name] != 'undefined') { return demoSites[name]; @@ -239,8 +239,8 @@ export class CoreSitesProvider { * @param {string} [protocol=https://] Protocol to use first. * @return {Promise} A promise resolved when the site is checked. */ - checkSite(siteUrl: string, protocol = 'https://') : Promise { - // formatURL adds the protocol if is missing. + checkSite(siteUrl: string, protocol: string = 'https://'): Promise { + // The formatURL function adds the protocol if is missing. siteUrl = this.urlUtils.formatURL(siteUrl); if (!this.urlUtils.isHttpURL(siteUrl)) { @@ -256,6 +256,7 @@ export class CoreSitesProvider { // Retry with the other protocol. protocol = protocol == 'https://' ? 'http://' : 'https://'; + return this.checkSiteWithProtocol(siteUrl, protocol).catch((secondError) => { // Site doesn't exist. if (secondError.error) { @@ -263,6 +264,7 @@ export class CoreSitesProvider { } else if (error.error) { return Promise.reject(error.error); } + return Promise.reject(this.translate.instant('core.login.checksiteversion')); }); }); @@ -276,7 +278,7 @@ export class CoreSitesProvider { * @param {string} protocol Protocol to use. * @return {Promise} A promise resolved when the site is checked. */ - checkSiteWithProtocol(siteUrl: string, protocol: string) : Promise { + checkSiteWithProtocol(siteUrl: string, protocol: string): Promise { let publicConfig; // Now, replace the siteUrl with the protocol. @@ -290,6 +292,7 @@ export class CoreSitesProvider { // Site doesn't exist. Try to add or remove 'www'. const treatedUrl = this.urlUtils.addOrRemoveWWW(siteUrl); + return this.siteExists(treatedUrl).then(() => { // Success, use this new URL as site url. siteUrl = treatedUrl; @@ -300,19 +303,21 @@ export class CoreSitesProvider { } error = secondError || error; - return Promise.reject({error: typeof error == 'object' ? error.error : error}); + + return Promise.reject({ error: typeof error == 'object' ? error.error : error }); }); }).then(() => { // Create a temporary site to check if local_mobile is installed. const temporarySite = this.sitesFactory.makeSite(undefined, siteUrl); + return temporarySite.checkLocalMobilePlugin().then((data) => { data.service = data.service || CoreConfigConstants.wsservice; this.services[siteUrl] = data.service; // No need to store it in DB. if (data.coreSupported || - (data.code != CoreConstants.LOGIN_SSO_CODE && data.code != CoreConstants.LOGIN_SSO_INAPP_CODE)) { + (data.code != CoreConstants.LOGIN_SSO_CODE && data.code != CoreConstants.LOGIN_SSO_INAPP_CODE)) { // SSO using local_mobile not needed, try to get the site public config. - return temporarySite.getPublicConfig().then((config) : any => { + return temporarySite.getPublicConfig().then((config): any => { publicConfig = config; // Check that the user can authenticate. @@ -325,6 +330,7 @@ export class CoreSitesProvider { if (config.maintenancemessage) { message += config.maintenancemessage; } + return rejectWithCriticalError(message); } @@ -332,12 +338,13 @@ export class CoreSitesProvider { if (data.code === 0) { data.code = config.typeoflogin; } + return data; - }, (error) : any => { + }, (error): any => { // Error, check if not supported. if (error.available === 1) { // Service supported but an error happened. Return error. - return Promise.reject({error: error.error}); + return Promise.reject({ error: error.error }); } return data; @@ -347,12 +354,13 @@ export class CoreSitesProvider { return data; }).then((data) => { siteUrl = temporarySite.getURL(); - return {siteUrl: siteUrl, code: data.code, warning: data.warning, service: data.service, config: publicConfig}; + + return { siteUrl: siteUrl, code: data.code, warning: data.warning, service: data.service, config: publicConfig }; }); }); // Return a rejected promise with a "critical" error. - function rejectWithCriticalError(message: string, errorCode?: string) { + function rejectWithCriticalError(message: string, errorCode?: string): Promise { return Promise.reject({ error: message, errorcode: errorCode, @@ -367,8 +375,8 @@ export class CoreSitesProvider { * @param {string} siteUrl URL of the site to check. * @return {Promise} A promise to be resolved if the site exists. */ - siteExists(siteUrl: string) : Promise { - let data: any = {}; + siteExists(siteUrl: string): Promise { + const data: any = {}; if (!this.appProvider.isMobile()) { // Send fake parameters for CORS. This is only needed in browser. @@ -378,13 +386,14 @@ export class CoreSitesProvider { } const observable = this.http.post(siteUrl + '/login/token.php', data).timeout(CoreConstants.WS_TIMEOUT); + return this.utils.observableToPromise(observable).catch((error) => { return Promise.reject(error.message); }).then((data: any) => { if (data.errorcode && (data.errorcode == 'enablewsdescription' || data.errorcode == 'requirecorrectaccess')) { - return Promise.reject({errorcode: data.errorcode, error: data.error}); + return Promise.reject({ errorcode: data.errorcode, error: data.error }); } else if (data.error && data.error == 'Web services must be enabled in Advanced features.') { - return Promise.reject({errorcode: 'enablewsdescription', error: data.error}); + return Promise.reject({ errorcode: 'enablewsdescription', error: data.error }); } // Other errors are not being checked because invalid login will be always raised and we cannot differ them. }); @@ -417,20 +426,21 @@ export class CoreSitesProvider { }, observable = this.http.post(siteUrl + '/login/token.php', params).timeout(CoreConstants.WS_TIMEOUT); - return this.utils.observableToPromise(observable).then((data: any) : any => { + return this.utils.observableToPromise(observable).then((data: any): any => { if (typeof data == 'undefined') { return Promise.reject(this.translate.instant('core.cannotconnect')); } else { if (typeof data.token != 'undefined') { - return {token: data.token, siteUrl: siteUrl, privateToken: data.privatetoken}; + return { token: data.token, siteUrl: siteUrl, privateToken: data.privatetoken }; } else { if (typeof data.error != 'undefined') { // We only allow one retry (to avoid loops). - if (!retry && data.errorcode == "requirecorrectaccess") { + if (!retry && data.errorcode == 'requirecorrectaccess') { siteUrl = this.urlUtils.addOrRemoveWWW(siteUrl); + return this.getUserToken(siteUrl, username, password, service, true); } else if (typeof data.errorcode != 'undefined') { - return Promise.reject({error: data.error, errorcode: data.errorcode}); + return Promise.reject({ error: data.error, errorcode: data.errorcode }); } else { return Promise.reject(data.error); } @@ -452,12 +462,12 @@ export class CoreSitesProvider { * @param {string} [privateToken=''] User's private token. * @return {Promise} A promise resolved when the site is added and the user is authenticated. */ - newSite(siteUrl: string, token: string, privateToken = '') : Promise { + newSite(siteUrl: string, token: string, privateToken: string = ''): Promise { // Create a "candidate" site to fetch the site info. const candidateSite = this.sitesFactory.makeSite(undefined, siteUrl, token, undefined, privateToken); return candidateSite.fetchSiteInfo().then((info) => { - let result = this.isValidMoodleVersion(info); + const result = this.isValidMoodleVersion(info); if (result == this.VALID_VERSION) { // Set site ID and info. const siteId = this.createSiteID(info.siteurl, info.username); @@ -498,7 +508,7 @@ export class CoreSitesProvider { * @param {string} username Username. * @return {string} Site ID. */ - createSiteID(siteUrl: string, username: string) : string { + createSiteID(siteUrl: string, username: string): string { return Md5.hashAsciiStr(siteUrl + username); } @@ -508,7 +518,7 @@ export class CoreSitesProvider { * @param {string} siteUrl The site URL. * @return {string} The service shortname. */ - determineService(siteUrl: string) : string { + determineService(siteUrl: string): string { // We need to try siteUrl in both https or http (due to loginhttps setting). // First http:// @@ -533,7 +543,7 @@ export class CoreSitesProvider { * @param {any} info Site info. * @return {number} Either VALID_VERSION, LEGACY_APP_VERSION or INVALID_VERSION. */ - protected isValidMoodleVersion(info: any) : number { + protected isValidMoodleVersion(info: any): number { if (!info) { return this.INVALID_VERSION; } @@ -581,11 +591,13 @@ export class CoreSitesProvider { * @param {any} info Site info. * @return {any} True if valid, object with error message to show and its params if not valid. */ - protected validateSiteInfo(info: any) : any { + protected validateSiteInfo(info: any): any { if (!info.firstname || !info.lastname) { const moodleLink = `${info.siteurl}`; - return {error: 'core.requireduserdatamissing', params: {'$a': moodleLink}}; + + return { error: 'core.requireduserdatamissing', params: { $a: moodleLink } }; } + return true; } @@ -600,7 +612,7 @@ export class CoreSitesProvider { * @param {any} [config] Site config (from tool_mobile_get_config). * @return {Promise} Promise resolved when done. */ - addSite(id: string, siteUrl: string, token: string, info: any, privateToken = '', config?: any) : Promise { + addSite(id: string, siteUrl: string, token: string, info: any, privateToken: string = '', config?: any): Promise { const entry = { id: id, siteUrl: siteUrl, @@ -610,7 +622,8 @@ export class CoreSitesProvider { config: config ? JSON.stringify(config) : config, loggedOut: 0 }; - return this.appDB.insertOrUpdateRecord(this.SITES_TABLE, entry, {id: id}); + + return this.appDB.insertOrUpdateRecord(this.SITES_TABLE, entry, { id: id }); } /** @@ -619,7 +632,7 @@ export class CoreSitesProvider { * @param {string} siteId ID of the site to load. * @return {Promise} Promise to be resolved when the site is loaded. */ - loadSite(siteId: string) : Promise { + loadSite(siteId: string): Promise { this.logger.debug(`Load site ${siteId}`); return this.getSite(siteId).then((site) => { @@ -647,7 +660,7 @@ export class CoreSitesProvider { * * @return {CoreSite} Current site. */ - getCurrentSite() : CoreSite { + getCurrentSite(): CoreSite { return this.currentSite; } @@ -656,7 +669,7 @@ export class CoreSitesProvider { * * @return {string} Current site ID. */ - getCurrentSiteId() : string { + getCurrentSiteId(): string { if (this.currentSite) { return this.currentSite.getId(); } else { @@ -669,7 +682,7 @@ export class CoreSitesProvider { * * @return {number} Current site home ID. */ - getCurrentSiteHomeId() : number { + getCurrentSiteHomeId(): number { if (this.currentSite) { return this.currentSite.getSiteHomeId(); } else { @@ -677,15 +690,14 @@ export class CoreSitesProvider { } } - /** * Check if the user is logged in a site. * * @return {boolean} Whether the user is logged in a site. */ - isLoggedIn() : boolean { + isLoggedIn(): boolean { return typeof this.currentSite != 'undefined' && typeof this.currentSite.token != 'undefined' && - this.currentSite.token != ''; + this.currentSite.token != ''; } /** @@ -694,7 +706,7 @@ export class CoreSitesProvider { * @param {string} siteId ID of the site to delete. * @return {Promise} Promise to be resolved when the site is deleted. */ - deleteSite(siteId: string) : Promise { + deleteSite(siteId: string): Promise { this.logger.debug(`Delete site ${siteId}`); if (typeof this.currentSite != 'undefined' && this.currentSite.id == siteId) { @@ -705,7 +717,8 @@ export class CoreSitesProvider { return site.deleteDB().then(() => { // Site DB deleted, now delete the app from the list of sites. delete this.sites[siteId]; - return this.appDB.deleteRecords(this.SITES_TABLE, {id: siteId}).then(() => { + + return this.appDB.deleteRecords(this.SITES_TABLE, { id: siteId }).then(() => { // Site deleted from sites list, now delete the folder. return site.deleteFolder(); }, () => { @@ -723,7 +736,7 @@ export class CoreSitesProvider { * * @return {Promise} Promise resolved if there are no sites, and rejected if there is at least one. */ - hasNoSites() : Promise { + hasNoSites(): Promise { return this.appDB.countRecords(this.SITES_TABLE).then((count) => { if (count > 0) { return Promise.reject(null); @@ -736,7 +749,7 @@ export class CoreSitesProvider { * * @return {Promise} Promise resolved if there is at least one site, and rejected if there aren't. */ - hasSites() : Promise { + hasSites(): Promise { return this.appDB.countRecords(this.SITES_TABLE).then((count) => { if (count == 0) { return Promise.reject(null); @@ -750,7 +763,7 @@ export class CoreSitesProvider { * @param {string} [siteId] The site ID. If not defined, current site (if available). * @return {Promise} Promise resolved with the site. */ - getSite(siteId?: string) : Promise { + getSite(siteId?: string): Promise { if (!siteId) { return this.currentSite ? Promise.resolve(this.currentSite) : Promise.reject(null); } else if (this.currentSite && this.currentSite.getId() == siteId) { @@ -758,8 +771,8 @@ export class CoreSitesProvider { } else if (typeof this.sites[siteId] != 'undefined') { return Promise.resolve(this.sites[siteId]); } else { - // retrieve and create the site. - return this.appDB.getRecord(this.SITES_TABLE, {id: siteId}).then((data) => { + // Retrieve and create the site. + return this.appDB.getRecord(this.SITES_TABLE, { id: siteId }).then((data) => { return this.makeSiteFromSiteListEntry(data); }); } @@ -771,7 +784,7 @@ export class CoreSitesProvider { * @param {any} entry Site list entry. * @return {CoreSite} Created site. */ - makeSiteFromSiteListEntry(entry) : CoreSite { + makeSiteFromSiteListEntry(entry: any): CoreSite { let site, info = entry.info, config = entry.config; @@ -779,14 +792,18 @@ export class CoreSitesProvider { // Try to parse info and config. try { info = info ? JSON.parse(info) : info; - } catch(ex) {} + } catch (ex) { + // Ignore errors. + } try { config = config ? JSON.parse(config) : config; - } catch(ex) {} + } catch (ex) { + // Ignore errors. + } site = this.sitesFactory.makeSite(entry.id, entry.siteUrl, entry.token, - info, entry.privateToken, config, entry.loggedOut == 1); + info, entry.privateToken, config, entry.loggedOut == 1); this.sites[entry.id] = site; if (this.siteTablesSchemas.length) { // Create tables in the site's database. @@ -802,12 +819,13 @@ export class CoreSitesProvider { * @param {string|CoreSite} [site] Site object or siteId to be compared. If not defined, use current site. * @return {boolean} Whether site or siteId is the current one. */ - isCurrentSite(site: string|CoreSite) : boolean { + isCurrentSite(site: string | CoreSite): boolean { if (!site || !this.currentSite) { return !!this.currentSite; } const siteId = typeof site == 'object' ? site.getId() : site; + return this.currentSite.getId() === siteId; } @@ -817,7 +835,7 @@ export class CoreSitesProvider { * @param {string} [siteId] The site ID. If not defined, current site (if available). * @return {Promise} Promise resolved with the database. */ - getSiteDb(siteId: string) : Promise { + getSiteDb(siteId: string): Promise { return this.getSite(siteId).then((site) => { return site.getDb(); }); @@ -829,7 +847,7 @@ export class CoreSitesProvider { * @param {number} [siteId] The site ID. If not defined, current site (if available). * @return {Promise} Promise resolved with site home ID. */ - getSiteHomeId(siteId?: string) : Promise { + getSiteHomeId(siteId?: string): Promise { return this.getSite(siteId).then((site) => { return site.getSiteHomeId(); }); @@ -841,16 +859,18 @@ export class CoreSitesProvider { * @param {String[]} [ids] IDs of the sites to get. If not defined, return all sites. * @return {Promise} Promise resolved when the sites are retrieved. */ - getSites(ids?: string[]) : Promise { + getSites(ids?: string[]): Promise { return this.appDB.getAllRecords(this.SITES_TABLE).then((sites) => { - let formattedSites = []; + const formattedSites = []; sites.forEach((site) => { if (!ids || ids.indexOf(site.id) > -1) { // Try to parse info. let siteInfo = site.info; try { siteInfo = siteInfo ? JSON.parse(siteInfo) : siteInfo; - } catch(ex) {} + } catch (ex) { + // Ignore errors. + } const basicInfo: CoreSiteBasicInfo = { id: site.id, @@ -862,6 +882,7 @@ export class CoreSitesProvider { formattedSites.push(basicInfo); } }); + return formattedSites; }); } @@ -885,12 +906,13 @@ export class CoreSitesProvider { * @param {string} siteid ID of the site the user is accessing. * @return {Promise} Promise resolved when current site is stored. */ - login(siteId: string) : Promise { + login(siteId: string): Promise { const entry = { id: 1, siteId: siteId }; - return this.appDB.insertOrUpdateRecord(this.CURRENT_SITE_TABLE, entry, {id: 1}).then(() => { + + return this.appDB.insertOrUpdateRecord(this.CURRENT_SITE_TABLE, entry, { id: 1 }).then(() => { this.eventsProvider.trigger(CoreEventsProvider.LOGIN, {}, siteId); }); } @@ -900,7 +922,7 @@ export class CoreSitesProvider { * * @return {Promise} Promise resolved when the user is logged out. */ - logout() : Promise { + logout(): Promise { if (!this.currentSite) { // Already logged out. return Promise.resolve(); @@ -916,7 +938,7 @@ export class CoreSitesProvider { promises.push(this.setSiteLoggedOut(siteId, true)); } - promises.push(this.appDB.deleteRecords(this.CURRENT_SITE_TABLE, {id: 1})); + promises.push(this.appDB.deleteRecords(this.CURRENT_SITE_TABLE, { id: 1 })); return Promise.all(promises).finally(() => { this.eventsProvider.trigger(CoreEventsProvider.LOGOUT, {}, siteId); @@ -928,16 +950,17 @@ export class CoreSitesProvider { * * @return {Promise} Promise resolved if a session is restored. */ - restoreSession() : Promise { + restoreSession(): Promise { if (this.sessionRestored) { return Promise.reject(null); } this.sessionRestored = true; - return this.appDB.getRecord(this.CURRENT_SITE_TABLE, {id: 1}).then((currentSite) => { + return this.appDB.getRecord(this.CURRENT_SITE_TABLE, { id: 1 }).then((currentSite) => { const siteId = currentSite.siteId; this.logger.debug(`Restore session in site ${siteId}`); + return this.loadSite(siteId); }); } @@ -949,7 +972,7 @@ export class CoreSitesProvider { * @param {boolean} loggedOut True to set the site as logged out, false otherwise. * @return {Promise} Promise resolved when done. */ - setSiteLoggedOut(siteId: string, loggedOut: boolean) : Promise { + setSiteLoggedOut(siteId: string, loggedOut: boolean): Promise { return this.getSite(siteId).then((site) => { const newValues = { token: '', // Erase the token for security. @@ -958,7 +981,7 @@ export class CoreSitesProvider { site.setLoggedOut(loggedOut); - return this.appDB.updateRecords(this.SITES_TABLE, newValues, {id: siteId}); + return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId }); }); } @@ -971,8 +994,9 @@ export class CoreSitesProvider { * @param {string} [privateToken=''] User's private token. * @return {Promise} A promise resolved when the site is updated. */ - updateSiteToken(siteUrl: string, username: string, token: string, privateToken = '') : Promise { + updateSiteToken(siteUrl: string, username: string, token: string, privateToken: string = ''): Promise { const siteId = this.createSiteID(siteUrl, username); + return this.updateSiteTokenBySiteId(siteId, token, privateToken); } @@ -984,7 +1008,7 @@ export class CoreSitesProvider { * @param {string} [privateToken=''] User's private token. * @return {Promise} A promise resolved when the site is updated. */ - updateSiteTokenBySiteId(siteId: string, token: string, privateToken = '') : Promise { + updateSiteTokenBySiteId(siteId: string, token: string, privateToken: string = ''): Promise { return this.getSite(siteId).then((site) => { const newValues = { token: token, @@ -996,7 +1020,7 @@ export class CoreSitesProvider { site.privateToken = privateToken; site.setLoggedOut(false); // Token updated means the user authenticated again, not logged out anymore. - return this.appDB.updateRecords(this.SITES_TABLE, newValues, {id: siteId}); + return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId }); }); } @@ -1006,7 +1030,7 @@ export class CoreSitesProvider { * @param {string} siteid Site's ID. * @return {Promise} A promise resolved when the site is updated. */ - updateSiteInfo(siteId: string) : Promise { + updateSiteInfo(siteId: string): Promise { return this.getSite(siteId).then((site) => { return site.fetchSiteInfo().then((info) => { site.setInfo(info); @@ -1015,7 +1039,7 @@ export class CoreSitesProvider { return this.getSiteConfig(site).catch(() => { // Error getting config, keep the current one. }).then((config) => { - let newValues: any = { + const newValues: any = { info: JSON.stringify(info), loggedOut: site.isLoggedOut() ? 1 : 0 }; @@ -1025,7 +1049,7 @@ export class CoreSitesProvider { newValues.config = JSON.stringify(config); } - return this.appDB.updateRecords(this.SITES_TABLE, newValues, {id: siteId}).finally(() => { + return this.appDB.updateRecords(this.SITES_TABLE, newValues, { id: siteId }).finally(() => { this.eventsProvider.trigger(CoreEventsProvider.SITE_UPDATED, {}, siteId); }); }); @@ -1040,8 +1064,9 @@ export class CoreSitesProvider { * @param {string} username Username. * @return {Promise} A promise to be resolved when the site is updated. */ - updateSiteInfoByUrl(siteUrl: string, username: string) : Promise { + updateSiteInfoByUrl(siteUrl: string, username: string): Promise { const siteId = this.createSiteID(siteUrl, username); + return this.updateSiteInfo(siteId); } @@ -1055,7 +1080,7 @@ export class CoreSitesProvider { * @param {string} [username] If set, it will return only the sites where the current user has this username. * @return {Promise} Promise resolved with the site IDs (array). */ - getSiteIdsFromUrl(url: string, prioritize?: boolean, username?: string) : Promise { + getSiteIdsFromUrl(url: string, prioritize?: boolean, username?: string): Promise { // If prioritize is true, check current site first. if (prioritize && this.currentSite && this.currentSite.containsUrl(url)) { if (!username || this.currentSite.getInfo().username == username) { @@ -1080,7 +1105,7 @@ export class CoreSitesProvider { } return this.appDB.getAllRecords(this.SITES_TABLE).then((siteEntries) => { - let ids = []; + const ids = []; siteEntries.forEach((site) => { if (!this.sites[site.id]) { this.makeSiteFromSiteListEntry(site); @@ -1092,6 +1117,7 @@ export class CoreSitesProvider { } } }); + return ids; }).catch(() => { // Shouldn't happen. @@ -1104,8 +1130,8 @@ export class CoreSitesProvider { * * @return {Promise} Promise resolved with the site ID. */ - getStoredCurrentSiteId() : Promise { - return this.appDB.getRecord(this.CURRENT_SITE_TABLE, {id: 1}).then((currentSite) => { + getStoredCurrentSiteId(): Promise { + return this.appDB.getRecord(this.CURRENT_SITE_TABLE, { id: 1 }).then((currentSite) => { return currentSite.siteId; }); } @@ -1116,8 +1142,9 @@ export class CoreSitesProvider { * @param {string} siteUrl URL of the site. * @return {Promise} Promise resolved with the public config. */ - getSitePublicConfig(siteUrl: string) : Promise { + getSitePublicConfig(siteUrl: string): Promise { const temporarySite = this.sitesFactory.makeSite(undefined, siteUrl); + return temporarySite.getPublicConfig(); } @@ -1127,7 +1154,7 @@ export class CoreSitesProvider { * @param {any} site The site to get the config. * @return {Promise} Promise resolved with config if available. */ - protected getSiteConfig(site: CoreSite) : Promise { + protected getSiteConfig(site: CoreSite): Promise { if (!site.wsAvailable('tool_mobile_get_config')) { // WS not available, cannot get config. return Promise.resolve(); @@ -1143,7 +1170,7 @@ export class CoreSitesProvider { * @param {string} [siteId] The site ID. If not defined, current site (if available). * @return {Promise} Promise resolved with true if disabled. */ - isFeatureDisabled(name: string, siteId?: string) : Promise { + isFeatureDisabled(name: string, siteId?: string): Promise { return this.getSite(siteId).then((site) => { return site.isFeatureDisabled(name); }); @@ -1154,7 +1181,7 @@ export class CoreSitesProvider { * * @param {any} table Table schema. */ - createTableFromSchema(table: any) : void { + createTableFromSchema(table: any): void { this.createTablesFromSchema([table]); } @@ -1163,12 +1190,12 @@ export class CoreSitesProvider { * * @param {any[]} tables List of tables schema. */ - createTablesFromSchema(tables: any[]) : void { + createTablesFromSchema(tables: any[]): void { // Add the tables to the list of schemas. This list is to create all the tables in new sites. this.siteTablesSchemas = this.siteTablesSchemas.concat(tables); // Now create these tables in current sites. - for (let id in this.sites) { + for (const id in this.sites) { this.sites[id].getDb().createTablesFromSchema(tables); } } diff --git a/src/providers/sync.ts b/src/providers/sync.ts index 00b376a11..ea8d6ee75 100644 --- a/src/providers/sync.ts +++ b/src/providers/sync.ts @@ -23,7 +23,7 @@ import { CoreSitesProvider } from './sites'; export class CoreSyncProvider { // Variables for the database. - public static SYNC_TABLE = 'sync'; + static SYNC_TABLE = 'sync'; protected tableSchema = { name: CoreSyncProvider.SYNC_TABLE, columns: [ @@ -50,7 +50,7 @@ export class CoreSyncProvider { }; // Store blocked sync objects. - protected blockedItems: {[siteId: string]: {[blockId: string]: {[operation: string]: boolean}}} = {}; + protected blockedItems: { [siteId: string]: { [blockId: string]: { [operation: string]: boolean } } } = {}; constructor(eventsProvider: CoreEventsProvider, private sitesProvider: CoreSitesProvider) { this.sitesProvider.createTableFromSchema(this.tableSchema); @@ -69,7 +69,7 @@ export class CoreSyncProvider { * @param {string} [operation] Operation name. If not defined, a default text is used. * @param {string} [siteId] Site ID. If not defined, current site. */ - blockOperation(component: string, id: number, operation?: string, siteId?: string) : void { + blockOperation(component: string, id: number, operation?: string, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const uniqueId = this.getUniqueSyncBlockId(component, id); @@ -92,7 +92,7 @@ export class CoreSyncProvider { * * @param {string} [siteId] If set, clear the blocked objects only for this site. Otherwise clear them for all sites. */ - clearAllBlocks(siteId?: string) : void { + clearAllBlocks(siteId?: string): void { if (siteId) { delete this.blockedItems[siteId]; } else { @@ -107,7 +107,7 @@ export class CoreSyncProvider { * @param {number} id Unique ID per component. * @param {string} [siteId] Site ID. If not defined, current site. */ - clearBlocks(component: string, id: number, siteId?: string) : void { + clearBlocks(component: string, id: number, siteId?: string): void { siteId = siteId || this.sitesProvider.getCurrentSiteId(); const uniqueId = this.getUniqueSyncBlockId(component, id); @@ -123,7 +123,7 @@ export class CoreSyncProvider { * @param {number} id Unique ID per component. * @return {string} Unique sync id. */ - protected getUniqueSyncBlockId(component: string, id: number) : string { + protected getUniqueSyncBlockId(component: string, id: number): string { return component + '#' + id; } @@ -136,7 +136,7 @@ export class CoreSyncProvider { * @param {string} [siteId] Site ID. If not defined, current site. * @return {boolean} Whether it's blocked. */ - isBlocked(component: string, id: number, siteId?: string) : boolean { + isBlocked(component: string, id: number, siteId?: string): boolean { siteId = siteId || this.sitesProvider.getCurrentSiteId(); if (!this.blockedItems[siteId]) { @@ -159,7 +159,7 @@ export class CoreSyncProvider { * @param {string} [operation] Operation name. If not defined, a default text is used. * @param {string} [siteId] Site ID. If not defined, current site. */ - unblockOperation(component: string, id: number, operation?: string, siteId?: string) : void { + unblockOperation(component: string, id: number, operation?: string, siteId?: string): void { operation = operation || '-'; siteId = siteId || this.sitesProvider.getCurrentSiteId(); diff --git a/src/providers/update-manager.ts b/src/providers/update-manager.ts index dc7d16889..659a069de 100644 --- a/src/providers/update-manager.ts +++ b/src/providers/update-manager.ts @@ -29,9 +29,9 @@ import { CoreConfigConstants } from '../configconstants'; @Injectable() export class CoreUpdateManagerProvider implements CoreInitHandler { // Data for init delegate. - public name = 'CoreUpdateManager'; - public priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 300; - public blocking = true; + name = 'CoreUpdateManager'; + priority = CoreInitDelegate.MAX_RECOMMENDED_PRIORITY + 300; + blocking = true; protected VERSION_APPLIED = 'version_applied'; protected logger; @@ -47,8 +47,8 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * * @return {Promise} Promise resolved when the update process finishes. */ - load() : Promise { - let promises = [], + load(): Promise { + const promises = [], versionCode = CoreConfigConstants.versioncode; return this.configProvider.get(this.VERSION_APPLIED, 0).then((versionApplied: number) => { @@ -80,13 +80,14 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * * @return {Promise} Promise resolved when the site migration is finished. */ - protected migrateFileExtensions() : Promise { + protected migrateFileExtensions(): Promise { return this.sitesProvider.getSitesIds().then((sites) => { - let promises = []; + const promises = []; sites.forEach((siteId) => { promises.push(this.filepoolProvider.fillMissingExtensionInFiles(siteId)); }); promises.push(this.filepoolProvider.treatExtensionInQueue()); + return Promise.all(promises); }); } @@ -97,7 +98,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * * @return {Promise} Promise resolved when the events are configured. */ - protected setCalendarDefaultNotifTime() : Promise { + protected setCalendarDefaultNotifTime(): Promise { if (!this.notifProvider.isAvailable()) { // Local notifications not available, nothing to do. return Promise.resolve(); @@ -113,7 +114,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * * @return {Promise} Promise resolved when the config is loaded for the current site (if any). */ - protected setSitesConfig() : Promise { + protected setSitesConfig(): Promise { return this.sitesProvider.getSitesIds().then((siteIds) => { return this.sitesProvider.getStoredCurrentSiteId().catch(() => { @@ -146,9 +147,9 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * @param {String} siteId Site ID. * @return {Promise} Promise resolved when the config is loaded for the site. */ - protected setSiteConfig(siteId: string) : Promise { + protected setSiteConfig(siteId: string): Promise { return this.sitesProvider.getSite(siteId).then((site) => { - if (site.getStoredConfig() || !site.wsAvailable('tool_mobile_get_config')) { + if (site.getStoredConfig() || !site.wsAvailable('tool_mobile_get_config')) { // Site already has the config or it cannot be retrieved. Stop. return; } @@ -156,7 +157,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { // Get the site config. return site.getConfig().then((config) => { return this.sitesProvider.addSite( - site.getId(), site.getURL(), site.getToken(), site.getInfo(), site.getPrivateToken(), config); + site.getId(), site.getURL(), site.getToken(), site.getInfo(), site.getPrivateToken(), config); }).catch(() => { // Ignore errors. }); @@ -169,7 +170,7 @@ export class CoreUpdateManagerProvider implements CoreInitHandler { * * @return {Promise} Promise resolved when the db is migrated. */ - protected adaptForumOfflineStores() : Promise { + protected adaptForumOfflineStores(): Promise { // @todo: Implement it once Forum addon is implemented. return Promise.resolve(); } diff --git a/src/providers/utils/dom.ts b/src/providers/utils/dom.ts index 2bc835057..3d9ea96fb 100644 --- a/src/providers/utils/dom.ts +++ b/src/providers/utils/dom.ts @@ -13,8 +13,10 @@ // limitations under the License. import { Injectable } from '@angular/core'; -import { LoadingController, Loading, ToastController, Toast, AlertController, Alert, Platform, Content, - ModalController } from 'ionic-angular'; +import { + LoadingController, Loading, ToastController, Toast, AlertController, Alert, Platform, Content, + ModalController +} from 'ionic-angular'; import { TranslateService } from '@ngx-translate/core'; import { CoreTextUtilsProvider } from './text'; import { CoreAppProvider } from '../app'; @@ -29,15 +31,15 @@ import { CoreConstants } from '../../core/constants'; export class CoreDomUtilsProvider { // List of input types that support keyboard. protected INPUT_SUPPORT_KEYBOARD = ['date', 'datetime', 'datetime-local', 'email', 'month', 'number', 'password', - 'search', 'tel', 'text', 'time', 'url', 'week']; + 'search', 'tel', 'text', 'time', 'url', 'week']; protected element = document.createElement('div'); // Fake element to use in some functions, to prevent creating it each time. protected matchesFn: string; // Name of the "matches" function to use when simulating a closest call. constructor(private translate: TranslateService, private loadingCtrl: LoadingController, private toastCtrl: ToastController, - private alertCtrl: AlertController, private textUtils: CoreTextUtilsProvider, private appProvider: CoreAppProvider, - private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider, - private modalCtrl: ModalController) {} + private alertCtrl: AlertController, private textUtils: CoreTextUtilsProvider, private appProvider: CoreAppProvider, + private platform: Platform, private configProvider: CoreConfigProvider, private urlUtils: CoreUrlUtilsProvider, + private modalCtrl: ModalController) { } /** * Wraps a message with core-format-text if the message contains HTML tags. @@ -46,11 +48,12 @@ export class CoreDomUtilsProvider { * @param {string} message Message to wrap. * @return {string} Result message. */ - private addFormatTextIfNeeded(message: string) : string { + private addFormatTextIfNeeded(message: string): string { // @todo if (this.textUtils.hasHTMLTags(message)) { return '' + message + ''; } + return message; } @@ -63,7 +66,7 @@ export class CoreDomUtilsProvider { * @param {string} selector Selector to search. * @return {Element} Closest ancestor. */ - closest(element: HTMLElement, selector: string) : Element { + closest(element: HTMLElement, selector: string): Element { // Try to use closest if the browser supports it. if (typeof element.closest == 'function') { return element.closest(selector); @@ -71,11 +74,13 @@ export class CoreDomUtilsProvider { if (!this.matchesFn) { // Find the matches function supported by the browser. - ['matches','webkitMatchesSelector','mozMatchesSelector','msMatchesSelector','oMatchesSelector'].some((fn) => { + ['matches', 'webkitMatchesSelector', 'mozMatchesSelector', 'msMatchesSelector', 'oMatchesSelector'].some((fn) => { if (typeof document.body[fn] == 'function') { this.matchesFn = fn; + return true; } + return false; }); @@ -105,25 +110,29 @@ export class CoreDomUtilsProvider { * @return {Promise} Promise resolved when the user confirms or if no confirm needed. */ confirmDownloadSize(size: any, message?: string, unknownMessage?: string, wifiThreshold?: number, limitedThreshold?: number, - alwaysConfirm?: boolean) : Promise { + alwaysConfirm?: boolean): Promise { wifiThreshold = typeof wifiThreshold == 'undefined' ? CoreConstants.WIFI_DOWNLOAD_THRESHOLD : wifiThreshold; limitedThreshold = typeof limitedThreshold == 'undefined' ? CoreConstants.DOWNLOAD_THRESHOLD : limitedThreshold; if (size.size < 0 || (size.size == 0 && !size.total)) { // Seems size was unable to be calculated. Show a warning. - unknownMessage = unknownMessage || 'core.course.confirmdownloadunknownsize'; + unknownMessage = unknownMessage || 'core.course.confirmdownloadunknownsize'; + return this.showConfirm(this.translate.instant(unknownMessage)); } else if (!size.total) { // Filesize is only partial. - let readableSize = this.textUtils.bytesToSize(size.size, 2); - return this.showConfirm(this.translate.instant('core.course.confirmpartialdownloadsize', {size: readableSize})); + const readableSize = this.textUtils.bytesToSize(size.size, 2); + + return this.showConfirm(this.translate.instant('core.course.confirmpartialdownloadsize', { size: readableSize })); } else if (size.size >= wifiThreshold || (this.appProvider.isNetworkAccessLimited() && size.size >= limitedThreshold)) { - message = message || 'core.course.confirmdownload'; - let readableSize = this.textUtils.bytesToSize(size.size, 2); - return this.showConfirm(this.translate.instant(message, {size: readableSize})); + message = message || 'core.course.confirmdownload'; + const readableSize = this.textUtils.bytesToSize(size.size, 2); + + return this.showConfirm(this.translate.instant(message, { size: readableSize })); } else if (alwaysConfirm) { return this.showConfirm(this.translate.instant('core.areyousure')); } + return Promise.resolve(); } @@ -133,16 +142,16 @@ export class CoreDomUtilsProvider { * @param {string} html HTML code. * @return {string[]} List of file urls. */ - extractDownloadableFilesFromHtml(html: string) : string[] { - let elements, - urls = []; + extractDownloadableFilesFromHtml(html: string): string[] { + const urls = []; + let elements; this.element.innerHTML = html; elements = this.element.querySelectorAll('a, img, audio, video, source, track'); - for (let i in elements) { - let element = elements[i], - url = element.tagName === 'A' ? element.href : element.src; + for (const i in elements) { + const element = elements[i]; + let url = element.tagName === 'A' ? element.href : element.src; if (url && this.urlUtils.isDownloadableUrl(url) && urls.indexOf(url) == -1) { urls.push(url); @@ -166,8 +175,9 @@ export class CoreDomUtilsProvider { * @param {string} html HTML code. * @return {any[]} List of fake file objects with file URLs. */ - extractDownloadableFilesFromHtmlAsFakeFileObjects(html: string) : any[] { - let urls = this.extractDownloadableFilesFromHtml(html); + extractDownloadableFilesFromHtmlAsFakeFileObjects(html: string): any[] { + const urls = this.extractDownloadableFilesFromHtml(html); + // Convert them to fake file objects. return urls.map((url) => { return { @@ -182,14 +192,14 @@ export class CoreDomUtilsProvider { * @param {string} code CSS code. * @return {string[]} List of URLs. */ - extractUrlsFromCSS(code: string) : string[] { + extractUrlsFromCSS(code: string): string[] { // First of all, search all the url(...) occurrences that don't include "data:". - let urls = [], + const urls = [], matches = code.match(/url\(\s*["']?(?!data:)([^)]+)\)/igm); // Extract the URL form each match. matches.forEach((match) => { - let submatches = match.match(/url\(\s*['"]?([^'"]*)['"]?\s*\)/im); + const submatches = match.match(/url\(\s*['"]?([^'"]*)['"]?\s*\)/im); if (submatches && submatches[1]) { urls.push(submatches[1]); } @@ -203,7 +213,7 @@ export class CoreDomUtilsProvider { * * @param {HTMLElement} el HTML element to focus. */ - focusElement(el: HTMLElement) : void { + focusElement(el: HTMLElement): void { if (el && el.focus) { el.focus(); if (this.platform.is('android') && this.supportsInputKeyboard(el)) { @@ -221,7 +231,7 @@ export class CoreDomUtilsProvider { * @param {any} size Size to format. * @return {string} Formatted size. If size is not valid, returns an empty string. */ - formatPixelsSize(size: any) : string { + formatPixelsSize(size: any): string { if (typeof size == 'string' && (size.indexOf('px') > -1 || size.indexOf('%') > -1)) { // It seems to be a valid size. return size; @@ -231,6 +241,7 @@ export class CoreDomUtilsProvider { if (!isNaN(size)) { return size + 'px'; } + return ''; } @@ -241,9 +252,9 @@ export class CoreDomUtilsProvider { * @param {string} selector Selector to search. * @return {string} Selection contents. Undefined if not found. */ - getContentsOfElement(element: HTMLElement, selector: string) : string { + getContentsOfElement(element: HTMLElement, selector: string): string { if (element) { - let selected = element.querySelector(selector); + const selected = element.querySelector(selector); if (selected) { return selected.innerHTML; } @@ -261,7 +272,7 @@ export class CoreDomUtilsProvider { * @return {number} Height in pixels. */ getElementHeight(element: any, usePadding?: boolean, useMargin?: boolean, useBorder?: boolean, - innerMeasure?: boolean) : number { + innerMeasure?: boolean): number { return this.getElementMeasure(element, false, usePadding, useMargin, useBorder, innerMeasure); } @@ -277,18 +288,18 @@ export class CoreDomUtilsProvider { * @return {number} Measure in pixels. */ getElementMeasure(element: any, getWidth?: boolean, usePadding?: boolean, useMargin?: boolean, useBorder?: boolean, - innerMeasure?: boolean) : number { + innerMeasure?: boolean): number { - let offsetMeasure = getWidth ? 'offsetWidth' : 'offsetHeight', + const offsetMeasure = getWidth ? 'offsetWidth' : 'offsetHeight', measureName = getWidth ? 'width' : 'height', clientMeasure = getWidth ? 'clientWidth' : 'clientHeight', priorSide = getWidth ? 'Left' : 'Top', - afterSide = getWidth ? 'Right' : 'Bottom', - measure = element[offsetMeasure] || element[measureName] || element[clientMeasure] || 0; + afterSide = getWidth ? 'Right' : 'Bottom'; + let measure = element[offsetMeasure] || element[measureName] || element[clientMeasure] || 0; // Measure not correctly taken. if (measure <= 0) { - let style = getComputedStyle(element); + const style = getComputedStyle(element); if (style && style.display == '') { element.style.display = 'inline-block'; measure = element[offsetMeasure] || element[measureName] || element[clientMeasure] || 0; @@ -297,8 +308,9 @@ export class CoreDomUtilsProvider { } if (usePadding || useMargin || useBorder) { - let surround = 0, - computedStyle = getComputedStyle(element); + const computedStyle = getComputedStyle(element); + let surround = 0; + if (usePadding) { surround += parseInt(computedStyle['padding' + priorSide], 10) + parseInt(computedStyle['padding' + afterSide], 10); } @@ -330,7 +342,7 @@ export class CoreDomUtilsProvider { * @return {number} Width in pixels. */ getElementWidth(element: any, usePadding?: boolean, useMargin?: boolean, useBorder?: boolean, - innerMeasure?: boolean) : number { + innerMeasure?: boolean): number { return this.getElementMeasure(element, true, usePadding, useMargin, useBorder, innerMeasure); } @@ -342,7 +354,7 @@ export class CoreDomUtilsProvider { * @param {string} [positionParentClass] Parent Class where to stop calculating the position. Default scroll-content. * @return {number[]} positionLeft, positionTop of the element relative to. */ - getElementXY(container: HTMLElement, selector?: string, positionParentClass?: string) : number[] { + getElementXY(container: HTMLElement, selector?: string, positionParentClass?: string): number[] { let element: HTMLElement = (selector ? container.querySelector(selector) : container), offsetElement, positionTop = 0, @@ -388,12 +400,13 @@ export class CoreDomUtilsProvider { * @param {string} message The error message. * @return {string} Title. */ - private getErrorTitle(message: string) : string { + private getErrorTitle(message: string): string { if (message == this.translate.instant('core.networkerrormsg') || - message == this.translate.instant('core.fileuploader.errormustbeonlinetoupload')) { + message == this.translate.instant('core.fileuploader.errormustbeonlinetoupload')) { return '\ '; } + return this.textUtils.decodeHTML(this.translate.instant('core.error')); } @@ -404,9 +417,9 @@ export class CoreDomUtilsProvider { * @param {HTMLElement} element DOM element to check. * @return {boolean} Whether the element is outside of the viewport. */ - isElementOutsideOfScreen(scrollEl: HTMLElement, element: HTMLElement) : boolean { - let elementRect = element.getBoundingClientRect(), - elementMidPoint, + isElementOutsideOfScreen(scrollEl: HTMLElement, element: HTMLElement): boolean { + const elementRect = element.getBoundingClientRect(); + let elementMidPoint, scrollElRect, scrollTopPos = 0; @@ -417,9 +430,9 @@ export class CoreDomUtilsProvider { elementMidPoint = Math.round((elementRect.bottom + elementRect.top) / 2); scrollElRect = scrollEl.getBoundingClientRect(); - scrollTopPos = (scrollElRect && scrollElRect.top) || 0; + scrollTopPos = (scrollElRect && scrollElRect.top) || 0; - return elementMidPoint > window.innerHeight || elementMidPoint < scrollTopPos; + return elementMidPoint > window.innerHeight || elementMidPoint < scrollTopPos; } /** @@ -427,7 +440,7 @@ export class CoreDomUtilsProvider { * * @return {Promise} Promise resolved with boolean: true if enabled, false otherwise. */ - isRichTextEditorEnabled() : Promise { + isRichTextEditorEnabled(): Promise { if (this.isRichTextEditorSupported()) { return this.configProvider.get(CoreConstants.SETTINGS_RICH_TEXT_EDITOR, true); } @@ -440,7 +453,7 @@ export class CoreDomUtilsProvider { * * @return {boolean} Whether it's supported. */ - isRichTextEditorSupported() : boolean { + isRichTextEditorSupported(): boolean { // Disabled just for iOS. return !this.platform.is('ios'); } @@ -451,7 +464,7 @@ export class CoreDomUtilsProvider { * @param {HTMLElement} oldParent The old parent. * @param {HTMLElement} newParent The new parent. */ - moveChildren(oldParent: HTMLElement, newParent: HTMLElement) : void { + moveChildren(oldParent: HTMLElement, newParent: HTMLElement): void { while (oldParent.childNodes.length > 0) { newParent.appendChild(oldParent.childNodes[0]); } @@ -463,9 +476,9 @@ export class CoreDomUtilsProvider { * @param {HTMLElement} element DOM element to search in. * @param {string} selector Selector to search. */ - removeElement(element: HTMLElement, selector: string) : void { + removeElement(element: HTMLElement, selector: string): void { if (element) { - let selected = element.querySelector(selector); + const selected = element.querySelector(selector); if (selected) { selected.remove(); } @@ -480,14 +493,14 @@ export class CoreDomUtilsProvider { * @param {boolean} [removeAll] True if it should remove all matches found, false if it should only remove the first one. * @return {string} HTML without the element. */ - removeElementFromHtml(html: string, selector: string, removeAll?: boolean) : string { + removeElementFromHtml(html: string, selector: string, removeAll?: boolean): string { let selected; this.element.innerHTML = html; if (removeAll) { selected = this.element.querySelectorAll(selector); - for (let i in selected) { + for (const i in selected) { selected[i].remove(); } } else { @@ -507,12 +520,12 @@ export class CoreDomUtilsProvider { * @param {any} map Mapping of the classes to replace. Keys must be the value to replace, values must be * the new class name. Example: {'correct': 'core-question-answer-correct'}. */ - replaceClassesInElement(element: HTMLElement, map: any) : void { - for (let key in map) { - let foundElements = element.querySelectorAll('.' + key); + replaceClassesInElement(element: HTMLElement, map: any): void { + for (const key in map) { + const foundElements = element.querySelectorAll('.' + key); - for (let i in foundElements) { - let foundElement = foundElements[i]; + for (const i in foundElements) { + const foundElement = foundElements[i]; foundElement.className = foundElement.className.replace(key, map[key]); } } @@ -526,7 +539,7 @@ export class CoreDomUtilsProvider { * @param {Function} [anchorFn] Function to call with each anchor. Optional. * @return {string} Treated HTML code. */ - restoreSourcesInHtml(html: string, paths: object, anchorFn?: Function) : string { + restoreSourcesInHtml(html: string, paths: object, anchorFn?: Function): string { let media, anchors; @@ -534,9 +547,9 @@ export class CoreDomUtilsProvider { // Treat elements with src (img, audio, video, ...). media = this.element.querySelectorAll('img, video, audio, source, track'); - for (let i in media) { - let el = media[i], - newSrc = paths[this.textUtils.decodeURIComponent(el.getAttribute('src'))]; + for (const i in media) { + const el = media[i]; + let newSrc = paths[this.textUtils.decodeURIComponent(el.getAttribute('src'))]; if (typeof newSrc != 'undefined') { el.setAttribute('src', newSrc); @@ -553,8 +566,8 @@ export class CoreDomUtilsProvider { // Now treat links. anchors = this.element.querySelectorAll('a'); - for (let i in anchors) { - let anchor = anchors[i], + for (const i in anchors) { + const anchor = anchors[i], href = this.textUtils.decodeURIComponent(anchor.getAttribute('href')), newUrl = paths[href]; @@ -579,14 +592,15 @@ export class CoreDomUtilsProvider { * @param {string} [scrollParentClass] Parent class where to stop calculating the position. Default scroll-content. * @return {boolean} True if the element is found, false otherwise. */ - scrollToElement(scrollEl: Content|HTMLElement, container: HTMLElement, selector?: string, scrollParentClass?: string) + scrollToElement(scrollEl: Content | HTMLElement, container: HTMLElement, selector?: string, scrollParentClass?: string) : boolean { - let position = this.getElementXY(container, selector, scrollParentClass); + const position = this.getElementXY(container, selector, scrollParentClass); if (!position) { return false; } scrollEl.scrollTo(position[0], position[1]); + return true; } @@ -598,7 +612,7 @@ export class CoreDomUtilsProvider { * @param [scrollParentClass] Parent class where to stop calculating the position. Default scroll-content. * @return {boolean} True if the element is found, false otherwise. */ - scrollToInputError(scrollEl: Content|HTMLElement, container: HTMLElement, scrollParentClass?: string) : boolean { + scrollToInputError(scrollEl: Content | HTMLElement, container: HTMLElement, scrollParentClass?: string): boolean { if (!scrollEl) { return false; } @@ -615,12 +629,12 @@ export class CoreDomUtilsProvider { * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. * @return {Alert} The alert modal. */ - showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number) : Alert { - let alert = this.alertCtrl.create({ - title: title, - message: this.addFormatTextIfNeeded(message), // Add format-text to handle links. - buttons: [buttonText || this.translate.instant('core.ok')] - }); + showAlert(title: string, message: string, buttonText?: string, autocloseTime?: number): Alert { + const alert = this.alertCtrl.create({ + title: title, + message: this.addFormatTextIfNeeded(message), // Add format-text to handle links. + buttons: [buttonText || this.translate.instant('core.ok')] + }); alert.present(); @@ -642,7 +656,7 @@ export class CoreDomUtilsProvider { * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. * @return {Alert} The alert modal. */ - showAlertTranslated(title: string, message: string, buttonText?: string, autocloseTime?: number) : Alert { + showAlertTranslated(title: string, message: string, buttonText?: string, autocloseTime?: number): Alert { title = title ? this.translate.instant(title) : title; message = message ? this.translate.instant(message) : message; buttonText = buttonText ? this.translate.instant(buttonText) : buttonText; @@ -660,8 +674,8 @@ export class CoreDomUtilsProvider { * @param {any} [options] More options. See https://ionicframework.com/docs/api/components/alert/AlertController/ * @return {Promise} Promise resolved if the user confirms and rejected if he cancels. */ - showConfirm(message: string, title?: string, okText?: string, cancelText?: string, options?: any) : Promise { - return new Promise((resolve, reject) => { + showConfirm(message: string, title?: string, okText?: string, cancelText?: string, options?: any): Promise { + return new Promise((resolve, reject): void => { options = options || {}; options.message = this.addFormatTextIfNeeded(message); // Add format-text to handle links. @@ -673,13 +687,13 @@ export class CoreDomUtilsProvider { { text: cancelText || this.translate.instant('core.cancel'), role: 'cancel', - handler: () => { + handler: (): void => { reject(); } }, { text: okText || this.translate.instant('core.ok'), - handler: () => { + handler: (): void => { resolve(); } } @@ -697,7 +711,7 @@ export class CoreDomUtilsProvider { * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. * @return {Alert} The alert modal. */ - showErrorModal(error: any, needsTranslate?: boolean, autocloseTime?: number) : Alert { + showErrorModal(error: any, needsTranslate?: boolean, autocloseTime?: number): Alert { if (typeof error == 'object') { // We received an object instead of a string. Search for common properties. if (typeof error.content != 'undefined') { @@ -714,13 +728,14 @@ export class CoreDomUtilsProvider { } // Try to remove tokens from the contents. - let matches = error.match(/token"?[=|:]"?(\w*)/, ''); + const matches = error.match(/token"?[=|:]"?(\w*)/, ''); if (matches && matches[1]) { error = error.replace(new RegExp(matches[1], 'g'), 'secret'); } } - let message = this.textUtils.decodeHTML(needsTranslate ? this.translate.instant(error) : error); + const message = this.textUtils.decodeHTML(needsTranslate ? this.translate.instant(error) : error); + return this.showAlert(this.getErrorTitle(message), message, undefined, autocloseTime); } @@ -733,12 +748,13 @@ export class CoreDomUtilsProvider { * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. * @return {Alert} The alert modal. */ - showErrorModalDefault(error: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number) : Alert { + showErrorModalDefault(error: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Alert { if (error != CoreConstants.DONT_SHOW_ERROR) { if (error && typeof error != 'string') { - error = error.message || error.error; + error = error.message || error.error; } error = typeof error == 'string' ? error : defaultError; + return this.showErrorModal(error, needsTranslate, autocloseTime); } } @@ -752,8 +768,9 @@ export class CoreDomUtilsProvider { * @param {number} [autocloseTime] Number of milliseconds to wait to close the modal. If not defined, modal won't be closed. * @return {Alert} The alert modal. */ - showErrorModalFirstWarning(warnings: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number) : Alert { - let error = warnings && warnings.length && warnings[0].message; + showErrorModalFirstWarning(warnings: any, defaultError: any, needsTranslate?: boolean, autocloseTime?: number): Alert { + const error = warnings && warnings.length && warnings[0].message; + return this.showErrorModalDefault(error, defaultError, needsTranslate, autocloseTime); } @@ -769,14 +786,14 @@ export class CoreDomUtilsProvider { * ... * modal.dismiss(); */ - showModalLoading(text?: string, needsTranslate?: boolean) : Loading { + showModalLoading(text?: string, needsTranslate?: boolean): Loading { if (!text) { text = this.translate.instant('core.loading'); } else if (needsTranslate) { text = this.translate.instant(text); } - let loader = this.loadingCtrl.create({ + const loader = this.loadingCtrl.create({ content: text }); @@ -794,8 +811,8 @@ export class CoreDomUtilsProvider { * @param {string} [type] Type of the input element. By default, password. * @return {Promise} Promise resolved with the input data if the user clicks OK, rejected if cancels. */ - showPrompt(message: string, title?: string, placeholder?: string, type = 'password') : Promise { - return new Promise((resolve, reject) => { + showPrompt(message: string, title?: string, placeholder?: string, type: string = 'password'): Promise { + return new Promise((resolve, reject): void => { this.alertCtrl.create({ message: this.addFormatTextIfNeeded(message), // Add format-text to handle links. title: title, @@ -810,13 +827,13 @@ export class CoreDomUtilsProvider { { text: this.translate.instant('core.cancel'), role: 'cancel', - handler: () => { + handler: (): void => { reject(); } }, { text: this.translate.instant('core.ok'), - handler: (data) => { + handler: (data): void => { resolve(data.promptinput); } } @@ -833,12 +850,12 @@ export class CoreDomUtilsProvider { * @param {number} [duration=2000] Duration in ms of the dimissable toast. * @return {Toast} Toast instance. */ - showToast(text: string, needsTranslate?: boolean, duration = 2000) : Toast { + showToast(text: string, needsTranslate?: boolean, duration: number = 2000): Toast { if (needsTranslate) { text = this.translate.instant(text); } - let loader = this.toastCtrl.create({ + const loader = this.toastCtrl.create({ message: text, duration: duration, position: 'bottom', @@ -856,7 +873,7 @@ export class CoreDomUtilsProvider { * @param {any} el HTML element to check. * @return {boolean} Whether it supports input using keyboard. */ - supportsInputKeyboard(el: any) : boolean { + supportsInputKeyboard(el: any): boolean { return el && !el.disabled && (el.tagName.toLowerCase() == 'textarea' || (el.tagName.toLowerCase() == 'input' && this.INPUT_SUPPORT_KEYBOARD.indexOf(el.type) != -1)); } @@ -869,16 +886,15 @@ export class CoreDomUtilsProvider { * @param {string} [component] Component to link the image to if needed. * @param {string|number} [componentId] An ID to use in conjunction with the component. */ - viewImage(image: string, title?: string, component?: string, componentId?: string|number) : void { + viewImage(image: string, title?: string, component?: string, componentId?: string | number): void { if (image) { - let params: any = { - title: title, - image: image, - component: component, - componentId: componentId - }; - - let modal = this.modalCtrl.create('CoreViewerImagePage', params); + const params: any = { + title: title, + image: image, + component: component, + componentId: componentId + }, + modal = this.modalCtrl.create('CoreViewerImagePage', params); modal.present(); } @@ -890,7 +906,7 @@ export class CoreDomUtilsProvider { * @param {HTMLElement} el The element to wrap. * @param {HTMLElement} wrapper Wrapper. */ - wrapElement(el: HTMLElement, wrapper: HTMLElement) : void { + wrapElement(el: HTMLElement, wrapper: HTMLElement): void { // Insert the wrapper before the element. el.parentNode.insertBefore(wrapper, el); // Now move the element into the wrapper. diff --git a/src/providers/utils/mimetype.ts b/src/providers/utils/mimetype.ts index fce9e3cd0..af20c6303 100644 --- a/src/providers/utils/mimetype.ts +++ b/src/providers/utils/mimetype.ts @@ -53,7 +53,7 @@ export class CoreMimetypeUtilsProvider { * @param {string} extension Extension. * @return {boolean} Whether it can be embedded. */ - canBeEmbedded(extension: string) : boolean { + canBeEmbedded(extension: string): boolean { return this.isExtensionInGroup(extension, ['web_image', 'web_video', 'web_audio']); } @@ -63,13 +63,13 @@ export class CoreMimetypeUtilsProvider { * @param {string} extension Extension to clean. * @return {string} Clean extension. */ - cleanExtension(extension: string) : string { + cleanExtension(extension: string): string { if (!extension) { return extension; } // If the extension has parameters, remove them. - let position = extension.indexOf('?'); + const position = extension.indexOf('?'); if (position > -1) { extension = extension.substr(0, position); } @@ -90,12 +90,12 @@ export class CoreMimetypeUtilsProvider { * * @param {string} group Group name. */ - protected fillGroupMimeInfo(group: string) : void { - let mimetypes = {}, // Use an object to prevent duplicates. + protected fillGroupMimeInfo(group: string): void { + const mimetypes = {}, // Use an object to prevent duplicates. extensions = []; // Extensions are unique. - for (let extension in this.extToMime) { - let data = this.extToMime[extension]; + for (const extension in this.extToMime) { + const data = this.extToMime[extension]; if (data.type && data.groups && data.groups.indexOf(group) != -1) { // This extension has the group, add it to the list. mimetypes[data.type] = true; @@ -116,8 +116,8 @@ export class CoreMimetypeUtilsProvider { * @param {string} [url] URL of the file. It will be used if there's more than one possible extension. * @return {string} Extension. */ - getExtension(mimetype: string, url?: string) : string { - mimetype = mimetype || ''; + getExtension(mimetype: string, url?: string): string { + mimetype = mimetype || ''; mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any. if (mimetype == 'application/x-forcedownload' || mimetype == 'application/forcedownload') { @@ -125,15 +125,16 @@ export class CoreMimetypeUtilsProvider { return this.guessExtensionFromUrl(url); } - let extensions = this.mimeToExt[mimetype]; + const extensions = this.mimeToExt[mimetype]; if (extensions && extensions.length) { if (extensions.length > 1 && url) { // There's more than one possible extension. Check if the URL has extension. - let candidate = this.guessExtensionFromUrl(url); + const candidate = this.guessExtensionFromUrl(url); if (extensions.indexOf(candidate) != -1) { return candidate; } } + return extensions[0]; } } @@ -144,7 +145,7 @@ export class CoreMimetypeUtilsProvider { * @param {string} extension Extension. * @return {string} Type of the extension. */ - getExtensionType(extension: string) : string { + getExtensionType(extension: string): string { extension = this.cleanExtension(extension); if (this.extToMime[extension] && this.extToMime[extension].string) { @@ -158,9 +159,10 @@ export class CoreMimetypeUtilsProvider { * @param {string} mimetype Mimetype. * @return {string[]} Extensions. */ - getExtensions(mimetype: string) : string[] { - mimetype = mimetype || ''; + getExtensions(mimetype: string): string[] { + mimetype = mimetype || ''; mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any. + return this.mimeToExt[mimetype] || []; } @@ -170,15 +172,15 @@ export class CoreMimetypeUtilsProvider { * @param {string} The name of the file. * @return {string} The path to a file icon. */ - getFileIcon(filename: string) : string { - let ext = this.getFileExtension(filename), - icon = 'unknown'; + getFileIcon(filename: string): string { + const ext = this.getFileExtension(filename); + let icon = 'unknown'; if (ext && this.extToMime[ext]) { if (this.extToMime[ext].icon) { icon = this.extToMime[ext].icon; } else { - let type = this.extToMime[ext].type.split('/')[0]; + const type = this.extToMime[ext].type.split('/')[0]; if (type == 'video' || type == 'text' || type == 'image' || type == 'document' || type == 'audio') { icon = type; } @@ -193,7 +195,7 @@ export class CoreMimetypeUtilsProvider { * * @return {string} The path to a folder icon. */ - getFolderIcon() : string { + getFolderIcon(): string { return 'assets/img/files/folder-64.png'; } @@ -204,9 +206,9 @@ export class CoreMimetypeUtilsProvider { * @param {string} url The URL of the file. * @return {Promise} Promise resolved with the mimetype. */ - getMimeTypeFromUrl(url: string) : Promise { + getMimeTypeFromUrl(url: string): Promise { // First check if it can be guessed from the URL. - let extension = this.guessExtensionFromUrl(url), + const extension = this.guessExtensionFromUrl(url), mimetype = this.getMimeType(extension); if (mimetype) { @@ -215,7 +217,7 @@ export class CoreMimetypeUtilsProvider { // Can't be guessed, get the remote mimetype. return this.wsProvider.getRemoteFileMimeType(url).then((mimetype) => { - return mimetype || ''; + return mimetype || ''; }); } @@ -226,9 +228,9 @@ export class CoreMimetypeUtilsProvider { * @param {string} fileUrl The file URL. * @return {string} The lowercased extension without the dot, or undefined. */ - guessExtensionFromUrl(fileUrl: string) : string { - let split = fileUrl.split('.'), - candidate, + guessExtensionFromUrl(fileUrl: string): string { + const split = fileUrl.split('.'); + let candidate, extension, position; @@ -248,6 +250,7 @@ export class CoreMimetypeUtilsProvider { // Check extension corresponds to a mimetype to know if it's valid. if (extension && typeof this.getMimeType(extension) == 'undefined') { this.logger.warn('Guess file extension: Not valid extension ' + extension); + return; } @@ -261,9 +264,9 @@ export class CoreMimetypeUtilsProvider { * @param {string} filename The file name. * @return {string} The lowercased extension, or undefined. */ - getFileExtension(filename: string) : string { - let dot = filename.lastIndexOf("."), - ext; + getFileExtension(filename: string): string { + const dot = filename.lastIndexOf('.'); + let ext; if (dot > -1) { ext = filename.substr(dot + 1).toLowerCase(); @@ -272,6 +275,7 @@ export class CoreMimetypeUtilsProvider { // Check extension corresponds to a mimetype to know if it's valid. if (typeof this.getMimeType(ext) == 'undefined') { this.logger.warn('Get file extension: Not valid extension ' + ext); + return; } } @@ -286,7 +290,7 @@ export class CoreMimetypeUtilsProvider { * @param {string} [field] The field to get. If not supplied, all the info will be returned. * @return {any} Info for the group. */ - getGroupMimeInfo(group: string, field?: string) : any { + getGroupMimeInfo(group: string, field?: string): any { if (typeof this.groupsMimeInfo[group] == 'undefined') { this.fillGroupMimeInfo(group); } @@ -294,6 +298,7 @@ export class CoreMimetypeUtilsProvider { if (field) { return this.groupsMimeInfo[group][field]; } + return this.groupsMimeInfo[group]; } @@ -303,7 +308,7 @@ export class CoreMimetypeUtilsProvider { * @param {string} extension Extension. * @return {string} Mimetype. */ - getMimeType(extension: string) : string { + getMimeType(extension: string): string { extension = this.cleanExtension(extension); if (this.extToMime[extension] && this.extToMime[extension].type) { @@ -319,18 +324,18 @@ export class CoreMimetypeUtilsProvider { * @param {boolean} [capitalise] If true, capitalises first character of result. * @return {string} Type description. */ - getMimetypeDescription(obj: any, capitalise?: boolean) : string { + getMimetypeDescription(obj: any, capitalise?: boolean): string { + const langPrefix = 'assets.mimetypes.'; let filename = '', mimetype = '', - extension = '', - langPrefix = 'assets.mimetypes.'; + extension = ''; if (typeof obj == 'object' && typeof obj.file == 'function') { // It's a FileEntry. Don't use the file function because it's asynchronous and the type isn't reliable. filename = obj.name; } else if (typeof obj == 'object') { - filename = obj.filename || ''; - mimetype = obj.mimetype || ''; + filename = obj.filename || ''; + mimetype = obj.mimetype || ''; } else { mimetype = obj; } @@ -353,30 +358,30 @@ export class CoreMimetypeUtilsProvider { extension = this.getExtension(mimetype); } - let mimetypeStr = this.getMimetypeType(mimetype) || '', + const mimetypeStr = this.getMimetypeType(mimetype) || '', chunks = mimetype.split('/'), attr = { mimetype: mimetype, ext: extension || '', mimetype1: chunks[0], - mimetype2: chunks[1] || '', + mimetype2: chunks[1] || '', }, translateParams = {}; - for (let key in attr) { - let value = attr[key]; + for (const key in attr) { + const value = attr[key]; translateParams[key] = value; translateParams[key.toUpperCase()] = value.toUpperCase(); translateParams[this.textUtils.ucFirst(key)] = this.textUtils.ucFirst(value); } // MIME types may include + symbol but this is not permitted in string ids. - let safeMimetype = mimetype.replace(/\+/g, '_'), + const safeMimetype = mimetype.replace(/\+/g, '_'), safeMimetypeStr = mimetypeStr.replace(/\+/g, '_'), - safeMimetypeTrns = this.translate.instant(langPrefix + safeMimetype, {$a: translateParams}), - safeMimetypeStrTrns = this.translate.instant(langPrefix + safeMimetypeStr, {$a: translateParams}), - defaultTrns = this.translate.instant(langPrefix + 'default', {$a: translateParams}), - result = mimetype; + safeMimetypeTrns = this.translate.instant(langPrefix + safeMimetype, { $a: translateParams }), + safeMimetypeStrTrns = this.translate.instant(langPrefix + safeMimetypeStr, { $a: translateParams }), + defaultTrns = this.translate.instant(langPrefix + 'default', { $a: translateParams }); + let result = mimetype; if (safeMimetypeTrns != langPrefix + safeMimetype) { result = safeMimetypeTrns; @@ -399,16 +404,16 @@ export class CoreMimetypeUtilsProvider { * @param {string} mimetype Mimetype. * @return {string} Type of the mimetype. */ - getMimetypeType(mimetype: string) : string { + getMimetypeType(mimetype: string): string { mimetype = mimetype.split(';')[0]; // Remove codecs from the mimetype if any. - let extensions = this.mimeToExt[mimetype]; + const extensions = this.mimeToExt[mimetype]; if (!extensions) { return; } for (let i = 0; i < extensions.length; i++) { - let extension = extensions[i]; + const extension = extensions[i]; if (this.extToMime[extension] && this.extToMime[extension].string) { return this.extToMime[extension].string; } @@ -421,9 +426,10 @@ export class CoreMimetypeUtilsProvider { * @param {string} name Group name. * @return {string} Translated name. */ - getTranslatedGroupName(name: string) : string { - let key = 'assets.mimetypes.group:' + name, + getTranslatedGroupName(name: string): string { + const key = 'assets.mimetypes.group:' + name, translated = this.translate.instant(key); + return translated != key ? translated : name; } @@ -435,17 +441,18 @@ export class CoreMimetypeUtilsProvider { * @param {string[]} groups List of groups to check. * @return {boolean} Whether the extension belongs to any of the groups. */ - isExtensionInGroup(extension: string, groups: string[]) : boolean { + isExtensionInGroup(extension: string, groups: string[]): boolean { extension = this.cleanExtension(extension); if (groups && groups.length && this.extToMime[extension] && this.extToMime[extension].groups) { for (let i = 0; i < this.extToMime[extension].groups.length; i++) { - let group = this.extToMime[extension].groups[i]; + const group = this.extToMime[extension].groups[i]; if (groups.indexOf(group) != -1) { return true; } } } + return false; } @@ -455,9 +462,9 @@ export class CoreMimetypeUtilsProvider { * @param {string} path Path. * @return {string} Path without extension. */ - removeExtension(path: string) : string { - let extension, - position = path.lastIndexOf('.'); + removeExtension(path: string): string { + const position = path.lastIndexOf('.'); + let extension; if (position > -1) { // Check extension corresponds to a mimetype to know if it's valid. @@ -466,6 +473,7 @@ export class CoreMimetypeUtilsProvider { return path.substr(0, position); // Remove extension. } } + return path; } } diff --git a/src/providers/utils/text.ts b/src/providers/utils/text.ts index f11b31500..11191ddaa 100644 --- a/src/providers/utils/text.ts +++ b/src/providers/utils/text.ts @@ -24,7 +24,7 @@ import { CoreLangProvider } from '../lang'; export class CoreTextUtilsProvider { protected element = document.createElement('div'); // Fake element to use in some functions, to prevent creating it each time. - constructor(private translate: TranslateService, private langProvider: CoreLangProvider, private modalCtrl: ModalController) {} + constructor(private translate: TranslateService, private langProvider: CoreLangProvider, private modalCtrl: ModalController) { } /** * Given a list of sentences, build a message with all of them wrapped in

. @@ -32,13 +32,15 @@ export class CoreTextUtilsProvider { * @param {string[]} messages Messages to show. * @return {string} Message with all the messages. */ - buildMessage(messages: string[]) : string { + buildMessage(messages: string[]): string { let result = ''; + messages.forEach((message) => { if (message) { result += `

${message}

`; } }); + return result; } @@ -49,7 +51,7 @@ export class CoreTextUtilsProvider { * @param {number} [precision=2] Number of digits after the decimal separator. * @return {string} Size in human readable format. */ - bytesToSize(bytes: number, precision = 2) : string { + bytesToSize(bytes: number, precision: number = 2): string { if (typeof bytes == 'undefined' || bytes < 0) { return this.translate.instant('core.notapplicable'); @@ -59,9 +61,9 @@ export class CoreTextUtilsProvider { precision = 2; } - let keys = ['core.sizeb', 'core.sizekb', 'core.sizemb', 'core.sizegb', 'core.sizetb'], - units = this.translate.instant(keys), - pos = 0; + const keys = ['core.sizeb', 'core.sizekb', 'core.sizemb', 'core.sizegb', 'core.sizetb'], + units = this.translate.instant(keys); + let pos = 0; if (bytes >= 1024) { while (bytes >= 1024) { @@ -71,7 +73,8 @@ export class CoreTextUtilsProvider { // Round to "precision" decimals if needed. bytes = Number(Math.round(parseFloat(bytes + 'e+' + precision)) + 'e-' + precision); } - return this.translate.instant('core.humanreadablesize', {size: bytes, unit: units[keys[pos]]}); + + return this.translate.instant('core.humanreadablesize', { size: bytes, unit: units[keys[pos]] }); } /** @@ -81,18 +84,19 @@ export class CoreTextUtilsProvider { * @param {boolean} [singleLine] True if new lines should be removed (all the text in a single line). * @return {string} Clean text. */ - cleanTags(text: string, singleLine?: boolean) : string { + cleanTags(text: string, singleLine?: boolean): string { if (!text) { return ''; } // First, we use a regexpr. - text = text.replace(/(<([^>]+)>)/ig,""); + text = text.replace(/(<([^>]+)>)/ig, ''); // Then, we rely on the browser. We need to wrap the text to be sure is HTML. this.element.innerHTML = text; text = this.element.textContent; // Recover or remove new lines. text = this.replaceNewLines(text, singleLine ? ' ' : '
'); + return text; } @@ -103,19 +107,19 @@ export class CoreTextUtilsProvider { * @param {string} rightPath Right path. * @return {string} Concatenated path. */ - concatenatePaths(leftPath: string, rightPath: string) : string { + concatenatePaths(leftPath: string, rightPath: string): string { if (!leftPath) { return rightPath; } else if (!rightPath) { return leftPath; } - let lastCharLeft = leftPath.slice(-1), + const lastCharLeft = leftPath.slice(-1), firstCharRight = rightPath.charAt(0); if (lastCharLeft === '/' && firstCharRight === '/') { return leftPath + rightPath.substr(1); - } else if(lastCharLeft !== '/' && firstCharRight !== '/') { + } else if (lastCharLeft !== '/' && firstCharRight !== '/') { return leftPath + '/' + rightPath; } else { return leftPath + rightPath; @@ -128,14 +132,14 @@ export class CoreTextUtilsProvider { * @param {string} text Text to count. * @return {number} Number of words. */ - countWords(text: string) : number { + countWords(text: string): number { // Clean HTML scripts and tags. text = text.replace(/]*>([\S\s]*?)<\/script>/gmi, ''); text = text.replace(/<\/?(?!\!)[^>]*>/gi, ''); // Decode HTML entities. text = this.decodeHTMLEntities(text); // Replace underscores (which are classed as word characters) with spaces. - text = text.replace(/_/gi, " "); + text = text.replace(/_/gi, ' '); // This RegEx will detect any word change including Unicode chars. Some languages without spaces won't be counted fine. return text.match(/\S+/gi).length; @@ -147,7 +151,7 @@ export class CoreTextUtilsProvider { * @param {string|number} text Text to decode. * @return {string} Decoded text. */ - decodeHTML(text: string|number) : string { + decodeHTML(text: string | number): string { if (typeof text == 'undefined' || text === null || (typeof text == 'number' && isNaN(text))) { return ''; } else if (typeof text != 'string') { @@ -159,7 +163,7 @@ export class CoreTextUtilsProvider { .replace(/</g, '<') .replace(/>/g, '>') .replace(/"/g, '"') - .replace(/'/g, "'") + .replace(/'/g, '') .replace(/ /g, ' '); } @@ -169,7 +173,7 @@ export class CoreTextUtilsProvider { * @param {string} text Text to decode. * @return {string} Decoded text. */ - decodeHTMLEntities(text: string) : string { + decodeHTMLEntities(text: string): string { if (text) { this.element.innerHTML = text; text = this.element.textContent; @@ -185,12 +189,13 @@ export class CoreTextUtilsProvider { * @param {string} uri URI to decode. * @return {string} Decoded URI, or original URI if an exception is thrown. */ - decodeURI(uri: string) : string { + decodeURI(uri: string): string { try { return decodeURI(uri); - } catch(ex) { + } catch (ex) { // Error, use the original URI. } + return uri; } @@ -200,12 +205,13 @@ export class CoreTextUtilsProvider { * @param {string} uri URI to decode. * @return {string} Decoded URI, or original URI if an exception is thrown. */ - decodeURIComponent(uri: string) : string { + decodeURIComponent(uri: string): string { try { return decodeURIComponent(uri); - } catch(ex) { + } catch (ex) { // Error, use the original URI. } + return uri; } @@ -215,10 +221,11 @@ export class CoreTextUtilsProvider { * @param {string} text Text to escape. * @return {string} Escaped text. */ - escapeForRegex(text: string) : string { + escapeForRegex(text: string): string { if (!text) { return ''; } + return text.replace(/[-[\]{}()*+?.,\\^$|#\s]/g, '\\$&'); } @@ -228,7 +235,7 @@ export class CoreTextUtilsProvider { * @param {string|number} text Text to escape. * @return {string} Escaped text. */ - escapeHTML(text: string|number) : string { + escapeHTML(text: string | number): string { if (typeof text == 'undefined' || text === null || (typeof text == 'number' && isNaN(text))) { return ''; } else if (typeof text != 'string') { @@ -251,9 +258,9 @@ export class CoreTextUtilsProvider { * @param {string} [component] Component to link the embedded files to. * @param {string|number} [componentId] An ID to use in conjunction with the component. */ - expandText(title: string, text: string, component?: string, componentId?: string|number) : void { + expandText(title: string, text: string, component?: string, componentId?: string | number): void { if (text.length > 0) { - let params: any = { + const params: any = { title: title, content: text, component: component, @@ -263,7 +270,7 @@ export class CoreTextUtilsProvider { // Open a modal with the contents. params.isModal = true; - let modal = this.modalCtrl.create('CoreViewerTextPage', params); + const modal = this.modalCtrl.create('CoreViewerTextPage', params); modal.present(); } } @@ -274,8 +281,8 @@ export class CoreTextUtilsProvider { * @param {string} text Text to format. * @return {string} Formatted text. */ - formatHtmlLines(text: string) : string { - let hasHTMLTags = this.hasHTMLTags(text); + formatHtmlLines(text: string): string { + const hasHTMLTags = this.hasHTMLTags(text); if (text.indexOf('

') == -1) { // Wrap the text in

tags. text = '

' + text + '

'; @@ -298,7 +305,7 @@ export class CoreTextUtilsProvider { * @param {number} [shortenLength] Number of characters to shorten the text. * @return {Promise} Promise resolved with the formatted text. */ - formatText(text: string, clean?: boolean, singleLine?: boolean, shortenLength?: number) : Promise { + formatText(text: string, clean?: boolean, singleLine?: boolean, shortenLength?: number): Promise { return this.treatMultilangTags(text).then((formatted) => { if (clean) { formatted = this.cleanTags(formatted, singleLine); @@ -306,6 +313,7 @@ export class CoreTextUtilsProvider { if (shortenLength > 0) { formatted = this.shortenText(formatted, shortenLength); } + return formatted; }); } @@ -316,9 +324,10 @@ export class CoreTextUtilsProvider { * @param {any[]} files Files to extract the URL from. They need to have the URL in a 'url' or 'fileurl' attribute. * @return {string} Pluginfile URL, undefined if no files found. */ - getTextPluginfileUrl(files: any[]) : string { + getTextPluginfileUrl(files: any[]): string { if (files && files.length) { - let fileURL = files[0].url || files[0].fileurl; + const fileURL = files[0].url || files[0].fileurl; + // Remove text after last slash (encoded or not). return fileURL.substr(0, Math.max(fileURL.lastIndexOf('/'), fileURL.lastIndexOf('%2F'))); } @@ -332,7 +341,7 @@ export class CoreTextUtilsProvider { * @param {string} text Text to check. * @return {boolean} Whether it has HTML tags. */ - hasHTMLTags(text: string) : boolean { + hasHTMLTags(text: string): boolean { return /<[a-z][\s\S]*>/i.test(text); } @@ -343,12 +352,13 @@ export class CoreTextUtilsProvider { * @param {string} text Text to check. * @return {boolean} True if has Unicode chars, false otherwise. */ - hasUnicode(text: string) : boolean { + hasUnicode(text: string): boolean { for (let x = 0; x < text.length; x++) { if (text.charCodeAt(x) > 55295) { return true; } } + return false; } @@ -358,8 +368,8 @@ export class CoreTextUtilsProvider { * @param {object} data Object to be checked. * @return {boolean} If the data has any long Unicode char on it. */ - hasUnicodeData(data: object) : boolean { - for (let el in data) { + hasUnicodeData(data: object): boolean { + for (const el in data) { if (typeof data[el] == 'object') { if (this.hasUnicodeData(data[el])) { return true; @@ -368,6 +378,7 @@ export class CoreTextUtilsProvider { return true; } } + return false; } @@ -377,12 +388,13 @@ export class CoreTextUtilsProvider { * @param {string} json JSON text. * @return {any} JSON parsed as object or what it gets. */ - parseJSON(json: string) : any { + parseJSON(json: string): any { try { return JSON.parse(json); - } catch(ex) { + } catch (ex) { // Error, use the json text. } + return json; } @@ -392,7 +404,7 @@ export class CoreTextUtilsProvider { * @param {string} text Text to treat. * @return {string} Treated text. */ - removeSpecialCharactersForFiles(text: string) : string { + removeSpecialCharactersForFiles(text: string): string { return text.replace(/[#:\/\?\\]+/g, '_'); } @@ -403,7 +415,7 @@ export class CoreTextUtilsProvider { * @param {string} newValue Text to use instead of new lines. * @return {string} Treated text. */ - replaceNewLines(text: string, newValue: string) : string { + replaceNewLines(text: string, newValue: string): string { return text.replace(/(?:\r\n|\r|\n)/g, newValue); } @@ -414,13 +426,14 @@ export class CoreTextUtilsProvider { * @param {any[]} files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. * @return {string} Treated text. */ - replacePluginfileUrls(text: string, files: any[]) : string { + replacePluginfileUrls(text: string, files: any[]): string { if (text) { - let fileURL = this.getTextPluginfileUrl(files); + const fileURL = this.getTextPluginfileUrl(files); if (fileURL) { return text.replace(/@@PLUGINFILE@@/g, fileURL); } } + return text; } @@ -431,13 +444,14 @@ export class CoreTextUtilsProvider { * @param {any[]} files Files to extract the pluginfile URL from. They need to have the URL in a url or fileurl attribute. * @return {string} Treated text. */ - restorePluginfileUrls(text: string, files: any[]) : string { + restorePluginfileUrls(text: string, files: any[]): string { if (text) { - let fileURL = this.getTextPluginfileUrl(files); + const fileURL = this.getTextPluginfileUrl(files); if (fileURL) { return text.replace(new RegExp(this.escapeForRegex(fileURL), 'g'), '@@PLUGINFILE@@'); } } + return text; } @@ -447,13 +461,14 @@ export class CoreTextUtilsProvider { * 7.toFixed(2) -> 7.00 * roundToDecimals(7, 2) -> 7 * - * @param {number} number Number to round. + * @param {number} num Number to round. * @param {number} [decimals=2] Number of decimals. By default, 2. * @return {number} Rounded number. */ - roundToDecimals(number: number, decimals = 2) : number { - let multiplier = Math.pow(10, decimals); - return Math.round(number * multiplier) / multiplier; + roundToDecimals(num: number, decimals: number = 2): number { + const multiplier = Math.pow(10, decimals); + + return Math.round(num * multiplier) / multiplier; } /** @@ -465,7 +480,7 @@ export class CoreTextUtilsProvider { * @param {string} text Text to treat. * @return {string} Treated text. */ - s(text: string) : string { + s(text: string): string { if (!text) { return ''; } @@ -480,17 +495,18 @@ export class CoreTextUtilsProvider { * @param {number} length The desired length. * @return {string} Shortened text. */ - shortenText(text: string, length: number) : string { + shortenText(text: string, length: number): string { if (text.length > length) { text = text.substr(0, length); // Now, truncate at the last word boundary (if exists). - let lastWordPos = text.lastIndexOf(' '); + const lastWordPos = text.lastIndexOf(' '); if (lastWordPos > 0) { text = text.substr(0, lastWordPos); } text += '…'; } + return text; } @@ -501,13 +517,14 @@ export class CoreTextUtilsProvider { * @param {string} text Text to check. * @return {string} Without the Unicode chars. */ - stripUnicode(text: string) : string { + stripUnicode(text: string): string { let stripped = ''; for (let x = 0; x < text.length; x++) { - if (text.charCodeAt(x) <= 55295){ + if (text.charCodeAt(x) <= 55295) { stripped += text.charAt(x); } } + return stripped; } @@ -517,19 +534,19 @@ export class CoreTextUtilsProvider { * @param {string} text The text to be treated. * @return {Promise} Promise resolved with the formatted text. */ - treatMultilangTags(text: string) : Promise { + treatMultilangTags(text: string): Promise { if (!text) { return Promise.resolve(''); } return this.langProvider.getCurrentLanguage().then((language) => { // Match the current language. - let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'), - anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; + const anyLangRegEx = /<(?:lang|span)[^>]+lang="[a-zA-Z0-9_-]+"[^>]*>(.*?)<\/(?:lang|span)>/g; + let currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); if (!text.match(currentLangRegEx)) { // Current lang not found. Try to find the first language. - let matches = text.match(anyLangRegEx); + const matches = text.match(anyLangRegEx); if (matches && matches[0]) { language = matches[0].match(/lang="([a-zA-Z0-9_-]+)"/)[1]; currentLangRegEx = new RegExp('<(?:lang|span)[^>]+lang="' + language + '"[^>]*>(.*?)<\/(?:lang|span)>', 'g'); @@ -542,6 +559,7 @@ export class CoreTextUtilsProvider { text = text.replace(currentLangRegEx, '$1'); // Delete the rest of languages text = text.replace(anyLangRegEx, ''); + return text; }); } @@ -552,7 +570,7 @@ export class CoreTextUtilsProvider { * @param {string|number} num Number to convert. * @return {string} Number with leading zeros. */ - twoDigits(num: string|number) : string { + twoDigits(num: string | number): string { if (num < 10) { return '0' + num; } else { @@ -566,7 +584,7 @@ export class CoreTextUtilsProvider { * @param {string} text Text to treat. * @return {string} Treated text. */ - ucFirst(text: string) : string { + ucFirst(text: string): string { return text.charAt(0).toUpperCase() + text.slice(1); } } diff --git a/src/providers/utils/time.ts b/src/providers/utils/time.ts index 69d62f5f0..2cdd4e771 100644 --- a/src/providers/utils/time.ts +++ b/src/providers/utils/time.ts @@ -23,7 +23,7 @@ import { CoreConstants } from '../../core/constants'; @Injectable() export class CoreTimeUtilsProvider { - constructor(private translate: TranslateService) {} + constructor(private translate: TranslateService) { } /** * Returns hours, minutes and seconds in a human readable format @@ -31,44 +31,53 @@ export class CoreTimeUtilsProvider { * @param {number} seconds A number of seconds * @return {string} Seconds in a human readable format. */ - formatTime(seconds: number) : string { - let totalSecs = Math.abs(seconds), - years = Math.floor(totalSecs / CoreConstants.SECONDS_YEAR), - remainder = totalSecs - (years * CoreConstants.SECONDS_YEAR), - days = Math.floor(remainder / CoreConstants.SECONDS_DAY); + formatTime(seconds: number): string { + let totalSecs, + years, + days, + hours, + mins, + secs, + remainder; + + totalSecs = Math.abs(seconds); + years = Math.floor(totalSecs / CoreConstants.SECONDS_YEAR); + remainder = totalSecs - (years * CoreConstants.SECONDS_YEAR); + days = Math.floor(remainder / CoreConstants.SECONDS_DAY); remainder = totalSecs - (days * CoreConstants.SECONDS_DAY); - let hours = Math.floor(remainder / CoreConstants.SECONDS_HOUR); + hours = Math.floor(remainder / CoreConstants.SECONDS_HOUR); remainder = remainder - (hours * CoreConstants.SECONDS_HOUR); - let mins = Math.floor(remainder / CoreConstants.SECONDS_MINUTE), - secs = remainder - (mins * CoreConstants.SECONDS_MINUTE), - ss = this.translate.instant('core.' + (secs == 1 ? 'sec' : 'secs')), + mins = Math.floor(remainder / CoreConstants.SECONDS_MINUTE); + secs = remainder - (mins * CoreConstants.SECONDS_MINUTE); + + const ss = this.translate.instant('core.' + (secs == 1 ? 'sec' : 'secs')), sm = this.translate.instant('core.' + (mins == 1 ? 'min' : 'mins')), sh = this.translate.instant('core.' + (hours == 1 ? 'hour' : 'hours')), sd = this.translate.instant('core.' + (days == 1 ? 'day' : 'days')), - sy = this.translate.instant('core.' + (years == 1 ? 'year' : 'years')), - oyears = '', + sy = this.translate.instant('core.' + (years == 1 ? 'year' : 'years')); + let oyears = '', odays = '', ohours = '', omins = '', osecs = ''; if (years) { - oyears = years + ' ' + sy; + oyears = years + ' ' + sy; } if (days) { - odays = days + ' ' + sd; + odays = days + ' ' + sd; } if (hours) { ohours = hours + ' ' + sh; } if (mins) { - omins = mins + ' ' + sm; + omins = mins + ' ' + sm; } if (secs) { - osecs = secs + ' ' + ss; + osecs = secs + ' ' + ss; } if (years) { @@ -97,11 +106,11 @@ export class CoreTimeUtilsProvider { * @param {number} [precision] Number of elements to have in precission. 0 or undefined to full precission. * @return {string} Duration in a human readable format. */ - formatDuration(duration: number, precision?: number) : string { + formatDuration(duration: number, precision?: number): string { precision = precision || 5; - let eventDuration = moment.duration(duration, 'seconds'), - durationString = ''; + const eventDuration = moment.duration(duration, 'seconds'); + let durationString = ''; if (precision && eventDuration.years() > 0) { durationString += ' ' + moment.duration(eventDuration.years(), 'years').humanize(); @@ -132,7 +141,7 @@ export class CoreTimeUtilsProvider { * * @return {string} The readable timestamp. */ - readableTimestamp() : string { + readableTimestamp(): string { return moment(Date.now()).format('YYYYMMDDHHmmSS'); } @@ -141,16 +150,17 @@ export class CoreTimeUtilsProvider { * * @return {number} The current timestamp in seconds. */ - timestamp() : number { + timestamp(): number { return Math.round(Date.now() / 1000); } /** * Return the localized ISO format (i.e DDMMYY) from the localized moment format. Useful for translations. * + * @param {any} localizedFormat Format to use. * @return {string} Localized ISO format */ - getLocalizedDateFormat(lozalizedFormat) : string { - return moment.localeData().longDateFormat(lozalizedFormat); + getLocalizedDateFormat(localizedFormat: any): string { + return moment.localeData().longDateFormat(localizedFormat); } } diff --git a/src/providers/utils/url.ts b/src/providers/utils/url.ts index 87a64d398..d3869b1e0 100644 --- a/src/providers/utils/url.ts +++ b/src/providers/utils/url.ts @@ -21,7 +21,7 @@ import { CoreLangProvider } from '../lang'; @Injectable() export class CoreUrlUtilsProvider { - constructor(private langProvider: CoreLangProvider) {} + constructor(private langProvider: CoreLangProvider) { } /** * Add or remove 'www' from a URL. The url needs to have http or https protocol. @@ -29,7 +29,7 @@ export class CoreUrlUtilsProvider { * @param {string} url URL to modify. * @return {string} Modified URL. */ - addOrRemoveWWW(url: string) : string { + addOrRemoveWWW(url: string): string { if (url) { if (url.match(/http(s)?:\/\/www\./)) { // Already has www. Remove it. @@ -39,6 +39,7 @@ export class CoreUrlUtilsProvider { url = url.replace('http://', 'http://www.'); } } + return url; } @@ -48,12 +49,13 @@ export class CoreUrlUtilsProvider { * @param {string} url URL to treat. * @return {any} Object with the params. */ - extractUrlParams(url: string) : any { - let regex = /[?&]+([^=&]+)=?([^&]*)?/gi, + extractUrlParams(url: string): any { + const regex = /[?&]+([^=&]+)=?([^&]*)?/gi, params = {}; - url.replace(regex, (match: string, key: string, value: string) : string => { + url.replace(regex, (match: string, key: string, value: string): string => { params[key] = typeof value != 'undefined' ? value : ''; + return match; }); @@ -69,7 +71,7 @@ export class CoreUrlUtilsProvider { * @param {string} token Token to use. * @return {string} Fixed URL. */ - fixPluginfileURL(url: string, token: string) : string { + fixPluginfileURL(url: string, token: string): string { if (!url || !token) { return ''; } @@ -97,6 +99,7 @@ export class CoreUrlUtilsProvider { if (url.indexOf('/webservice/pluginfile') == -1) { url = url.replace('/pluginfile', '/webservice/pluginfile'); } + return url; } @@ -106,7 +109,7 @@ export class CoreUrlUtilsProvider { * @param {string} url The url to be formatted. * @return {string} Fromatted url. */ - formatURL(url: string) : string { + formatURL(url: string): string { url = url.trim(); // Check if the URL starts by http or https. @@ -120,7 +123,7 @@ export class CoreUrlUtilsProvider { url = url.replace(/^https/i, 'https'); // Replace last slash. - url = url.replace(/\/$/, ""); + url = url.replace(/\/$/, ''); return url; } @@ -132,11 +135,11 @@ export class CoreUrlUtilsProvider { * @param {string} [page=Mobile_app] Docs page to go to. * @return {Promise} Promise resolved with the Moodle docs URL. */ - getDocsUrl(release?: string, page = 'Mobile_app') : Promise { + getDocsUrl(release?: string, page: string = 'Mobile_app'): Promise { let docsUrl = 'https://docs.moodle.org/en/' + page; if (typeof release != 'undefined') { - let version = release.substr(0, 3).replace('.', ''); + const version = release.substr(0, 3).replace('.', ''); // Check is a valid number. if (parseInt(version) >= 24) { // Append release number. @@ -159,11 +162,12 @@ export class CoreUrlUtilsProvider { * @param {string} url URL to treat. * @return {string} Last file without params. */ - getLastFileWithoutParams(url: string) : string { + getLastFileWithoutParams(url: string): string { let filename = url.substr(url.lastIndexOf('/') + 1); if (filename.indexOf('?') != -1) { filename = filename.substr(0, filename.indexOf('?')); } + return filename; } @@ -174,12 +178,12 @@ export class CoreUrlUtilsProvider { * @param {string} url URL to treat. * @return {string} Protocol, undefined if no protocol found. */ - getUrlProtocol(url: string) : string { + getUrlProtocol(url: string): string { if (!url) { return; } - let matches = url.match(/^([^\/:\.\?]*):\/\//); + const matches = url.match(/^([^\/:\.\?]*):\/\//); if (matches && matches[1]) { return matches[1]; } @@ -192,12 +196,12 @@ export class CoreUrlUtilsProvider { * @param {string} url URL to treat. * @return {string} Scheme, undefined if no scheme found. */ - getUrlScheme(url: string) : string { + getUrlScheme(url: string): string { if (!url) { return; } - let matches = url.match(/^([a-z][a-z0-9+\-.]*):/); + const matches = url.match(/^([a-z][a-z0-9+\-.]*):/); if (matches && matches[1]) { return matches[1]; } @@ -209,10 +213,10 @@ export class CoreUrlUtilsProvider { * @param {string} url URL to treat. * @return {string} Username. Undefined if no username found. */ - getUsernameFromUrl(url: string) : string { + getUsernameFromUrl(url: string): string { if (url.indexOf('@') > -1) { // Get URL without protocol. - let withoutProtocol = url.replace(/.*?:\/\//, ''), + const withoutProtocol = url.replace(/.*?:\/\//, ''), matches = withoutProtocol.match(/[^@]*/); // Make sure that @ is at the start of the URL, not in a param at the end. @@ -228,8 +232,8 @@ export class CoreUrlUtilsProvider { * @param {string} url The url to test against the pattern. * @return {boolean} Whether the url is absolute. */ - isAbsoluteURL(url: string) : boolean { - return /^[^:]{2,}:\/\//i.test(url) || /^(tel:|mailto:|geo:)/.test(url); + isAbsoluteURL(url: string): boolean { + return /^[^:]{2,}:\/\//i.test(url) || /^(tel:|mailto:|geo:)/.test(url); } /** @@ -238,7 +242,7 @@ export class CoreUrlUtilsProvider { * @param {string} url The URL to test. * @return {boolean} Whether the URL is downloadable. */ - isDownloadableUrl(url: string) : boolean { + isDownloadableUrl(url: string): boolean { return this.isPluginFileUrl(url) || this.isThemeImageUrl(url) || this.isGravatarUrl(url); } @@ -248,7 +252,7 @@ export class CoreUrlUtilsProvider { * @param {string} url The URL to test. * @return {boolean} Whether the URL is a gravatar URL. */ - isGravatarUrl(url: string) : boolean { + isGravatarUrl(url: string): boolean { return url && url.indexOf('gravatar.com/avatar') !== -1; } @@ -258,7 +262,7 @@ export class CoreUrlUtilsProvider { * @param {string} url The url to test. * @return {boolean} Whether the url uses http or https protocol. */ - isHttpURL(url: string) : boolean { + isHttpURL(url: string): boolean { return /^https?\:\/\/.+/i.test(url); } @@ -268,7 +272,7 @@ export class CoreUrlUtilsProvider { * @param {string} url The URL to test. * @return {boolean} Whether the URL is a pluginfile URL. */ - isPluginFileUrl(url: string) : boolean { + isPluginFileUrl(url: string): boolean { return url && url.indexOf('/pluginfile.php') !== -1; } @@ -278,7 +282,7 @@ export class CoreUrlUtilsProvider { * @param {string} url The URL to test. * @return {boolean} Whether the URL is a theme image URL. */ - isThemeImageUrl(url: string) : boolean { + isThemeImageUrl(url: string): boolean { return url && url.indexOf('/theme/image.php') !== -1; } @@ -288,11 +292,12 @@ export class CoreUrlUtilsProvider { * @param {string} url URL to treat. * @return {string} Treated URL. */ - removeProtocolAndWWW(url: string) : string { + removeProtocolAndWWW(url: string): string { // Remove protocol. url = url.replace(/.*?:\/\//g, ''); // Remove www. url = url.replace(/^www./, ''); + return url; } @@ -302,8 +307,9 @@ export class CoreUrlUtilsProvider { * @param {string} url URL to treat. * @return {string} URL without params. */ - removeUrlParams(url: string) : string { - let matches = url.match(/^[^\?]+/); + removeUrlParams(url: string): string { + const matches = url.match(/^[^\?]+/); + return matches && matches[0]; } } diff --git a/src/providers/utils/utils.ts b/src/providers/utils/utils.ts index b96d29995..87b800364 100644 --- a/src/providers/utils/utils.ts +++ b/src/providers/utils/utils.ts @@ -74,9 +74,9 @@ export class CoreUtilsProvider { return Promise.resolve(); } - return new Promise((resolve, reject) => { + return new Promise((resolve, reject): void => { + const total = promises.length; let count = 0, - total = promises.length, error; promises.forEach((promise) => { @@ -107,11 +107,12 @@ export class CoreUtilsProvider { * @param {any} [result] Object where to put the properties. If not defined, a new object will be created. * @return {any} The object. */ - arrayToObject(array: any[], propertyName: string, result?: any) : any { + arrayToObject(array: any[], propertyName: string, result?: any): any { result = result || {}; array.forEach((entry) => { result[entry[propertyName]] = entry; }); + return result; } @@ -127,7 +128,7 @@ export class CoreUtilsProvider { * @param {boolean} [undefinedIsNull=true] True if undefined is equal to null. Defaults to true. * @return {boolean} Whether both items are equal. */ - basicLeftCompare(itemA: any, itemB: any, maxLevels = 0, level = 0, undefinedIsNull = true) : boolean { + basicLeftCompare(itemA: any, itemB: any, maxLevels: number = 0, level: number = 0, undefinedIsNull: boolean = true): boolean { if (typeof itemA == 'function' || typeof itemB == 'function') { return true; // Don't compare functions. } else if (typeof itemA == 'object' && typeof itemB == 'object') { @@ -136,8 +137,8 @@ export class CoreUtilsProvider { } let equal = true; - for (let name in itemA) { - let value = itemA[name]; + for (const name in itemA) { + const value = itemA[name]; if (name == '$$hashKey') { // Ignore $$hashKey property since it's a "calculated" property. return; @@ -151,134 +152,27 @@ export class CoreUtilsProvider { return equal; } else { if (undefinedIsNull && ( - (typeof itemA == 'undefined' && itemB === null) || (itemA === null && typeof itemB == 'undefined'))) { + (typeof itemA == 'undefined' && itemB === null) || (itemA === null && typeof itemB == 'undefined'))) { return true; } // We'll treat "2" and 2 as the same value. - let floatA = parseFloat(itemA), + const floatA = parseFloat(itemA), floatB = parseFloat(itemB); if (!isNaN(floatA) && !isNaN(floatB)) { return floatA == floatB; } + return itemA === itemB; } } /** - * Blocks leaving a view. This function should be used in views that want to perform a certain action before - * leaving (usually, ask the user if he wants to leave because some data isn't saved). - * - * @param {object} scope View's scope. - * @param {Function} canLeaveFn Function called when the user wants to leave the view. Must return a promise - * resolved if the view should be left, rejected if the user should stay in the view. - * @param {object} [currentView] Current view. Defaults to $ionicHistory.currentView(). - * @return {object} Object with: - * -back: Original back function. - * -unblock: Function to unblock. It is called automatically when scope is destroyed. + * Blocks leaving a view. */ - blockLeaveView = function(scope, canLeaveFn, currentView) { + blockLeaveView(): void { // @todo - // currentView = currentView || $ionicHistory.currentView(); - - // var unregisterHardwareBack, - // leaving = false, - // hasSplitView = $ionicPlatform.isTablet() && $state.current.name.split('.').length == 3, - // skipSplitViewLeave = false; - - // // Override Ionic's back button behavior. - // $rootScope.$ionicGoBack = goBack; - - // // Override Android's back button. We set a priority of 101 to override the "Return to previous view" action. - // unregisterHardwareBack = $ionicPlatform.registerBackButtonAction(goBack, 101); - - // // Add function to the stack. - // backFunctionsStack.push(goBack); - - // if (hasSplitView) { - // // Block split view. - // blockSplitView(true); - // } - - // scope.$on('$destroy', unblock); - - // return { - // back: originalBackFunction, - // unblock: unblock - // }; - - // // Function called when the user wants to leave the view. - // function goBack() { - // // Check that we're leaving the current view, since the user can navigate to other views from here. - // if ($ionicHistory.currentView() !== currentView) { - // // It's another view. - // originalBackFunction(); - // return; - // } - - // if (leaving) { - // // Leave view pending, don't call again. - // return; - // } - // leaving = true; - - // canLeaveFn().then(function() { - // // User confirmed to leave or there was no need to confirm, go back. - // // Skip next leave view from split view if there's one since we already checked if user can leave. - // skipSplitViewLeave = hasSplitView; - // originalBackFunction(); - // }).finally(function() { - // leaving = false; - // }); - // } - - // // Leaving current view when it's in split view. - // function leaveViewInSplitView() { - // if (skipSplitViewLeave) { - // skipSplitViewLeave = false; - // return $q.when(); - // } - - // return canLeaveFn(); - // } - - // // Restore original back functions. - // function unblock() { - // unregisterHardwareBack(); - - // if (hasSplitView) { - // // Unblock split view. - // blockSplitView(false); - // } - - // // Remove function from the stack. - // var position = backFunctionsStack.indexOf(goBack); - // if (position > -1) { - // backFunctionsStack.splice(position, 1); - // } - - // // Revert go back only if it hasn't been overridden by another view. - // if ($rootScope.$ionicGoBack === goBack) { - // if (!backFunctionsStack.length) { - // // Shouldn't happen. Reset stack. - // backFunctionsStack = [originalBackFunction]; - // $rootScope.$ionicGoBack = originalBackFunction; - // } else { - // $rootScope.$ionicGoBack = backFunctionsStack[backFunctionsStack.length - 1]; - // } - // } - // } - - // // Block or unblock split view. - // function blockSplitView(block) { - // $rootScope.$broadcast(mmCoreSplitViewBlock, { - // block: block, - // blockFunction: leaveViewInSplitView, - // state: currentView.stateName, - // stateParams: currentView.stateParams - // }); - // } } /** @@ -286,7 +180,7 @@ export class CoreUtilsProvider { * * @param {boolean} [closeAll] Desktop only. True to close all secondary windows, false to close only the "current" one. */ - closeInAppBrowser(closeAll?: boolean) : void { + closeInAppBrowser(closeAll?: boolean): void { if (this.iabInstance) { this.iabInstance.close(); if (closeAll && this.appProvider.isDesktop()) { @@ -301,20 +195,22 @@ export class CoreUtilsProvider { * @param {any} source The variable to clone. * @return {any} Cloned variable. */ - clone(source: any) : any { + clone(source: any): any { if (Array.isArray(source)) { // Clone the array and all the entries. - let newArray = []; + const newArray = []; for (let i = 0; i < source.length; i++) { newArray[i] = this.clone(source[i]); } + return newArray; } else if (typeof source == 'object') { // Clone the object and all the subproperties. - let newObject = {}; - for (let name in source) { + const newObject = {}; + for (const name in source) { newObject[name] = this.clone(source[name]); } + return newObject; } else { // Primitive type or unknown, return it as it is. @@ -329,8 +225,8 @@ export class CoreUtilsProvider { * @param {any} to Object where to store the properties. * @param {boolean} [clone=true] Whether the properties should be cloned (so they are different instances). */ - copyProperties(from: any, to: any, clone = true) : void { - for (let name in from) { + copyProperties(from: any, to: any, clone: boolean = true): void { + for (const name in from) { if (clone) { to[name] = this.clone(from[name]); } else { @@ -345,7 +241,7 @@ export class CoreUtilsProvider { * @param {string} text Text to be copied * @return {Promise} Promise resolved when text is copied. */ - copyToClipboard(text: string) : Promise { + copyToClipboard(text: string): Promise { return this.clipboard.copy(text).then(() => { // Show toast using ionicLoading. return this.domUtils.showToast('core.copiedtoclipboard', true); @@ -359,7 +255,7 @@ export class CoreUtilsProvider { * * @param {any[]} array Array to empty. */ - emptyArray(array: any[]) : void { + emptyArray(array: any[]): void { array.length = 0; // Empty array without losing its reference. } @@ -368,8 +264,8 @@ export class CoreUtilsProvider { * * @param {object} object Object to remove the properties. */ - emptyObject(object: object) : void { - for (let key in object) { + emptyObject(object: object): void { + for (const key in object) { if (object.hasOwnProperty(key)) { delete object[key]; } @@ -386,14 +282,14 @@ export class CoreUtilsProvider { * - blocking: Boolean. If promise should block the following. * @return {Promise} Promise resolved when all promises are resolved. */ - executeOrderedPromises(orderedPromisesData: any[]) : Promise { - let promises = [], - dependency = Promise.resolve(); + executeOrderedPromises(orderedPromisesData: any[]): Promise { + const promises = []; + let dependency = Promise.resolve(); // Execute all the processes in order. - for (let i in orderedPromisesData) { - let data = orderedPromisesData[i], - promise; + for (const i in orderedPromisesData) { + const data = orderedPromisesData[i]; + let promise; // Add the process to the dependency stack. promise = dependency.finally(() => { @@ -403,8 +299,10 @@ export class CoreUtilsProvider { prom = data.func.apply(data.context, data.params || []); } catch (e) { this.logger.error(e.message); + return; } + return prom; }); promises.push(promise); @@ -426,17 +324,21 @@ export class CoreUtilsProvider { * @param {object} obj Object to flatten. * @return {object} Flatten object. */ - flattenObject(obj: object) : object { - let toReturn = {}; + flattenObject(obj: object): object { + const toReturn = {}; - for (let name in obj) { - if (!obj.hasOwnProperty(name)) continue; + for (const name in obj) { + if (!obj.hasOwnProperty(name)) { + continue; + } - let value = obj[name]; + const value = obj[name]; if (typeof value == 'object' && !Array.isArray(value)) { - let flatObject = this.flattenObject(value); - for (let subName in flatObject) { - if (!flatObject.hasOwnProperty(subName)) continue; + const flatObject = this.flattenObject(value); + for (const subName in flatObject) { + if (!flatObject.hasOwnProperty(subName)) { + continue; + } toReturn[name + '.' + subName] = flatObject[subName]; } @@ -455,13 +357,14 @@ export class CoreUtilsProvider { * @param {RegExp} regex RegExp to apply to each string. * @return {string[]} Filtered array. */ - filterByRegexp(array: string[], regex: RegExp) : string[] { + filterByRegexp(array: string[], regex: RegExp): string[] { if (!array || !array.length) { return []; } return array.filter((entry) => { - let matches = entry.match(regex); + const matches = entry.match(regex); + return matches && matches.length; }); } @@ -477,12 +380,12 @@ export class CoreUtilsProvider { * @param {any} ...args All the params sent after checkAll will be passed to isEnabledFn. * @return {Promise} Promise resolved with the list of enabled sites. */ - filterEnabledSites(siteIds: string[], isEnabledFn: Function, checkAll?: boolean, ...args) : Promise { - let promises = [], + filterEnabledSites(siteIds: string[], isEnabledFn: Function, checkAll?: boolean, ...args: any[]): Promise { + const promises = [], enabledSites = []; - for (let i in siteIds) { - let siteId = siteIds[i]; + for (const i in siteIds) { + const siteId = siteIds[i]; if (checkAll || !promises.length) { promises.push(Promise.resolve(isEnabledFn.apply(isEnabledFn, [siteId].concat(args))).then((enabled) => { if (enabled) { @@ -511,15 +414,16 @@ export class CoreUtilsProvider { * @param {any} float The float to print. * @return {string} Locale float. */ - formatFloat(float: any) : string { + formatFloat(float: any): string { if (typeof float == 'undefined') { return ''; } - let localeSeparator = this.translate.instant('core.decsep'); + const localeSeparator = this.translate.instant('core.decsep'); // Convert float to string. float += ''; + return float.replace('.', localeSeparator); } @@ -535,14 +439,15 @@ export class CoreUtilsProvider { * @param {number} [maxDepth=5] Max Depth to convert to tree. Children found will be in the last level of depth. * @return {any[]} Array with the formatted tree, children will be on each node under children field. */ - formatTree(list: any[], parentFieldName = 'parent', idFieldName = 'id', rootParentId = 0, maxDepth = 5) : any[] { - let map = {}, + formatTree(list: any[], parentFieldName: string = 'parent', idFieldName: string = 'id', rootParentId: number = 0, + maxDepth: number = 5): any[] { + const map = {}, mapDepth = {}, - parent, - id, tree = []; + let parent, + id; - list.forEach((node, index) => { + list.forEach((node, index): void => { id = node[idFieldName]; parent = node[parentFieldName]; node.children = []; @@ -550,11 +455,11 @@ export class CoreUtilsProvider { // Use map to look-up the parents. map[id] = index; if (parent != rootParentId) { - let parentNode = list[map[parent]]; + const parentNode = list[map[parent]]; if (parentNode) { if (mapDepth[parent] == maxDepth) { // Reached max level of depth. Proceed with flat order. Find parent object of the current node. - let parentOfParent = parentNode[parentFieldName]; + const parentOfParent = parentNode[parentFieldName]; if (parentOfParent) { // This element will be the child of the node that is two levels up the hierarchy // (i.e. the child of node.parent.parent). @@ -587,8 +492,8 @@ export class CoreUtilsProvider { * @param {string} code Country code (AF, ES, US, ...). * @return {string} Country name. If the country is not found, return the country code. */ - getCountryName(code: string) : string { - let countryKey = 'assets.countries.' + code, + getCountryName(code: string): string { + const countryKey = 'assets.countries.' + code, countryName = this.translate.instant(countryKey); return countryName !== countryKey ? countryName : code; @@ -599,12 +504,12 @@ export class CoreUtilsProvider { * * @return {Promise} Promise resolved with the list of countries. */ - getCountryList() : Promise { + getCountryList(): Promise { // Get the current language. return this.langProvider.getCurrentLanguage().then((lang) => { // Get the full list of translations. Create a promise to convert the observable into a promise. - return new Promise((resolve, reject) => { - let observer = this.translate.getTranslation(lang).subscribe((table) => { + return new Promise((resolve, reject): void => { + const observer = this.translate.getTranslation(lang).subscribe((table) => { resolve(table); observer.unsubscribe(); }, (err) => { @@ -613,11 +518,11 @@ export class CoreUtilsProvider { }); }); }).then((table) => { - let countries = {}; + const countries = {}; - for (let name in table) { + for (const name in table) { if (name.indexOf('assets.countries.') === 0) { - let code = name.replace('assets.countries.', ''); + const code = name.replace('assets.countries.', ''); countries[code] = table[name]; } } @@ -632,18 +537,18 @@ export class CoreUtilsProvider { * @param {any[]} files List of files. * @return {string|boolean} String with error message if repeated, false if no repeated. */ - hasRepeatedFilenames(files: any[]) : string|boolean { - if (!files || !files.length) { + hasRepeatedFilenames(files: any[]): string | boolean { + if (!files || !files.length) { return false; } - let names = []; + const names = []; // Check if there are 2 files with the same name. for (let i = 0; i < files.length; i++) { - let name = files[i].filename || files[i].name; + const name = files[i].filename || files[i].name; if (names.indexOf(name) > -1) { - return this.translate.instant('core.filenameexist', {$a: name}); + return this.translate.instant('core.filenameexist', { $a: name }); } else { names.push(name); } @@ -659,13 +564,13 @@ export class CoreUtilsProvider { * @param {RegExp} regex RegExp to apply to each string. * @return {number} Index of the first string that matches the RegExp. -1 if not found. */ - indexOfRegexp(array: string[], regex: RegExp) : number { + indexOfRegexp(array: string[], regex: RegExp): number { if (!array || !array.length) { return -1; } for (let i = 0; i < array.length; i++) { - let entry = array[i], + const entry = array[i], matches = entry.match(regex); if (matches && matches.length) { @@ -682,8 +587,8 @@ export class CoreUtilsProvider { * @param {any} value Value to check. * @return {boolean} Whether the value is false, 0 or "0". */ - isFalseOrZero(value: any) : boolean { - return typeof value != 'undefined' && (value === false || value === "false" || parseInt(value, 10) === 0); + isFalseOrZero(value: any): boolean { + return typeof value != 'undefined' && (value === false || value === 'false' || parseInt(value, 10) === 0); } /** @@ -692,8 +597,8 @@ export class CoreUtilsProvider { * @param {any} value Value to check. * @return {boolean} Whether the value is true, 1 or "1". */ - isTrueOrOne(value: any) : boolean { - return typeof value != 'undefined' && (value === true || value === "true" || parseInt(value, 10) === 1); + isTrueOrOne(value: any): boolean { + return typeof value != 'undefined' && (value === true || value === 'true' || parseInt(value, 10) === 1); } /** @@ -702,8 +607,8 @@ export class CoreUtilsProvider { * @param {string} error Error to check. * @return {boolean} Whether the error was returned by the WebService. */ - isWebServiceError(error: string) : boolean { - let localErrors = [ + isWebServiceError(error: string): boolean { + const localErrors = [ this.translate.instant('core.wsfunctionnotavailable'), this.translate.instant('core.lostconnection'), this.translate.instant('core.userdeleted'), @@ -716,6 +621,7 @@ export class CoreUtilsProvider { this.translate.instant('core.nopasswordchangeforced'), this.translate.instant('core.unicodenotsupported') ]; + return error && localErrors.indexOf(error) == -1; } @@ -727,7 +633,7 @@ export class CoreUtilsProvider { * @param [key] Key of the property that must be unique. If not specified, the whole entry. * @return {any[]} Merged array. */ - mergeArraysWithoutDuplicates(array1: any[], array2: any[], key?: string) : any[] { + mergeArraysWithoutDuplicates(array1: any[], array2: any[], key?: string): any[] { return this.uniqueArray(array1.concat(array2), key); } @@ -741,84 +647,20 @@ export class CoreUtilsProvider { * @param {string} path The local path of the file to be open. * @return {Promise} Promise resolved when done. */ - openFile(path: string) : Promise { - return new Promise((resolve, reject) => { + openFile(path: string): Promise { + return new Promise((resolve, reject): void => { if (this.appProvider.isDesktop()) { - // It's a desktop app, send an event so the file is opened. It has to be done with an event - // because opening the file from here (renderer process) doesn't focus the opened app. + // It's a desktop app, send an event so the file is opened. + // Opening the file from here (renderer process) doesn't focus the opened app, that's why an event is needed. // Use sendSync so we can receive the result. if (require('electron').ipcRenderer.sendSync('openItem', path)) { resolve(); } else { reject(this.translate.instant('core.erroropenfilenoapp')); } - } else if ((window).plugins) { + } else if (( window).plugins) { // @todo reject('TODO'); - - // var extension = $mmFS.getFileExtension(path), - // mimetype = $mmFS.getMimeType(extension); - - // if (ionic.Platform.isAndroid() && window.plugins.webintent) { - // var iParams = { - // action: "android.intent.action.VIEW", - // url: path, - // type: mimetype - // }; - - // window.plugins.webintent.startActivity( - // iParams, - // function() { - // $log.debug('Intent launched'); - // deferred.resolve(); - // }, - // function() { - // $log.debug('Intent launching failed.'); - // $log.debug('action: ' + iParams.action); - // $log.debug('url: ' + iParams.url); - // $log.debug('type: ' + iParams.type); - - // if (!extension || extension.indexOf('/') > -1 || extension.indexOf('\\') > -1) { - // // Extension not found. - // $mmLang.translateAndRejectDeferred(deferred, 'core.erroropenfilenoextension'); - // } else { - // $mmLang.translateAndRejectDeferred(deferred, 'core.erroropenfilenoapp'); - // } - // } - // ); - - // } else if (ionic.Platform.isIOS() && typeof handleDocumentWithURL == 'function') { - - // $mmFS.getBasePath().then(function(fsRoot) { - // // Encode/decode the specific file path, note that a path may contain directories - // // with white spaces, special characters... - // if (path.indexOf(fsRoot > -1)) { - // path = path.replace(fsRoot, ""); - // path = encodeURIComponent($mmText.decodeURIComponent(path)); - // path = fsRoot + path; - // } - - // handleDocumentWithURL( - // function() { - // $log.debug('File opened with handleDocumentWithURL' + path); - // deferred.resolve(); - // }, - // function(error) { - // $log.debug('Error opening with handleDocumentWithURL' + path); - // if(error == 53) { - // $log.error('No app that handles this file type.'); - // } - // self.openInBrowser(path); - // deferred.resolve(); - // }, - // path - // ); - // }, deferred.reject); - // } else { - // // Last try, launch the file with the browser. - // this.openInBrowser(path); - // resolve(); - // } } else { // Changing _blank for _system may work in cordova 2.4 and onwards. this.logger.log('Opening external file using window.open()'); @@ -836,7 +678,7 @@ export class CoreUtilsProvider { * @param {any} [options] Override default options passed to InAppBrowser. * @return {InAppBrowserObject} The opened window. */ - openInApp(url: string, options?: any) : InAppBrowserObject { + openInApp(url: string, options?: any): InAppBrowserObject { if (!url) { return; } @@ -854,10 +696,11 @@ export class CoreUtilsProvider { } // Convert the options to a string. - let optionsArray = [], - optionsString; - for (let name in options) { - optionsArray.push(`${name}=${options[name]}`) + const optionsArray = []; + let optionsString; + + for (const name in options) { + optionsArray.push(`${name}=${options[name]}`); } optionsString = optionsArray.join(','); @@ -865,10 +708,10 @@ export class CoreUtilsProvider { if (this.appProvider.isDesktop() || this.appProvider.isMobile()) { // Trigger global events when a url is loaded or the window is closed. This is to make it work like in Ionic 1. - let loadStartSubscription = this.iabInstance.on('loadstart').subscribe((event) => { + const loadStartSubscription = this.iabInstance.on('loadstart').subscribe((event) => { this.eventsProvider.trigger(CoreEventsProvider.IAB_LOAD_START, event); }); - let exitSubscription = this.iabInstance.on('exit').subscribe((event) => { + const exitSubscription = this.iabInstance.on('exit').subscribe((event) => { loadStartSubscription.unsubscribe(); exitSubscription.unsubscribe(); this.eventsProvider.trigger(CoreEventsProvider.IAB_EXIT, event); @@ -884,10 +727,10 @@ export class CoreUtilsProvider { * * @param {string} url The URL to open. */ - openInBrowser(url: string) : void { + openInBrowser(url: string): void { if (this.appProvider.isDesktop()) { // It's a desktop app, use Electron shell library to open the browser. - let shell = require('electron').shell; + const shell = require('electron').shell; if (!shell.openExternal(url)) { // Open browser failed, open a new window in the app. window.open(url, '_system'); @@ -909,50 +752,10 @@ export class CoreUtilsProvider { * @param {string} url The URL of the file. * @return {Promise} Promise resolved when opened. */ - openOnlineFile(url: string) : Promise { - return new Promise((resolve, reject) => { + openOnlineFile(url: string): Promise { + return new Promise((resolve, reject): void => { // @todo reject('TODO'); - // if (ionic.Platform.isAndroid() && window.plugins && window.plugins.webintent) { - // // In Android we need the mimetype to open it. - // var iParams; - - // self.getMimeTypeFromUrl(url).catch(function() { - // // Error getting mimetype, return undefined. - // }).then(function(mimetype) { - // if (!mimetype) { - // // Couldn't retrieve mimetype. Return error. - // $mmLang.translateAndRejectDeferred(deferred, 'core.erroropenfilenoextension'); - // return; - // } - - // iParams = { - // action: "android.intent.action.VIEW", - // url: url, - // type: mimetype - // }; - - // window.plugins.webintent.startActivity( - // iParams, - // function() { - // $log.debug('Intent launched'); - // deferred.resolve(); - // }, - // function() { - // $log.debug('Intent launching failed.'); - // $log.debug('action: ' + iParams.action); - // $log.debug('url: ' + iParams.url); - // $log.debug('type: ' + iParams.type); - - // $mmLang.translateAndRejectDeferred(deferred, 'core.erroropenfilenoapp'); - // } - // ); - // }); - // } else { - // this.logger.log('Opening remote file using window.open()'); - // window.open(url, '_blank'); - // resolve(); - // } }); } @@ -962,7 +765,7 @@ export class CoreUtilsProvider { * @param {object} obj Object to convert. * @return {any[]} Array with the values of the object but losing the keys. */ - objectToArray(obj: object) : any[] { + objectToArray(obj: object): any[] { return Object.keys(obj).map((key) => { return obj[key]; }); @@ -977,27 +780,28 @@ export class CoreUtilsProvider { * @param {string} keyName Name of the properties where to store the keys. * @param {string} valueName Name of the properties where to store the values. * @param {boolean} [sort] True to sort keys alphabetically, false otherwise. - * @return {object[]} Array of objects with the name & value of each property. + * @return {any[]} Array of objects with the name & value of each property. */ - objectToArrayOfObjects(obj: object, keyName: string, valueName: string, sort?: boolean) : object[] { + objectToArrayOfObjects(obj: object, keyName: string, valueName: string, sort?: boolean): any[] { // Get the entries from an object or primitive value. - let getEntries = (elKey, value) => { + const getEntries = (elKey, value): any[] | any => { if (typeof value == 'object') { // It's an object, return at least an entry for each property. - let keys = Object.keys(value), - entries = []; + const keys = Object.keys(value); + let entries = []; keys.forEach((key) => { - let newElKey = elKey ? elKey + '[' + key + ']' : key; + const newElKey = elKey ? elKey + '[' + key + ']' : key; entries = entries.concat(getEntries(newElKey, value[key])); }); return entries; } else { // Not an object, return a single entry. - let entry = {}; + const entry = {}; entry[keyName] = elKey; entry[valueName] = value; + return entry; } }; @@ -1007,12 +811,13 @@ export class CoreUtilsProvider { } // "obj" will always be an object, so "entries" will always be an array. - let entries = getEntries('', obj); + const entries = getEntries('', obj); if (sort) { return entries.sort((a, b) => { return a.name >= b.name ? 1 : -1; }); } + return entries; } @@ -1026,13 +831,14 @@ export class CoreUtilsProvider { * @param [keyPrefix] Key prefix if neededs to delete it. * @return {object} Object. */ - objectToKeyValueMap(objects: object[], keyName: string, valueName: string, keyPrefix?: string) : object { - let prefixSubstr = keyPrefix ? keyPrefix.length : 0, + objectToKeyValueMap(objects: object[], keyName: string, valueName: string, keyPrefix?: string): object { + const prefixSubstr = keyPrefix ? keyPrefix.length : 0, mapped = {}; objects.forEach((item) => { - let key = prefixSubstr > 0 ? item[keyName].substr(prefixSubstr) : item[keyName]; + const key = prefixSubstr > 0 ? item[keyName].substr(prefixSubstr) : item[keyName]; mapped[key] = item[valueName]; }); + return mapped; } @@ -1042,9 +848,9 @@ export class CoreUtilsProvider { * @param {Observable} obs The observable to convert. * @return {Promise} Promise. */ - observableToPromise(obs: Observable) : Promise { - return new Promise((resolve, reject) => { - let subscription = obs.subscribe((data) => { + observableToPromise(obs: Observable): Promise { + return new Promise((resolve, reject): void => { + const subscription = obs.subscribe((data) => { // Data received, unsubscribe. subscription.unsubscribe(); resolve(data); @@ -1061,12 +867,13 @@ export class CoreUtilsProvider { * * @return {PromiseDefer} The deferred promise. */ - promiseDefer() : PromiseDefer { - let deferred: PromiseDefer = {}; - deferred.promise = new Promise((resolve, reject) => { + promiseDefer(): PromiseDefer { + const deferred: PromiseDefer = {}; + deferred.promise = new Promise((resolve, reject): void => { deferred.resolve = resolve; deferred.reject = reject; }); + return deferred; } @@ -1076,7 +883,7 @@ export class CoreUtilsProvider { * @param {Promise} promise Promise to check * @return {Promise} Promise resolved with boolean: true if the promise is rejected or false if it's resolved. */ - promiseFails(promise: Promise) : Promise { + promiseFails(promise: Promise): Promise { return promise.then(() => { return false; }).catch(() => { @@ -1090,7 +897,7 @@ export class CoreUtilsProvider { * @param {Promise} promise Promise to check * @return {Promise} Promise resolved with boolean: true if the promise it's resolved or false if it's rejected. */ - promiseWorks(promise: Promise) : Promise { + promiseWorks(promise: Promise): Promise { return promise.then(() => { return true; }).catch(() => { @@ -1108,16 +915,17 @@ export class CoreUtilsProvider { * @param {string} key Key to check. * @return {boolean} Whether the two objects/arrays have the same value (or lack of one) for a given key. */ - sameAtKeyMissingIsBlank(obj1: any, obj2: any, key: string) : boolean { + sameAtKeyMissingIsBlank(obj1: any, obj2: any, key: string): boolean { let value1 = typeof obj1[key] != 'undefined' ? obj1[key] : '', value2 = typeof obj2[key] != 'undefined' ? obj2[key] : ''; - if (typeof value1 == 'number' || typeof value1 == 'boolean') { + if (typeof value1 == 'number' || typeof value1 == 'boolean') { value1 = '' + value1; } - if (typeof value2 == 'number' || typeof value2 == 'boolean') { + if (typeof value2 == 'number' || typeof value2 == 'boolean') { value2 = '' + value2; } + return value1 === value2; } @@ -1128,7 +936,7 @@ export class CoreUtilsProvider { * @param {object} obj Object to stringify. * @return {string} Stringified object. */ - sortAndStringify(obj: object) : string { + sortAndStringify(obj: object): string { return JSON.stringify(this.sortProperties(obj)); } @@ -1138,12 +946,13 @@ export class CoreUtilsProvider { * @param {object} obj The object to sort. If it isn't an object, the original value will be returned. * @return {object} Sorted object. */ - sortProperties(obj: object) : object { + sortProperties(obj: object): object { if (typeof obj == 'object' && !Array.isArray(obj)) { // It's an object, sort it. return Object.keys(obj).sort().reduce((accumulator, key) => { // Always call sort with the value. If it isn't an object, the original value will be returned. accumulator[key] = this.sortProperties(obj[key]); + return accumulator; }, {}); } else { @@ -1157,8 +966,8 @@ export class CoreUtilsProvider { * @param {any[]} files List of files to sum its filesize. * @return {{size: number, total: boolean}} File size and a boolean to indicate if it is the total size or only partial. */ - sumFileSizes(files: any[]) : {size: number, total: boolean} { - let result = { + sumFileSizes(files: any[]): { size: number, total: boolean } { + const result = { size: 0, total: true }; @@ -1183,9 +992,9 @@ export class CoreUtilsProvider { * @param {any} localeFloat Locale aware float representation. * @return {any} False if bad format, empty string if empty value or the parsed float if not. */ - unformatFloat(localeFloat: any) : any { + unformatFloat(localeFloat: any): any { // Bad format on input type number. - if (typeof localeFloat == "undefined") { + if (typeof localeFloat == 'undefined') { return false; } @@ -1202,7 +1011,7 @@ export class CoreUtilsProvider { return ''; } - let localeSeparator = this.translate.instant('core.decsep'); + const localeSeparator = this.translate.instant('core.decsep'); localeFloat = localeFloat.replace(' ', ''); // No spaces - those might be used as thousand separators. localeFloat = localeFloat.replace(localeSeparator, '.'); @@ -1212,6 +1021,7 @@ export class CoreUtilsProvider { if (isNaN(localeFloat)) { return false; } + return localeFloat; } @@ -1222,13 +1032,13 @@ export class CoreUtilsProvider { * @param [key] Key of the property that must be unique. If not specified, the whole entry. * @return {any[]} Array without duplicate values. */ - uniqueArray(array: any[], key?: string) : any[] { - let filtered = [], + uniqueArray(array: any[], key?: string): any[] { + const filtered = [], unique = [], len = array.length; for (let i = 0; i < len; i++) { - let entry = array[i], + const entry = array[i], value = key ? entry[key] : entry; if (unique.indexOf(value) == -1) { diff --git a/src/providers/ws.ts b/src/providers/ws.ts index 355001123..dac52324d 100644 --- a/src/providers/ws.ts +++ b/src/providers/ws.ts @@ -60,7 +60,7 @@ export interface CoreWSPreSets { * @type {string} */ cleanUnicode?: boolean; -}; +} /** * PreSets accepted by AJAX WS calls. @@ -77,7 +77,7 @@ export interface CoreWSAjaxPreSets { * @type {boolean} */ responseExpected?: boolean; -}; +} /** * Error returned by a WS call. @@ -100,7 +100,7 @@ export interface CoreWSError { * @type {string} */ errorcode?: string; -}; +} /** * File upload options. @@ -117,7 +117,7 @@ export interface CoreWSFileUploadOptions extends FileUploadOptions { * @type {number} */ itemId?: number; -}; +} /** * This service allows performing WS calls and download/upload files. @@ -147,8 +147,8 @@ export class CoreWSProvider { * @return {Promise} Deferred promise resolved with the response data in success and rejected with the error message * if it fails. */ - protected addToRetryQueue(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets) : Promise { - let call = { + protected addToRetryQueue(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets): Promise { + const call = { method: method, siteUrl: siteUrl, ajaxData: ajaxData, @@ -157,6 +157,7 @@ export class CoreWSProvider { }; this.retryCalls.push(call); + return call.deferred.promise; } @@ -168,7 +169,7 @@ export class CoreWSProvider { * @param {CoreWSPreSets} preSets Extra settings and information. * @return {Promise} Promise resolved with the response data in success and rejected if it fails. */ - call(method: string, data: any, preSets: CoreWSPreSets) : Promise { + call(method: string, data: any, preSets: CoreWSPreSets): Promise { let siteUrl; @@ -215,7 +216,7 @@ export class CoreWSProvider { * - errorcode: Error code returned by the site (if any). * - available: 0 if unknown, 1 if available, -1 if not available. */ - callAjax(method: string, data: any, preSets: CoreWSAjaxPreSets) : Promise { + callAjax(method: string, data: any, preSets: CoreWSAjaxPreSets): Promise { let siteUrl, ajaxData; @@ -237,16 +238,17 @@ export class CoreWSProvider { siteUrl = preSets.siteUrl + '/lib/ajax/service.php'; - let observable = this.http.post(siteUrl, JSON.stringify(ajaxData)).timeout(CoreConstants.WS_TIMEOUT); + const observable = this.http.post(siteUrl, JSON.stringify(ajaxData)).timeout(CoreConstants.WS_TIMEOUT); + return this.utils.observableToPromise(observable).then((data: any) => { - // 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. + // 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) { data = [{}]; } // Check if error. Ajax layer should always return an object (if error) or an array (if success). - if (!data || typeof data != 'object') { + if (!data || typeof data != 'object') { return rejectWithError(this.translate.instant('core.serverconnection')); } else if (data.error) { return rejectWithError(data.error, data.errorcode); @@ -261,12 +263,13 @@ export class CoreWSProvider { return data.data; }, (data) => { - let available = data.status == 404 ? -1 : 0; + const available = data.status == 404 ? -1 : 0; + return rejectWithError(this.translate.instant('core.serverconnection'), '', available); }); // Convenience function to return an error. - function rejectWithError(message: string, code?: string, available?: number) { + function rejectWithError(message: string, code?: string, available?: number): Promise { if (typeof available == 'undefined') { if (code) { available = code == 'invalidrecord' ? -1 : 1; @@ -291,7 +294,7 @@ export class CoreWSProvider { * @param {boolean} [stripUnicode] If Unicode long chars need to be stripped. * @return {object} The cleaned object, with multilevel array and objects preserved. */ - convertValuesToString(data: object, stripUnicode?: boolean) : object { + convertValuesToString(data: object, stripUnicode?: boolean): object { let result; if (!Array.isArray(data) && typeof data == 'object') { result = {}; @@ -299,7 +302,7 @@ export class CoreWSProvider { result = []; } - for (let el in data) { + for (const el in data) { if (typeof data[el] == 'object') { result[el] = this.convertValuesToString(data[el], stripUnicode); } else { @@ -313,6 +316,7 @@ export class CoreWSProvider { } } } + return result; } @@ -323,10 +327,11 @@ export class CoreWSProvider { * @param {boolean} [needsTranslate] If the message needs to be translated. * @return {CoreWSError} Fake WS error. */ - createFakeWSError(message: string, needsTranslate?: boolean) : CoreWSError { + createFakeWSError(message: string, needsTranslate?: boolean): CoreWSError { if (needsTranslate) { message = this.translate.instant(message); } + return { message: message }; @@ -341,45 +346,46 @@ export class CoreWSProvider { * @param {Function} [onProgress] Function to call on progress. * @return {Promise} Promise resolved with the downloaded file. */ - downloadFile(url: string, path: string, addExtension?: boolean, onProgress?: (event: ProgressEvent) => any) : Promise { + downloadFile(url: string, path: string, addExtension?: boolean, onProgress?: (event: ProgressEvent) => any): Promise { this.logger.debug('Downloading file', url, path, addExtension); if (!this.appProvider.isOnline()) { return Promise.reject(this.translate.instant('core.networkerrormsg')); } - // Use a tmp path to download the file and then move it to final location. This is because if the download fails, - // the local file is deleted. - let tmpPath = path + '.tmp'; + // Use a tmp path to download the file and then move it to final location. + // This is because if the download fails, the local file is deleted. + const tmpPath = path + '.tmp'; // Create the tmp file as an empty file. return this.fileProvider.createFile(tmpPath).then((fileEntry) => { - let transfer = this.fileTransfer.create(); + const transfer = this.fileTransfer.create(); transfer.onProgress(onProgress); return transfer.download(url, fileEntry.toURL(), true).then(() => { let promise; if (addExtension) { - let ext = this.mimeUtils.getFileExtension(path); + const ext = this.mimeUtils.getFileExtension(path); // Google Drive extensions will be considered invalid since Moodle usually converts them. - if (!ext || ext == 'gdoc' || ext == 'gsheet' || ext == 'gslides' || ext == 'gdraw') { + if (!ext || ext == 'gdoc' || ext == 'gsheet' || ext == 'gslides' || ext == 'gdraw') { // Not valid, get the file's mimetype. promise = this.getRemoteFileMimeType(url).then((mime) => { if (mime) { - let remoteExt = this.mimeUtils.getExtension(mime, url); - // If the file is from Google Drive, ignore mimetype application/json (sometimes pluginfile - // returns an invalid mimetype for files). + const remoteExt = this.mimeUtils.getExtension(mime, url); + // If the file is from Google Drive, ignore mimetype application/json. if (remoteExt && (!ext || mime != 'application/json')) { if (ext) { // Remove existing extension since we will use another one. path = this.mimeUtils.removeExtension(path); } path += '.' + remoteExt; + return remoteExt; } } + return ext; }); } else { @@ -395,12 +401,14 @@ export class CoreWSProvider { movedEntry.extension = extension; movedEntry.path = path; this.logger.debug(`Success downloading file ${url} to ${path} with extension ${extension}`); + return movedEntry; }); }); }); }).catch((err) => { this.logger.error(`Error downloading ${url} to ${path}`, err); + return Promise.reject(err); }); } @@ -412,8 +420,8 @@ export class CoreWSProvider { * @param {string} url Base URL of the HTTP request. * @param {any} [params] Params of the HTTP request. */ - protected getPromiseHttp(method: string, url: string, params?: any) : any { - let queueItemId = this.getQueueItemId(method, url, params); + protected getPromiseHttp(method: string, url: string, params?: any): any { + const queueItemId = this.getQueueItemId(method, url, params); if (typeof this.ongoingCalls[queueItemId] != 'undefined') { return this.ongoingCalls[queueItemId]; } @@ -428,7 +436,7 @@ export class CoreWSProvider { * @param {boolean} [ignoreCache] True to ignore cache, false otherwise. * @return {Promise} Promise resolved with the mimetype or '' if failure. */ - getRemoteFileMimeType(url: string, ignoreCache?: boolean) : Promise { + getRemoteFileMimeType(url: string, ignoreCache?: boolean): Promise { if (this.mimeTypeCache[url] && !ignoreCache) { return Promise.resolve(this.mimeTypeCache[url]); } @@ -441,7 +449,7 @@ export class CoreWSProvider { } this.mimeTypeCache[url] = mimeType; - return mimeType || ''; + return mimeType || ''; }).catch(() => { // Error, resolve with empty mimetype. return ''; @@ -454,13 +462,14 @@ export class CoreWSProvider { * @param {string} url File URL. * @return {Promise} Promise resolved with the size or -1 if failure. */ - getRemoteFileSize(url: string) : Promise { + getRemoteFileSize(url: string): Promise { return this.performHead(url).then((data) => { - let size = parseInt(data.headers.get('Content-Length'), 10); + const size = parseInt(data.headers.get('Content-Length'), 10); if (size) { return size; } + return -1; }).catch(() => { // Error, return -1. @@ -476,10 +485,11 @@ export class CoreWSProvider { * @param {object} [params] Params of the HTTP request. * @return {string} Queue item ID. */ - protected getQueueItemId(method: string, url: string, params?: any) : string { + protected getQueueItemId(method: string, url: string, params?: any): string { if (params) { url += '###' + CoreInterceptor.serialize(params); } + return method + '#' + Md5.hashAsciiStr(url); } @@ -489,7 +499,7 @@ export class CoreWSProvider { * @param {string} url URL to perform the request. * @return {Promise} Promise resolved with the response. */ - performHead(url: string) : Promise { + performHead(url: string): Promise { let promise = this.getPromiseHttp('head', url); if (!promise) { @@ -509,10 +519,10 @@ export class CoreWSProvider { * @param {CoreWSPreSets} preSets Extra settings and information. * @return {Promise} Promise resolved with the response data in success and rejected with CoreWSError if it fails. */ - performPost(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets) : Promise { + performPost(method: string, siteUrl: string, ajaxData: any, preSets: CoreWSPreSets): Promise { // Perform the post request. - let observable = this.http.post(siteUrl, ajaxData).timeout(CoreConstants.WS_TIMEOUT), - promise; + const observable = this.http.post(siteUrl, ajaxData).timeout(CoreConstants.WS_TIMEOUT); + let promise; promise = this.utils.observableToPromise(observable).then((data: any) => { // Some moodle web services return null. @@ -525,6 +535,7 @@ export class CoreWSProvider { return Promise.reject(this.createFakeWSError('core.serverconnection', true)); } else if (typeof data != preSets.typeExpected) { this.logger.warn('Response of type "' + typeof data + `" received, expecting "${preSets.typeExpected}"`); + return Promise.reject(this.createFakeWSError('core.errorinvalidresponse', true)); } @@ -540,13 +551,13 @@ export class CoreWSProvider { }, (error) => { // If server has heavy load, retry after some seconds. if (error.status == 429) { - let retryPromise = this.addToRetryQueue(method, siteUrl, ajaxData, preSets); + const retryPromise = this.addToRetryQueue(method, siteUrl, ajaxData, preSets); // Only process the queue one time. if (this.retryTimeout == 0) { this.retryTimeout = parseInt(error.headers.get('Retry-After'), 10) || 5; this.logger.warn(`${error.statusText}. Retrying in ${this.retryTimeout} seconds. ` + - `${this.retryCalls.length} calls left.`); + `${this.retryCalls.length} calls left.`); setTimeout(() => { this.logger.warn(`Retrying now with ${this.retryCalls.length} calls to process.`); @@ -573,9 +584,9 @@ export class CoreWSProvider { * Retry all requests in the queue. * This function uses recursion in order to add a delay between requests to reduce stress. */ - protected processRetryQueue() : void { + protected processRetryQueue(): void { if (this.retryCalls.length > 0 && this.retryTimeout == 0) { - let call = this.retryCalls.shift(); + const call = this.retryCalls.shift(); // Add a delay between calls. setTimeout(() => { call.deferred.resolve(this.performPost(call.method, call.siteUrl, call.ajaxData, call.preSets)); @@ -595,9 +606,9 @@ export class CoreWSProvider { * @param {any} [params] Params of the HTTP request. * @return {Promise} The promise saved. */ - protected setPromiseHttp(promise: Promise, method: string, url: string, params?: any) : Promise { - let timeout, - queueItemId = this.getQueueItemId(method, url, params); + protected setPromiseHttp(promise: Promise, method: string, url: string, params?: any): Promise { + const queueItemId = this.getQueueItemId(method, url, params); + let timeout; this.ongoingCalls[queueItemId] = promise; @@ -624,19 +635,21 @@ export class CoreWSProvider { * @return {Promise} Promise resolved with the response data in success and rejected with the error message if it fails. * @return {any} Request response. If the request fails, returns an object with 'error'=true and 'message' properties. */ - syncCall(method: string, data: any, preSets: CoreWSPreSets) : any { - let siteUrl, - xhr, - errorResponse = { + syncCall(method: string, data: any, preSets: CoreWSPreSets): any { + const errorResponse = { error: true, message: '' }; + let siteUrl, + xhr; if (!preSets) { errorResponse.message = this.translate.instant('core.unexpectederror'); + return errorResponse; } else if (!this.appProvider.isOnline()) { errorResponse.message = this.translate.instant('core.networkerrormsg'); + return errorResponse; } @@ -650,6 +663,7 @@ export class CoreWSProvider { } catch (e) { // Empty cleaned text found. errorResponse.message = this.translate.instant('core.unicodenotsupportedcleanerror'); + return errorResponse; } @@ -661,7 +675,7 @@ export class CoreWSProvider { data = CoreInterceptor.serialize(data); // Perform sync request using XMLHttpRequest. - xhr = new (window).XMLHttpRequest(); + xhr = new ( window).XMLHttpRequest(); xhr.open('post', siteUrl, false); xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded;charset=utf-8'); @@ -675,13 +689,16 @@ export class CoreWSProvider { if (xhr.status < 200 || xhr.status >= 300) { // Request failed. errorResponse.message = data; + return errorResponse; } // Treat response. try { data = JSON.parse(data); - } catch(ex) {} + } catch (ex) { + // Ignore errors. + } // 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. @@ -696,7 +713,7 @@ export class CoreWSProvider { errorResponse.message = this.translate.instant('core.errorinvalidresponse'); } - if (typeof data.exception != 'undefined' || typeof data.debuginfo != 'undefined') { + if (typeof data.exception != 'undefined' || typeof data.debuginfo != 'undefined') { errorResponse.message = data.message; } @@ -717,7 +734,7 @@ export class CoreWSProvider { * @return {Promise} Promise resolved when uploaded. */ uploadFile(filePath: string, options: CoreWSFileUploadOptions, preSets: CoreWSPreSets, - onProgress?: (event: ProgressEvent) => any) : Promise { + onProgress?: (event: ProgressEvent) => any): Promise { this.logger.debug(`Trying to upload file: ${filePath}`); if (!filePath || !options || !preSets) { @@ -728,7 +745,7 @@ export class CoreWSProvider { return Promise.reject(this.translate.instant('core.networkerrormsg')); } - let uploadUrl = preSets.siteUrl + '/webservice/upload.php', + const uploadUrl = preSets.siteUrl + '/webservice/upload.php', transfer = this.fileTransfer.create(); transfer.onProgress(onProgress); @@ -737,19 +754,20 @@ export class CoreWSProvider { options.params = { token: preSets.wsToken, filearea: options.fileArea || 'draft', - itemid: options.itemId || 0 + itemid: options.itemId || 0 }; options.chunkedMode = false; options.headers = { - Connection: "close" + Connection: 'close' }; return transfer.upload(filePath, uploadUrl, options, true).then((success) => { let data: any = success.response; try { data = JSON.parse(data); - } catch(err) { + } catch (err) { this.logger.error('Error parsing response from upload:', err, data); + return Promise.reject(this.translate.instant('core.errorinvalidresponse')); } @@ -757,6 +775,7 @@ export class CoreWSProvider { return Promise.reject(this.translate.instant('core.serverconnection')); } else if (typeof data != 'object') { this.logger.warn('Upload file: Response of type "' + typeof data + '" received, expecting "object"'); + return Promise.reject(this.translate.instant('core.errorinvalidresponse')); } @@ -770,9 +789,11 @@ export class CoreWSProvider { // We uploaded only 1 file, so we only return the first file returned. this.logger.debug('Successfully uploaded file', filePath); + return data[0]; }).catch((error) => { this.logger.error('Error while uploading file', filePath, error); + return Promise.reject(this.translate.instant('core.errorinvalidresponse')); }); } diff --git a/tslint.json b/tslint.json index ec7b715c0..7149be671 100644 --- a/tslint.json +++ b/tslint.json @@ -1,7 +1,7 @@ { "rules": { "adjacent-overload-signatures": true, - "member-access": [true, "no-public", "check-parameter-property"], + "member-access": [true, "no-public"], "member-ordering": [ true, { @@ -11,22 +11,15 @@ "public-constructor", "private-static-field", "private-instance-field", - "private-constructor", - "public-instance-method", - "protected-instance-method", - "private-instance-method" + "private-constructor" ] } ], "no-empty-interface": true, - "no-inferrable-types": true, + "no-inferrable-types": [true, "ignore-params"], "no-duplicate-variable": [true, "check-parameters"], - "no-unused-variable": true, "no-non-null-assertion": true, - "no-unnecessary-type-assertion": true, "only-arrow-functions": [true, "allow-declarations", "allow-named-functions"], - "prefer-for-of": true, - "promise-function-async": true, "typedef": [true, "call-signature", "arrow-call-signature", "parameter", "property-declaration", "object-destructuring", "array-destructuring"], "typedef-whitespace": [ @@ -47,10 +40,8 @@ } ], "unified-signatures": true, - "await-promise": true, "ban-comma-operator": true, "curly": true, - "forin": true, "label-position": true, "no-bitwise": true, "no-conditional-assignment": true, @@ -60,34 +51,24 @@ "no-duplicate-super": true, "no-duplicate-switch-case": true, "no-duplicate-variable": [true, "check-parameters"], - "no-dynamic-delete": true, "no-empty": true, "no-eval": true, - "no-floating-promises": true, - "no-for-in-array": true, - "no-inferred-empty-object-type": true, "no-invalid-this": true, "no-this-assignment": true, - "no-unsafe-any": true, "no-var-keyword": true, "switch-default": true, "typeof-compare": true, - "use-default-type-parameter": true, "use-isnan": true, - "deprecation": true, "eofline": true, "indent": [true, "spaces", 4], "linebreak-style": [true, "LF"], "max-line-length": [true, 132], "no-duplicate-imports": true, - "object-literal-sort-keys": true, "prefer-const": true, - "prefer-readonly": true, "arrow-parens": true, "binary-expression-operand-order": true, "class-name": true, - "comment-format": [true, "check-space", "check-uppercase", {"ignore-words": ["you", "distributed", "limitations", "http"]}], - "completed-docs": true, + "comment-format": [true, "check-space", "check-uppercase", {"ignore-words": ["you", "distributed", "limitations", "http", "@todo"]}], "encoding": true, "file-header": [true, "Copyright \\d{4}"], "jsdoc-format": [true, "check-multiline-start"],