forked from EVOgeek/Vmeda.Online
		
	
						commit
						3f68944a66
					
				| @ -40,6 +40,7 @@ import { CoreNavigator } from '@services/navigator'; | |||||||
| import { AddonCalendarFilter } from './calendar-helper'; | import { AddonCalendarFilter } from './calendar-helper'; | ||||||
| import { AddonCalendarSyncEvents, AddonCalendarSyncProvider } from './calendar-sync'; | import { AddonCalendarSyncEvents, AddonCalendarSyncProvider } from './calendar-sync'; | ||||||
| import { CoreEvents } from '@singletons/events'; | import { CoreEvents } from '@singletons/events'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| const ROOT_CACHE_KEY = 'mmaCalendar:'; | const ROOT_CACHE_KEY = 'mmaCalendar:'; | ||||||
| 
 | 
 | ||||||
| @ -1218,7 +1219,7 @@ export class AddonCalendarProvider { | |||||||
|      */ |      */ | ||||||
|     async getViewUrl(view: string, time?: number, courseId?: string, siteId?: string): Promise<string> { |     async getViewUrl(view: string, time?: number, courseId?: string, siteId?: string): Promise<string> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         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) { |         if (time) { | ||||||
|             url += '&time=' + time; |             url += '&time=' + time; | ||||||
|  | |||||||
| @ -23,8 +23,8 @@ import { CoreWSError } from '@classes/errors/wserror'; | |||||||
| import { makeSingleton, Translate } from '@singletons'; | import { makeSingleton, Translate } from '@singletons'; | ||||||
| import { CoreEvents, CoreEventSiteData } from '@singletons/events'; | import { CoreEvents, CoreEventSiteData } from '@singletons/events'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| const ROOT_CACHE_KEY = 'mmaMessageOutputAirnotifier:'; | const ROOT_CACHE_KEY = 'mmaMessageOutputAirnotifier:'; | ||||||
| 
 | 
 | ||||||
| @ -214,7 +214,7 @@ export class AddonMessageOutputAirnotifierProvider { | |||||||
|                         handler: (data, resolve) => { |                         handler: (data, resolve) => { | ||||||
|                             resolve(data[0]); |                             resolve(data[0]); | ||||||
| 
 | 
 | ||||||
|                             const url = CoreTextUtils.concatenatePaths( |                             const url = CoreText.concatenatePaths( | ||||||
|                                 site.getURL(), |                                 site.getURL(), | ||||||
|                                 site.isVersionGreaterEqualThan('3.11') ? |                                 site.isVersionGreaterEqualThan('3.11') ? | ||||||
|                                     'message/output/airnotifier/checkconfiguration.php' : |                                     'message/output/airnotifier/checkconfiguration.php' : | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ import { CoreSites } from '@services/sites'; | |||||||
| import { CoreTextUtils } from '@services/utils/text'; | import { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { CoreTimeUtils } from '@services/utils/time'; | import { CoreTimeUtils } from '@services/utils/time'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { AddonModAssignOutcomes, AddonModAssignSavePluginData } from './assign'; | import { AddonModAssignOutcomes, AddonModAssignSavePluginData } from './assign'; | ||||||
| import { | import { | ||||||
|     AddonModAssignSubmissionsDBRecord, |     AddonModAssignSubmissionsDBRecord, | ||||||
| @ -236,7 +237,7 @@ export class AddonModAssignOfflineProvider { | |||||||
|         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); |         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); | ||||||
|         const submissionFolderPath = 'offlineassign/' + assignId + '/' + userId; |         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> { |     async getSubmissionPluginFolder(assignId: number, pluginName: string, userId?: number, siteId?: string): Promise<string> { | ||||||
|         const folderPath = await this.getSubmissionFolder(assignId, userId, siteId); |         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 { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { AddonModDataAction, AddonModDataEntryWSField } from './data'; | import { AddonModDataAction, AddonModDataEntryWSField } from './data'; | ||||||
| import { AddonModDataEntryDBRecord, DATA_ENTRY_TABLE } from './database/data'; | import { AddonModDataEntryDBRecord, DATA_ENTRY_TABLE } from './database/data'; | ||||||
| 
 | 
 | ||||||
| @ -206,7 +207,7 @@ export class AddonModDataOfflineProvider { | |||||||
|         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); |         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); | ||||||
|         const folderPath = 'offlinedatabase/' + dataId; |         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> { |     async getEntryFieldFolder(dataId: number, entryId: number, fieldId: number, siteId?: string): Promise<string> { | ||||||
|         const folderPath = await this.getDatabaseFolder(dataId, siteId); |         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, |     DISCUSSIONS_TABLE, | ||||||
|     REPLIES_TABLE, |     REPLIES_TABLE, | ||||||
| } from './database/offline'; | } from './database/offline'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to handle offline forum. |  * Service to handle offline forum. | ||||||
| @ -341,7 +342,7 @@ export class AddonModForumOfflineProvider { | |||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
|         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); |         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> { |     async getNewDiscussionFolder(forumId: number, timeCreated: number, siteId?: string): Promise<string> { | ||||||
|         const folderPath = await this.getForumFolder(forumId, siteId); |         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); |         const site = await CoreSites.getSite(siteId); | ||||||
|         userId = userId || site.getUserId(); |         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 { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary'; | import { AddonModGlossaryOfflineEntryDBRecord, OFFLINE_ENTRIES_TABLE_NAME } from './database/glossary'; | ||||||
| import { AddonModGlossaryDiscardedEntry, AddonModGlossaryEntryOption } from './glossary'; | import { AddonModGlossaryDiscardedEntry, AddonModGlossaryEntryOption } from './glossary'; | ||||||
| 
 | 
 | ||||||
| @ -213,7 +214,7 @@ export class AddonModGlossaryOfflineProvider { | |||||||
|         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); |         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); | ||||||
|         const folderPath = 'offlineglossary/' + glossaryId; |         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> { |     async getEntryFolder(glossaryId: number, concept: string, timeCreated: number, siteId?: string): Promise<string> { | ||||||
|         const folderPath = await this.getGlossaryFolder(glossaryId, siteId); |         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 { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; | import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws'; | ||||||
| import { makeSingleton, Translate } from '@singletons'; | import { makeSingleton, Translate } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| const ROOT_CACHE_KEY = 'mmaModImscp:'; | const ROOT_CACHE_KEY = 'mmaModImscp:'; | ||||||
| 
 | 
 | ||||||
| @ -154,7 +155,7 @@ export class AddonModImscpProvider { | |||||||
|                 return false; |                 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; |             const filePathAlt = filePath.charAt(0) === '/' ? filePath.substring(1) : '/' + filePath; | ||||||
| 
 | 
 | ||||||
|             // Check if it's main file.
 |             // Check if it's main file.
 | ||||||
| @ -177,7 +178,7 @@ export class AddonModImscpProvider { | |||||||
|         try { |         try { | ||||||
|             const dirPath = await CoreFilepool.getPackageDirUrlByUrl(siteId, module.url || ''); |             const dirPath = await CoreFilepool.getPackageDirUrlByUrl(siteId, module.url || ''); | ||||||
| 
 | 
 | ||||||
|             return CoreTextUtils.concatenatePaths(dirPath, itemHref); |             return CoreText.concatenatePaths(dirPath, itemHref); | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|             // Error getting directory, there was an error downloading or we're in browser. Return online URL if connected.
 |             // Error getting directory, there was an error downloading or we're in browser. Return online URL if connected.
 | ||||||
|             if (CoreApp.isOnline()) { |             if (CoreApp.isOnline()) { | ||||||
|  | |||||||
| @ -24,9 +24,9 @@ import { CoreFilepool } from '@services/filepool'; | |||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtilsOpenFileOptions } from '@services/utils/utils'; | import { CoreUtilsOpenFileOptions } from '@services/utils/utils'; | ||||||
| import { makeSingleton, Translate } from '@singletons'; | import { makeSingleton, Translate } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { AddonModResource, AddonModResourceProvider } from './resource'; | import { AddonModResource, AddonModResourceProvider } from './resource'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -77,7 +77,7 @@ export class AddonModResourceHelperProvider { | |||||||
|             const dirPath = await CoreFilepool.getPackageDirUrlByUrl(CoreSites.getCurrentSiteId(), module.url!); |             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.
 |             // 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) { |         } catch (e) { | ||||||
|             // Error getting directory, there was an error downloading or we're in browser. Return online URL.
 |             // Error getting directory, there was an error downloading or we're in browser. Return online URL.
 | ||||||
|             if (CoreApp.isOnline() && mainFile.fileurl) { |             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 { CoreWS, CoreWSExternalFile, CoreWSExternalWarning, CoreWSFile, CoreWSPreSets } from '@services/ws'; | ||||||
| import { makeSingleton, Translate } from '@singletons'; | import { makeSingleton, Translate } from '@singletons'; | ||||||
| import { CoreEvents } from '@singletons/events'; | import { CoreEvents } from '@singletons/events'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { AddonModScormOffline } from './scorm-offline'; | import { AddonModScormOffline } from './scorm-offline'; | ||||||
| import { AddonModScormAutoSyncEventData, AddonModScormSyncProvider } from './scorm-sync'; | import { AddonModScormAutoSyncEventData, AddonModScormSyncProvider } from './scorm-sync'; | ||||||
| 
 | 
 | ||||||
| @ -960,7 +961,7 @@ export class AddonModScormProvider { | |||||||
| 
 | 
 | ||||||
|         const dirPath = await CoreFilepool.getPackageDirUrlByUrl(siteId, scorm.moduleurl!); |         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 { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { Network, Translate, NgZone } from '@singletons'; | import { Network, Translate, NgZone } from '@singletons'; | ||||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | import { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { Subscription } from 'rxjs'; | import { Subscription } from 'rxjs'; | ||||||
| import { Md5 } from 'ts-md5'; | import { Md5 } from 'ts-md5'; | ||||||
| import { AddonModWikiPageDBRecord } from '../../services/database/wiki'; | import { AddonModWikiPageDBRecord } from '../../services/database/wiki'; | ||||||
| @ -676,7 +676,7 @@ export class AddonModWikiIndexComponent extends CoreCourseModuleMainActivityComp | |||||||
|         content = content.trim(); |         content = content.trim(); | ||||||
| 
 | 
 | ||||||
|         if (content.length > 0) { |         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); |             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 { CoreTimeUtils } from '@services/utils/time'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { CoreFormFields } from '@singletons/form'; | import { CoreFormFields } from '@singletons/form'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { | import { | ||||||
|     AddonModWorkshopAssessmentDBRecord, |     AddonModWorkshopAssessmentDBRecord, | ||||||
|     AddonModWorkshopEvaluateAssessmentDBRecord, |     AddonModWorkshopEvaluateAssessmentDBRecord, | ||||||
| @ -629,7 +630,7 @@ export class AddonModWorkshopOfflineProvider { | |||||||
|         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); |         const siteFolderPath = CoreFile.getSiteFolder(site.getId()); | ||||||
|         const workshopFolderPath = 'offlineworkshop/' + workshopId + '/'; |         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> { |     async getSubmissionFolder(workshopId: number, siteId?: string): Promise<string> { | ||||||
|         const folderPath = await this.getWorkshopFolder(workshopId, siteId); |         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/'; |         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 != '') { |         if (style != '') { | ||||||
|             // Treat the CSS.
 |             // Treat the CSS.
 | ||||||
|             CoreUtils.ignoreErrors( |             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(); |             this.calculateSlides(); | ||||||
|         }); |         }); | ||||||
| 
 | 
 | ||||||
|         let selectedTab: T | undefined = this.tabs[this.selectedIndex || 0] || undefined; |         const selectedTab = this.calculateInitialTab(); | ||||||
| 
 |  | ||||||
|         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; |  | ||||||
|         } |  | ||||||
| 
 |  | ||||||
|         if (!selectedTab) { |         if (!selectedTab) { | ||||||
|             return; |             return; | ||||||
|         } |         } | ||||||
| @ -329,6 +323,22 @@ export class CoreTabsBaseComponent<T extends CoreTabBase> implements OnInit, Aft | |||||||
|         this.calculateSlides(); |         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. |      * 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]; |         const tabToSelect = this.tabs[index]; | ||||||
|         if (!tabToSelect || !tabToSelect.enabled || tabToSelect.id == this.selected) { |         if (!tabToSelect || !tabToSelect.enabled) { | ||||||
|             // Already selected or not enabled.
 |             // Not enabled.
 | ||||||
|             return; |             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); |         const ok = await this.loadTab(tabToSelect); | ||||||
| 
 | 
 | ||||||
|         if (ok !== false) { |         if (ok !== false) { | ||||||
|             this.selectHistory.push(tabToSelect.id!); |             this.tabSelected(tabToSelect, index); | ||||||
|             this.selected = tabToSelect.id; |  | ||||||
|             this.selectedIndex = index; |  | ||||||
| 
 |  | ||||||
|             this.ionChange.emit(tabToSelect); |  | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|  |     /** | ||||||
|  |      * 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. |      * Load the tab. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -26,6 +26,7 @@ import { CoreTimeUtils } from '@services/utils/time'; | |||||||
| import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/utils/utils'; | import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/utils/utils'; | ||||||
| import { CoreForms } from '@singletons/form'; | import { CoreForms } from '@singletons/form'; | ||||||
| import { CoreApp } from '@services/app'; | 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. |  * 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 modal = await CoreDomUtils.showModalLoading(); | ||||||
|         const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.relativePath!); |         const fileAndDir = CoreFile.getFileAndDirectoryFromPath(this.relativePath!); | ||||||
|         const newPath = CoreTextUtils.concatenatePaths(fileAndDir.directory, newName); |         const newPath = CoreText.concatenatePaths(fileAndDir.directory, newName); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             // Check if there's a file with this name.
 |             // 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 { CoreLang } from '@services/lang'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Component that allows answering a recaptcha. |  * Component that allows answering a recaptcha. | ||||||
| @ -62,7 +62,7 @@ export class CoreRecaptchaComponent implements OnInit { | |||||||
|         // Open the recaptcha challenge in an InAppBrowser.
 |         // 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 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.
 |         // 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); |         const inAppBrowserWindow = CoreUtils.openInApp(src); | ||||||
|         if (!inAppBrowserWindow) { |         if (!inAppBrowserWindow) { | ||||||
|  | |||||||
| @ -88,7 +88,7 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle | |||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * View has been initialized. |      * @inheritdoc | ||||||
|      */ |      */ | ||||||
|     async ngAfterViewInit(): Promise<void> { |     async ngAfterViewInit(): Promise<void> { | ||||||
|         super.ngAfterViewInit(); |         super.ngAfterViewInit(); | ||||||
| @ -103,12 +103,20 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle | |||||||
|                 return; |                 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.
 |             // Add tabid to the tab content element.
 | ||||||
|             if (stackEvent.enteringView.element.id == '') { |             if (stackEvent.enteringView.element.id == '') { | ||||||
|                 const tab = this.tabs.find((tab) => tab.page == stackEvent.enteringView.url); |  | ||||||
|                 stackEvent.enteringView.element.id = tab?.id || ''; |                 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); |             this.showHideNavBarButtons(stackEvent.enteringView.element.tagName); | ||||||
| 
 | 
 | ||||||
|             await this.listenContentScroll(stackEvent.enteringView.element, stackEvent.enteringView.id); |             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 { |     ngOnChanges(changes: Record<string, SimpleChange>): void { | ||||||
|         if (changes.tabs) { |         if (changes.tabs) { | ||||||
| @ -168,6 +176,21 @@ export class CoreTabsOutletComponent extends CoreTabsBaseComponent<CoreTabsOutle | |||||||
|         this.lastActiveComponent?.ionViewDidLeave?.(); |         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. |      * Get router outlet. | ||||||
|      * |      * | ||||||
|  | |||||||
| @ -44,6 +44,7 @@ import { CoreComponentsRegistry } from '@singletons/components-registry'; | |||||||
| import { CoreCollapsibleItemDirective } from './collapsible-item'; | import { CoreCollapsibleItemDirective } from './collapsible-item'; | ||||||
| import { CoreCancellablePromise } from '@classes/cancellable-promise'; | import { CoreCancellablePromise } from '@classes/cancellable-promise'; | ||||||
| import { AsyncComponent } from '@classes/async-component'; | 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 |  * 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.
 |             // 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]*))?/); |             const matches = src.match(/https?:\/\/player\.vimeo\.com\/video\/([0-9]+)([?&]+h=([a-zA-Z0-9]*))?/); | ||||||
|             if (matches && matches[1]) { |             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(); |                     matches[1] + '&token=' + site.getToken(); | ||||||
| 
 | 
 | ||||||
|                 let privacyHash: string | undefined | null = matches[3]; |                 let privacyHash: string | undefined | null = matches[3]; | ||||||
|  | |||||||
| @ -27,6 +27,7 @@ import { CoreContentLinksHelper } from '@features/contentlinks/services/contentl | |||||||
| import { CoreCustomURLSchemes } from '@services/urlschemes'; | import { CoreCustomURLSchemes } from '@services/urlschemes'; | ||||||
| import { DomSanitizer } from '@singletons'; | import { DomSanitizer } from '@singletons'; | ||||||
| import { CoreFilepool } from '@services/filepool'; | import { CoreFilepool } from '@services/filepool'; | ||||||
|  | import { CoreUrl } from '@singletons/url'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Directive to open a link in external browser or in the app. |  * 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(); |         const currentSite = CoreSites.getRequiredCurrentSite(); | ||||||
| 
 | 
 | ||||||
|         // Check if URL does not have any protocol, so it's a relative URL.
 |         // Make sure it's an absolute URL.
 | ||||||
|         if (!CoreUrlUtils.isAbsoluteURL(href)) { |         href = CoreUrl.toAbsoluteURL(currentSite.getURL(), href); | ||||||
|             // Add the site URL at the begining.
 |  | ||||||
|             if (href.charAt(0) == '/') { |  | ||||||
|                 href = currentSite.getURL() + href; |  | ||||||
|             } else { |  | ||||||
|                 href = currentSite.getURL() + '/' + href; |  | ||||||
|             } |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         if (currentSite.isSitePluginFileUrl(href)) { |         if (currentSite.isSitePluginFileUrl(href)) { | ||||||
|             // It's a site file. Check if it's being downloaded right now.
 |             // 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 { CoreEventObserver, CoreEvents } from '@singletons/events'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { | import { | ||||||
|     CoreCourseCustomField, |     CoreCourseCustomField, | ||||||
|     CoreCourseEnrolmentMethod, |     CoreCourseEnrolmentMethod, | ||||||
| @ -38,6 +37,7 @@ import { CoreUtils } from '@services/utils/utils'; | |||||||
| import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper'; | import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper'; | ||||||
| import { Subscription } from 'rxjs'; | import { Subscription } from 'rxjs'; | ||||||
| import { CoreColors } from '@singletons/colors'; | 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. |  * 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(); |         const currentSiteUrl = CoreSites.getRequiredCurrentSite().getURL(); | ||||||
|         this.enrolUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId); |         this.enrolUrl = CoreText.concatenatePaths(currentSiteUrl, 'enrol/index.php?id=' + this.courseId); | ||||||
|         this.courseUrl = CoreTextUtils.concatenatePaths(currentSiteUrl, 'course/view.php?id=' + this.courseId); |         this.courseUrl = CoreText.concatenatePaths(currentSiteUrl, 'course/view.php?id=' + this.courseId); | ||||||
| 
 | 
 | ||||||
|         await this.getCourse(); |         await this.getCourse(); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -23,13 +23,13 @@ import { CoreEventObserver, CoreEvents } from '@singletons/events'; | |||||||
| import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseWSSection } from '@features/course/services/course'; | import { CoreCourse, CoreCourseModuleCompletionStatus, CoreCourseWSSection } from '@features/course/services/course'; | ||||||
| import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper'; | import { CoreCourseHelper, CoreCourseModuleData } from '@features/course/services/course-helper'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | import { CoreNavigationOptions, CoreNavigator } from '@services/navigator'; | ||||||
| import { CONTENTS_PAGE_NAME } from '@features/course/course.module'; | import { CONTENTS_PAGE_NAME } from '@features/course/course.module'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreCourseSummaryPage } from '../course-summary/course-summary'; | import { CoreCourseSummaryPage } from '../course-summary/course-summary'; | ||||||
| import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper'; | import { CoreCoursesHelper, CoreCourseWithImageAndColor } from '@features/courses/services/courses-helper'; | ||||||
| import { CoreColors } from '@singletons/colors'; | import { CoreColors } from '@singletons/colors'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page that displays the list of courses the user is enrolled in. |  * 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.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 = { |         this.contentsTab.pageParams = { | ||||||
|             course: this.course, |             course: this.course, | ||||||
|             sectionId: CoreNavigator.getRouteNumberParam('sectionId'), |             sectionId: CoreNavigator.getRouteNumberParam('sectionId'), | ||||||
| @ -207,7 +207,7 @@ export class CoreCourseIndexPage implements OnInit, OnDestroy { | |||||||
| 
 | 
 | ||||||
|         // Create the full path.
 |         // Create the full path.
 | ||||||
|         handlers.forEach((handler, index) => { |         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 || {}; |             handler.data.pageParams = handler.data.pageParams || {}; | ||||||
| 
 | 
 | ||||||
|             // Check if this handler should be the first selected tab.
 |             // Check if this handler should be the first selected tab.
 | ||||||
|  | |||||||
| @ -1266,6 +1266,13 @@ export class CoreCourseProvider { | |||||||
|         course: CoreCourseAnyCourseData | { id: number }, |         course: CoreCourseAnyCourseData | { id: number }, | ||||||
|         navOptions?: CoreNavigationOptions, |         navOptions?: CoreNavigationOptions, | ||||||
|     ): Promise<void> { |     ): Promise<void> { | ||||||
|  |         if (course.id === CoreSites.getCurrentSite()?.getSiteHomeId()) { | ||||||
|  |             // Open site home.
 | ||||||
|  |             await CoreNavigator.navigate('/main/home/site', navOptions); | ||||||
|  | 
 | ||||||
|  |             return; | ||||||
|  |         } | ||||||
|  | 
 | ||||||
|         const loading = await CoreDomUtils.showModalLoading(); |         const loading = await CoreDomUtils.showModalLoading(); | ||||||
| 
 | 
 | ||||||
|         // Wait for site plugins to be fetched.
 |         // Wait for site plugins to be fetched.
 | ||||||
|  | |||||||
| @ -12,35 +12,53 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { NgModule } from '@angular/core'; | import { Injector, NgModule } from '@angular/core'; | ||||||
| import { RouterModule, Routes } from '@angular/router'; | 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', |             path: 'my', | ||||||
|         pathMatch: 'full', |             data: { | ||||||
|     }, |                 mainMenuTabRoot: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, | ||||||
|     { |             }, | ||||||
|         path: 'categories', |             loadChildren: () => import('./pages/my/my.module').then(m => m.CoreCoursesMyCoursesPageModule), | ||||||
|         redirectTo: 'categories/root', // Fake "id".
 |         }, | ||||||
|         pathMatch: 'full', |         { | ||||||
|     }, |             path: 'categories', | ||||||
|     { |             redirectTo: 'categories/root', // Fake "id".
 | ||||||
|         path: 'categories/:id', |             pathMatch: 'full', | ||||||
|         loadChildren: () => |         }, | ||||||
|             import('./pages/categories/categories.module') |         { | ||||||
|                 .then(m => m.CoreCoursesCategoriesPageModule), |             path: 'categories/:id', | ||||||
|     }, |             loadChildren: () => | ||||||
|     { |                 import('./pages/categories/categories.module') | ||||||
|         path: 'list', |                     .then(m => m.CoreCoursesCategoriesPageModule), | ||||||
|         loadChildren: () => |         }, | ||||||
|             import('./pages/list/list.module') |         { | ||||||
|                 .then(m => m.CoreCoursesListPageModule), |             path: 'list', | ||||||
|     }, |             loadChildren: () => | ||||||
| ]; |                 import('./pages/list/list.module') | ||||||
|  |                     .then(m => m.CoreCoursesListPageModule), | ||||||
|  |         }, | ||||||
|  |         ...buildTabMainRoutes(injector, { | ||||||
|  |             redirectTo: 'my', | ||||||
|  |             pathMatch: 'full', | ||||||
|  |         }), | ||||||
|  |     ]; | ||||||
|  | } | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     imports: [RouterModule.forChild(routes)], |     exports: [RouterModule], | ||||||
|  |     providers: [ | ||||||
|  |         { | ||||||
|  |             provide: ROUTES, | ||||||
|  |             multi: true, | ||||||
|  |             deps: [Injector], | ||||||
|  |             useFactory: buildRoutes, | ||||||
|  |         }, | ||||||
|  |     ], | ||||||
| }) | }) | ||||||
| export class CoreCoursesLazyModule {} | export class CoreCoursesLazyModule {} | ||||||
|  | |||||||
| @ -50,17 +50,10 @@ const mainMenuHomeChildrenRoutes: Routes = [ | |||||||
|     }, |     }, | ||||||
| ]; | ]; | ||||||
| 
 | 
 | ||||||
| const mainMenuHomeSiblingRoutes: Routes = [ | const routes: Routes = [ | ||||||
|     { |  | ||||||
|         path: 'courses', |  | ||||||
|         loadChildren: () => import('./courses-lazy.module').then(m => m.CoreCoursesLazyModule), |  | ||||||
|     }, |  | ||||||
| ]; |  | ||||||
| 
 |  | ||||||
| const mainMenuTabRoutes: Routes = [ |  | ||||||
|     { |     { | ||||||
|         path: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, |         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: [ |     imports: [ | ||||||
|         CoreMainMenuHomeRoutingModule.forChild({ |         CoreMainMenuHomeRoutingModule.forChild({ | ||||||
|             children: mainMenuHomeChildrenRoutes, |             children: mainMenuHomeChildrenRoutes, | ||||||
|             siblings: mainMenuHomeSiblingRoutes, |  | ||||||
|         }), |         }), | ||||||
|         CoreMainMenuRoutingModule.forChild({ children: mainMenuTabRoutes }), |         CoreMainMenuRoutingModule.forChild({ children: routes }), | ||||||
|         CoreMainMenuTabRoutingModule.forChild(mainMenuTabRoutes), |         CoreMainMenuTabRoutingModule.forChild(routes), | ||||||
|     ], |     ], | ||||||
|     exports: [CoreMainMenuRoutingModule], |     exports: [CoreMainMenuRoutingModule], | ||||||
|     providers: [ |     providers: [ | ||||||
|  | |||||||
| @ -12,53 +12,29 @@ | |||||||
| // See the License for the specific language governing permissions and
 | // See the License for the specific language governing permissions and
 | ||||||
| // limitations under the License.
 | // limitations under the License.
 | ||||||
| 
 | 
 | ||||||
| import { Injector, NgModule } from '@angular/core'; | import { NgModule } from '@angular/core'; | ||||||
| import { RouterModule, ROUTES, Routes } from '@angular/router'; | import { RouterModule, Routes } from '@angular/router'; | ||||||
| 
 | 
 | ||||||
| import { CoreSharedModule } from '@/core/shared.module'; | import { CoreSharedModule } from '@/core/shared.module'; | ||||||
| import { CoreBlockComponentsModule } from '@features/block/components/components.module'; | import { CoreBlockComponentsModule } from '@features/block/components/components.module'; | ||||||
| 
 | 
 | ||||||
| import { CoreCoursesMyCoursesPage } from './my'; | import { CoreCoursesMyCoursesPage } from './my'; | ||||||
| import { CoreMainMenuComponentsModule } from '@features/mainmenu/components/components.module'; | 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 { | const routes: Routes = [ | ||||||
|     return [ |     { | ||||||
|         { |         path: '', | ||||||
|             path: '', |         component: CoreCoursesMyCoursesPage, | ||||||
|             component: CoreCoursesMyCoursesPage, |     }, | ||||||
|             data: { | ]; | ||||||
|                 mainMenuTabRoot: CoreCoursesMyCoursesMainMenuHandlerService.PAGE_NAME, |  | ||||||
|             }, |  | ||||||
|         }, |  | ||||||
|         { |  | ||||||
|             path: 'list', |  | ||||||
|             loadChildren: () => |  | ||||||
|                 import('../list/list.module') |  | ||||||
|                     .then(m => m.CoreCoursesListPageModule), |  | ||||||
|         }, |  | ||||||
|         ...buildTabMainRoutes(injector, { |  | ||||||
|             redirectTo: '', |  | ||||||
|             pathMatch: 'full', |  | ||||||
|         }), |  | ||||||
|     ]; |  | ||||||
| } |  | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     imports: [ |     imports: [ | ||||||
|  |         RouterModule.forChild(routes), | ||||||
|         CoreSharedModule, |         CoreSharedModule, | ||||||
|         CoreBlockComponentsModule, |         CoreBlockComponentsModule, | ||||||
|         CoreMainMenuComponentsModule, |         CoreMainMenuComponentsModule, | ||||||
|     ], |     ], | ||||||
|     providers: [ |  | ||||||
|         { |  | ||||||
|             provide: ROUTES, |  | ||||||
|             multi: true, |  | ||||||
|             deps: [Injector], |  | ||||||
|             useFactory: buildRoutes, |  | ||||||
|         }, |  | ||||||
|     ], |  | ||||||
|     declarations: [ |     declarations: [ | ||||||
|         CoreCoursesMyCoursesPage, |         CoreCoursesMyCoursesPage, | ||||||
|     ], |     ], | ||||||
|  | |||||||
| @ -27,7 +27,7 @@ import { CoreDashboardHomeHandler } from './dashboard-home'; | |||||||
| @Injectable({ providedIn: 'root' }) | @Injectable({ providedIn: 'root' }) | ||||||
| export class CoreCoursesMyCoursesMainMenuHandlerService implements CoreMainMenuHandler { | export class CoreCoursesMyCoursesMainMenuHandlerService implements CoreMainMenuHandler { | ||||||
| 
 | 
 | ||||||
|     static readonly PAGE_NAME = 'my'; |     static readonly PAGE_NAME = 'courses'; | ||||||
| 
 | 
 | ||||||
|     name = 'CoreCoursesMyCourses'; |     name = 'CoreCoursesMyCourses'; | ||||||
|     priority = 900; |     priority = 900; | ||||||
|  | |||||||
| @ -20,9 +20,9 @@ import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifi | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { CoreCourses } from '../courses'; | import { CoreCourses } from '../courses'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
| @ -58,7 +58,7 @@ export class CoreCoursesRequestPushClickHandlerService implements CorePushNotifi | |||||||
|         if (notification.name == 'courserequested') { |         if (notification.name == 'courserequested') { | ||||||
|             // Feature not supported in the app, open in browser.
 |             // Feature not supported in the app, open in browser.
 | ||||||
|             const site = await CoreSites.getSite(notification.site); |             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); |             await site.openInBrowserWithAutoLogin(url); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -21,12 +21,12 @@ import { CoreApp } from '@services/app'; | |||||||
| import { CoreFile, CoreFileProvider } from '@services/file'; | import { CoreFile, CoreFileProvider } from '@services/file'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreTimeUtils } from '@services/utils/time'; | import { CoreTimeUtils } from '@services/utils/time'; | ||||||
| import { Platform, ModalController, Media, Translate } from '@singletons'; | import { Platform, ModalController, Media, Translate } from '@singletons'; | ||||||
| import { CoreError } from '@classes/errors/error'; | import { CoreError } from '@classes/errors/error'; | ||||||
| import { CoreCaptureError } from '@classes/errors/captureerror'; | import { CoreCaptureError } from '@classes/errors/captureerror'; | ||||||
| import { CoreCanceledError } from '@classes/errors/cancelederror'; | import { CoreCanceledError } from '@classes/errors/cancelederror'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page to capture media in browser, or to capture audio in mobile devices. |  * 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> { |     protected async initCordovaMediaPlugin(): Promise<void> { | ||||||
|         this.filePath = this.getFilePath(); |         this.filePath = this.getFilePath(); | ||||||
|         let absolutePath = CoreTextUtils.concatenatePaths(CoreFile.getBasePathInstant(), this.filePath); |         let absolutePath = CoreText.concatenatePaths(CoreFile.getBasePathInstant(), this.filePath); | ||||||
| 
 | 
 | ||||||
|         if (Platform.is('ios')) { |         if (Platform.is('ios')) { | ||||||
|             // In iOS we need to remove the file:// part.
 |             // In iOS we need to remove the file:// part.
 | ||||||
| @ -534,7 +534,7 @@ export class CoreEmulatorCaptureMediaComponent implements OnInit, OnDestroy { | |||||||
|     protected getFilePath(): string { |     protected getFilePath(): string { | ||||||
|         const fileName = this.type + '_' + CoreTimeUtils.readableTimestamp() + '.' + this.extension; |         const fileName = this.type + '_' + CoreTimeUtils.readableTimestamp() + '.' + this.extension; | ||||||
| 
 | 
 | ||||||
|         return CoreTextUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'media/' + fileName); |         return CoreText.concatenatePaths(CoreFileProvider.TMPFOLDER, 'media/' + fileName); | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|  | |||||||
| @ -113,7 +113,10 @@ export class FileTransferObjectMock extends FileTransferObject { | |||||||
|             xhr.open('GET', source, true); |             xhr.open('GET', source, true); | ||||||
|             xhr.responseType = 'blob'; |             xhr.responseType = 'blob'; | ||||||
|             for (const name in headers) { |             for (const name in headers) { | ||||||
|                 xhr.setRequestHeader(name, headers[name]); |                 // We can't set the User-Agent in browser.
 | ||||||
|  |                 if (name !== 'User-Agent') { | ||||||
|  |                     xhr.setRequestHeader(name, headers[name]); | ||||||
|  |                 } | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             xhr.onprogress = (ev: ProgressEvent): void => { |             xhr.onprogress = (ev: ProgressEvent): void => { | ||||||
| @ -332,7 +335,7 @@ export class FileTransferObjectMock extends FileTransferObject { | |||||||
|                 xhr.open(httpMethod || 'POST', url); |                 xhr.open(httpMethod || 'POST', url); | ||||||
|                 for (const name in headers) { |                 for (const name in headers) { | ||||||
|                     // Filter "unsafe" headers.
 |                     // Filter "unsafe" headers.
 | ||||||
|                     if (name != 'Connection') { |                     if (name !=='Connection' && name !== 'User-Agent') { | ||||||
|                         xhr.setRequestHeader(name, headers[name]); |                         xhr.setRequestHeader(name, headers[name]); | ||||||
|                     } |                     } | ||||||
|                 } |                 } | ||||||
|  | |||||||
| @ -15,7 +15,7 @@ | |||||||
| import { Injectable } from '@angular/core'; | import { Injectable } from '@angular/core'; | ||||||
| import { File, Entry, DirectoryEntry, FileEntry, IWriteOptions, RemoveResult } from '@ionic-native/file/ngx'; | 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. |  * 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. |      * @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> { |     async checkDir(path: string, dir: string): Promise<boolean> { | ||||||
|         const fullPath = CoreTextUtils.concatenatePaths(path, dir); |         const fullPath = CoreText.concatenatePaths(path, dir); | ||||||
| 
 | 
 | ||||||
|         await this.resolveDirectoryUrl(fullPath); |         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. |      * @return Returns a Promise that resolves with a boolean or rejects with an error. | ||||||
|      */ |      */ | ||||||
|     async checkFile(path: string, file: string): Promise<boolean> { |     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) { |         if (entry.isFile) { | ||||||
|             return true; |             return true; | ||||||
| @ -144,7 +144,7 @@ export class FileMock extends File { | |||||||
|     async copyFileOrDir(sourcePath: string, sourceName: string, destPath: string, destName: string): Promise<Entry> { |     async copyFileOrDir(sourcePath: string, sourceName: string, destPath: string, destName: string): Promise<Entry> { | ||||||
|         const destFixed = this.fixPathAndName(destPath, destName); |         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); |         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> { |     async moveFileOrDir(sourcePath: string, sourceName: string, destPath: string, destName: string): Promise<Entry> { | ||||||
|         const destFixed = this.fixPathAndName(destPath, destName); |         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); |         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} { |     protected fixPathAndName(path: string, name: string): {path: string; name: string} { | ||||||
| 
 | 
 | ||||||
|         const fullPath = CoreTextUtils.concatenatePaths(path, name); |         const fullPath = CoreText.concatenatePaths(path, name); | ||||||
| 
 | 
 | ||||||
|         return { |         return { | ||||||
|             path: fullPath.substring(0, fullPath.lastIndexOf('/')), |             path: fullPath.substring(0, fullPath.lastIndexOf('/')), | ||||||
|  | |||||||
| @ -16,8 +16,7 @@ import { Injectable } from '@angular/core'; | |||||||
| import { File } from '@ionic-native/file/ngx'; | import { File } from '@ionic-native/file/ngx'; | ||||||
| import { Zip } from '@ionic-native/zip/ngx'; | import { Zip } from '@ionic-native/zip/ngx'; | ||||||
| import * as JSZip from 'jszip'; | import * as JSZip from 'jszip'; | ||||||
| 
 | import { CoreText } from '@singletons/text'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Emulates the Cordova Zip plugin in browser. |  * Emulates the Cordova Zip plugin in browser. | ||||||
| @ -46,7 +45,7 @@ export class ZipMock extends Zip { | |||||||
|             await this.file.createDir(destination, folder, true); |             await this.file.createDir(destination, folder, true); | ||||||
| 
 | 
 | ||||||
|             // Folder created, add it to the destination path.
 |             // 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'); |                     const fileData = await file.async('blob'); | ||||||
| 
 | 
 | ||||||
|                     // File read and parent folder created, now write the file.
 |                     // 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 }); |                     await this.file.writeFile(parentFolder, fileName, fileData, { replace: true }); | ||||||
|                 } else { |                 } else { | ||||||
|  | |||||||
| @ -35,6 +35,7 @@ import { CoreCaptureError } from '@classes/errors/captureerror'; | |||||||
| import { CoreIonLoadingElement } from '@classes/ion-loading'; | import { CoreIonLoadingElement } from '@classes/ion-loading'; | ||||||
| import { CoreWSUploadFileResult } from '@services/ws'; | import { CoreWSUploadFileResult } from '@services/ws'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Helper service to upload files. |  * Helper service to upload files. | ||||||
| @ -158,7 +159,7 @@ export class CoreFileUploaderHelperProvider { | |||||||
|             // Get unique name for the copy.
 |             // Get unique name for the copy.
 | ||||||
|             const newName = await CoreFile.getUniqueNameInFolder(CoreFileProvider.TMPFOLDER, name); |             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.
 |             // Write the data into the file.
 | ||||||
|             fileEntry = await CoreFile.writeFileDataInFile( |             fileEntry = await CoreFile.writeFileDataInFile( | ||||||
| @ -218,7 +219,7 @@ export class CoreFileUploaderHelperProvider { | |||||||
|         const newName = await CoreFile.getUniqueNameInFolder(CoreFileProvider.TMPFOLDER, fileName, defaultExt); |         const newName = await CoreFile.getUniqueNameInFolder(CoreFileProvider.TMPFOLDER, fileName, defaultExt); | ||||||
| 
 | 
 | ||||||
|         // Now move or copy the file.
 |         // Now move or copy the file.
 | ||||||
|         const destPath = CoreTextUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); |         const destPath = CoreText.concatenatePaths(CoreFileProvider.TMPFOLDER, newName); | ||||||
|         if (shouldDelete) { |         if (shouldDelete) { | ||||||
|             return CoreFile.moveExternalFile(path, destPath); |             return CoreFile.moveExternalFile(path, destPath); | ||||||
|         } else { |         } else { | ||||||
|  | |||||||
| @ -23,7 +23,6 @@ import { CoreFile, CoreFileProvider } from '@services/file'; | |||||||
| import { CoreFilepool } from '@services/filepool'; | import { CoreFilepool } from '@services/filepool'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreTimeUtils } from '@services/utils/time'; | import { CoreTimeUtils } from '@services/utils/time'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreWSFile, CoreWSFileUploadOptions, CoreWSUploadFileResult } from '@services/ws'; | import { CoreWSFile, CoreWSFileUploadOptions, CoreWSUploadFileResult } from '@services/ws'; | ||||||
| @ -33,6 +32,7 @@ import { CoreEmulatorCaptureMediaComponent } from '@features/emulator/components | |||||||
| import { CoreError } from '@classes/errors/error'; | import { CoreError } from '@classes/errors/error'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
| import { CoreFileEntry, CoreFileHelper } from '@services/file-helper'; | import { CoreFileEntry, CoreFileHelper } from '@services/file-helper'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * File upload options. |  * File upload options. | ||||||
| @ -579,7 +579,7 @@ export class CoreFileUploaderProvider { | |||||||
|             } else { |             } else { | ||||||
|                 // Local file, copy it.
 |                 // Local file, copy it.
 | ||||||
|                 // Use copy instead of move to prevent having a unstable state if some copies succeed and others don't.
 |                 // 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++; |                 result.offline++; | ||||||
| 
 | 
 | ||||||
|                 await CoreFile.copyFile(file.toURL(), destFile); |                 await CoreFile.copyFile(file.toURL(), destFile); | ||||||
|  | |||||||
| @ -24,6 +24,7 @@ import { CoreH5PContentValidator, CoreH5PSemantics } from './content-validator'; | |||||||
| import { Translate } from '@singletons'; | import { Translate } from '@singletons'; | ||||||
| import { CoreH5PContentBeingSaved } from './storage'; | import { CoreH5PContentBeingSaved } from './storage'; | ||||||
| import { CoreH5PLibraryAddTo } from './validator'; | import { CoreH5PLibraryAddTo } from './validator'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Equivalent to H5P's H5PCore class. |  * Equivalent to H5P's H5PCore class. | ||||||
| @ -148,7 +149,7 @@ export class CoreH5PCore { | |||||||
|             urls.push(libUrl + script); |             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; |         return urls; | ||||||
|     } |     } | ||||||
| @ -456,7 +457,7 @@ export class CoreH5PCore { | |||||||
| 
 | 
 | ||||||
|             // Add URL prefix if not external.
 |             // Add URL prefix if not external.
 | ||||||
|             if (asset.path.indexOf('://') == -1 && assetsFolderPath) { |             if (asset.path.indexOf('://') == -1 && assetsFolderPath) { | ||||||
|                 url = CoreTextUtils.concatenatePaths(assetsFolderPath, url); |                 url = CoreText.concatenatePaths(assetsFolderPath, url); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             // Add version if set.
 |             // Add version if set.
 | ||||||
|  | |||||||
| @ -28,6 +28,7 @@ import { | |||||||
| } from './core'; | } from './core'; | ||||||
| import { CONTENTS_LIBRARIES_TABLE_NAME, CONTENT_TABLE_NAME, CoreH5PLibraryCachedAssetsDBRecord } from '../services/database/h5p'; | import { CONTENTS_LIBRARIES_TABLE_NAME, CONTENT_TABLE_NAME, CoreH5PLibraryCachedAssetsDBRecord } from '../services/database/h5p'; | ||||||
| import { CoreH5PLibraryBeingSaved } from './storage'; | import { CoreH5PLibraryBeingSaved } from './storage'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Equivalent to Moodle's implementation of H5PFileStorage. |  * Equivalent to Moodle's implementation of H5PFileStorage. | ||||||
| @ -60,7 +61,7 @@ export class CoreH5PFileStorage { | |||||||
| 
 | 
 | ||||||
|             // Create new file for cached assets.
 |             // Create new file for cached assets.
 | ||||||
|             const fileName = key + '.' + (type == 'scripts' ? 'js' : 'css'); |             const fileName = key + '.' + (type == 'scripts' ? 'js' : 'css'); | ||||||
|             const path = CoreTextUtils.concatenatePaths(cachedAssetsPath, fileName); |             const path = CoreText.concatenatePaths(cachedAssetsPath, fileName); | ||||||
| 
 | 
 | ||||||
|             // Store concatenated content.
 |             // Store concatenated content.
 | ||||||
|             const content = await this.concatenateFiles(assets, type); |             const content = await this.concatenateFiles(assets, type); | ||||||
| @ -70,7 +71,7 @@ export class CoreH5PFileStorage { | |||||||
|             // Now update the files data.
 |             // Now update the files data.
 | ||||||
|             files[type] = [ |             files[type] = [ | ||||||
|                 { |                 { | ||||||
|                     path: CoreTextUtils.concatenatePaths(CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, fileName), |                     path: CoreText.concatenatePaths(CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, fileName), | ||||||
|                     version: '', |                     version: '', | ||||||
|                 }, |                 }, | ||||||
|             ]; |             ]; | ||||||
| @ -142,7 +143,7 @@ export class CoreH5PFileStorage { | |||||||
| 
 | 
 | ||||||
|                     fileContent = fileContent.replace( |                     fileContent = fileContent.replace( | ||||||
|                         new RegExp(CoreTextUtils.escapeForRegex(match), 'g'), |                         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()); |             const cachedAssetsFolder = this.getCachedAssetsFolderPath(entry.foldername, site.getId()); | ||||||
| 
 | 
 | ||||||
|             ['js', 'css'].forEach((type) => { |             ['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)); |                 promises.push(CoreFile.removeFile(path)); | ||||||
|             }); |             }); | ||||||
| @ -282,7 +283,7 @@ export class CoreH5PFileStorage { | |||||||
|     protected async getCachedAsset(key: string, extension: string): Promise<CoreH5PDependencyAsset[] | undefined> { |     protected async getCachedAsset(key: string, extension: string): Promise<CoreH5PDependencyAsset[] | undefined> { | ||||||
| 
 | 
 | ||||||
|         try { |         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); |             const size = await CoreFile.getFileSize(path); | ||||||
| 
 | 
 | ||||||
| @ -307,7 +308,7 @@ export class CoreH5PFileStorage { | |||||||
|      * @return Path. |      * @return Path. | ||||||
|      */ |      */ | ||||||
|     getCachedAssetsFolderPath(folderName: string, siteId: string): string { |     getCachedAssetsFolderPath(folderName: string, siteId: string): string { | ||||||
|         return CoreTextUtils.concatenatePaths( |         return CoreText.concatenatePaths( | ||||||
|             this.getContentFolderPath(folderName, siteId), |             this.getContentFolderPath(folderName, siteId), | ||||||
|             CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, |             CoreH5PFileStorage.CACHED_ASSETS_FOLDER_NAME, | ||||||
|         ); |         ); | ||||||
| @ -336,7 +337,7 @@ export class CoreH5PFileStorage { | |||||||
|      * @return Folder path. |      * @return Folder path. | ||||||
|      */ |      */ | ||||||
|     getContentFolderPath(folderName: string, siteId: string): string { |     getContentFolderPath(folderName: string, siteId: string): string { | ||||||
|         return CoreTextUtils.concatenatePaths( |         return CoreText.concatenatePaths( | ||||||
|             this.getExternalH5PFolderPath(siteId), |             this.getExternalH5PFolderPath(siteId), | ||||||
|             'packages/' + folderName + '/content', |             'packages/' + folderName + '/content', | ||||||
|         ); |         ); | ||||||
| @ -367,7 +368,7 @@ export class CoreH5PFileStorage { | |||||||
|      * @return Folder path. |      * @return Folder path. | ||||||
|      */ |      */ | ||||||
|     getContentIndexPath(folderName: string, siteId: string): string { |     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. |      * @return Folder path. | ||||||
|      */ |      */ | ||||||
|     getCoreH5PPath(): string { |     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. |      * @return Folder path. | ||||||
|      */ |      */ | ||||||
|     getExternalH5PFolderPath(siteId: string): string { |     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. |      * @return Folder path. | ||||||
|      */ |      */ | ||||||
|     getLibrariesFolderPath(siteId: string): string { |     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); |             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 { CoreFile, CoreFileProvider } from '@services/file'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreH5P } from '../services/h5p'; | import { CoreH5P } from '../services/h5p'; | ||||||
| import { CoreH5PCore, CoreH5PDisplayOptions } from './core'; | import { CoreH5PCore, CoreH5PDisplayOptions } from './core'; | ||||||
| import { CoreError } from '@classes/errors/error'; | import { CoreError } from '@classes/errors/error'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Equivalent to Moodle's H5P helper class. |  * Equivalent to Moodle's H5P helper class. | ||||||
| @ -131,13 +131,13 @@ export class CoreH5PHelper { | |||||||
|         return { |         return { | ||||||
|             baseUrl: CoreFile.getWWWPath(), |             baseUrl: CoreFile.getWWWPath(), | ||||||
|             url: CoreFile.convertFileSrc( |             url: CoreFile.convertFileSrc( | ||||||
|                 CoreTextUtils.concatenatePaths( |                 CoreText.concatenatePaths( | ||||||
|                     basePath, |                     basePath, | ||||||
|                     CoreH5P.h5pCore.h5pFS.getExternalH5PFolderPath(site.getId()), |                     CoreH5P.h5pCore.h5pFS.getExternalH5PFolderPath(site.getId()), | ||||||
|                 ), |                 ), | ||||||
|             ), |             ), | ||||||
|             urlLibraries: CoreFile.convertFileSrc( |             urlLibraries: CoreFile.convertFileSrc( | ||||||
|                 CoreTextUtils.concatenatePaths( |                 CoreText.concatenatePaths( | ||||||
|                     basePath, |                     basePath, | ||||||
|                     CoreH5P.h5pCore.h5pFS.getLibrariesFolderPath(site.getId()), |                     CoreH5P.h5pCore.h5pFS.getLibrariesFolderPath(site.getId()), | ||||||
|                 ), |                 ), | ||||||
| @ -155,7 +155,7 @@ export class CoreH5PHelper { | |||||||
|             crossorigin: null, |             crossorigin: null, | ||||||
|             libraryConfig: null, |             libraryConfig: null, | ||||||
|             pluginCacheBuster: '', |             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> { |     ): Promise<void> { | ||||||
| 
 | 
 | ||||||
|         const folderName = CoreMimetypeUtils.removeExtension(file.name); |         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.
 |         // Unzip the file.
 | ||||||
|         await CoreFile.unzipFile(file.toURL(), destFolder, onProgress); |         await CoreFile.unzipFile(file.toURL(), destFolder, onProgress); | ||||||
|  | |||||||
| @ -14,7 +14,6 @@ | |||||||
| 
 | 
 | ||||||
| import { CoreFile } from '@services/file'; | import { CoreFile } from '@services/file'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUrlUtils } from '@services/utils/url'; | import { CoreUrlUtils } from '@services/utils/url'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreXAPI } from '@features/xapi/services/xapi'; | import { CoreXAPI } from '@features/xapi/services/xapi'; | ||||||
| @ -22,6 +21,7 @@ import { CoreH5P } from '../services/h5p'; | |||||||
| import { CoreH5PCore, CoreH5PDisplayOptions, CoreH5PContentData, CoreH5PDependenciesFiles } from './core'; | import { CoreH5PCore, CoreH5PDisplayOptions, CoreH5PContentData, CoreH5PDependenciesFiles } from './core'; | ||||||
| import { CoreH5PCoreSettings, CoreH5PHelper } from './helper'; | import { CoreH5PCoreSettings, CoreH5PHelper } from './helper'; | ||||||
| import { CoreH5PStorage } from './storage'; | import { CoreH5PStorage } from './storage'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Equivalent to Moodle's H5P player class. |  * Equivalent to Moodle's H5P player class. | ||||||
| @ -51,7 +51,7 @@ export class CoreH5PPlayer { | |||||||
|             params.component = component; |             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 contentId = this.getContentId(id); | ||||||
|         const basePath = CoreFile.getBasePathInstant(); |         const basePath = CoreFile.getBasePathInstant(); | ||||||
|         const contentUrl = CoreFile.convertFileSrc( |         const contentUrl = CoreFile.convertFileSrc( | ||||||
|             CoreTextUtils.concatenatePaths( |             CoreText.concatenatePaths( | ||||||
|                 basePath, |                 basePath, | ||||||
|                 this.h5pCore.h5pFS.getContentFolderPath(content.folderName, site.getId()), |                 this.h5pCore.h5pFS.getContentFolderPath(content.folderName, site.getId()), | ||||||
|             ), |             ), | ||||||
| @ -122,7 +122,7 @@ export class CoreH5PPlayer { | |||||||
|                 JSON.stringify(result.settings).replace(/\//g, '\\/') + '</script>'; |                 JSON.stringify(result.settings).replace(/\//g, '\\/') + '</script>'; | ||||||
| 
 | 
 | ||||||
|         // Add our own script to handle the params.
 |         // 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(), |             this.h5pCore.h5pFS.getCoreH5PPath(), | ||||||
|             'moodle/js/params.js', |             'moodle/js/params.js', | ||||||
|         ) + '"></script>'; |         ) + '"></script>'; | ||||||
| @ -132,7 +132,7 @@ export class CoreH5PPlayer { | |||||||
|         // Include the required JS at the beginning of the body, like Moodle web does.
 |         // 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.
 |         // Load the embed.js to allow communication with the parent window.
 | ||||||
|         html += '<script type="text/javascript" src="' + |         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) => { |         result.jsRequires.forEach((jsUrl) => { | ||||||
|             html += '<script type="text/javascript" src="' + jsUrl + '"></script>'; |             html += '<script type="text/javascript" src="' + jsUrl + '"></script>'; | ||||||
| @ -364,7 +364,7 @@ export class CoreH5PPlayer { | |||||||
|      * @return The embed URL. |      * @return The embed URL. | ||||||
|      */ |      */ | ||||||
|     protected getEmbedUrl(siteUrl: string, h5pUrl: string): string { |     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. |      * @return URL. | ||||||
|      */ |      */ | ||||||
|     getResizerScriptUrl(): string { |     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 { CoreFile, CoreFileProvider } from '@services/file'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { CoreH5PCore, CoreH5PLibraryBasicData } from './core'; | import { CoreH5PCore, CoreH5PLibraryBasicData } from './core'; | ||||||
| import { CoreH5PFramework } from './framework'; | import { CoreH5PFramework } from './framework'; | ||||||
| import { CoreH5PMetadata } from './metadata'; | import { CoreH5PMetadata } from './metadata'; | ||||||
| @ -199,8 +199,8 @@ export class CoreH5PStorage { | |||||||
|             await this.h5pCore.saveContent(content, folderName, fileUrl, siteId); |             await this.h5pCore.saveContent(content, folderName, fileUrl, siteId); | ||||||
| 
 | 
 | ||||||
|             // Save the content files in their right place in FS.
 |             // Save the content files in their right place in FS.
 | ||||||
|             const destFolder = CoreTextUtils.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); |             const destFolder = CoreText.concatenatePaths(CoreFileProvider.TMPFOLDER, 'h5p/' + folderName); | ||||||
|             const contentPath = CoreTextUtils.concatenatePaths(destFolder, 'content'); |             const contentPath = CoreText.concatenatePaths(destFolder, 'content'); | ||||||
| 
 | 
 | ||||||
|             try { |             try { | ||||||
|                 await this.h5pCore.h5pFS.saveContent(contentPath, folderName, siteId); |                 await this.h5pCore.h5pFS.saveContent(contentPath, folderName, siteId); | ||||||
|  | |||||||
| @ -15,8 +15,8 @@ | |||||||
| import { CoreError } from '@classes/errors/error'; | import { CoreError } from '@classes/errors/error'; | ||||||
| import { FileEntry, DirectoryEntry } from '@ionic-native/file/ngx'; | import { FileEntry, DirectoryEntry } from '@ionic-native/file/ngx'; | ||||||
| import { CoreFile, CoreFileFormat } from '@services/file'; | import { CoreFile, CoreFileFormat } from '@services/file'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { Translate } from '@singletons'; | import { Translate } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { CoreH5PSemantics } from './content-validator'; | import { CoreH5PSemantics } from './content-validator'; | ||||||
| import { CoreH5PCore, CoreH5PLibraryBasicData, CoreH5PMissingLibrary } from './core'; | import { CoreH5PCore, CoreH5PLibraryBasicData, CoreH5PMissingLibrary } from './core'; | ||||||
| import { CoreH5PFramework } from './framework'; | import { CoreH5PFramework } from './framework'; | ||||||
| @ -126,7 +126,7 @@ export class CoreH5PValidator { | |||||||
|                 return; |                 return; | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             const libDirPath = CoreTextUtils.concatenatePaths(packagePath, entry.name); |             const libDirPath = CoreText.concatenatePaths(packagePath, entry.name); | ||||||
| 
 | 
 | ||||||
|             const libraryData = await this.getLibraryData(<DirectoryEntry> entry, libDirPath); |             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. |      * @return Promise resolved with boolean: whether the library has an icon file. | ||||||
|      */ |      */ | ||||||
|     protected async libraryHasIcon(libPath: string): Promise<boolean> { |     protected async libraryHasIcon(libPath: string): Promise<boolean> { | ||||||
|         const path = CoreTextUtils.concatenatePaths(libPath, 'icon.svg'); |         const path = CoreText.concatenatePaths(libPath, 'icon.svg'); | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
|             // Check if the file exists.
 |             // Check if the file exists.
 | ||||||
| @ -219,7 +219,7 @@ export class CoreH5PValidator { | |||||||
|      * @return Promise resolved with the parsed file contents. |      * @return Promise resolved with the parsed file contents. | ||||||
|      */ |      */ | ||||||
|     protected readH5PContentJsonFile(packagePath: string): Promise<unknown> { |     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); |         return CoreFile.readFile(path, CoreFileFormat.FORMATJSON); | ||||||
|     } |     } | ||||||
| @ -231,7 +231,7 @@ export class CoreH5PValidator { | |||||||
|      * @return Promise resolved with the parsed file contents. |      * @return Promise resolved with the parsed file contents. | ||||||
|      */ |      */ | ||||||
|     protected readH5PJsonFile(packagePath: string): Promise<CoreH5PMainJSONData> { |     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); |         return CoreFile.readFile(path, CoreFileFormat.FORMATJSON); | ||||||
|     } |     } | ||||||
| @ -243,7 +243,7 @@ export class CoreH5PValidator { | |||||||
|      * @return Promise resolved with the parsed file contents. |      * @return Promise resolved with the parsed file contents. | ||||||
|      */ |      */ | ||||||
|     protected readLibraryJsonFile(libPath: string): Promise<CoreH5PLibraryMainJsonData> { |     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); |         return CoreFile.readFile<CoreH5PLibraryMainJsonData>(path, CoreFileFormat.FORMATJSON); | ||||||
|     } |     } | ||||||
| @ -256,14 +256,14 @@ export class CoreH5PValidator { | |||||||
|      */ |      */ | ||||||
|     protected async readLibraryLanguageFiles(libPath: string): Promise<CoreH5PLibraryLangsJsonData | undefined> { |     protected async readLibraryLanguageFiles(libPath: string): Promise<CoreH5PLibraryLangsJsonData | undefined> { | ||||||
|         try { |         try { | ||||||
|             const path = CoreTextUtils.concatenatePaths(libPath, 'language'); |             const path = CoreText.concatenatePaths(libPath, 'language'); | ||||||
|             const langIndex: CoreH5PLibraryLangsJsonData = {}; |             const langIndex: CoreH5PLibraryLangsJsonData = {}; | ||||||
| 
 | 
 | ||||||
|             // Read all the files in the language directory.
 |             // Read all the files in the language directory.
 | ||||||
|             const entries = await CoreFile.getDirectoryContents(path); |             const entries = await CoreFile.getDirectoryContents(path); | ||||||
| 
 | 
 | ||||||
|             await Promise.all(entries.map(async (entry) => { |             await Promise.all(entries.map(async (entry) => { | ||||||
|                 const langFilePath = CoreTextUtils.concatenatePaths(path, entry.name); |                 const langFilePath = CoreText.concatenatePaths(path, entry.name); | ||||||
| 
 | 
 | ||||||
|                 try { |                 try { | ||||||
|                     const langFileData = await CoreFile.readFile<CoreH5PLibraryLangJsonData>( |                     const langFileData = await CoreFile.readFile<CoreH5PLibraryLangJsonData>( | ||||||
| @ -293,7 +293,7 @@ export class CoreH5PValidator { | |||||||
|      */ |      */ | ||||||
|     protected async readLibrarySemanticsFile(libPath: string): Promise<CoreH5PSemantics[] | undefined> { |     protected async readLibrarySemanticsFile(libPath: string): Promise<CoreH5PSemantics[] | undefined> { | ||||||
|         try { |         try { | ||||||
|             const path = CoreTextUtils.concatenatePaths(libPath, 'semantics.json'); |             const path = CoreText.concatenatePaths(libPath, 'semantics.json'); | ||||||
| 
 | 
 | ||||||
|             return await CoreFile.readFile<CoreH5PSemantics[]>(path, CoreFileFormat.FORMATJSON); |             return await CoreFile.readFile<CoreH5PSemantics[]>(path, CoreFileFormat.FORMATJSON); | ||||||
|         } catch (error) { |         } catch (error) { | ||||||
|  | |||||||
| @ -16,7 +16,6 @@ import { Injectable } from '@angular/core'; | |||||||
| 
 | 
 | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreWSExternalWarning, CoreWSExternalFile, CoreWSFile } from '@services/ws'; | import { CoreWSExternalWarning, CoreWSExternalFile, CoreWSFile } from '@services/ws'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUrlUtils } from '@services/utils/url'; | import { CoreUrlUtils } from '@services/utils/url'; | ||||||
| import { CoreQueueRunner } from '@classes/queue-runner'; | import { CoreQueueRunner } from '@classes/queue-runner'; | ||||||
| import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | import { CoreSite, CoreSiteWSPreSets } from '@classes/site'; | ||||||
| @ -29,6 +28,7 @@ import { CoreH5PValidator } from '../classes/validator'; | |||||||
| 
 | 
 | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { CoreError } from '@classes/errors/error'; | import { CoreError } from '@classes/errors/error'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to provide H5P functionalities. |  * Service to provide H5P functionalities. | ||||||
| @ -207,7 +207,7 @@ export class CoreH5PProvider { | |||||||
|      * @return Treated url. |      * @return Treated url. | ||||||
|      */ |      */ | ||||||
|     treatH5PUrl(url: string, siteUrl: string): string { |     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'); |             url = url.replace('/webservice/pluginfile', '/pluginfile'); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -34,6 +34,7 @@ import { | |||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreForms } from '@singletons/form'; | import { CoreForms } from '@singletons/form'; | ||||||
| import { CoreRecaptchaComponent } from '@components/recaptcha/recaptcha'; | import { CoreRecaptchaComponent } from '@components/recaptcha/recaptcha'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Page to signup using email. |  * Page to signup using email. | ||||||
| @ -161,7 +162,7 @@ export class CoreLoginEmailSignupPage implements OnInit { | |||||||
|         try { |         try { | ||||||
|             // Get site config.
 |             // Get site config.
 | ||||||
|             this.siteConfig = await CoreSites.getSitePublicConfig(this.siteUrl); |             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()) { |             if (this.treatSiteConfig()) { | ||||||
|                 // Check content verification.
 |                 // Check content verification.
 | ||||||
| @ -385,7 +386,7 @@ export class CoreLoginEmailSignupPage implements OnInit { | |||||||
|      */ |      */ | ||||||
|     showContactOnSite(): void { |     showContactOnSite(): void { | ||||||
|         CoreUtils.openInBrowser( |         CoreUtils.openInBrowser( | ||||||
|             CoreTextUtils.concatenatePaths(this.siteUrl, '/login/verify_age_location.php'), |             CoreText.concatenatePaths(this.siteUrl, '/login/verify_age_location.php'), | ||||||
|             { showBrowserWarning: false }, |             { showBrowserWarning: false }, | ||||||
|         ); |         ); | ||||||
|     } |     } | ||||||
|  | |||||||
| @ -36,6 +36,7 @@ import { CoreNavigator, CoreRedirectPayload } from '@services/navigator'; | |||||||
| import { CoreCanceledError } from '@classes/errors/cancelederror'; | import { CoreCanceledError } from '@classes/errors/cancelederror'; | ||||||
| import { CoreCustomURLSchemes } from '@services/urlschemes'; | import { CoreCustomURLSchemes } from '@services/urlschemes'; | ||||||
| import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | import { CorePushNotifications } from '@features/pushnotifications/services/pushnotifications'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Helper provider that provides some common features regarding authentication. |  * Helper provider that provides some common features regarding authentication. | ||||||
| @ -385,8 +386,8 @@ export class CoreLoginHelperProvider { | |||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const validProviders: CoreSiteIdentityProvider[] = []; |         const validProviders: CoreSiteIdentityProvider[] = []; | ||||||
|         const httpUrl = CoreTextUtils.concatenatePaths(siteConfig.wwwroot, 'auth/oauth2/'); |         const httpUrl = CoreText.concatenatePaths(siteConfig.wwwroot, 'auth/oauth2/'); | ||||||
|         const httpsUrl = CoreTextUtils.concatenatePaths(siteConfig.httpswwwroot, 'auth/oauth2/'); |         const httpsUrl = CoreText.concatenatePaths(siteConfig.httpswwwroot, 'auth/oauth2/'); | ||||||
| 
 | 
 | ||||||
|         if (siteConfig.identityproviders && siteConfig.identityproviders.length) { |         if (siteConfig.identityproviders && siteConfig.identityproviders.length) { | ||||||
|             siteConfig.identityproviders.forEach((provider) => { |             siteConfig.identityproviders.forEach((provider) => { | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ import { CoreTimeUtils } from '@services/utils/time'; | |||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreWSExternalFile } from '@services/ws'; | import { CoreWSExternalFile } from '@services/ws'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { | import { | ||||||
|     CoreQuestionAnswerDBRecord, |     CoreQuestionAnswerDBRecord, | ||||||
|     CoreQuestionDBRecord, |     CoreQuestionDBRecord, | ||||||
| @ -331,7 +332,7 @@ export class CoreQuestionProvider { | |||||||
|         const siteFolderPath = CoreFile.getSiteFolder(siteId); |         const siteFolderPath = CoreFile.getSiteFolder(siteId); | ||||||
|         const questionFolderPath = 'offlinequestion/' + type + '/' + component + '/' + componentId; |         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 { CoreSharedFiles } from '@features/sharedfiles/services/sharedfiles'; | ||||||
| import { CoreNavigator } from '@services/navigator'; | import { CoreNavigator } from '@services/navigator'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreEventObserver, CoreEvents } from '@singletons/events'; | 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. |  * 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. |      * @param folder The folder to open. | ||||||
|      */ |      */ | ||||||
|     openFolder(folder: DirectoryEntry): void { |     openFolder(folder: DirectoryEntry): void { | ||||||
|         const path = CoreTextUtils.concatenatePaths(this.path || '', folder.name); |         const path = CoreText.concatenatePaths(this.path || '', folder.name); | ||||||
| 
 | 
 | ||||||
|         if (this.isModal) { |         if (this.isModal) { | ||||||
|             this.path = path; |             this.path = path; | ||||||
|  | |||||||
| @ -21,12 +21,12 @@ import { CoreLogger } from '@singletons/logger'; | |||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreFile } from '@services/file'; | import { CoreFile } from '@services/file'; | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreMimetypeUtils } from '@services/utils/mimetype'; | import { CoreMimetypeUtils } from '@services/utils/mimetype'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreEvents } from '@singletons/events'; | import { CoreEvents } from '@singletons/events'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
| import { APP_SCHEMA, CoreSharedFilesDBRecord, SHARED_FILES_TABLE_NAME } from './database/sharedfiles'; | import { APP_SCHEMA, CoreSharedFilesDBRecord, SHARED_FILES_TABLE_NAME } from './database/sharedfiles'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to share files with the app. |  * 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)[]> { |     async getSiteSharedFiles(siteId?: string, path?: string, mimetypes?: string[]): Promise<(FileEntry | DirectoryEntry)[]> { | ||||||
|         let pathToGet = this.getSiteSharedFilesDirPath(siteId); |         let pathToGet = this.getSiteSharedFilesDirPath(siteId); | ||||||
|         if (path) { |         if (path) { | ||||||
|             pathToGet = CoreTextUtils.concatenatePaths(pathToGet, path); |             pathToGet = CoreText.concatenatePaths(pathToGet, path); | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         try { |         try { | ||||||
| @ -240,7 +240,7 @@ export class CoreSharedFilesProvider { | |||||||
|         newName = newName || entry.name; |         newName = newName || entry.name; | ||||||
| 
 | 
 | ||||||
|         const sharedFilesFolder = this.getSiteSharedFilesDirPath(siteId); |         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.
 |         // Create dir if it doesn't exist already.
 | ||||||
|         await CoreFile.createDir(sharedFilesFolder); |         await CoreFile.createDir(sharedFilesFolder); | ||||||
|  | |||||||
| @ -32,32 +32,21 @@ export class CoreSiteHomeIndexLinkHandlerService extends CoreContentLinksHandler | |||||||
|     pattern = /\/course\/view\.php.*([?&]id=\d+)/; |     pattern = /\/course\/view\.php.*([?&]id=\d+)/; | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Get the list of actions for a link (url). |      * @inheritdoc | ||||||
|      * |  | ||||||
|      * @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. |  | ||||||
|      */ |      */ | ||||||
|     getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { |     getActions(): CoreContentLinksAction[] | Promise<CoreContentLinksAction[]> { | ||||||
|         return [{ |         return [{ | ||||||
|             action: (siteId: string): void => { |             action: (siteId: string): void => { | ||||||
|                 // @todo This should open the 'sitehome' setting as well.
 |                 CoreNavigator.navigateToSitePath('/home/site', { | ||||||
|                 CoreNavigator.navigateToSiteHome({ siteId }); |                     preferCurrentTab: false, | ||||||
|  |                     siteId, | ||||||
|  |                 }); | ||||||
|             }, |             }, | ||||||
|         }]; |         }]; | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
|      * Check if the handler is enabled for a certain site (site + user) and a URL. |      * @inheritdoc | ||||||
|      * 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. |  | ||||||
|      */ |      */ | ||||||
|     async isEnabled(siteId: string, url: string, params: Record<string, string>, courseId?: number): Promise<boolean> { |     async isEnabled(siteId: string, url: string, params: Record<string, string>, courseId?: number): Promise<boolean> { | ||||||
|         courseId = parseInt(params.id, 10); |         courseId = parseInt(params.id, 10); | ||||||
|  | |||||||
| @ -38,7 +38,6 @@ import { CoreFilepool } from '@services/filepool'; | |||||||
| import { CoreLang } from '@services/lang'; | import { CoreLang } from '@services/lang'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; | import { CoreTextUtils } from '@services/utils/text'; | ||||||
| import { CoreUrlUtils } from '@services/utils/url'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreWS } from '@services/ws'; | import { CoreWS } from '@services/ws'; | ||||||
| import { CoreEvents } from '@singletons/events'; | import { CoreEvents } from '@singletons/events'; | ||||||
| @ -85,6 +84,7 @@ import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/class | |||||||
| import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; | import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate'; | ||||||
| import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler'; | ||||||
| import { CoreObject } from '@singletons/object'; | import { CoreObject } from '@singletons/object'; | ||||||
|  | import { CoreUrl } from '@singletons/url'; | ||||||
| 
 | 
 | ||||||
| const HANDLER_DISABLED = 'core_site_plugins_helper_handler_disabled'; | const HANDLER_DISABLED = 'core_site_plugins_helper_handler_disabled'; | ||||||
| 
 | 
 | ||||||
| @ -164,11 +164,8 @@ export class CoreSitePluginsHelperProvider { | |||||||
|     ): Promise<string> { |     ): Promise<string> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         const site = await CoreSites.getSite(siteId); | ||||||
| 
 | 
 | ||||||
|         // Get the absolute URL. If it's a relative URL, add the site URL to it.
 |         // Make sure it's an absolute URL.
 | ||||||
|         let url = handlerSchema.styles?.url; |         let url = handlerSchema.styles?.url ? CoreUrl.toAbsoluteURL(site.getURL(), handlerSchema.styles.url) : undefined; | ||||||
|         if (url && !CoreUrlUtils.isAbsoluteURL(url)) { |  | ||||||
|             url = CoreTextUtils.concatenatePaths(site.getURL(), url); |  | ||||||
|         } |  | ||||||
| 
 | 
 | ||||||
|         if (url && handlerSchema.styles?.version) { |         if (url && handlerSchema.styles?.version) { | ||||||
|             // Add the version to the URL to prevent getting a cached file.
 |             // 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 { CoreApp } from '@services/app'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUtils } from '@services/utils/utils'; | import { CoreUtils } from '@services/utils/utils'; | ||||||
| import { CoreSite } from '@classes/site'; | import { CoreSite } from '@classes/site'; | ||||||
| import { CoreXAPIOffline, CoreXAPIOfflineSaveStatementsOptions } from './offline'; | import { CoreXAPIOffline, CoreXAPIOfflineSaveStatementsOptions } from './offline'; | ||||||
| import { makeSingleton } from '@singletons'; | import { makeSingleton } from '@singletons'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Service to provide XAPI functionalities. |  * Service to provide XAPI functionalities. | ||||||
| @ -65,7 +65,7 @@ export class CoreXAPIProvider { | |||||||
|     async getUrl(contextId: number, type: string, siteId?: string): Promise<string> { |     async getUrl(contextId: number, type: string, siteId?: string): Promise<string> { | ||||||
|         const site = await CoreSites.getSite(siteId); |         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.
 |         // Redirect to site path.
 | ||||||
|         if (redirect.siteId && redirect.siteId !== CoreConstants.NO_SITE_ID) { |         if (redirect.siteId && redirect.siteId !== CoreConstants.NO_SITE_ID) { | ||||||
|             const redirectData: CoreRedirectPayload = { |             const redirectData: CoreRedirectPayload = { | ||||||
|                 redirectPath: redirect.redirectPath, |  | ||||||
|                 redirectOptions: redirect.redirectOptions, |  | ||||||
|                 urlToOpen: redirect.urlToOpen, |                 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( |             const loggedIn = await CoreSites.loadSite( | ||||||
|                 redirect.siteId, |                 redirect.siteId, | ||||||
|                 redirectData, |                 redirectData, | ||||||
|  | |||||||
| @ -22,6 +22,7 @@ import { CoreFormatDatePipe } from './format-date'; | |||||||
| import { CoreNoTagsPipe } from './no-tags'; | import { CoreNoTagsPipe } from './no-tags'; | ||||||
| import { CoreSecondsToHMSPipe } from './seconds-to-hms'; | import { CoreSecondsToHMSPipe } from './seconds-to-hms'; | ||||||
| import { CoreTimeAgoPipe } from './time-ago'; | import { CoreTimeAgoPipe } from './time-ago'; | ||||||
|  | import { CoreToLocaleStringPipe } from './to-locale-string'; | ||||||
| 
 | 
 | ||||||
| @NgModule({ | @NgModule({ | ||||||
|     declarations: [ |     declarations: [ | ||||||
| @ -33,6 +34,7 @@ import { CoreTimeAgoPipe } from './time-ago'; | |||||||
|         CoreNoTagsPipe, |         CoreNoTagsPipe, | ||||||
|         CoreSecondsToHMSPipe, |         CoreSecondsToHMSPipe, | ||||||
|         CoreTimeAgoPipe, |         CoreTimeAgoPipe, | ||||||
|  |         CoreToLocaleStringPipe, | ||||||
|     ], |     ], | ||||||
|     exports: [ |     exports: [ | ||||||
|         CoreBytesToSizePipe, |         CoreBytesToSizePipe, | ||||||
| @ -43,6 +45,7 @@ import { CoreTimeAgoPipe } from './time-ago'; | |||||||
|         CoreNoTagsPipe, |         CoreNoTagsPipe, | ||||||
|         CoreSecondsToHMSPipe, |         CoreSecondsToHMSPipe, | ||||||
|         CoreTimeAgoPipe, |         CoreTimeAgoPipe, | ||||||
|  |         CoreToLocaleStringPipe, | ||||||
|     ], |     ], | ||||||
| }) | }) | ||||||
| export class CorePipesModule {} | 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 { CoreLogger } from '@singletons/logger'; | ||||||
| import { makeSingleton, File, Zip, Platform, WebView } from '@singletons'; | import { makeSingleton, File, Zip, Platform, WebView } from '@singletons'; | ||||||
| import { CoreFileEntry } from '@services/file-helper'; | import { CoreFileEntry } from '@services/file-helper'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Progress event used when writing a file data into a file. |  * Progress event used when writing a file data into a file. | ||||||
| @ -916,7 +917,7 @@ export class CoreFileProvider { | |||||||
|         if (path.indexOf(this.basePath) > -1) { |         if (path.indexOf(this.basePath) > -1) { | ||||||
|             return path; |             return path; | ||||||
|         } else { |         } else { | ||||||
|             return CoreTextUtils.concatenatePaths(this.basePath, path); |             return CoreText.concatenatePaths(this.basePath, path); | ||||||
|         } |         } | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
| @ -1245,7 +1246,7 @@ export class CoreFileProvider { | |||||||
|      */ |      */ | ||||||
|     getWWWAbsolutePath(): string { |     getWWWAbsolutePath(): string { | ||||||
|         if (window.cordova && cordova.file && cordova.file.applicationDirectory) { |         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.
 |         // 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 { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy'; | ||||||
| import { lazyMap, LazyMap } from '../utils/lazy-map'; | import { lazyMap, LazyMap } from '../utils/lazy-map'; | ||||||
| import { asyncInstance, AsyncInstance } from '../utils/async-instance'; | import { asyncInstance, AsyncInstance } from '../utils/async-instance'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  * Factory for handling downloading files and retrieve downloaded files. |  * Factory for handling downloading files and retrieve downloaded files. | ||||||
| @ -811,7 +812,7 @@ export class CoreFilepoolProvider { | |||||||
|                 if (file.filepath && file.filepath !== '/') { |                 if (file.filepath && file.filepath !== '/') { | ||||||
|                     path = file.filepath.substring(1) + path; |                     path = file.filepath.substring(1) + path; | ||||||
|                 } |                 } | ||||||
|                 path = CoreTextUtils.concatenatePaths(dirPath, path); |                 path = CoreText.concatenatePaths(dirPath, path); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|             if (prefetch) { |             if (prefetch) { | ||||||
| @ -905,7 +906,7 @@ export class CoreFilepoolProvider { | |||||||
|                     if (file.filepath && file.filepath !== '/') { |                     if (file.filepath && file.filepath !== '/') { | ||||||
|                         path = file.filepath.substring(1) + path; |                         path = file.filepath.substring(1) + path; | ||||||
|                     } |                     } | ||||||
|                     path = CoreTextUtils.concatenatePaths(dirPath, path); |                     path = CoreText.concatenatePaths(dirPath, path); | ||||||
|                 } |                 } | ||||||
| 
 | 
 | ||||||
|                 if (prefetch) { |                 if (prefetch) { | ||||||
| @ -2948,7 +2949,7 @@ export class CoreFilepoolProvider { | |||||||
|      * and store the result in the CSS file. |      * and store the result in the CSS file. | ||||||
|      * |      * | ||||||
|      * @param siteId Site ID. |      * @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 cssCode CSS code. | ||||||
|      * @param component The component to link the file to. |      * @param component The component to link the file to. | ||||||
|      * @param componentId An ID to use in conjunction with the component. |      * @param componentId An ID to use in conjunction with the component. | ||||||
| @ -2967,19 +2968,24 @@ export class CoreFilepoolProvider { | |||||||
|         let updated = false; |         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.
 |         // 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) ? |         const filePath = await this.getFilePathByUrl(siteId, fileUrl); | ||||||
|             fileUrl : |  | ||||||
|             await this.getFilePathByUrl(siteId, fileUrl); |  | ||||||
| 
 | 
 | ||||||
|         // Download all files in the CSS.
 |         // Download all files in the CSS.
 | ||||||
|         await Promise.all(urls.map(async (url) => { |         await Promise.all(urls.map(async (url) => { | ||||||
|  |             if (!url.trim()) { | ||||||
|  |                 return; // Ignore empty URLs.
 | ||||||
|  |             } | ||||||
|  | 
 | ||||||
|  |             const absoluteUrl = CoreUrl.toAbsoluteURL(fileUrl, url); | ||||||
|  | 
 | ||||||
|             try { |             try { | ||||||
|                 let fileUrl = url; |                 let fileUrl = absoluteUrl; | ||||||
|                 if (!CoreUrlUtils.isLocalFileUrl(url)) { | 
 | ||||||
|  |                 if (!CoreUrlUtils.isLocalFileUrl(absoluteUrl)) { | ||||||
|                     // Not a local file, download it.
 |                     // Not a local file, download it.
 | ||||||
|                     fileUrl = await this.downloadUrl( |                     fileUrl = await this.downloadUrl( | ||||||
|                         siteId, |                         siteId, | ||||||
|                         url, |                         absoluteUrl, | ||||||
|                         false, |                         false, | ||||||
|                         component, |                         component, | ||||||
|                         componentId, |                         componentId, | ||||||
| @ -2994,12 +3000,18 @@ export class CoreFilepoolProvider { | |||||||
|                 // Convert the URL so it works in mobile devices.
 |                 // Convert the URL so it works in mobile devices.
 | ||||||
|                 fileUrl = CoreFile.convertFileSrc(fileUrl); |                 fileUrl = CoreFile.convertFileSrc(fileUrl); | ||||||
| 
 | 
 | ||||||
|                 if (fileUrl != url) { |                 if (fileUrl !== url) { | ||||||
|                     cssCode = cssCode.replace(new RegExp(CoreTextUtils.escapeForRegex(url), 'g'), fileUrl); |                     cssCode = cssCode.replace(new RegExp(CoreTextUtils.escapeForRegex(url), 'g'), fileUrl); | ||||||
|                     updated = true; |                     updated = true; | ||||||
|                 } |                 } | ||||||
|             } catch (error) { |             } catch (error) { | ||||||
|                 this.logger.warn('Error treating file ', url, 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. |      * @return Whether navigation suceeded. | ||||||
|      */ |      */ | ||||||
|     async navigateToSiteHome(options: Omit<CoreNavigationOptions, 'reset'> & { siteId?: string } = {}): Promise<boolean> { |     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, { |         return this.navigateToSitePath(landingPagePath, { | ||||||
|             ...options, |             ...options, | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ import { CoreContentLinksHelper } from '@features/contentlinks/services/contentl | |||||||
| import { CoreLoginHelper, CoreLoginSSOData } from '@features/login/services/login-helper'; | import { CoreLoginHelper, CoreLoginSSOData } from '@features/login/services/login-helper'; | ||||||
| import { ApplicationInit, makeSingleton, Translate } from '@singletons'; | import { ApplicationInit, makeSingleton, Translate } from '@singletons'; | ||||||
| import { CoreLogger } from '@singletons/logger'; | import { CoreLogger } from '@singletons/logger'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| import { CoreConstants } from '../constants'; | import { CoreConstants } from '../constants'; | ||||||
| import { CoreApp } from './app'; | import { CoreApp } from './app'; | ||||||
| import { CoreNavigator, CoreRedirectPayload } from './navigator'; | import { CoreNavigator, CoreRedirectPayload } from './navigator'; | ||||||
| @ -163,7 +164,7 @@ export class CoreCustomURLSchemesProvider { | |||||||
| 
 | 
 | ||||||
|             if (data.redirect && !data.redirect.match(/^https?:\/\//)) { |             if (data.redirect && !data.redirect.match(/^https?:\/\//)) { | ||||||
|                 // Redirect is a relative URL. Append the site URL.
 |                 // 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]; |             let siteIds = [siteId]; | ||||||
|  | |||||||
| @ -21,7 +21,6 @@ import { CoreFile } from '@services/file'; | |||||||
| import { CoreFileHelper } from '@services/file-helper'; | import { CoreFileHelper } from '@services/file-helper'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
| import { CoreDomUtils } from '@services/utils/dom'; | import { CoreDomUtils } from '@services/utils/dom'; | ||||||
| import { CoreTextUtils } from '@services/utils/text'; |  | ||||||
| import { CoreUrlUtils } from '@services/utils/url'; | import { CoreUrlUtils } from '@services/utils/url'; | ||||||
| import { CoreUtils, PromiseDefer } from '@services/utils/utils'; | import { CoreUtils, PromiseDefer } from '@services/utils/utils'; | ||||||
| 
 | 
 | ||||||
| @ -30,6 +29,7 @@ import { CoreLogger } from '@singletons/logger'; | |||||||
| import { CoreUrl } from '@singletons/url'; | import { CoreUrl } from '@singletons/url'; | ||||||
| import { CoreWindow } from '@singletons/window'; | import { CoreWindow } from '@singletons/window'; | ||||||
| import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; | import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * Possible types of frame elements. |  * Possible types of frame elements. | ||||||
| @ -420,7 +420,7 @@ export class CoreIframeUtilsProvider { | |||||||
|             if (src) { |             if (src) { | ||||||
|                 const dirAndFile = CoreFile.getFileAndDirectoryFromPath(src); |                 const dirAndFile = CoreFile.getFileAndDirectoryFromPath(src); | ||||||
|                 if (dirAndFile.directory) { |                 if (dirAndFile.directory) { | ||||||
|                     url = CoreTextUtils.concatenatePaths(dirAndFile.directory, url); |                     url = CoreText.concatenatePaths(dirAndFile.directory, url); | ||||||
|                 } else { |                 } else { | ||||||
|                     this.logger.warn('Cannot get iframe dir path to open relative url', url, element); |                     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 { |     injectiOSScripts(userScriptWindow: WKUserScriptWindow): void { | ||||||
|         const wwwPath = CoreFile.getWWWAbsolutePath(); |         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 }); |         userScriptWindow.WKUserScript?.addScript({ id: 'CoreIframeUtilsLinksScript', file: linksPath }); | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -269,24 +269,10 @@ export class CoreTextUtilsProvider { | |||||||
|      * @param leftPath Left path. |      * @param leftPath Left path. | ||||||
|      * @param rightPath Right path. |      * @param rightPath Right path. | ||||||
|      * @return Concatenated path. |      * @return Concatenated path. | ||||||
|  |      * @deprecated since 4.0. Use CoreText instead. | ||||||
|      */ |      */ | ||||||
|     concatenatePaths(leftPath: string, rightPath: string): string { |     concatenatePaths(leftPath: string, rightPath: string): string { | ||||||
|         if (!leftPath) { |         return CoreText.concatenatePaths(leftPath, rightPath); | ||||||
|             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; |  | ||||||
|         } |  | ||||||
|     } |     } | ||||||
| 
 | 
 | ||||||
|     /** |     /** | ||||||
| @ -770,7 +756,7 @@ export class CoreTextUtilsProvider { | |||||||
|             return { text }; |             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')); |         const matches = text.match(new RegExp(this.escapeForRegex(draftfileUrl) + '[^\'" ]+', 'ig')); | ||||||
| 
 | 
 | ||||||
|         if (!matches || !matches.length) { |         if (!matches || !matches.length) { | ||||||
| @ -839,7 +825,7 @@ export class CoreTextUtilsProvider { | |||||||
|             return treatedText; |             return treatedText; | ||||||
|         } |         } | ||||||
| 
 | 
 | ||||||
|         const draftfileUrl = this.concatenatePaths(siteUrl, 'draftfile.php'); |         const draftfileUrl = CoreText.concatenatePaths(siteUrl, 'draftfile.php'); | ||||||
|         const draftfileUrlRegexPrefix = this.escapeForRegex(draftfileUrl) + '/[^/]+/[^/]+/[^/]+/[^/]+/'; |         const draftfileUrlRegexPrefix = this.escapeForRegex(draftfileUrl) + '/[^/]+/[^/]+/[^/]+/[^/]+/'; | ||||||
| 
 | 
 | ||||||
|         files.forEach((file) => { |         files.forEach((file) => { | ||||||
|  | |||||||
| @ -21,6 +21,7 @@ import { makeSingleton } from '@singletons'; | |||||||
| import { CoreUrl } from '@singletons/url'; | import { CoreUrl } from '@singletons/url'; | ||||||
| import { CoreApp } from '@services/app'; | import { CoreApp } from '@services/app'; | ||||||
| import { CoreSites } from '@services/sites'; | import { CoreSites } from '@services/sites'; | ||||||
|  | import { CoreText } from '@singletons/text'; | ||||||
| 
 | 
 | ||||||
| /* | /* | ||||||
|  * "Utils" service with helper functions for URLs. |  * "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.
 |         // 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).
 |         // Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert).
 | ||||||
|         return !!accessKey && !url.match(/[&?]file=/) && ( |         return !!accessKey && !url.match(/[&?]file=/) && ( | ||||||
|             url.indexOf(CoreTextUtils.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 || |             url.indexOf(CoreText.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 || | ||||||
|             url.indexOf(CoreTextUtils.concatenatePaths(siteUrl, 'webservice/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); |             url = url.replace(/(\/webservice)?\/pluginfile\.php/, '/tokenpluginfile.php/' + accessKey); | ||||||
|         } else { |         } else { | ||||||
|             // Use pluginfile.php. Some webservices returns directly the correct download url, others not.
 |             // 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'); |                 url = url.replace('/pluginfile', '/webservice/pluginfile'); | ||||||
|             } |             } | ||||||
| 
 | 
 | ||||||
|  | |||||||
| @ -270,7 +270,11 @@ export class CoreWSProvider { | |||||||
|             onProgress && transfer.onProgress(onProgress); |             onProgress && transfer.onProgress(onProgress); | ||||||
| 
 | 
 | ||||||
|             // Download the file in the tmp file.
 |             // 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 = ''; |             let extension = ''; | ||||||
| 
 | 
 | ||||||
| @ -885,7 +889,9 @@ export class CoreWSProvider { | |||||||
|             itemid: options.itemId || 0, |             itemid: options.itemId || 0, | ||||||
|         }; |         }; | ||||||
|         options.chunkedMode = false; |         options.chunkedMode = false; | ||||||
|         options.headers = {}; |         options.headers = { | ||||||
|  |             'User-Agent': navigator.userAgent, // eslint-disable-line @typescript-eslint/naming-convention
 | ||||||
|  |         }; | ||||||
|         options['Connection'] = 'close'; |         options['Connection'] = 'close'; | ||||||
| 
 | 
 | ||||||
|         let success: FileUploadResult; |         let success: FileUploadResult; | ||||||
|  | |||||||
| @ -56,4 +56,12 @@ describe('CoreUrl singleton', () => { | |||||||
|         expect(CoreUrl.sameDomainAndPath('https://school.edu/moodle', 'https://school.edu/moodle/about')).toBe(false); |         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; |         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. |      * Guess the Moodle domain from a site url. | ||||||
|      * |      * | ||||||
| @ -231,4 +247,37 @@ export class CoreUrl { | |||||||
|         return urlAndAnchor[0]; |         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