From 68708d116b1c2bf855a202d360c89d36bcae3553 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Wed, 16 Mar 2022 15:24:43 +0100 Subject: [PATCH 1/7] MOBILE-3833 pipes: Restore coreToLocaleString pipe --- src/core/pipes/pipes.module.ts | 3 ++ src/core/pipes/to-locale-string.ts | 67 ++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+) create mode 100644 src/core/pipes/to-locale-string.ts diff --git a/src/core/pipes/pipes.module.ts b/src/core/pipes/pipes.module.ts index b8b197300..a45223af7 100644 --- a/src/core/pipes/pipes.module.ts +++ b/src/core/pipes/pipes.module.ts @@ -22,6 +22,7 @@ import { CoreFormatDatePipe } from './format-date'; import { CoreNoTagsPipe } from './no-tags'; import { CoreSecondsToHMSPipe } from './seconds-to-hms'; import { CoreTimeAgoPipe } from './time-ago'; +import { CoreToLocaleStringPipe } from './to-locale-string'; @NgModule({ declarations: [ @@ -33,6 +34,7 @@ import { CoreTimeAgoPipe } from './time-ago'; CoreNoTagsPipe, CoreSecondsToHMSPipe, CoreTimeAgoPipe, + CoreToLocaleStringPipe, ], exports: [ CoreBytesToSizePipe, @@ -43,6 +45,7 @@ import { CoreTimeAgoPipe } from './time-ago'; CoreNoTagsPipe, CoreSecondsToHMSPipe, CoreTimeAgoPipe, + CoreToLocaleStringPipe, ], }) export class CorePipesModule {} diff --git a/src/core/pipes/to-locale-string.ts b/src/core/pipes/to-locale-string.ts new file mode 100644 index 000000000..5783fea6c --- /dev/null +++ b/src/core/pipes/to-locale-string.ts @@ -0,0 +1,67 @@ +// (C) Copyright 2015 Moodle Pty Ltd. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +import { Pipe, PipeTransform } from '@angular/core'; +import { CoreTimeUtils } from '@services/utils/time'; + +import { CoreLogger } from '@singletons/logger'; + +/** + * Filter to format a timestamp to a locale string. Timestamp can be in seconds or milliseconds. + * + * @deprecated since 3.6. Use coreFormatDate instead. + * This pipe wasn't removed in app 4.0 because some site plugins still used it. It will be removed in future versions. + */ +@Pipe({ + name: 'coreToLocaleString', +}) +export class CoreToLocaleStringPipe implements PipeTransform { + + protected logger: CoreLogger; + + constructor() { + this.logger = CoreLogger.getInstance('CoreToLocaleStringPipe'); + } + + /** + * Format a timestamp to a locale string. + * + * @param timestamp The timestamp (can be in seconds or milliseconds). + * @return Formatted time. + */ + 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; + } + + if (timestamp < 0) { + // Date not valid. + return ''; + } + if (timestamp < 100000000000) { + // Timestamp is in seconds, convert it to milliseconds. + timestamp = timestamp * 1000; + } + + return CoreTimeUtils.userDate(timestamp, 'core.strftimedatetimeshort'); + } + +} From e5ba19a835c1263e01ad0413c6cb2dacb42cbc5d Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 17 Mar 2022 12:15:03 +0100 Subject: [PATCH 2/7] MOBILE-3833 ws: Send User Agent in file transfer requests --- src/core/features/emulator/services/file-transfer.ts | 7 +++++-- src/core/services/ws.ts | 10 ++++++++-- 2 files changed, 13 insertions(+), 4 deletions(-) diff --git a/src/core/features/emulator/services/file-transfer.ts b/src/core/features/emulator/services/file-transfer.ts index 59961318f..63f8baa8f 100644 --- a/src/core/features/emulator/services/file-transfer.ts +++ b/src/core/features/emulator/services/file-transfer.ts @@ -113,7 +113,10 @@ export class FileTransferObjectMock extends FileTransferObject { xhr.open('GET', source, true); xhr.responseType = 'blob'; for (const name in headers) { - xhr.setRequestHeader(name, headers[name]); + // We can't set the User-Agent in browser. + if (name !== 'User-Agent') { + xhr.setRequestHeader(name, headers[name]); + } } xhr.onprogress = (ev: ProgressEvent): void => { @@ -332,7 +335,7 @@ export class FileTransferObjectMock extends FileTransferObject { xhr.open(httpMethod || 'POST', url); for (const name in headers) { // Filter "unsafe" headers. - if (name != 'Connection') { + if (name !=='Connection' && name !== 'User-Agent') { xhr.setRequestHeader(name, headers[name]); } } diff --git a/src/core/services/ws.ts b/src/core/services/ws.ts index 866ed0724..9d9e270de 100644 --- a/src/core/services/ws.ts +++ b/src/core/services/ws.ts @@ -270,7 +270,11 @@ export class CoreWSProvider { onProgress && transfer.onProgress(onProgress); // Download the file in the tmp file. - await transfer.download(url, fileEntry.toURL(), true); + await transfer.download(url, fileEntry.toURL(), true, { + headers: { + 'User-Agent': navigator.userAgent, // eslint-disable-line @typescript-eslint/naming-convention + }, + }); let extension = ''; @@ -885,7 +889,9 @@ export class CoreWSProvider { itemid: options.itemId || 0, }; options.chunkedMode = false; - options.headers = {}; + options.headers = { + 'User-Agent': navigator.userAgent, // eslint-disable-line @typescript-eslint/naming-convention + }; options['Connection'] = 'close'; let success: FileUploadResult; From 265a170b1352866736116d11a507ecc361b432c2 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Thu, 17 Mar 2022 16:47:55 +0100 Subject: [PATCH 3/7] MOBILE-3833 core: Treat relative URLs inside CSS files --- src/addons/calendar/services/calendar.ts | 3 +- .../airnotifier/services/airnotifier.ts | 4 +- .../mod/assign/services/assign-offline.ts | 5 +- src/addons/mod/data/services/data-offline.ts | 5 +- .../mod/forum/services/forum-offline.ts | 7 +-- .../mod/glossary/services/glossary-offline.ts | 5 +- src/addons/mod/imscp/services/imscp.ts | 5 +- .../mod/resource/services/resource-helper.ts | 4 +- src/addons/mod/scorm/services/scorm.ts | 3 +- src/addons/mod/wiki/components/index/index.ts | 4 +- .../mod/workshop/services/workshop-offline.ts | 7 +-- .../services/remotethemes-handler.ts | 2 +- src/core/components/local-file/local-file.ts | 3 +- src/core/components/recaptcha/recaptcha.ts | 4 +- src/core/directives/format-text.ts | 3 +- src/core/directives/link.ts | 12 ++--- .../pages/course-summary/course-summary.ts | 6 +-- src/core/features/course/pages/index/index.ts | 6 +-- .../services/handlers/request-push-click.ts | 4 +- .../components/capture-media/capture-media.ts | 6 +-- src/core/features/emulator/services/file.ts | 12 ++--- src/core/features/emulator/services/zip.ts | 7 ++- .../services/fileuploader-helper.ts | 5 +- .../fileuploader/services/fileuploader.ts | 4 +- src/core/features/h5p/classes/core.ts | 5 +- src/core/features/h5p/classes/file-storage.ts | 25 +++++----- src/core/features/h5p/classes/helper.ts | 10 ++-- src/core/features/h5p/classes/player.ts | 14 +++--- src/core/features/h5p/classes/storage.ts | 6 +-- src/core/features/h5p/classes/validator.ts | 18 +++---- src/core/features/h5p/services/h5p.ts | 4 +- .../login/pages/email-signup/email-signup.ts | 5 +- .../features/login/services/login-helper.ts | 5 +- .../features/question/services/question.ts | 3 +- .../sharedfiles/components/list/list.ts | 4 +- .../sharedfiles/services/sharedfiles.ts | 6 +-- .../services/siteplugins-helper.ts | 9 ++-- src/core/features/xapi/services/xapi.ts | 4 +- src/core/services/file.ts | 5 +- src/core/services/filepool.ts | 32 ++++++++---- src/core/services/urlschemes.ts | 3 +- src/core/services/utils/iframe.ts | 6 +-- src/core/services/utils/text.ts | 22 ++------- src/core/services/utils/url.ts | 7 +-- src/core/singletons/tests/url.test.ts | 8 +++ src/core/singletons/text.ts | 26 ++++++++++ src/core/singletons/url.ts | 49 +++++++++++++++++++ 47 files changed, 246 insertions(+), 156 deletions(-) diff --git a/src/addons/calendar/services/calendar.ts b/src/addons/calendar/services/calendar.ts index d010a96d9..c8bbf90d3 100644 --- a/src/addons/calendar/services/calendar.ts +++ b/src/addons/calendar/services/calendar.ts @@ -40,6 +40,7 @@ import { CoreNavigator } from '@services/navigator'; import { AddonCalendarFilter } from './calendar-helper'; import { AddonCalendarSyncEvents, AddonCalendarSyncProvider } from './calendar-sync'; import { CoreEvents } from '@singletons/events'; +import { CoreText } from '@singletons/text'; const ROOT_CACHE_KEY = 'mmaCalendar:'; @@ -1218,7 +1219,7 @@ export class AddonCalendarProvider { */ async getViewUrl(view: string, time?: number, courseId?: string, siteId?: string): Promise { const site = await CoreSites.getSite(siteId); - let url = CoreTextUtils.concatenatePaths(site.getURL(), 'calendar/view.php?view=' + view); + let url = CoreText.concatenatePaths(site.getURL(), 'calendar/view.php?view=' + view); if (time) { url += '&time=' + time; diff --git a/src/addons/messageoutput/airnotifier/services/airnotifier.ts b/src/addons/messageoutput/airnotifier/services/airnotifier.ts index a7783ac6f..86c498b18 100644 --- a/src/addons/messageoutput/airnotifier/services/airnotifier.ts +++ b/src/addons/messageoutput/airnotifier/services/airnotifier.ts @@ -23,8 +23,8 @@ import { CoreWSError } from '@classes/errors/wserror'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents, CoreEventSiteData } from '@singletons/events'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; +import { CoreText } from '@singletons/text'; const ROOT_CACHE_KEY = 'mmaMessageOutputAirnotifier:'; @@ -214,7 +214,7 @@ export class AddonMessageOutputAirnotifierProvider { handler: (data, resolve) => { resolve(data[0]); - const url = CoreTextUtils.concatenatePaths( + const url = CoreText.concatenatePaths( site.getURL(), site.isVersionGreaterEqualThan('3.11') ? 'message/output/airnotifier/checkconfiguration.php' : diff --git a/src/addons/mod/assign/services/assign-offline.ts b/src/addons/mod/assign/services/assign-offline.ts index a2024439b..d57ce3d2e 100644 --- a/src/addons/mod/assign/services/assign-offline.ts +++ b/src/addons/mod/assign/services/assign-offline.ts @@ -20,6 +20,7 @@ import { CoreSites } from '@services/sites'; import { CoreTextUtils } from '@services/utils/text'; import { CoreTimeUtils } from '@services/utils/time'; import { makeSingleton } from '@singletons'; +import { CoreText } from '@singletons/text'; import { AddonModAssignOutcomes, AddonModAssignSavePluginData } from './assign'; import { AddonModAssignSubmissionsDBRecord, @@ -236,7 +237,7 @@ export class AddonModAssignOfflineProvider { const siteFolderPath = CoreFile.getSiteFolder(site.getId()); const submissionFolderPath = 'offlineassign/' + assignId + '/' + userId; - return CoreTextUtils.concatenatePaths(siteFolderPath, submissionFolderPath); + return CoreText.concatenatePaths(siteFolderPath, submissionFolderPath); } /** @@ -276,7 +277,7 @@ export class AddonModAssignOfflineProvider { async getSubmissionPluginFolder(assignId: number, pluginName: string, userId?: number, siteId?: string): Promise { const folderPath = await this.getSubmissionFolder(assignId, userId, siteId); - return CoreTextUtils.concatenatePaths(folderPath, pluginName); + return CoreText.concatenatePaths(folderPath, pluginName); } /** diff --git a/src/addons/mod/data/services/data-offline.ts b/src/addons/mod/data/services/data-offline.ts index 67fdb5b78..4fa5d30b9 100644 --- a/src/addons/mod/data/services/data-offline.ts +++ b/src/addons/mod/data/services/data-offline.ts @@ -19,6 +19,7 @@ import { CoreSites } from '@services/sites'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton } from '@singletons'; +import { CoreText } from '@singletons/text'; import { AddonModDataAction, AddonModDataEntryWSField } from './data'; import { AddonModDataEntryDBRecord, DATA_ENTRY_TABLE } from './database/data'; @@ -206,7 +207,7 @@ export class AddonModDataOfflineProvider { const siteFolderPath = CoreFile.getSiteFolder(site.getId()); const folderPath = 'offlinedatabase/' + dataId; - return CoreTextUtils.concatenatePaths(siteFolderPath, folderPath); + return CoreText.concatenatePaths(siteFolderPath, folderPath); } /** @@ -221,7 +222,7 @@ export class AddonModDataOfflineProvider { async getEntryFieldFolder(dataId: number, entryId: number, fieldId: number, siteId?: string): Promise { const folderPath = await this.getDatabaseFolder(dataId, siteId); - return CoreTextUtils.concatenatePaths(folderPath, entryId + '_' + fieldId); + return CoreText.concatenatePaths(folderPath, entryId + '_' + fieldId); } /** diff --git a/src/addons/mod/forum/services/forum-offline.ts b/src/addons/mod/forum/services/forum-offline.ts index bb28c0287..01d91e606 100644 --- a/src/addons/mod/forum/services/forum-offline.ts +++ b/src/addons/mod/forum/services/forum-offline.ts @@ -25,6 +25,7 @@ import { DISCUSSIONS_TABLE, REPLIES_TABLE, } from './database/offline'; +import { CoreText } from '@singletons/text'; /** * Service to handle offline forum. @@ -341,7 +342,7 @@ export class AddonModForumOfflineProvider { const site = await CoreSites.getSite(siteId); const siteFolderPath = CoreFile.getSiteFolder(site.getId()); - return CoreTextUtils.concatenatePaths(siteFolderPath, 'offlineforum/' + forumId); + return CoreText.concatenatePaths(siteFolderPath, 'offlineforum/' + forumId); } /** @@ -355,7 +356,7 @@ export class AddonModForumOfflineProvider { async getNewDiscussionFolder(forumId: number, timeCreated: number, siteId?: string): Promise { const folderPath = await this.getForumFolder(forumId, siteId); - return CoreTextUtils.concatenatePaths(folderPath, 'newdisc_' + timeCreated); + return CoreText.concatenatePaths(folderPath, 'newdisc_' + timeCreated); } /** @@ -372,7 +373,7 @@ export class AddonModForumOfflineProvider { const site = await CoreSites.getSite(siteId); userId = userId || site.getUserId(); - return CoreTextUtils.concatenatePaths(folderPath, 'reply_' + postId + '_' + userId); + return CoreText.concatenatePaths(folderPath, 'reply_' + postId + '_' + userId); } /** diff --git a/src/addons/mod/glossary/services/glossary-offline.ts b/src/addons/mod/glossary/services/glossary-offline.ts index 4c9d7216b..47319388b 100644 --- a/src/addons/mod/glossary/services/glossary-offline.ts +++ b/src/addons/mod/glossary/services/glossary-offline.ts @@ -19,6 +19,7 @@ import { CoreSites } from '@services/sites'; import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton } from '@singletons'; +import { CoreText } from '@singletons/text'; import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary'; import { AddonModGlossaryDiscardedEntry, AddonModGlossaryEntryOption } from './glossary'; @@ -213,7 +214,7 @@ export class AddonModGlossaryOfflineProvider { const siteFolderPath = CoreFile.getSiteFolder(site.getId()); const folderPath = 'offlineglossary/' + glossaryId; - return CoreTextUtils.concatenatePaths(siteFolderPath, folderPath); + return CoreText.concatenatePaths(siteFolderPath, folderPath); } /** @@ -228,7 +229,7 @@ export class AddonModGlossaryOfflineProvider { async getEntryFolder(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise { const folderPath = await this.getGlossaryFolder(glossaryId, siteId); - return CoreTextUtils.concatenatePaths(folderPath, 'newentry_' + concept + '_' + timeCreated); + return CoreText.concatenatePaths(folderPath, 'newentry_' + concept + '_' + timeCreated); } /** diff --git a/src/addons/mod/imscp/services/imscp.ts b/src/addons/mod/imscp/services/imscp.ts index 226dcd7c5..824dbb33f 100644 --- a/src/addons/mod/imscp/services/imscp.ts +++ b/src/addons/mod/imscp/services/imscp.ts @@ -25,6 +25,7 @@ import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; import { makeSingleton, Translate } from '@singletons'; +import { CoreText } from '@singletons/text'; const ROOT_CACHE_KEY = 'mmaModImscp:'; @@ -154,7 +155,7 @@ export class AddonModImscpProvider { return false; } - const filePath = CoreTextUtils.concatenatePaths(item.filepath, item.filename); + const filePath = CoreText.concatenatePaths(item.filepath, item.filename); const filePathAlt = filePath.charAt(0) === '/' ? filePath.substring(1) : '/' + filePath; // Check if it's main file. @@ -177,7 +178,7 @@ export class AddonModImscpProvider { try { const dirPath = await CoreFilepool.getPackageDirUrlByUrl(siteId, module.url || ''); - return CoreTextUtils.concatenatePaths(dirPath, itemHref); + return CoreText.concatenatePaths(dirPath, itemHref); } catch (error) { // Error getting directory, there was an error downloading or we're in browser. Return online URL if connected. if (CoreApp.isOnline()) { diff --git a/src/addons/mod/resource/services/resource-helper.ts b/src/addons/mod/resource/services/resource-helper.ts index 943bee509..41e4eec84 100644 --- a/src/addons/mod/resource/services/resource-helper.ts +++ b/src/addons/mod/resource/services/resource-helper.ts @@ -24,9 +24,9 @@ import { CoreFilepool } from '@services/filepool'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtilsOpenFileOptions } from '@services/utils/utils'; import { makeSingleton, Translate } from '@singletons'; +import { CoreText } from '@singletons/text'; import { AddonModResource, AddonModResourceProvider } from './resource'; /** @@ -77,7 +77,7 @@ export class AddonModResourceHelperProvider { const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url!); // This URL is going to be injected in an iframe, we need trustAsResourceUrl to make it work in a browser. - return CoreTextUtils.concatenatePaths(dirPath, mainFilePath); + return CoreText.concatenatePaths(dirPath, mainFilePath); } catch (e) { // Error getting directory, there was an error downloading or we're in browser. Return online URL. if (CoreApp.isOnline() && mainFile.fileurl) { diff --git a/src/addons/mod/scorm/services/scorm.ts b/src/addons/mod/scorm/services/scorm.ts index be1ac928c..27bb5c945 100644 --- a/src/addons/mod/scorm/services/scorm.ts +++ b/src/addons/mod/scorm/services/scorm.ts @@ -28,6 +28,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreWS, CoreWSExternalFile, CoreWSExternalWarning, CoreWSFile, CoreWSPreSets } from '@services/ws'; import { makeSingleton, Translate } from '@singletons'; import { CoreEvents } from '@singletons/events'; +import { CoreText } from '@singletons/text'; import { AddonModScormOffline } from './scorm-offline'; import { AddonModScormAutoSyncEventData, AddonModScormSyncProvider } from './scorm-sync'; @@ -960,7 +961,7 @@ export class AddonModScormProvider { const dirPath = await CoreFilepool.getPackageDirUrlByUrl(siteId, scorm.moduleurl!); - return CoreTextUtils.concatenatePaths(dirPath, launchUrl); + return CoreText.concatenatePaths(dirPath, launchUrl); } /** diff --git a/src/addons/mod/wiki/components/index/index.ts b/src/addons/mod/wiki/components/index/index.ts index bbe537f99..558db1026 100644 --- a/src/addons/mod/wiki/components/index/index.ts +++ b/src/addons/mod/wiki/components/index/index.ts @@ -26,10 +26,10 @@ import { CoreGroup, CoreGroups } from '@services/groups'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { Network, Translate, NgZone } from '@singletons'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; +import { CoreText } from '@singletons/text'; import { Subscription } from 'rxjs'; import { Md5 } from 'ts-md5'; import { AddonModWikiPageDBRecord } from '../../services/database/wiki'; @@ -676,7 +676,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp content = content.trim(); if (content.length > 0) { - const editUrl = CoreTextUtils.concatenatePaths(CoreSites.getRequiredCurrentSite().getURL(), '/mod/wiki/edit.php'); + const editUrl = CoreText.concatenatePaths(CoreSites.getRequiredCurrentSite().getURL(), '/mod/wiki/edit.php'); content = content.replace(/href="edit\.php/g, 'href="' + editUrl); } diff --git a/src/addons/mod/workshop/services/workshop-offline.ts b/src/addons/mod/workshop/services/workshop-offline.ts index fdf5f0152..1246ef1f1 100644 --- a/src/addons/mod/workshop/services/workshop-offline.ts +++ b/src/addons/mod/workshop/services/workshop-offline.ts @@ -20,6 +20,7 @@ import { CoreTextUtils } from '@services/utils/text'; import { CoreTimeUtils } from '@services/utils/time'; import { makeSingleton } from '@singletons'; import { CoreFormFields } from '@singletons/form'; +import { CoreText } from '@singletons/text'; import { AddonModWorkshopAssessmentDBRecord, AddonModWorkshopEvaluateAssessmentDBRecord, @@ -629,7 +630,7 @@ export class AddonModWorkshopOfflineProvider { const siteFolderPath = CoreFile.getSiteFolder(site.getId()); const workshopFolderPath = 'offlineworkshop/' + workshopId + '/'; - return CoreTextUtils.concatenatePaths(siteFolderPath, workshopFolderPath); + return CoreText.concatenatePaths(siteFolderPath, workshopFolderPath); } /** @@ -642,7 +643,7 @@ export class AddonModWorkshopOfflineProvider { async getSubmissionFolder(workshopId: number, siteId?: string): Promise { const folderPath = await this.getWorkshopFolder(workshopId, siteId); - return CoreTextUtils.concatenatePaths(folderPath, 'submission'); + return CoreText.concatenatePaths(folderPath, 'submission'); } /** @@ -658,7 +659,7 @@ export class AddonModWorkshopOfflineProvider { folderPath += 'assessment/'; - return CoreTextUtils.concatenatePaths(folderPath, String(assessmentId)); + return CoreText.concatenatePaths(folderPath, String(assessmentId)); } } diff --git a/src/addons/remotethemes/services/remotethemes-handler.ts b/src/addons/remotethemes/services/remotethemes-handler.ts index d04eb9cd3..2d553046f 100644 --- a/src/addons/remotethemes/services/remotethemes-handler.ts +++ b/src/addons/remotethemes/services/remotethemes-handler.ts @@ -90,7 +90,7 @@ export class AddonRemoteThemesHandlerService implements CoreStyleHandler { if (style != '') { // Treat the CSS. CoreUtils.ignoreErrors( - CoreFilepool.treatCSSCode(siteId, fileUrl, style, COMPONENT, 1), + CoreFilepool.treatCSSCode(siteId, infos.mobilecssurl, style, COMPONENT, 1), ); } diff --git a/src/core/components/local-file/local-file.ts b/src/core/components/local-file/local-file.ts index 8f942bc6a..3ae64c44c 100644 --- a/src/core/components/local-file/local-file.ts +++ b/src/core/components/local-file/local-file.ts @@ -26,6 +26,7 @@ import { CoreTimeUtils } from '@services/utils/time'; import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/utils/utils'; import { CoreForms } from '@singletons/form'; import { CoreApp } from '@services/app'; +import { CoreText } from '@singletons/text'; /** * Component to handle a local file. Only files inside the app folder can be managed. @@ -174,7 +175,7 @@ export class CoreLocalFileComponent implements OnInit { const modal = await CoreDomUtils.showModalLoading(); const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.relativePath!); - const newPath = CoreTextUtils.concatenatePaths(fileAndDir.directory, newName); + const newPath = CoreText.concatenatePaths(fileAndDir.directory, newName); try { // Check if there's a file with this name. diff --git a/src/core/components/recaptcha/recaptcha.ts b/src/core/components/recaptcha/recaptcha.ts index 45e1c8934..0e143c079 100644 --- a/src/core/components/recaptcha/recaptcha.ts +++ b/src/core/components/recaptcha/recaptcha.ts @@ -16,8 +16,8 @@ import { Component, Input, OnInit } from '@angular/core'; import { CoreLang } from '@services/lang'; import { CoreSites } from '@services/sites'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; +import { CoreText } from '@singletons/text'; /** * Component that allows answering a recaptcha. @@ -62,7 +62,7 @@ export class CoreRecaptchaComponent implements OnInit { // Open the recaptcha challenge in an InAppBrowser. // The app used to use an iframe for this, but the app can no longer access the iframe to create the required callbacks. // The app cannot render the recaptcha directly because it has problems with the local protocols and domains. - const src = CoreTextUtils.concatenatePaths(this.siteUrl!, 'webservice/recaptcha.php?lang=' + this.lang); + const src = CoreText.concatenatePaths(this.siteUrl!, 'webservice/recaptcha.php?lang=' + this.lang); const inAppBrowserWindow = CoreUtils.openInApp(src); if (!inAppBrowserWindow) { diff --git a/src/core/directives/format-text.ts b/src/core/directives/format-text.ts index afd53beae..c52760192 100644 --- a/src/core/directives/format-text.ts +++ b/src/core/directives/format-text.ts @@ -44,6 +44,7 @@ import { CoreComponentsRegistry } from '@singletons/components-registry'; import { CoreCollapsibleItemDirective } from './collapsible-item'; import { CoreCancellablePromise } from '@classes/cancellable-promise'; import { AsyncComponent } from '@classes/async-component'; +import { CoreText } from '@singletons/text'; /** * Directive to format text rendered. It renders the HTML and treats all links and media, using CoreLinkDirective @@ -695,7 +696,7 @@ export class CoreFormatTextDirective implements OnChanges, OnDestroy, AsyncCompo // Check if it's a Vimeo video. If it is, use the wsplayer script instead to make restricted videos work. const matches = src.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)([?&]+h=([a-zA-Z0-9]*))?/); if (matches && matches[1]) { - let newUrl = CoreTextUtils.concatenatePaths(site.getURL(), '/media/player/vimeo/wsplayer.php?video=') + + let newUrl = CoreText.concatenatePaths(site.getURL(), '/media/player/vimeo/wsplayer.php?video=') + matches[1] + '&token=' + site.getToken(); let privacyHash: string | undefined | null = matches[3]; diff --git a/src/core/directives/link.ts b/src/core/directives/link.ts index e177b4a97..31a405e88 100644 --- a/src/core/directives/link.ts +++ b/src/core/directives/link.ts @@ -27,6 +27,7 @@ import { CoreContentLinksHelper } from '@features/contentlinks/services/contentl import { CoreCustomURLSchemes } from '@services/urlschemes'; import { DomSanitizer } from '@singletons'; import { CoreFilepool } from '@services/filepool'; +import { CoreUrl } from '@singletons/url'; /** * Directive to open a link in external browser or in the app. @@ -209,15 +210,8 @@ export class CoreLinkDirective implements OnInit { const currentSite = CoreSites.getRequiredCurrentSite(); - // Check if URL does not have any protocol, so it's a relative URL. - if (!CoreUrlUtils.isAbsoluteURL(href)) { - // Add the site URL at the begining. - if (href.charAt(0) == '/') { - href = currentSite.getURL() + href; - } else { - href = currentSite.getURL() + '/' + href; - } - } + // Make sure it's an absolute URL. + href = CoreUrl.toAbsoluteURL(currentSite.getURL(), href); if (currentSite.isSitePluginFileUrl(href)) { // It's a site file. Check if it's being downloaded right now. diff --git a/src/core/features/course/pages/course-summary/course-summary.ts b/src/core/features/course/pages/course-summary/course-summary.ts index 694346601..a34255bfa 100644 --- a/src/core/features/course/pages/course-summary/course-summary.ts +++ b/src/core/features/course/pages/course-summary/course-summary.ts @@ -17,7 +17,6 @@ import { ActionSheetButton, IonRefresher } from '@ionic/angular'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreCourseCustomField, CoreCourseEnrolmentMethod, @@ -38,6 +37,7 @@ import { CoreUtils } from '@services/utils/utils'; import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper'; import { Subscription } from 'rxjs'; import { CoreColors } from '@singletons/colors'; +import { CoreText } from '@singletons/text'; /** * Page that shows the summary of a course including buttons to enrol and other available options. @@ -117,8 +117,8 @@ export class CoreCourseSummaryPage implements OnInit, OnDestroy { } const currentSiteUrl = CoreSites.getRequiredCurrentSite().getURL(); - this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId); - this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'course/view.php?id=' + this.courseId); + this.enrolUrl = CoreText.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId); + this.courseUrl = CoreText.concatenatePaths(currentSiteUrl, 'course/view.php?id=' + this.courseId); await this.getCourse(); } diff --git a/src/core/features/course/pages/index/index.ts b/src/core/features/course/pages/index/index.ts index 3377bde84..fdd5e47c8 100644 --- a/src/core/features/course/pages/index/index.ts +++ b/src/core/features/course/pages/index/index.ts @@ -23,13 +23,13 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events'; import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseWSSection } from '@features/course/services/course'; import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper'; import { CoreUtils } from '@services/utils/utils'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; import { CONTENTS_PAGE_NAME } from '@features/course/course.module'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreCourseSummaryPage } from '../course-summary/course-summary'; import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper'; import { CoreColors } from '@singletons/colors'; +import { CoreText } from '@singletons/text'; /** * Page that displays the list of courses the user is enrolled in. @@ -149,7 +149,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { } this.currentPagePath = CoreNavigator.getCurrentPath(); - this.contentsTab.page = CoreTextUtils.concatenatePaths(this.currentPagePath, this.contentsTab.page); + this.contentsTab.page = CoreText.concatenatePaths(this.currentPagePath, this.contentsTab.page); this.contentsTab.pageParams = { course: this.course, sectionId: CoreNavigator.getRouteNumberParam('sectionId'), @@ -207,7 +207,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { // Create the full path. handlers.forEach((handler, index) => { - handler.data.page = CoreTextUtils.concatenatePaths(this.currentPagePath, handler.data.page); + handler.data.page = CoreText.concatenatePaths(this.currentPagePath, handler.data.page); handler.data.pageParams = handler.data.pageParams || {}; // Check if this handler should be the first selected tab. diff --git a/src/core/features/courses/services/handlers/request-push-click.ts b/src/core/features/courses/services/handlers/request-push-click.ts index 2eb12d9a9..265e9f0f9 100644 --- a/src/core/features/courses/services/handlers/request-push-click.ts +++ b/src/core/features/courses/services/handlers/request-push-click.ts @@ -20,9 +20,9 @@ import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifi import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { makeSingleton } from '@singletons'; +import { CoreText } from '@singletons/text'; import { CoreCourses } from '../courses'; /** @@ -58,7 +58,7 @@ export class CoreCoursesRequestPushClickHandlerService implements CorePushNotifi if (notification.name == 'courserequested') { // Feature not supported in the app, open in browser. const site = await CoreSites.getSite(notification.site); - const url = CoreTextUtils.concatenatePaths(site.getURL(), 'course/pending.php'); + const url = CoreText.concatenatePaths(site.getURL(), 'course/pending.php'); await site.openInBrowserWithAutoLogin(url); diff --git a/src/core/features/emulator/components/capture-media/capture-media.ts b/src/core/features/emulator/components/capture-media/capture-media.ts index aec2e3955..4d7fabd8e 100644 --- a/src/core/features/emulator/components/capture-media/capture-media.ts +++ b/src/core/features/emulator/components/capture-media/capture-media.ts @@ -21,12 +21,12 @@ import { CoreApp } from '@services/app'; import { CoreFile, CoreFileProvider } from '@services/file'; import { CoreDomUtils } from '@services/utils/dom'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreTimeUtils } from '@services/utils/time'; import { Platform, ModalController, Media, Translate } from '@singletons'; import { CoreError } from '@classes/errors/error'; import { CoreCaptureError } from '@classes/errors/captureerror'; import { CoreCanceledError } from '@classes/errors/cancelederror'; +import { CoreText } from '@singletons/text'; /** * Page to capture media in browser, or to capture audio in mobile devices. @@ -131,7 +131,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { */ protected async initCordovaMediaPlugin(): Promise { this.filePath = this.getFilePath(); - let absolutePath = CoreTextUtils.concatenatePaths(CoreFile.getBasePathInstant(), this.filePath); + let absolutePath = CoreText.concatenatePaths(CoreFile.getBasePathInstant(), this.filePath); if (Platform.is('ios')) { // In iOS we need to remove the file:// part. @@ -534,7 +534,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { protected getFilePath(): string { const fileName = this.type + '_' + CoreTimeUtils.readableTimestamp() + '.' + this.extension; - return CoreTextUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'media/' + fileName); + return CoreText.concatenatePaths(CoreFileProvider.TMPFOLDER, 'media/' + fileName); } /** diff --git a/src/core/features/emulator/services/file.ts b/src/core/features/emulator/services/file.ts index 4ae67a3d5..cfd35de11 100644 --- a/src/core/features/emulator/services/file.ts +++ b/src/core/features/emulator/services/file.ts @@ -15,7 +15,7 @@ import { Injectable } from '@angular/core'; import { File, Entry, DirectoryEntry, FileEntry, IWriteOptions, RemoveResult } from '@ionic-native/file/ngx'; -import { CoreTextUtils } from '@services/utils/text'; +import { CoreText } from '@singletons/text'; /** * Implement the File Error because the ionic-native plugin doesn't implement it. @@ -58,7 +58,7 @@ export class FileMock extends File { * @return Returns a Promise that resolves to true if the directory exists or rejects with an error. */ async checkDir(path: string, dir: string): Promise { - const fullPath = CoreTextUtils.concatenatePaths(path, dir); + const fullPath = CoreText.concatenatePaths(path, dir); await this.resolveDirectoryUrl(fullPath); @@ -73,7 +73,7 @@ export class FileMock extends File { * @return Returns a Promise that resolves with a boolean or rejects with an error. */ async checkFile(path: string, file: string): Promise { - const entry = await this.resolveLocalFilesystemUrl(CoreTextUtils.concatenatePaths(path, file)); + const entry = await this.resolveLocalFilesystemUrl(CoreText.concatenatePaths(path, file)); if (entry.isFile) { return true; @@ -144,7 +144,7 @@ export class FileMock extends File { async copyFileOrDir(sourcePath: string, sourceName: string, destPath: string, destName: string): Promise { const destFixed = this.fixPathAndName(destPath, destName); - const source = await this.resolveLocalFilesystemUrl(CoreTextUtils.concatenatePaths(sourcePath, sourceName)); + const source = await this.resolveLocalFilesystemUrl(CoreText.concatenatePaths(sourcePath, sourceName)); const destParentDir = await this.resolveDirectoryUrl(destFixed.path); @@ -424,7 +424,7 @@ export class FileMock extends File { async moveFileOrDir(sourcePath: string, sourceName: string, destPath: string, destName: string): Promise { const destFixed = this.fixPathAndName(destPath, destName); - const source = await this.resolveLocalFilesystemUrl(CoreTextUtils.concatenatePaths(sourcePath, sourceName)); + const source = await this.resolveLocalFilesystemUrl(CoreText.concatenatePaths(sourcePath, sourceName)); const destParentDir = await this.resolveDirectoryUrl(destFixed.path); @@ -440,7 +440,7 @@ export class FileMock extends File { */ protected fixPathAndName(path: string, name: string): {path: string; name: string} { - const fullPath = CoreTextUtils.concatenatePaths(path, name); + const fullPath = CoreText.concatenatePaths(path, name); return { path: fullPath.substring(0, fullPath.lastIndexOf('/')), diff --git a/src/core/features/emulator/services/zip.ts b/src/core/features/emulator/services/zip.ts index 47fa54b55..0420b2dce 100644 --- a/src/core/features/emulator/services/zip.ts +++ b/src/core/features/emulator/services/zip.ts @@ -16,8 +16,7 @@ import { Injectable } from '@angular/core'; import { File } from '@ionic-native/file/ngx'; import { Zip } from '@ionic-native/zip/ngx'; import * as JSZip from 'jszip'; - -import { CoreTextUtils } from '@services/utils/text'; +import { CoreText } from '@singletons/text'; /** * Emulates the Cordova Zip plugin in browser. @@ -46,7 +45,7 @@ export class ZipMock extends Zip { await this.file.createDir(destination, folder, true); // Folder created, add it to the destination path. - destination = CoreTextUtils.concatenatePaths(destination, folder); + destination = CoreText.concatenatePaths(destination, folder); } } @@ -106,7 +105,7 @@ export class ZipMock extends Zip { const fileData = await file.async('blob'); // File read and parent folder created, now write the file. - const parentFolder = CoreTextUtils.concatenatePaths(destination, fileDir); + const parentFolder = CoreText.concatenatePaths(destination, fileDir); await this.file.writeFile(parentFolder, fileName, fileData, { replace: true }); } else { diff --git a/src/core/features/fileuploader/services/fileuploader-helper.ts b/src/core/features/fileuploader/services/fileuploader-helper.ts index 0aae60598..d8cdfb14f 100644 --- a/src/core/features/fileuploader/services/fileuploader-helper.ts +++ b/src/core/features/fileuploader/services/fileuploader-helper.ts @@ -35,6 +35,7 @@ import { CoreCaptureError } from '@classes/errors/captureerror'; import { CoreIonLoadingElement } from '@classes/ion-loading'; import { CoreWSUploadFileResult } from '@services/ws'; import { CoreSites } from '@services/sites'; +import { CoreText } from '@singletons/text'; /** * Helper service to upload files. @@ -158,7 +159,7 @@ export class CoreFileUploaderHelperProvider { // Get unique name for the copy. const newName = await CoreFile.getUniqueNameInFolder(CoreFileProvider.TMPFOLDER, name); - const filePath = CoreTextUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); + const filePath = CoreText.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); // Write the data into the file. fileEntry = await CoreFile.writeFileDataInFile( @@ -218,7 +219,7 @@ export class CoreFileUploaderHelperProvider { const newName = await CoreFile.getUniqueNameInFolder(CoreFileProvider.TMPFOLDER, fileName, defaultExt); // Now move or copy the file. - const destPath = CoreTextUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); + const destPath = CoreText.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); if (shouldDelete) { return CoreFile.moveExternalFile(path, destPath); } else { diff --git a/src/core/features/fileuploader/services/fileuploader.ts b/src/core/features/fileuploader/services/fileuploader.ts index cf17de72b..c9f94dac0 100644 --- a/src/core/features/fileuploader/services/fileuploader.ts +++ b/src/core/features/fileuploader/services/fileuploader.ts @@ -23,7 +23,6 @@ import { CoreFile, CoreFileProvider } from '@services/file'; import { CoreFilepool } from '@services/filepool'; import { CoreSites } from '@services/sites'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreTimeUtils } from '@services/utils/time'; import { CoreUtils } from '@services/utils/utils'; import { CoreWSFile, CoreWSFileUploadOptions, CoreWSUploadFileResult } from '@services/ws'; @@ -33,6 +32,7 @@ import { CoreEmulatorCaptureMediaComponent } from '@features/emulator/components import { CoreError } from '@classes/errors/error'; import { CoreSite } from '@classes/site'; import { CoreFileEntry, CoreFileHelper } from '@services/file-helper'; +import { CoreText } from '@singletons/text'; /** * File upload options. @@ -579,7 +579,7 @@ export class CoreFileUploaderProvider { } else { // 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 = CoreTextUtils.concatenatePaths(folderPath, file.name); + const destFile = CoreText.concatenatePaths(folderPath, file.name); result.offline++; await CoreFile.copyFile(file.toURL(), destFile); diff --git a/src/core/features/h5p/classes/core.ts b/src/core/features/h5p/classes/core.ts index 948dabf40..8c50ea5eb 100644 --- a/src/core/features/h5p/classes/core.ts +++ b/src/core/features/h5p/classes/core.ts @@ -24,6 +24,7 @@ import { CoreH5PContentValidator, CoreH5PSemantics } from './content-validator'; import { Translate } from '@singletons'; import { CoreH5PContentBeingSaved } from './storage'; import { CoreH5PLibraryAddTo } from './validator'; +import { CoreText } from '@singletons/text'; /** * Equivalent to H5P's H5PCore class. @@ -148,7 +149,7 @@ export class CoreH5PCore { urls.push(libUrl + script); }); - urls.push(CoreTextUtils.concatenatePaths(libUrl, 'moodle/js/h5p_overrides.js')); + urls.push(CoreText.concatenatePaths(libUrl, 'moodle/js/h5p_overrides.js')); return urls; } @@ -456,7 +457,7 @@ export class CoreH5PCore { // Add URL prefix if not external. if (asset.path.indexOf('://') == -1 && assetsFolderPath) { - url = CoreTextUtils.concatenatePaths(assetsFolderPath, url); + url = CoreText.concatenatePaths(assetsFolderPath, url); } // Add version if set. diff --git a/src/core/features/h5p/classes/file-storage.ts b/src/core/features/h5p/classes/file-storage.ts index 1c811674b..5bbab236d 100644 --- a/src/core/features/h5p/classes/file-storage.ts +++ b/src/core/features/h5p/classes/file-storage.ts @@ -28,6 +28,7 @@ import { } from './core'; import { CONTENTS_LIBRARIES_TABLE_NAME, CONTENT_TABLE_NAME, CoreH5PLibraryCachedAssetsDBRecord } from '../services/database/h5p'; import { CoreH5PLibraryBeingSaved } from './storage'; +import { CoreText } from '@singletons/text'; /** * Equivalent to Moodle's implementation of H5PFileStorage. @@ -60,7 +61,7 @@ export class CoreH5PFileStorage { // Create new file for cached assets. const fileName = key + '.' + (type == 'scripts' ? 'js' : 'css'); - const path = CoreTextUtils.concatenatePaths(cachedAssetsPath, fileName); + const path = CoreText.concatenatePaths(cachedAssetsPath, fileName); // Store concatenated content. const content = await this.concatenateFiles(assets, type); @@ -70,7 +71,7 @@ export class CoreH5PFileStorage { // Now update the files data. files[type] = [ { - path: CoreTextUtils.concatenatePaths(CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, fileName), + path: CoreText.concatenatePaths(CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, fileName), version: '', }, ]; @@ -142,7 +143,7 @@ export class CoreH5PFileStorage { fileContent = fileContent.replace( new RegExp(CoreTextUtils.escapeForRegex(match), 'g'), - 'url("' + CoreTextUtils.concatenatePaths(basePath, url) + '")', + 'url("' + CoreText.concatenatePaths(basePath, url) + '")', ); }); } @@ -170,7 +171,7 @@ export class CoreH5PFileStorage { const cachedAssetsFolder = this.getCachedAssetsFolderPath(entry.foldername, site.getId()); ['js', 'css'].forEach((type) => { - const path = CoreTextUtils.concatenatePaths(cachedAssetsFolder, entry.hash + '.' + type); + const path = CoreText.concatenatePaths(cachedAssetsFolder, entry.hash + '.' + type); promises.push(CoreFile.removeFile(path)); }); @@ -282,7 +283,7 @@ export class CoreH5PFileStorage { protected async getCachedAsset(key: string, extension: string): Promise { try { - const path = CoreTextUtils.concatenatePaths(CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, key + extension); + const path = CoreText.concatenatePaths(CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, key + extension); const size = await CoreFile.getFileSize(path); @@ -307,7 +308,7 @@ export class CoreH5PFileStorage { * @return Path. */ getCachedAssetsFolderPath(folderName: string, siteId: string): string { - return CoreTextUtils.concatenatePaths( + return CoreText.concatenatePaths( this.getContentFolderPath(folderName, siteId), CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, ); @@ -336,7 +337,7 @@ export class CoreH5PFileStorage { * @return Folder path. */ getContentFolderPath(folderName: string, siteId: string): string { - return CoreTextUtils.concatenatePaths( + return CoreText.concatenatePaths( this.getExternalH5PFolderPath(siteId), 'packages/' + folderName + '/content', ); @@ -367,7 +368,7 @@ export class CoreH5PFileStorage { * @return Folder path. */ getContentIndexPath(folderName: string, siteId: string): string { - return CoreTextUtils.concatenatePaths(this.getContentFolderPath(folderName, siteId), 'index.html'); + return CoreText.concatenatePaths(this.getContentFolderPath(folderName, siteId), 'index.html'); } /** @@ -376,7 +377,7 @@ export class CoreH5PFileStorage { * @return Folder path. */ getCoreH5PPath(): string { - return CoreTextUtils.concatenatePaths(CoreFile.getWWWPath(), '/assets/lib/h5p/'); + return CoreText.concatenatePaths(CoreFile.getWWWPath(), '/assets/lib/h5p/'); } /** @@ -396,7 +397,7 @@ export class CoreH5PFileStorage { * @return Folder path. */ getExternalH5PFolderPath(siteId: string): string { - return CoreTextUtils.concatenatePaths(CoreFile.getSiteFolder(siteId), 'h5p'); + return CoreText.concatenatePaths(CoreFile.getSiteFolder(siteId), 'h5p'); } /** @@ -406,7 +407,7 @@ export class CoreH5PFileStorage { * @return Folder path. */ getLibrariesFolderPath(siteId: string): string { - return CoreTextUtils.concatenatePaths(this.getExternalH5PFolderPath(siteId), 'libraries'); + return CoreText.concatenatePaths(this.getExternalH5PFolderPath(siteId), 'libraries'); } /** @@ -426,7 +427,7 @@ export class CoreH5PFileStorage { folderName = CoreH5PCore.libraryToString(libraryData, true); } - return CoreTextUtils.concatenatePaths(this.getLibrariesFolderPath(siteId), folderName); + return CoreText.concatenatePaths(this.getLibrariesFolderPath(siteId), folderName); } /** diff --git a/src/core/features/h5p/classes/helper.ts b/src/core/features/h5p/classes/helper.ts index 7e685b06b..d418fa615 100644 --- a/src/core/features/h5p/classes/helper.ts +++ b/src/core/features/h5p/classes/helper.ts @@ -17,11 +17,11 @@ import { FileEntry } from '@ionic-native/file/ngx'; import { CoreFile, CoreFileProvider } from '@services/file'; import { CoreSites } from '@services/sites'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreH5P } from '../services/h5p'; import { CoreH5PCore, CoreH5PDisplayOptions } from './core'; import { CoreError } from '@classes/errors/error'; +import { CoreText } from '@singletons/text'; /** * Equivalent to Moodle's H5P helper class. @@ -131,13 +131,13 @@ export class CoreH5PHelper { return { baseUrl: CoreFile.getWWWPath(), url: CoreFile.convertFileSrc( - CoreTextUtils.concatenatePaths( + CoreText.concatenatePaths( basePath, CoreH5P.h5pCore.h5pFS.getExternalH5PFolderPath(site.getId()), ), ), urlLibraries: CoreFile.convertFileSrc( - CoreTextUtils.concatenatePaths( + CoreText.concatenatePaths( basePath, CoreH5P.h5pCore.h5pFS.getLibrariesFolderPath(site.getId()), ), @@ -155,7 +155,7 @@ export class CoreH5PHelper { crossorigin: null, libraryConfig: null, pluginCacheBuster: '', - libraryUrl: CoreTextUtils.concatenatePaths(CoreH5P.h5pCore.h5pFS.getCoreH5PPath(), 'js'), + libraryUrl: CoreText.concatenatePaths(CoreH5P.h5pCore.h5pFS.getCoreH5PPath(), 'js'), }; } @@ -197,7 +197,7 @@ export class CoreH5PHelper { ): Promise { const folderName = CoreMimetypeUtils.removeExtension(file.name); - const destFolder = CoreTextUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); + const destFolder = CoreText.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); // Unzip the file. await CoreFile.unzipFile(file.toURL(), destFolder, onProgress); diff --git a/src/core/features/h5p/classes/player.ts b/src/core/features/h5p/classes/player.ts index 73da742fe..b42a61ebc 100644 --- a/src/core/features/h5p/classes/player.ts +++ b/src/core/features/h5p/classes/player.ts @@ -14,7 +14,6 @@ import { CoreFile } from '@services/file'; import { CoreSites } from '@services/sites'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils } from '@services/utils/utils'; import { CoreXAPI } from '@features/xapi/services/xapi'; @@ -22,6 +21,7 @@ import { CoreH5P } from '../services/h5p'; import { CoreH5PCore, CoreH5PDisplayOptions, CoreH5PContentData, CoreH5PDependenciesFiles } from './core'; import { CoreH5PCoreSettings, CoreH5PHelper } from './helper'; import { CoreH5PStorage } from './storage'; +import { CoreText } from '@singletons/text'; /** * Equivalent to Moodle's H5P player class. @@ -51,7 +51,7 @@ export class CoreH5PPlayer { params.component = component; } - return CoreUrlUtils.addParamsToUrl(CoreTextUtils.concatenatePaths(siteUrl, '/h5p/embed.php'), params); + return CoreUrlUtils.addParamsToUrl(CoreText.concatenatePaths(siteUrl, '/h5p/embed.php'), params); } /** @@ -78,7 +78,7 @@ export class CoreH5PPlayer { const contentId = this.getContentId(id); const basePath = CoreFile.getBasePathInstant(); const contentUrl = CoreFile.convertFileSrc( - CoreTextUtils.concatenatePaths( + CoreText.concatenatePaths( basePath, this.h5pCore.h5pFS.getContentFolderPath(content.folderName, site.getId()), ), @@ -122,7 +122,7 @@ export class CoreH5PPlayer { JSON.stringify(result.settings).replace(/\//g, '\\/') + ''; // Add our own script to handle the params. - html += ''; @@ -132,7 +132,7 @@ export class CoreH5PPlayer { // Include the required JS at the beginning of the body, like Moodle web does. // Load the embed.js to allow communication with the parent window. html += ''; + CoreText.concatenatePaths(this.h5pCore.h5pFS.getCoreH5PPath(), 'moodle/js/embed.js') + '">'; result.jsRequires.forEach((jsUrl) => { html += ''; @@ -364,7 +364,7 @@ export class CoreH5PPlayer { * @return The embed URL. */ protected getEmbedUrl(siteUrl: string, h5pUrl: string): string { - return CoreTextUtils.concatenatePaths(siteUrl, '/h5p/embed.php') + '?url=' + h5pUrl; + return CoreText.concatenatePaths(siteUrl, '/h5p/embed.php') + '?url=' + h5pUrl; } /** @@ -382,7 +382,7 @@ export class CoreH5PPlayer { * @return URL. */ getResizerScriptUrl(): string { - return CoreTextUtils.concatenatePaths(this.h5pCore.h5pFS.getCoreH5PPath(), 'js/h5p-resizer.js'); + return CoreText.concatenatePaths(this.h5pCore.h5pFS.getCoreH5PPath(), 'js/h5p-resizer.js'); } /** diff --git a/src/core/features/h5p/classes/storage.ts b/src/core/features/h5p/classes/storage.ts index 984dd022d..a94453190 100644 --- a/src/core/features/h5p/classes/storage.ts +++ b/src/core/features/h5p/classes/storage.ts @@ -14,8 +14,8 @@ import { CoreFile, CoreFileProvider } from '@services/file'; import { CoreSites } from '@services/sites'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; +import { CoreText } from '@singletons/text'; import { CoreH5PCore, CoreH5PLibraryBasicData } from './core'; import { CoreH5PFramework } from './framework'; import { CoreH5PMetadata } from './metadata'; @@ -199,8 +199,8 @@ export class CoreH5PStorage { await this.h5pCore.saveContent(content, folderName, fileUrl, siteId); // Save the content files in their right place in FS. - const destFolder = CoreTextUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); - const contentPath = CoreTextUtils.concatenatePaths(destFolder, 'content'); + const destFolder = CoreText.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); + const contentPath = CoreText.concatenatePaths(destFolder, 'content'); try { await this.h5pCore.h5pFS.saveContent(contentPath, folderName, siteId); diff --git a/src/core/features/h5p/classes/validator.ts b/src/core/features/h5p/classes/validator.ts index b95399f4b..edc952aa2 100644 --- a/src/core/features/h5p/classes/validator.ts +++ b/src/core/features/h5p/classes/validator.ts @@ -15,8 +15,8 @@ import { CoreError } from '@classes/errors/error'; import { FileEntry, DirectoryEntry } from '@ionic-native/file/ngx'; import { CoreFile, CoreFileFormat } from '@services/file'; -import { CoreTextUtils } from '@services/utils/text'; import { Translate } from '@singletons'; +import { CoreText } from '@singletons/text'; import { CoreH5PSemantics } from './content-validator'; import { CoreH5PCore, CoreH5PLibraryBasicData, CoreH5PMissingLibrary } from './core'; import { CoreH5PFramework } from './framework'; @@ -126,7 +126,7 @@ export class CoreH5PValidator { return; } - const libDirPath = CoreTextUtils.concatenatePaths(packagePath, entry.name); + const libDirPath = CoreText.concatenatePaths(packagePath, entry.name); const libraryData = await this.getLibraryData( entry, libDirPath); @@ -144,7 +144,7 @@ export class CoreH5PValidator { * @return Promise resolved with boolean: whether the library has an icon file. */ protected async libraryHasIcon(libPath: string): Promise { - const path = CoreTextUtils.concatenatePaths(libPath, 'icon.svg'); + const path = CoreText.concatenatePaths(libPath, 'icon.svg'); try { // Check if the file exists. @@ -219,7 +219,7 @@ export class CoreH5PValidator { * @return Promise resolved with the parsed file contents. */ protected readH5PContentJsonFile(packagePath: string): Promise { - const path = CoreTextUtils.concatenatePaths(packagePath, 'content/content.json'); + const path = CoreText.concatenatePaths(packagePath, 'content/content.json'); return CoreFile.readFile(path, CoreFileFormat.FORMATJSON); } @@ -231,7 +231,7 @@ export class CoreH5PValidator { * @return Promise resolved with the parsed file contents. */ protected readH5PJsonFile(packagePath: string): Promise { - const path = CoreTextUtils.concatenatePaths(packagePath, 'h5p.json'); + const path = CoreText.concatenatePaths(packagePath, 'h5p.json'); return CoreFile.readFile(path, CoreFileFormat.FORMATJSON); } @@ -243,7 +243,7 @@ export class CoreH5PValidator { * @return Promise resolved with the parsed file contents. */ protected readLibraryJsonFile(libPath: string): Promise { - const path = CoreTextUtils.concatenatePaths(libPath, 'library.json'); + const path = CoreText.concatenatePaths(libPath, 'library.json'); return CoreFile.readFile(path, CoreFileFormat.FORMATJSON); } @@ -256,14 +256,14 @@ export class CoreH5PValidator { */ protected async readLibraryLanguageFiles(libPath: string): Promise { try { - const path = CoreTextUtils.concatenatePaths(libPath, 'language'); + const path = CoreText.concatenatePaths(libPath, 'language'); const langIndex: CoreH5PLibraryLangsJsonData = {}; // Read all the files in the language directory. const entries = await CoreFile.getDirectoryContents(path); await Promise.all(entries.map(async (entry) => { - const langFilePath = CoreTextUtils.concatenatePaths(path, entry.name); + const langFilePath = CoreText.concatenatePaths(path, entry.name); try { const langFileData = await CoreFile.readFile( @@ -293,7 +293,7 @@ export class CoreH5PValidator { */ protected async readLibrarySemanticsFile(libPath: string): Promise { try { - const path = CoreTextUtils.concatenatePaths(libPath, 'semantics.json'); + const path = CoreText.concatenatePaths(libPath, 'semantics.json'); return await CoreFile.readFile(path, CoreFileFormat.FORMATJSON); } catch (error) { diff --git a/src/core/features/h5p/services/h5p.ts b/src/core/features/h5p/services/h5p.ts index 92bf18bc8..fc4693645 100644 --- a/src/core/features/h5p/services/h5p.ts +++ b/src/core/features/h5p/services/h5p.ts @@ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; import { CoreSites } from '@services/sites'; import { CoreWSExternalWarning, CoreWSExternalFile, CoreWSFile } from '@services/ws'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUrlUtils } from '@services/utils/url'; import { CoreQueueRunner } from '@classes/queue-runner'; import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; @@ -29,6 +28,7 @@ import { CoreH5PValidator } from '../classes/validator'; import { makeSingleton } from '@singletons'; import { CoreError } from '@classes/errors/error'; +import { CoreText } from '@singletons/text'; /** * Service to provide H5P functionalities. @@ -207,7 +207,7 @@ export class CoreH5PProvider { * @return Treated url. */ treatH5PUrl(url: string, siteUrl: string): string { - if (url.indexOf(CoreTextUtils.concatenatePaths(siteUrl, '/webservice/pluginfile.php')) === 0) { + if (url.indexOf(CoreText.concatenatePaths(siteUrl, '/webservice/pluginfile.php')) === 0) { url = url.replace('/webservice/pluginfile', '/pluginfile'); } diff --git a/src/core/features/login/pages/email-signup/email-signup.ts b/src/core/features/login/pages/email-signup/email-signup.ts index 794e81e1d..f74f28463 100644 --- a/src/core/features/login/pages/email-signup/email-signup.ts +++ b/src/core/features/login/pages/email-signup/email-signup.ts @@ -34,6 +34,7 @@ import { import { CoreNavigator } from '@services/navigator'; import { CoreForms } from '@singletons/form'; import { CoreRecaptchaComponent } from '@components/recaptcha/recaptcha'; +import { CoreText } from '@singletons/text'; /** * Page to signup using email. @@ -161,7 +162,7 @@ export class CoreLoginEmailSignupPage implements OnInit { try { // Get site config. this.siteConfig = await CoreSites.getSitePublicConfig(this.siteUrl); - this.signupUrl = CoreTextUtils.concatenatePaths(this.siteConfig.httpswwwroot, 'login/signup.php'); + this.signupUrl = CoreText.concatenatePaths(this.siteConfig.httpswwwroot, 'login/signup.php'); if (this.treatSiteConfig()) { // Check content verification. @@ -385,7 +386,7 @@ export class CoreLoginEmailSignupPage implements OnInit { */ showContactOnSite(): void { CoreUtils.openInBrowser( - CoreTextUtils.concatenatePaths(this.siteUrl, '/login/verify_age_location.php'), + CoreText.concatenatePaths(this.siteUrl, '/login/verify_age_location.php'), { showBrowserWarning: false }, ); } diff --git a/src/core/features/login/services/login-helper.ts b/src/core/features/login/services/login-helper.ts index 7416cf7c4..fb14d6eea 100644 --- a/src/core/features/login/services/login-helper.ts +++ b/src/core/features/login/services/login-helper.ts @@ -36,6 +36,7 @@ import { CoreNavigator, CoreRedirectPayload } from '@services/navigator'; import { CoreCanceledError } from '@classes/errors/cancelederror'; import { CoreCustomURLSchemes } from '@services/urlschemes'; import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; +import { CoreText } from '@singletons/text'; /** * Helper provider that provides some common features regarding authentication. @@ -385,8 +386,8 @@ export class CoreLoginHelperProvider { } const validProviders: CoreSiteIdentityProvider[] = []; - const httpUrl = CoreTextUtils.concatenatePaths(siteConfig.wwwroot, 'auth/oauth2/'); - const httpsUrl = CoreTextUtils.concatenatePaths(siteConfig.httpswwwroot, 'auth/oauth2/'); + const httpUrl = CoreText.concatenatePaths(siteConfig.wwwroot, 'auth/oauth2/'); + const httpsUrl = CoreText.concatenatePaths(siteConfig.httpswwwroot, 'auth/oauth2/'); if (siteConfig.identityproviders && siteConfig.identityproviders.length) { siteConfig.identityproviders.forEach((provider) => { diff --git a/src/core/features/question/services/question.ts b/src/core/features/question/services/question.ts index 7514400fe..37914ca4b 100644 --- a/src/core/features/question/services/question.ts +++ b/src/core/features/question/services/question.ts @@ -21,6 +21,7 @@ import { CoreTimeUtils } from '@services/utils/time'; import { CoreUtils } from '@services/utils/utils'; import { CoreWSExternalFile } from '@services/ws'; import { makeSingleton } from '@singletons'; +import { CoreText } from '@singletons/text'; import { CoreQuestionAnswerDBRecord, CoreQuestionDBRecord, @@ -331,7 +332,7 @@ export class CoreQuestionProvider { const siteFolderPath = CoreFile.getSiteFolder(siteId); const questionFolderPath = 'offlinequestion/' + type + '/' + component + '/' + componentId; - return CoreTextUtils.concatenatePaths(siteFolderPath, questionFolderPath); + return CoreText.concatenatePaths(siteFolderPath, questionFolderPath); } /** diff --git a/src/core/features/sharedfiles/components/list/list.ts b/src/core/features/sharedfiles/components/list/list.ts index 936c2b71c..f56e5318f 100644 --- a/src/core/features/sharedfiles/components/list/list.ts +++ b/src/core/features/sharedfiles/components/list/list.ts @@ -20,8 +20,8 @@ import { Md5 } from 'ts-md5'; import { CoreSharedFiles } from '@features/sharedfiles/services/sharedfiles'; import { CoreNavigator } from '@services/navigator'; import { CoreSites } from '@services/sites'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreEventObserver, CoreEvents } from '@singletons/events'; +import { CoreText } from '@singletons/text'; /** * Component to display the list of shared files, either as a modal or inside a page. @@ -113,7 +113,7 @@ export class CoreSharedFilesListComponent implements OnInit, OnDestroy { * @param folder The folder to open. */ openFolder(folder: DirectoryEntry): void { - const path = CoreTextUtils.concatenatePaths(this.path || '', folder.name); + const path = CoreText.concatenatePaths(this.path || '', folder.name); if (this.isModal) { this.path = path; diff --git a/src/core/features/sharedfiles/services/sharedfiles.ts b/src/core/features/sharedfiles/services/sharedfiles.ts index 8384b6de2..599299576 100644 --- a/src/core/features/sharedfiles/services/sharedfiles.ts +++ b/src/core/features/sharedfiles/services/sharedfiles.ts @@ -21,12 +21,12 @@ import { CoreLogger } from '@singletons/logger'; import { CoreApp } from '@services/app'; import { CoreFile } from '@services/file'; import { CoreUtils } from '@services/utils/utils'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreMimetypeUtils } from '@services/utils/mimetype'; import { CoreSites } from '@services/sites'; import { CoreEvents } from '@singletons/events'; import { makeSingleton } from '@singletons'; import { APP_SCHEMA, CoreSharedFilesDBRecord, SHARED_FILES_TABLE_NAME } from './database/sharedfiles'; +import { CoreText } from '@singletons/text'; /** * Service to share files with the app. @@ -157,7 +157,7 @@ export class CoreSharedFilesProvider { async getSiteSharedFiles(siteId?: string, path?: string, mimetypes?: string[]): Promise<(FileEntry | DirectoryEntry)[]> { let pathToGet = this.getSiteSharedFilesDirPath(siteId); if (path) { - pathToGet = CoreTextUtils.concatenatePaths(pathToGet, path); + pathToGet = CoreText.concatenatePaths(pathToGet, path); } try { @@ -240,7 +240,7 @@ export class CoreSharedFilesProvider { newName = newName || entry.name; const sharedFilesFolder = this.getSiteSharedFilesDirPath(siteId); - const newPath = CoreTextUtils.concatenatePaths(sharedFilesFolder, newName); + const newPath = CoreText.concatenatePaths(sharedFilesFolder, newName); // Create dir if it doesn't exist already. await CoreFile.createDir(sharedFilesFolder); diff --git a/src/core/features/siteplugins/services/siteplugins-helper.ts b/src/core/features/siteplugins/services/siteplugins-helper.ts index 5de98ba3a..c036daacf 100644 --- a/src/core/features/siteplugins/services/siteplugins-helper.ts +++ b/src/core/features/siteplugins/services/siteplugins-helper.ts @@ -38,7 +38,6 @@ import { CoreFilepool } from '@services/filepool'; import { CoreLang } from '@services/lang'; import { CoreSites } from '@services/sites'; import { CoreTextUtils } from '@services/utils/text'; -import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils } from '@services/utils/utils'; import { CoreWS } from '@services/ws'; import { CoreEvents } from '@singletons/events'; @@ -85,6 +84,7 @@ import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/class import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; import { CoreObject } from '@singletons/object'; +import { CoreUrl } from '@singletons/url'; const HANDLER_DISABLED = 'core_site_plugins_helper_handler_disabled'; @@ -164,11 +164,8 @@ export class CoreSitePluginsHelperProvider { ): Promise { const site = await CoreSites.getSite(siteId); - // Get the absolute URL. If it's a relative URL, add the site URL to it. - let url = handlerSchema.styles?.url; - if (url && !CoreUrlUtils.isAbsoluteURL(url)) { - url = CoreTextUtils.concatenatePaths(site.getURL(), url); - } + // Make sure it's an absolute URL. + let url = handlerSchema.styles?.url ? CoreUrl.toAbsoluteURL(site.getURL(), handlerSchema.styles.url) : undefined; if (url && handlerSchema.styles?.version) { // Add the version to the URL to prevent getting a cached file. diff --git a/src/core/features/xapi/services/xapi.ts b/src/core/features/xapi/services/xapi.ts index 5e53fa214..8f6a6e75f 100644 --- a/src/core/features/xapi/services/xapi.ts +++ b/src/core/features/xapi/services/xapi.ts @@ -16,11 +16,11 @@ import { Injectable } from '@angular/core'; import { CoreApp } from '@services/app'; import { CoreSites } from '@services/sites'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUtils } from '@services/utils/utils'; import { CoreSite } from '@classes/site'; import { CoreXAPIOffline, CoreXAPIOfflineSaveStatementsOptions } from './offline'; import { makeSingleton } from '@singletons'; +import { CoreText } from '@singletons/text'; /** * Service to provide XAPI functionalities. @@ -65,7 +65,7 @@ export class CoreXAPIProvider { async getUrl(contextId: number, type: string, siteId?: string): Promise { const site = await CoreSites.getSite(siteId); - return CoreTextUtils.concatenatePaths(site.getURL(), `xapi/${type}/${contextId}`); + return CoreText.concatenatePaths(site.getURL(), `xapi/${type}/${contextId}`); } /** diff --git a/src/core/services/file.ts b/src/core/services/file.ts index 21fdc08bd..ce592709e 100644 --- a/src/core/services/file.ts +++ b/src/core/services/file.ts @@ -26,6 +26,7 @@ import { CoreError } from '@classes/errors/error'; import { CoreLogger } from '@singletons/logger'; import { makeSingleton, File, Zip, Platform, WebView } from '@singletons'; import { CoreFileEntry } from '@services/file-helper'; +import { CoreText } from '@singletons/text'; /** * Progress event used when writing a file data into a file. @@ -916,7 +917,7 @@ export class CoreFileProvider { if (path.indexOf(this.basePath) > -1) { return path; } else { - return CoreTextUtils.concatenatePaths(this.basePath, path); + return CoreText.concatenatePaths(this.basePath, path); } } @@ -1245,7 +1246,7 @@ export class CoreFileProvider { */ getWWWAbsolutePath(): string { if (window.cordova && cordova.file && cordova.file.applicationDirectory) { - return CoreTextUtils.concatenatePaths(cordova.file.applicationDirectory, 'www'); + return CoreText.concatenatePaths(cordova.file.applicationDirectory, 'www'); } // Cannot use Cordova to get it, use the WebView URL. diff --git a/src/core/services/filepool.ts b/src/core/services/filepool.ts index 55675f65f..3378566b6 100644 --- a/src/core/services/filepool.ts +++ b/src/core/services/filepool.ts @@ -52,6 +52,7 @@ import { CoreDatabaseTable } from '@classes/database/database-table'; import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; import { lazyMap, LazyMap } from '../utils/lazy-map'; import { asyncInstance, AsyncInstance } from '../utils/async-instance'; +import { CoreText } from '@singletons/text'; /* * Factory for handling downloading files and retrieve downloaded files. @@ -811,7 +812,7 @@ export class CoreFilepoolProvider { if (file.filepath && file.filepath !== '/') { path = file.filepath.substring(1) + path; } - path = CoreTextUtils.concatenatePaths(dirPath, path); + path = CoreText.concatenatePaths(dirPath, path); } if (prefetch) { @@ -905,7 +906,7 @@ export class CoreFilepoolProvider { if (file.filepath && file.filepath !== '/') { path = file.filepath.substring(1) + path; } - path = CoreTextUtils.concatenatePaths(dirPath, path); + path = CoreText.concatenatePaths(dirPath, path); } if (prefetch) { @@ -2948,7 +2949,7 @@ export class CoreFilepoolProvider { * and store the result in the CSS file. * * @param siteId Site ID. - * @param fileUrl CSS file URL. If a local path is supplied the app will assume it's the path where to write the file. + * @param fileUrl CSS file URL. It must be the online URL, not a local path. * @param cssCode CSS code. * @param component The component to link the file to. * @param componentId An ID to use in conjunction with the component. @@ -2967,19 +2968,24 @@ export class CoreFilepoolProvider { let updated = false; // Get the path of the CSS file. If it's a local file, assume it's the path where to write the file. - const filePath = CoreUrlUtils.isLocalFileUrl(fileUrl) ? - fileUrl : - await this.getFilePathByUrl(siteId, fileUrl); + const filePath = await this.getFilePathByUrl(siteId, fileUrl); // Download all files in the CSS. await Promise.all(urls.map(async (url) => { + if (!url.trim()) { + return; // Ignore empty URLs. + } + + const absoluteUrl = CoreUrl.toAbsoluteURL(fileUrl, url); + try { - let fileUrl = url; - if (!CoreUrlUtils.isLocalFileUrl(url)) { + let fileUrl = absoluteUrl; + + if (!CoreUrlUtils.isLocalFileUrl(absoluteUrl)) { // Not a local file, download it. fileUrl = await this.downloadUrl( siteId, - url, + absoluteUrl, false, component, componentId, @@ -2994,12 +3000,18 @@ export class CoreFilepoolProvider { // Convert the URL so it works in mobile devices. fileUrl = CoreFile.convertFileSrc(fileUrl); - if (fileUrl != url) { + if (fileUrl !== url) { cssCode = cssCode.replace(new RegExp(CoreTextUtils.escapeForRegex(url), 'g'), fileUrl); updated = true; } } catch (error) { this.logger.warn('Error treating file ', url, error); + + // If the URL is relative, store the absolute URL. + if (absoluteUrl !== url) { + cssCode = cssCode.replace(new RegExp(CoreTextUtils.escapeForRegex(url), 'g'), absoluteUrl); + updated = true; + } } })); diff --git a/src/core/services/urlschemes.ts b/src/core/services/urlschemes.ts index 9ae998d07..131d2d41c 100644 --- a/src/core/services/urlschemes.ts +++ b/src/core/services/urlschemes.ts @@ -21,6 +21,7 @@ import { CoreContentLinksHelper } from '@features/contentlinks/services/contentl import { CoreLoginHelper, CoreLoginSSOData } from '@features/login/services/login-helper'; import { ApplicationInit, makeSingleton, Translate } from '@singletons'; import { CoreLogger } from '@singletons/logger'; +import { CoreText } from '@singletons/text'; import { CoreConstants } from '../constants'; import { CoreApp } from './app'; import { CoreNavigator, CoreRedirectPayload } from './navigator'; @@ -163,7 +164,7 @@ export class CoreCustomURLSchemesProvider { if (data.redirect && !data.redirect.match(/^https?:\/\//)) { // Redirect is a relative URL. Append the site URL. - data.redirect = CoreTextUtils.concatenatePaths(data.siteUrl, data.redirect); + data.redirect = CoreText.concatenatePaths(data.siteUrl, data.redirect); } let siteIds = [siteId]; diff --git a/src/core/services/utils/iframe.ts b/src/core/services/utils/iframe.ts index 2e93414c7..31c32e7f2 100644 --- a/src/core/services/utils/iframe.ts +++ b/src/core/services/utils/iframe.ts @@ -21,7 +21,6 @@ import { CoreFile } from '@services/file'; import { CoreFileHelper } from '@services/file-helper'; import { CoreSites } from '@services/sites'; import { CoreDomUtils } from '@services/utils/dom'; -import { CoreTextUtils } from '@services/utils/text'; import { CoreUrlUtils } from '@services/utils/url'; import { CoreUtils, PromiseDefer } from '@services/utils/utils'; @@ -30,6 +29,7 @@ import { CoreLogger } from '@singletons/logger'; import { CoreUrl } from '@singletons/url'; import { CoreWindow } from '@singletons/window'; import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; +import { CoreText } from '@singletons/text'; /** * Possible types of frame elements. @@ -415,7 +415,7 @@ export class CoreIframeUtilsProvider { if (src) { const dirAndFile = CoreFile.getFileAndDirectoryFromPath(src); if (dirAndFile.directory) { - url = CoreTextUtils.concatenatePaths(dirAndFile.directory, url); + url = CoreText.concatenatePaths(dirAndFile.directory, url); } else { this.logger.warn('Cannot get iframe dir path to open relative url', url, element); @@ -555,7 +555,7 @@ export class CoreIframeUtilsProvider { */ injectiOSScripts(userScriptWindow: WKUserScriptWindow): void { const wwwPath = CoreFile.getWWWAbsolutePath(); - const linksPath = CoreTextUtils.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js'); + const linksPath = CoreText.concatenatePaths(wwwPath, 'assets/js/iframe-treat-links.js'); userScriptWindow.WKUserScript?.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath }); diff --git a/src/core/services/utils/text.ts b/src/core/services/utils/text.ts index 9a091db74..b54f6d575 100644 --- a/src/core/services/utils/text.ts +++ b/src/core/services/utils/text.ts @@ -269,24 +269,10 @@ export class CoreTextUtilsProvider { * @param leftPath Left path. * @param rightPath Right path. * @return Concatenated path. + * @deprecated since 4.0. Use CoreText instead. */ concatenatePaths(leftPath: string, rightPath: string): string { - if (!leftPath) { - return rightPath; - } else if (!rightPath) { - return leftPath; - } - - const lastCharLeft = leftPath.slice(-1); - const firstCharRight = rightPath.charAt(0); - - if (lastCharLeft === '/' && firstCharRight === '/') { - return leftPath + rightPath.substring(1); - } else if (lastCharLeft !== '/' && firstCharRight !== '/') { - return leftPath + '/' + rightPath; - } else { - return leftPath + rightPath; - } + return CoreText.concatenatePaths(leftPath, rightPath); } /** @@ -770,7 +756,7 @@ export class CoreTextUtilsProvider { return { text }; } - const draftfileUrl = this.concatenatePaths(siteUrl, 'draftfile.php'); + const draftfileUrl = CoreText.concatenatePaths(siteUrl, 'draftfile.php'); const matches = text.match(new RegExp(this.escapeForRegex(draftfileUrl) + '[^\'" ]+', 'ig')); if (!matches || !matches.length) { @@ -839,7 +825,7 @@ export class CoreTextUtilsProvider { return treatedText; } - const draftfileUrl = this.concatenatePaths(siteUrl, 'draftfile.php'); + const draftfileUrl = CoreText.concatenatePaths(siteUrl, 'draftfile.php'); const draftfileUrlRegexPrefix = this.escapeForRegex(draftfileUrl) + '/[^/]+/[^/]+/[^/]+/[^/]+/'; files.forEach((file) => { diff --git a/src/core/services/utils/url.ts b/src/core/services/utils/url.ts index 06681b696..a75e8d911 100644 --- a/src/core/services/utils/url.ts +++ b/src/core/services/utils/url.ts @@ -21,6 +21,7 @@ import { makeSingleton } from '@singletons'; import { CoreUrl } from '@singletons/url'; import { CoreApp } from '@services/app'; import { CoreSites } from '@services/sites'; +import { CoreText } from '@singletons/text'; /* * "Utils" service with helper functions for URLs. @@ -118,8 +119,8 @@ export class CoreUrlUtilsProvider { // Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work. // Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert). return !!accessKey && !url.match(/[&?]file=/) && ( - url.indexOf(CoreTextUtils.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 || - url.indexOf(CoreTextUtils.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0); + url.indexOf(CoreText.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 || + url.indexOf(CoreText.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0); } /** @@ -201,7 +202,7 @@ export class CoreUrlUtilsProvider { url = url.replace(/(\/webservice)?\/pluginfile\.php/, '/tokenpluginfile.php/' + accessKey); } else { // Use pluginfile.php. Some webservices returns directly the correct download url, others not. - if (url.indexOf(CoreTextUtils.concatenatePaths(siteUrl, 'pluginfile.php')) === 0) { + if (url.indexOf(CoreText.concatenatePaths(siteUrl, 'pluginfile.php')) === 0) { url = url.replace('/pluginfile', '/webservice/pluginfile'); } diff --git a/src/core/singletons/tests/url.test.ts b/src/core/singletons/tests/url.test.ts index b15d0e918..e8c7b7755 100644 --- a/src/core/singletons/tests/url.test.ts +++ b/src/core/singletons/tests/url.test.ts @@ -56,4 +56,12 @@ describe('CoreUrl singleton', () => { expect(CoreUrl.sameDomainAndPath('https://school.edu/moodle', 'https://school.edu/moodle/about')).toBe(false); }); + it('converts to absolute URLs', () => { + expect(CoreUrl.toAbsoluteURL('https://school.edu/foo/bar', 'https://mysite.edu')).toBe('https://mysite.edu'); + expect(CoreUrl.toAbsoluteURL('https://school.edu/foo/bar', '//mysite.edu')).toBe('https://mysite.edu'); + expect(CoreUrl.toAbsoluteURL('https://school.edu/foo/bar', '/image.png')).toBe('https://school.edu/image.png'); + expect(CoreUrl.toAbsoluteURL('https://school.edu/foo/bar', 'image.png')).toBe('https://school.edu/foo/bar/image.png'); + expect(CoreUrl.toAbsoluteURL('https://school.edu/foo.php', 'image.png')).toBe('https://school.edu/foo.php/image.png'); + }); + }); diff --git a/src/core/singletons/text.ts b/src/core/singletons/text.ts index 9f6c1af11..d84955206 100644 --- a/src/core/singletons/text.ts +++ b/src/core/singletons/text.ts @@ -40,4 +40,30 @@ export class CoreText { return text; } + /** + * Concatenate two paths, adding a slash between them if needed. + * + * @param leftPath Left path. + * @param rightPath Right path. + * @return Concatenated path. + */ + static concatenatePaths(leftPath: string, rightPath: string): string { + if (!leftPath) { + return rightPath; + } else if (!rightPath) { + return leftPath; + } + + const lastCharLeft = leftPath.slice(-1); + const firstCharRight = rightPath.charAt(0); + + if (lastCharLeft === '/' && firstCharRight === '/') { + return leftPath + rightPath.substring(1); + } else if (lastCharLeft !== '/' && firstCharRight !== '/') { + return leftPath + '/' + rightPath; + } else { + return leftPath + rightPath; + } + } + } diff --git a/src/core/singletons/url.ts b/src/core/singletons/url.ts index ebb7aa533..7a4bab437 100644 --- a/src/core/singletons/url.ts +++ b/src/core/singletons/url.ts @@ -111,6 +111,22 @@ export class CoreUrl { }; } + /** + * Given some parts of a URL, returns the URL as a string. + * + * @param parts Parts. + * @return Assembled URL. + */ + static assemble(parts: UrlParts): string { + return (parts.protocol ? `${parts.protocol}://` : '') + + (parts.credentials ? `${parts.credentials}@` : '') + + (parts.domain ?? '') + + (parts.port ? `:${parts.port}` : '') + + (parts.path ?? '') + + (parts.query ? `?${parts.query}` : '') + + (parts.fragment ? `#${parts.fragment}` : ''); + } + /** * Guess the Moodle domain from a site url. * @@ -231,4 +247,37 @@ export class CoreUrl { return urlAndAnchor[0]; } + /** + * Convert a URL to an absolute URL (if it isn't already). + * + * @param parentUrl The parent URL. + * @param url The url to convert. + * @return Absolute URL. + */ + static toAbsoluteURL(parentUrl: string, url: string): string { + const parsedUrl = CoreUrl.parse(url); + + if (parsedUrl?.protocol) { + return url; // Already absolute URL. + } + + const parsedParentUrl = CoreUrl.parse(parentUrl); + + if (url.startsWith('//')) { + // It only lacks the protocol, add it. + return (parsedParentUrl?.protocol || 'https') + ':' + url; + } + + // The URL should be added after the domain (if starts with /) or after the parent path. + const treatedParentUrl = CoreUrl.assemble({ + protocol: parsedParentUrl?.protocol || 'https', + domain: parsedParentUrl?.domain, + port: parsedParentUrl?.port, + credentials: parsedParentUrl?.credentials, + path: url.startsWith('/') ? undefined : parsedParentUrl?.path, + }); + + return CoreText.concatenatePaths(treatedParentUrl, url); + } + } From 1211bf1c032b12aa8377e4cb6da8a5567eecc511 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 18 Mar 2022 10:13:56 +0100 Subject: [PATCH 4/7] MOBILE-3833 course: Change courses routes to fix links --- .../features/courses/courses-lazy.module.ts | 72 ++++++++++++------- src/core/features/courses/courses.module.ts | 16 ++--- .../features/courses/pages/my/my.module.ts | 42 +++-------- .../services/handlers/my-courses-mainmenu.ts | 2 +- 4 files changed, 59 insertions(+), 73 deletions(-) diff --git a/src/core/features/courses/courses-lazy.module.ts b/src/core/features/courses/courses-lazy.module.ts index cfffc2db4..595dbc641 100644 --- a/src/core/features/courses/courses-lazy.module.ts +++ b/src/core/features/courses/courses-lazy.module.ts @@ -12,35 +12,53 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; +import { Injector, NgModule } from '@angular/core'; +import { RouterModule, ROUTES, Routes } from '@angular/router'; +import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module'; +import { CoreCoursesMyCoursesMainMenuHandlerService } from './services/handlers/my-courses-mainmenu'; -const routes: Routes = [ - { - path: '', - redirectTo: 'list', - pathMatch: 'full', - }, - { - path: 'categories', - redirectTo: 'categories/root', // Fake "id". - pathMatch: 'full', - }, - { - path: 'categories/:id', - loadChildren: () => - import('./pages/categories/categories.module') - .then(m => m.CoreCoursesCategoriesPageModule), - }, - { - path: 'list', - loadChildren: () => - import('./pages/list/list.module') - .then(m => m.CoreCoursesListPageModule), - }, -]; +function buildRoutes(injector: Injector): Routes { + return [ + { + path: 'my', + data: { + mainMenuTabRoot: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, + }, + loadChildren: () => import('./pages/my/my.module').then(m => m.CoreCoursesMyCoursesPageModule), + }, + { + path: 'categories', + redirectTo: 'categories/root', // Fake "id". + pathMatch: 'full', + }, + { + path: 'categories/:id', + loadChildren: () => + import('./pages/categories/categories.module') + .then(m => m.CoreCoursesCategoriesPageModule), + }, + { + path: 'list', + loadChildren: () => + import('./pages/list/list.module') + .then(m => m.CoreCoursesListPageModule), + }, + ...buildTabMainRoutes(injector, { + redirectTo: 'my', + pathMatch: 'full', + }), + ]; +} @NgModule({ - imports: [RouterModule.forChild(routes)], + exports: [RouterModule], + providers: [ + { + provide: ROUTES, + multi: true, + deps: [Injector], + useFactory: buildRoutes, + }, + ], }) export class CoreCoursesLazyModule {} diff --git a/src/core/features/courses/courses.module.ts b/src/core/features/courses/courses.module.ts index f4f801622..3423b63b2 100644 --- a/src/core/features/courses/courses.module.ts +++ b/src/core/features/courses/courses.module.ts @@ -50,17 +50,10 @@ const mainMenuHomeChildrenRoutes: Routes = [ }, ]; -const mainMenuHomeSiblingRoutes: Routes = [ - { - path: 'courses', - loadChildren: () => import('./courses-lazy.module').then(m => m.CoreCoursesLazyModule), - }, -]; - -const mainMenuTabRoutes: Routes = [ +const routes: Routes = [ { path: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, - loadChildren: () => import('./pages/my/my.module').then(m => m.CoreCoursesMyCoursesPageModule), + loadChildren: () => import('./courses-lazy.module').then(m => m.CoreCoursesLazyModule), }, ]; @@ -68,10 +61,9 @@ const mainMenuTabRoutes: Routes = [ imports: [ CoreMainMenuHomeRoutingModule.forChild({ children: mainMenuHomeChildrenRoutes, - siblings: mainMenuHomeSiblingRoutes, }), - CoreMainMenuRoutingModule.forChild({ children: mainMenuTabRoutes }), - CoreMainMenuTabRoutingModule.forChild(mainMenuTabRoutes), + CoreMainMenuRoutingModule.forChild({ children: routes }), + CoreMainMenuTabRoutingModule.forChild(routes), ], exports: [CoreMainMenuRoutingModule], providers: [ diff --git a/src/core/features/courses/pages/my/my.module.ts b/src/core/features/courses/pages/my/my.module.ts index c6ed5b0b3..96b405151 100644 --- a/src/core/features/courses/pages/my/my.module.ts +++ b/src/core/features/courses/pages/my/my.module.ts @@ -12,53 +12,29 @@ // See the License for the specific language governing permissions and // limitations under the License. -import { Injector, NgModule } from '@angular/core'; -import { RouterModule, ROUTES, Routes } from '@angular/router'; +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; import { CoreSharedModule } from '@/core/shared.module'; import { CoreBlockComponentsModule } from '@features/block/components/components.module'; import { CoreCoursesMyCoursesPage } from './my'; import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module'; -import { buildTabMainRoutes } from '@features/mainmenu/mainmenu-tab-routing.module'; -import { CoreCoursesMyCoursesMainMenuHandlerService } from '@features/courses/services/handlers/my-courses-mainmenu'; -function buildRoutes(injector: Injector): Routes { - return [ - { - path: '', - component: CoreCoursesMyCoursesPage, - data: { - mainMenuTabRoot: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, - }, - }, - { - path: 'list', - loadChildren: () => - import('../list/list.module') - .then(m => m.CoreCoursesListPageModule), - }, - ...buildTabMainRoutes(injector, { - redirectTo: '', - pathMatch: 'full', - }), - ]; -} +const routes: Routes = [ + { + path: '', + component: CoreCoursesMyCoursesPage, + }, +]; @NgModule({ imports: [ + RouterModule.forChild(routes), CoreSharedModule, CoreBlockComponentsModule, CoreMainMenuComponentsModule, ], - providers: [ - { - provide: ROUTES, - multi: true, - deps: [Injector], - useFactory: buildRoutes, - }, - ], declarations: [ CoreCoursesMyCoursesPage, ], diff --git a/src/core/features/courses/services/handlers/my-courses-mainmenu.ts b/src/core/features/courses/services/handlers/my-courses-mainmenu.ts index b800900f8..619327dc7 100644 --- a/src/core/features/courses/services/handlers/my-courses-mainmenu.ts +++ b/src/core/features/courses/services/handlers/my-courses-mainmenu.ts @@ -27,7 +27,7 @@ import { CoreDashboardHomeHandler } from './dashboard-home'; @Injectable({ providedIn: 'root' }) export class CoreCoursesMyCoursesMainMenuHandlerService implements CoreMainMenuHandler { - static readonly PAGE_NAME = 'my'; + static readonly PAGE_NAME = 'courses'; name = 'CoreCoursesMyCourses'; priority = 900; From 5e0a46aa75f3488551746832db6c40f60a537aa6 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 18 Mar 2022 10:15:48 +0100 Subject: [PATCH 5/7] MOBILE-3833 core: Fix page loaded when switch account --- src/core/guards/redirect.ts | 8 ++++++-- src/core/services/navigator.ts | 4 +++- 2 files changed, 9 insertions(+), 3 deletions(-) diff --git a/src/core/guards/redirect.ts b/src/core/guards/redirect.ts index e8dc44e78..da7c11a3e 100644 --- a/src/core/guards/redirect.ts +++ b/src/core/guards/redirect.ts @@ -55,11 +55,15 @@ export class CoreRedirectGuard implements CanLoad, CanActivate { // Redirect to site path. if (redirect.siteId && redirect.siteId !== CoreConstants.NO_SITE_ID) { const redirectData: CoreRedirectPayload = { - redirectPath: redirect.redirectPath, - redirectOptions: redirect.redirectOptions, urlToOpen: redirect.urlToOpen, }; + if (redirect.redirectPath !== 'main') { + // Only pass redirect path if the page to load isn't the main menu. + redirectData.redirectPath = redirect.redirectPath; + redirectData.redirectOptions = redirect.redirectOptions; + } + const loggedIn = await CoreSites.loadSite( redirect.siteId, redirectData, diff --git a/src/core/services/navigator.ts b/src/core/services/navigator.ts index 88e012094..e2613e579 100644 --- a/src/core/services/navigator.ts +++ b/src/core/services/navigator.ts @@ -182,7 +182,9 @@ export class CoreNavigatorService { * @return Whether navigation suceeded. */ async navigateToSiteHome(options: Omit & { siteId?: string } = {}): Promise { - const landingPagePath = this.getLandingTabPage(); + const siteId = options.siteId ?? CoreSites.getCurrentSiteId(); + const landingPagePath = CoreSites.isLoggedIn() && CoreSites.getCurrentSiteId() === siteId ? + this.getLandingTabPage() : 'main'; return this.navigateToSitePath(landingPagePath, { ...options, From da11a08c074d593f927d0df65f669520615ef1b7 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 18 Mar 2022 12:51:30 +0100 Subject: [PATCH 6/7] MOBILE-3833 course: Fix open site home from module summary --- src/core/features/course/services/course.ts | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/src/core/features/course/services/course.ts b/src/core/features/course/services/course.ts index d0997cd51..94609b78b 100644 --- a/src/core/features/course/services/course.ts +++ b/src/core/features/course/services/course.ts @@ -1266,6 +1266,13 @@ export class CoreCourseProvider { course: CoreCourseAnyCourseData | { id: number }, navOptions?: CoreNavigationOptions, ): Promise { + if (course.id === CoreSites.getCurrentSite()?.getSiteHomeId()) { + // Open site home. + await CoreNavigator.navigate('/main/home/site', navOptions); + + return; + } + const loading = await CoreDomUtils.showModalLoading(); // Wait for site plugins to be fetched. From 5ee3dc13e20d31dd20e44a663b282b4aa74cb2c4 Mon Sep 17 00:00:00 2001 From: Dani Palou Date: Fri, 18 Mar 2022 15:10:02 +0100 Subject: [PATCH 7/7] MOBILE-3833 sitehome: Fix site home link handler --- src/core/classes/tabs.ts | 53 ++++++++++++++----- .../components/tabs-outlet/tabs-outlet.ts | 29 ++++++++-- .../sitehome/services/handlers/index-link.ts | 23 +++----- 3 files changed, 71 insertions(+), 34 deletions(-) diff --git a/src/core/classes/tabs.ts b/src/core/classes/tabs.ts index bd4cf9efe..a535e831b 100644 --- a/src/core/classes/tabs.ts +++ b/src/core/classes/tabs.ts @@ -306,13 +306,7 @@ export class CoreTabsBaseComponent implements OnInit, Aft this.calculateSlides(); }); - let selectedTab: T | undefined = this.tabs[this.selectedIndex || 0] || undefined; - - if (!selectedTab || !selectedTab.enabled) { - // The tab is not enabled or not shown. Get the first tab that is enabled. - selectedTab = this.tabs.find((tab) => tab.enabled) || undefined; - } - + const selectedTab = this.calculateInitialTab(); if (!selectedTab) { return; } @@ -329,6 +323,22 @@ export class CoreTabsBaseComponent implements OnInit, Aft this.calculateSlides(); } + /** + * Calculate the initial tab to load. + * + * @return Initial tab, undefined if no valid tab found. + */ + protected calculateInitialTab(): T | undefined { + const selectedTab: T | undefined = this.tabs[this.selectedIndex || 0] || undefined; + + if (selectedTab && selectedTab.enabled) { + return selectedTab; + } + + // The tab is not enabled or not shown. Get the first tab that is enabled. + return this.tabs.find((tab) => tab.enabled) || undefined; + } + /** * Method executed when the slides are changed. */ @@ -564,8 +574,8 @@ export class CoreTabsBaseComponent implements OnInit, Aft } const tabToSelect = this.tabs[index]; - if (!tabToSelect || !tabToSelect.enabled || tabToSelect.id == this.selected) { - // Already selected or not enabled. + if (!tabToSelect || !tabToSelect.enabled) { + // Not enabled. return; } @@ -578,17 +588,32 @@ export class CoreTabsBaseComponent implements OnInit, Aft } } + if (tabToSelect.id === this.selected) { + // Already selected. + return; + } + const ok = await this.loadTab(tabToSelect); if (ok !== false) { - this.selectHistory.push(tabToSelect.id!); - this.selected = tabToSelect.id; - this.selectedIndex = index; - - this.ionChange.emit(tabToSelect); + this.tabSelected(tabToSelect, index); } } + /** + * Update selected tab. + * + * @param tab Tab. + * @param tabIndex Tab index. + */ + protected tabSelected(tab: T, tabIndex: number): void { + this.selectHistory.push(tab.id ?? ''); + this.selected = tab.id; + this.selectedIndex = tabIndex; + + this.ionChange.emit(tab); + } + /** * Load the tab. * diff --git a/src/core/components/tabs-outlet/tabs-outlet.ts b/src/core/components/tabs-outlet/tabs-outlet.ts index e2ffef98f..4a75166a9 100644 --- a/src/core/components/tabs-outlet/tabs-outlet.ts +++ b/src/core/components/tabs-outlet/tabs-outlet.ts @@ -88,7 +88,7 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent { super.ngAfterViewInit(); @@ -103,12 +103,20 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent tab.page == stackEvent.enteringView.url); + const tab = tabIndex >= 0 ? this.tabs[tabIndex] : undefined; + // Add tabid to the tab content element. if (stackEvent.enteringView.element.id == '') { - const tab = this.tabs.find((tab) => tab.page == stackEvent.enteringView.url); stackEvent.enteringView.element.id = tab?.id || ''; } + if (tab && this.selected !== tab.id) { + // Tab loaded using a navigation, update the selected tab. + this.tabSelected(tab, tabIndex); + } + this.showHideNavBarButtons(stackEvent.enteringView.element.tagName); await this.listenContentScroll(stackEvent.enteringView.element, stackEvent.enteringView.id); @@ -125,7 +133,7 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent): void { if (changes.tabs) { @@ -168,6 +176,21 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent tab.page === currentPath); + + if (currentPathTab && currentPathTab.enabled) { + return currentPathTab; + } + + return super.calculateInitialTab(); + } + /** * Get router outlet. * diff --git a/src/core/features/sitehome/services/handlers/index-link.ts b/src/core/features/sitehome/services/handlers/index-link.ts index fbc73a2f0..fcb3b4b99 100644 --- a/src/core/features/sitehome/services/handlers/index-link.ts +++ b/src/core/features/sitehome/services/handlers/index-link.ts @@ -32,32 +32,21 @@ export class CoreSiteHomeIndexLinkHandlerService extends CoreContentLinksHandler pattern = /\/course\/view\.php.*([?&]id=\d+)/; /** - * Get the list of actions for a link (url). - * - * @param siteIds List of sites the URL belongs to. - * @param url The URL to treat. - * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param courseId Course ID related to the URL. Optional but recommended. - * @return List of (or promise resolved with list of) actions. + * @inheritdoc */ getActions(): CoreContentLinksAction[] | Promise { return [{ action: (siteId: string): void => { - // @todo This should open the 'sitehome' setting as well. - CoreNavigator.navigateToSiteHome({ siteId }); + CoreNavigator.navigateToSitePath('/home/site', { + preferCurrentTab: false, + siteId, + }); }, }]; } /** - * Check if the handler is enabled for a certain site (site + user) and a URL. - * If not defined, defaults to true. - * - * @param siteId The site ID. - * @param url The URL to treat. - * @param params The params of the URL. E.g. 'mysite.com?id=1' -> {id: 1} - * @param courseId Course ID related to the URL. Optional but recommended. - * @return Whether the handler is enabled for the URL and site. + * @inheritdoc */ async isEnabled(siteId: string, url: string, params: Record, courseId?: number): Promise { courseId = parseInt(params.id, 10);