forked from EVOgeek/Vmeda.Online
		
	
						commit
						3f68944a66
					
				| @ -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<string> { | ||||
|         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; | ||||
|  | ||||
| @ -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' : | ||||
|  | ||||
| @ -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<string> { | ||||
|         const folderPath = await this.getSubmissionFolder(assignId, userId, siteId); | ||||
| 
 | ||||
|         return CoreTextUtils.concatenatePaths(folderPath, pluginName); | ||||
|         return CoreText.concatenatePaths(folderPath, pluginName); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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<string> { | ||||
|         const folderPath = await this.getDatabaseFolder(dataId, siteId); | ||||
| 
 | ||||
|         return CoreTextUtils.concatenatePaths(folderPath, entryId + '_' + fieldId); | ||||
|         return CoreText.concatenatePaths(folderPath, entryId + '_' + fieldId); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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<string> { | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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<string> { | ||||
|         const folderPath = await this.getGlossaryFolder(glossaryId, siteId); | ||||
| 
 | ||||
|         return CoreTextUtils.concatenatePaths(folderPath, 'newentry_' + concept + '_' + timeCreated); | ||||
|         return CoreText.concatenatePaths(folderPath, 'newentry_' + concept + '_' + timeCreated); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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()) { | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -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<string> { | ||||
|         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)); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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), | ||||
|             ); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -306,13 +306,7 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> 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<T extends CoreTabBase> 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<T extends CoreTabBase> 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<T extends CoreTabBase> 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. | ||||
|      * | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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) { | ||||
|  | ||||
| @ -88,7 +88,7 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * View has been initialized. | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     async ngAfterViewInit(): Promise<void> { | ||||
|         super.ngAfterViewInit(); | ||||
| @ -103,12 +103,20 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle | ||||
|                 return; | ||||
|             } | ||||
| 
 | ||||
|             // Search the tab loaded.
 | ||||
|             const tabIndex = this.tabs.findIndex((tab) => 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<CoreTabsOutle | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Detect changes on input properties. | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     ngOnChanges(changes: Record<string, SimpleChange>): void { | ||||
|         if (changes.tabs) { | ||||
| @ -168,6 +176,21 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle | ||||
|         this.lastActiveComponent?.ionViewDidLeave?.(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * @inheritdoc | ||||
|      */ | ||||
|     protected calculateInitialTab(): CoreTabsOutletTab | undefined { | ||||
|         // Check if a tab should be selected because it was loaded by path.
 | ||||
|         const currentPath = CoreNavigator.getCurrentPath(); | ||||
|         const currentPathTab = this.tabs.find(tab => tab.page === currentPath); | ||||
| 
 | ||||
|         if (currentPathTab && currentPathTab.enabled) { | ||||
|             return currentPathTab; | ||||
|         } | ||||
| 
 | ||||
|         return super.calculateInitialTab(); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Get router outlet. | ||||
|      * | ||||
|  | ||||
| @ -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 | ||||
| @ -689,7 +690,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]; | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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(); | ||||
|     } | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -1266,6 +1266,13 @@ export class CoreCourseProvider { | ||||
|         course: CoreCourseAnyCourseData | { id: number }, | ||||
|         navOptions?: CoreNavigationOptions, | ||||
|     ): Promise<void> { | ||||
|         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.
 | ||||
|  | ||||
| @ -12,14 +12,19 @@ | ||||
| // 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 = [ | ||||
| function buildRoutes(injector: Injector): Routes { | ||||
|     return [ | ||||
|         { | ||||
|         path: '', | ||||
|         redirectTo: 'list', | ||||
|         pathMatch: 'full', | ||||
|             path: 'my', | ||||
|             data: { | ||||
|                 mainMenuTabRoot: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, | ||||
|             }, | ||||
|             loadChildren: () => import('./pages/my/my.module').then(m => m.CoreCoursesMyCoursesPageModule), | ||||
|         }, | ||||
|         { | ||||
|             path: 'categories', | ||||
| @ -38,9 +43,22 @@ const routes: Routes = [ | ||||
|                 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 {} | ||||
|  | ||||
| @ -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: [ | ||||
|  | ||||
| @ -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 [ | ||||
| const routes: Routes = [ | ||||
|     { | ||||
|         path: '', | ||||
|         component: CoreCoursesMyCoursesPage, | ||||
|             data: { | ||||
|                 mainMenuTabRoot: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, | ||||
|     }, | ||||
|         }, | ||||
|         { | ||||
|             path: 'list', | ||||
|             loadChildren: () => | ||||
|                 import('../list/list.module') | ||||
|                     .then(m => m.CoreCoursesListPageModule), | ||||
|         }, | ||||
|         ...buildTabMainRoutes(injector, { | ||||
|             redirectTo: '', | ||||
|             pathMatch: 'full', | ||||
|         }), | ||||
|     ]; | ||||
| } | ||||
| ]; | ||||
| 
 | ||||
| @NgModule({ | ||||
|     imports: [ | ||||
|         RouterModule.forChild(routes), | ||||
|         CoreSharedModule, | ||||
|         CoreBlockComponentsModule, | ||||
|         CoreMainMenuComponentsModule, | ||||
|     ], | ||||
|     providers: [ | ||||
|         { | ||||
|             provide: ROUTES, | ||||
|             multi: true, | ||||
|             deps: [Injector], | ||||
|             useFactory: buildRoutes, | ||||
|         }, | ||||
|     ], | ||||
|     declarations: [ | ||||
|         CoreCoursesMyCoursesPage, | ||||
|     ], | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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); | ||||
| 
 | ||||
|  | ||||
| @ -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<void> { | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -113,8 +113,11 @@ export class FileTransferObjectMock extends FileTransferObject { | ||||
|             xhr.open('GET', source, true); | ||||
|             xhr.responseType = 'blob'; | ||||
|             for (const name in headers) { | ||||
|                 // We can't set the User-Agent in browser.
 | ||||
|                 if (name !== 'User-Agent') { | ||||
|                     xhr.setRequestHeader(name, headers[name]); | ||||
|                 } | ||||
|             } | ||||
| 
 | ||||
|             xhr.onprogress = (ev: ProgressEvent): void => { | ||||
|                 if (this.progressListener) { | ||||
| @ -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]); | ||||
|                     } | ||||
|                 } | ||||
|  | ||||
| @ -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<boolean> { | ||||
|         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<boolean> { | ||||
|         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<Entry> { | ||||
|         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<Entry> { | ||||
|         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('/')), | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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 { | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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<CoreH5PDependencyAsset[] | undefined> { | ||||
| 
 | ||||
|         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); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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<void> { | ||||
| 
 | ||||
|         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); | ||||
|  | ||||
| @ -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, '\\/') + '</script>'; | ||||
| 
 | ||||
|         // Add our own script to handle the params.
 | ||||
|         html += '<script type="text/javascript" src="' + CoreTextUtils.concatenatePaths( | ||||
|         html += '<script type="text/javascript" src="' + CoreText.concatenatePaths( | ||||
|             this.h5pCore.h5pFS.getCoreH5PPath(), | ||||
|             'moodle/js/params.js', | ||||
|         ) + '"></script>'; | ||||
| @ -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 += '<script type="text/javascript" src="' + | ||||
|                 CoreTextUtils.concatenatePaths(this.h5pCore.h5pFS.getCoreH5PPath(), 'moodle/js/embed.js') + '"></script>'; | ||||
|                 CoreText.concatenatePaths(this.h5pCore.h5pFS.getCoreH5PPath(), 'moodle/js/embed.js') + '"></script>'; | ||||
| 
 | ||||
|         result.jsRequires.forEach((jsUrl) => { | ||||
|             html += '<script type="text/javascript" src="' + jsUrl + '"></script>'; | ||||
| @ -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'); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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(<DirectoryEntry> 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<boolean> { | ||||
|         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<unknown> { | ||||
|         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<CoreH5PMainJSONData> { | ||||
|         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<CoreH5PLibraryMainJsonData> { | ||||
|         const path = CoreTextUtils.concatenatePaths(libPath, 'library.json'); | ||||
|         const path = CoreText.concatenatePaths(libPath, 'library.json'); | ||||
| 
 | ||||
|         return CoreFile.readFile<CoreH5PLibraryMainJsonData>(path, CoreFileFormat.FORMATJSON); | ||||
|     } | ||||
| @ -256,14 +256,14 @@ export class CoreH5PValidator { | ||||
|      */ | ||||
|     protected async readLibraryLanguageFiles(libPath: string): Promise<CoreH5PLibraryLangsJsonData | undefined> { | ||||
|         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<CoreH5PLibraryLangJsonData>( | ||||
| @ -293,7 +293,7 @@ export class CoreH5PValidator { | ||||
|      */ | ||||
|     protected async readLibrarySemanticsFile(libPath: string): Promise<CoreH5PSemantics[] | undefined> { | ||||
|         try { | ||||
|             const path = CoreTextUtils.concatenatePaths(libPath, 'semantics.json'); | ||||
|             const path = CoreText.concatenatePaths(libPath, 'semantics.json'); | ||||
| 
 | ||||
|             return await CoreFile.readFile<CoreH5PSemantics[]>(path, CoreFileFormat.FORMATJSON); | ||||
|         } catch (error) { | ||||
|  | ||||
| @ -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'); | ||||
|         } | ||||
| 
 | ||||
|  | ||||
| @ -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 }, | ||||
|         ); | ||||
|     } | ||||
|  | ||||
| @ -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) => { | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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); | ||||
|  | ||||
| @ -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<CoreContentLinksAction[]> { | ||||
|         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<string, string>, courseId?: number): Promise<boolean> { | ||||
|         courseId = parseInt(params.id, 10); | ||||
|  | ||||
| @ -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<string> { | ||||
|         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.
 | ||||
|  | ||||
| @ -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<string> { | ||||
|         const site = await CoreSites.getSite(siteId); | ||||
| 
 | ||||
|         return CoreTextUtils.concatenatePaths(site.getURL(), `xapi/${type}/${contextId}`); | ||||
|         return CoreText.concatenatePaths(site.getURL(), `xapi/${type}/${contextId}`); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|  | ||||
| @ -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, | ||||
|  | ||||
| @ -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 {} | ||||
|  | ||||
							
								
								
									
										67
									
								
								src/core/pipes/to-locale-string.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										67
									
								
								src/core/pipes/to-locale-string.ts
									
									
									
									
									
										Normal file
									
								
							| @ -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'); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
| @ -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.
 | ||||
|  | ||||
| @ -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; | ||||
|                 } | ||||
|             } | ||||
|         })); | ||||
| 
 | ||||
|  | ||||
| @ -182,7 +182,9 @@ export class CoreNavigatorService { | ||||
|      * @return Whether navigation suceeded. | ||||
|      */ | ||||
|     async navigateToSiteHome(options: Omit<CoreNavigationOptions, 'reset'> & { siteId?: string } = {}): Promise<boolean> { | ||||
|         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, | ||||
|  | ||||
| @ -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]; | ||||
|  | ||||
| @ -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. | ||||
| @ -420,7 +420,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); | ||||
| 
 | ||||
| @ -560,7 +560,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 }); | ||||
| 
 | ||||
|  | ||||
| @ -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) => { | ||||
|  | ||||
| @ -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'); | ||||
|             } | ||||
| 
 | ||||
|  | ||||
| @ -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; | ||||
|  | ||||
| @ -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'); | ||||
|     }); | ||||
| 
 | ||||
| }); | ||||
|  | ||||
| @ -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; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
| @ -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); | ||||
|     } | ||||
| 
 | ||||
| } | ||||
|  | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user