MOBILE-4616 url: Migrate all CoreUrlUtils to CoreUrl static singleton

main
Pau Ferrer Ocaña 2024-07-16 13:55:35 +02:00
parent 5bd549477e
commit 1186694c5f
71 changed files with 998 additions and 667 deletions

View File

@ -69,8 +69,7 @@ jobs:
cat circular-dependencies
lines=$(cat circular-dependencies | wc -l)
echo "Total circular dependencies: $lines"
test $lines -ge 138
test $lines -le 148
test $lines -eq 135
- name: JavaScript code compatibility
run: |
npx check-es-compat www/*.js --polyfills="\{Array,String,TypedArray\}.prototype.at,Object.hasOwn"

View File

@ -454,7 +454,7 @@ class behat_app_helper extends behat_base {
$result = $this->zone_js("customUrlSchemes.handleCustomURL('$customurl')");
if ($result !== 'OK') {
throw new DriverException('Error handling url - ' . $result);
throw new DriverException('Error handling url - ' . $customurl . ' - '.$result);
}
if (!empty($successXPath)) {
// Wait until the page appears.

View File

@ -22,7 +22,7 @@ import { Translate } from '@singletons';
import { CoreUtils } from '@services/utils/utils';
import { CoreNavigator } from '@services/navigator';
import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreSharedModule } from '@/core/shared.module';
/**
@ -103,7 +103,7 @@ export class AddonBlockActivityModulesComponent extends CoreBlockBaseComponent i
brandedIcons[mod.modname] = mod.branded;
// If this is not a theme image, leave it undefined to avoid having specific activity icons.
if (CoreUrlUtils.isThemeImageUrl(mod.modicon)) {
if (CoreUrl.isThemeImageUrl(mod.modicon)) {
modIcons[mod.modname] = mod.modicon;
}
});

View File

@ -26,7 +26,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreArray } from '@singletons/array';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
@ -79,7 +79,7 @@ export class AddonBlogIndexPage implements OnInit, OnDestroy {
...this.filter,
category: 'blog',
},
url: CoreUrlUtils.addParamsToUrl('/blog/index.php', {
url: CoreUrl.addParamsToUrl('/blog/index.php', {
...this.filter,
modid: this.filter.cmid,
cmid: undefined,

View File

@ -50,7 +50,7 @@ import {
import { CoreSwipeSlidesDynamicItemsManager } from '@classes/items-management/swipe-slides-dynamic-items-manager';
import moment from 'moment-timezone';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreTime } from '@singletons/time';
import { Translate } from '@singletons';
@ -132,7 +132,7 @@ export class AddonCalendarCalendarComponent implements OnInit, DoCheck, OnDestro
...params,
category: 'calendar',
},
url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=month', params),
url: CoreUrl.addParamsToUrl('/calendar/view.php?view=month', params),
});
});
}

View File

@ -26,7 +26,7 @@ import { AddonCalendarOffline } from '../../services/calendar-offline';
import { CoreCategoryData, CoreCourses } from '@features/courses/services/courses';
import { CoreConstants } from '@/core/constants';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreTime } from '@singletons/time';
import { Translate } from '@singletons';
@ -103,7 +103,7 @@ export class AddonCalendarUpcomingEventsComponent implements OnInit, DoCheck, On
...params,
category: 'calendar',
},
url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=upcoming', params),
url: CoreUrl.addParamsToUrl('/calendar/view.php?view=upcoming', params),
});
});
}

View File

@ -47,7 +47,7 @@ import {
import { CoreRoutedItemsManagerSourcesTracker } from '@classes/items-management/routed-items-manager-sources-tracker';
import { AddonCalendarEventsSource } from '@addons/calendar/classes/events-source';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreTime } from '@singletons/time';
/**
@ -201,7 +201,7 @@ export class AddonCalendarDayPage implements OnInit, OnDestroy {
...params,
category: 'calendar',
},
url: CoreUrlUtils.addParamsToUrl('/calendar/view.php?view=day', params),
url: CoreUrl.addParamsToUrl('/calendar/view.php?view=day', params),
});
});
}

View File

@ -18,7 +18,7 @@ import { CoreSite } from '@classes/sites/site';
import { CoreNetwork } from '@services/network';
import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreGroups } from '@services/groups';
import { CoreLocalNotifications } from '@services/local-notifications';
@ -362,14 +362,14 @@ export class AddonCalendarProvider {
// Add links to the days if needed.
if (dayStart && (!seenDay || !moment(seenDay).isSame(start, 'day'))) {
promises.push(this.getViewUrl('day', event.timestart, undefined, siteId).then((url) => {
dayStart = CoreUrlUtils.buildLink(url, dayStart);
dayStart = CoreUrl.buildLink(url, dayStart);
return;
}));
}
if (dayEnd && (!seenDay || !moment(seenDay).isSame(end, 'day'))) {
promises.push(this.getViewUrl('day', end / 1000, undefined, siteId).then((url) => {
dayEnd = CoreUrlUtils.buildLink(url, dayEnd);
dayEnd = CoreUrl.buildLink(url, dayEnd);
return;
}));
@ -398,7 +398,7 @@ export class AddonCalendarProvider {
// Add link to view the day.
const url = await this.getViewUrl('day', event.timestart, undefined, siteId);
return CoreUrlUtils.buildLink(url, this.getDayRepresentation(start, useCommonWords)) + ', ' + time;
return CoreUrl.buildLink(url, this.getDayRepresentation(start, useCommonWords)) + ', ' + time;
}
/**

View File

@ -40,7 +40,7 @@ import { ActivatedRoute, ActivatedRouteSnapshot } from '@angular/router';
import { AddonCompetencyCourseCompetenciesSource } from '@addons/competency/classes/competency-course-competencies-source';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
/**
* Page that displays the competency information.
@ -306,7 +306,7 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
planstatus: this.planStatus,
userid: userId,
},
url: CoreUrlUtils.addParamsToUrl('/admin/tool/lp/user_competency_in_plan.php', {
url: CoreUrl.addParamsToUrl('/admin/tool/lp/user_competency_in_plan.php', {
planid: source.PLAN_ID,
userid: userId,
competencyid: compId,
@ -328,7 +328,7 @@ export class AddonCompetencyCompetencyPage implements OnInit, OnDestroy {
courseid: source.COURSE_ID,
userid: userId,
},
url: CoreUrlUtils.addParamsToUrl('/admin/tool/lp/user_competency_in_course.php', {
url: CoreUrl.addParamsToUrl('/admin/tool/lp/user_competency_in_course.php', {
courseid: source.COURSE_ID,
competencyid: compId,
userid: userId,

View File

@ -18,7 +18,7 @@ import { COURSE_PAGE_NAME } from '@features/course/constants';
import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate';
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
import { CoreNavigator } from '@services/navigator';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonCompetency } from '../competency';
@ -49,7 +49,7 @@ export class AddonCompetencyPushClickHandlerService implements CorePushNotificat
* @inheritdoc
*/
async handleClick(notification: AddonCompetencyPushNotificationData): Promise<void> {
const contextUrlParams = CoreUrlUtils.extractUrlParams(notification.contexturl);
const contextUrlParams = CoreUrl.extractUrlParams(notification.contexturl);
if (notification.name == 'competencyplancomment') {
// Open the learning plan.

View File

@ -18,7 +18,7 @@ import { CoreFilterDefaultHandler } from '@features/filter/services/handlers/def
import { CoreFilterFilter, CoreFilterFormatTextOptions } from '@features/filter/services/filter';
import { makeSingleton } from '@singletons';
import { CoreH5PPlayerComponent } from '@features/h5p/components/h5p-player/h5p-player';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreH5PHelper } from '@features/h5p/classes/helper';
/**
@ -57,7 +57,7 @@ export class AddonFilterDisplayH5PHandlerService extends CoreFilterDefaultHandle
embeddedH5PIframes.forEach((iframe) => {
// Add the preventredirect param to allow authenticating if auto-login fails.
iframe.src = CoreUrlUtils.addParamsToUrl(iframe.src, { preventredirect: false });
iframe.src = CoreUrl.addParamsToUrl(iframe.src, { preventredirect: false });
// Add resizer script so the H5P has the right height.
CoreH5PHelper.addResizerScript();

View File

@ -17,7 +17,7 @@ import { CorePromisedValue } from '@classes/promised-value';
import { CoreExternalContentDirective } from '@directives/external-content';
import { CoreLang } from '@services/lang';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { makeSingleton } from '@singletons';
import { CoreDirectivesRegistry } from '@singletons/directives-registry';
import { CoreEvents } from '@singletons/events';
@ -107,7 +107,7 @@ export class AddonFilterMediaPluginVideoJSService {
const dataSetupString = video.getAttribute('data-setup') || video.getAttribute('data-setup-lazy') || '{}';
const data = CoreTextUtils.parseJSON<VideoJSOptions>(dataSetupString, {});
const youtubeUrl = data.techOrder?.[0] == 'youtube' && CoreUrlUtils.getYoutubeEmbedUrl(data.sources?.[0]?.src);
const youtubeUrl = data.techOrder?.[0] == 'youtube' && CoreUrl.getYoutubeEmbedUrl(data.sources?.[0]?.src);
if (!youtubeUrl) {
return;

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate';
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonModAssign } from '../assign';
@ -50,7 +50,7 @@ export class AddonModAssignPushClickHandlerService implements CorePushNotificati
* @returns Promise resolved when done.
*/
async handleClick(notification: NotificationData): Promise<void> {
const contextUrlParams = CoreUrlUtils.extractUrlParams(notification.contexturl);
const contextUrlParams = CoreUrl.extractUrlParams(notification.contexturl);
const courseId = Number(notification.courseid);
const moduleId = Number(contextUrlParams.id);

View File

@ -38,7 +38,7 @@ import {
AddonModBookTocChapter,
} from '../../services/book';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { ADDON_MOD_BOOK_COMPONENT, AddonModBookNavStyle } from '../../constants';
/**
@ -293,7 +293,7 @@ export class AddonModBookContentsPage implements OnInit, OnDestroy {
ws: 'mod_book_view_book',
name: this.module.name,
data: { id: this.module.instance, category: 'book', chapterid: chapterId },
url: CoreUrlUtils.addParamsToUrl(`/mod/book/view.php?id=${this.module.id}`, { chapterid: chapterId }),
url: CoreUrl.addParamsToUrl(`/mod/book/view.php?id=${this.module.id}`, { chapterid: chapterId }),
});
const currentChapterIndex = this.chapters.findIndex((chapter) => chapter.id == chapterId);

View File

@ -18,7 +18,7 @@ import { CoreTagFeedComponent } from '@features/tag/components/feed/feed';
import { CoreTagAreaHandler } from '@features/tag/services/tag-area-delegate';
import { CoreTagFeedElement, CoreTagHelper } from '@features/tag/services/tag-helper';
import { CoreSitesReadingStrategy } from '@services/sites';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { makeSingleton } from '@singletons';
import { AddonModBook } from '../book';
@ -51,7 +51,7 @@ export class AddonModBookTagAreaHandlerService implements CoreTagAreaHandler {
// Find module ids of the returned books, they are needed by the link delegate.
await Promise.all(items.map(async (item) => {
const params = item.url ? CoreUrlUtils.extractUrlParams(item.url) : {};
const params = item.url ? CoreUrl.extractUrlParams(item.url) : {};
if (params.b && !params.id) {
const bookId = parseInt(params.b, 10);

View File

@ -40,7 +40,7 @@ import { AddonModDataHelper, AddonModDatDisplayFieldsOptions } from '../../servi
import { AddonModDataAutoSyncData, AddonModDataSyncResult } from '../../services/data-sync';
import { AddonModDataPrefetchHandler } from '../../services/handlers/prefetch-lazy';
import { AddonModDataComponentsCompileModule } from '../components-compile.module';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreTime } from '@singletons/time';
import {
ADDON_MOD_DATA_AUTO_SYNCED,
@ -568,7 +568,7 @@ export class AddonModDataIndexComponent extends CoreCourseModuleMainActivityComp
this.analyticsLogEvent('mod_data_search_entries', {
data: params,
url: CoreUrlUtils.addParamsToUrl(`/mod/data/view.php?d=${this.database.id}`, params),
url: CoreUrl.addParamsToUrl(`/mod/data/view.php?d=${this.database.id}`, params),
});
}

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate';
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonModFeedbackHelper } from '../feedback-helper';
@ -48,7 +48,7 @@ export class AddonModFeedbackPushClickHandlerService implements CorePushNotifica
* @inheritdoc
*/
handleClick(notification: AddonModFeedbackPushNotificationData): Promise<void> {
const contextUrlParams = CoreUrlUtils.extractUrlParams(notification.contexturl!);
const contextUrlParams = CoreUrl.extractUrlParams(notification.contexturl!);
const courseId = Number(notification.courseid);
const moduleId = Number(contextUrlParams.id);

View File

@ -27,7 +27,7 @@ import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
@ -116,7 +116,7 @@ export class AddonModForumSearchPage implements OnInit {
query,
filters: JSON.stringify(this.resultsSource.getFilters()),
},
url: CoreUrlUtils.addParamsToUrl('/search/index.php', {
url: CoreUrl.addParamsToUrl('/search/index.php', {
q: query,
}),
});

View File

@ -24,7 +24,7 @@ import { CoreNetwork } from '@services/network';
import { CoreFileEntry } from '@services/file-helper';
import { CoreGroups } from '@services/groups';
import { CoreSitesCommonWSOptions, CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreStatusWithWarningsWSResponse, CoreWSExternalFile, CoreWSExternalWarning, CoreWSStoredFile } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
@ -1312,7 +1312,7 @@ export class AddonModForumProvider {
protected translateWSPost(post: AddonModForumWSPost): AddonModForumPost {
(post as unknown as AddonModForumPost).tags = (post.tags || []).map((tag) => {
const viewUrl = (tag.urls && tag.urls.view) || '';
const params = CoreUrlUtils.extractUrlParams(viewUrl);
const params = CoreUrl.extractUrlParams(viewUrl);
return {
id: tag.tagid,

View File

@ -19,7 +19,7 @@ import { AddonModForum } from '@addons/mod/forum/services/forum';
import { CoreNavigator } from '@services/navigator';
import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate';
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
@ -56,7 +56,7 @@ export class AddonModForumPushClickHandlerService implements CorePushNotificatio
* @returns Promise resolved when done.
*/
async handleClick(notification: NotificationData): Promise<void> {
const contextUrlParams = CoreUrlUtils.extractUrlParams(notification.contexturl);
const contextUrlParams = CoreUrl.extractUrlParams(notification.contexturl);
const data = notification.customdata || {};
const courseId = Number(notification.courseid);
const discussionId = Number(contextUrlParams.d || data.discussionid);

View File

@ -23,7 +23,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSync } from '@services/sync';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile } from '@services/ws';
import { ModalController, Translate } from '@singletons';
@ -439,7 +439,7 @@ export class AddonModLessonPlayerPage implements OnInit, OnDestroy, CanLeave {
// Format review lesson if present.
if (this.eolData.reviewlesson) {
const params = CoreUrlUtils.extractUrlParams(<string> this.eolData.reviewlesson.value);
const params = CoreUrl.extractUrlParams(<string> this.eolData.reviewlesson.value);
if (!params || !params.pageid) {
// No pageid in the URL, the user cannot review (probably didn't answer any question).

View File

@ -23,7 +23,7 @@ import { CoreNetwork } from '@services/network';
import { CoreSites, CoreSitesReadingStrategy } from '@services/sites';
import { CoreSync, CoreSyncResult } from '@services/sync';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton, Translate } from '@singletons';
import { CoreEvents } from '@singletons/events';
@ -463,7 +463,7 @@ export class AddonModLessonSyncProvider extends CoreCourseActivitySyncBaseProvid
// Mark the retake as finished in a sync if it can be reviewed.
if (!ignoreBlock && response.data?.reviewlesson) {
const params = CoreUrlUtils.extractUrlParams(<string> response.data.reviewlesson.value);
const params = CoreUrl.extractUrlParams(<string> response.data.reviewlesson.value);
if (params.pageid) {
// The retake can be reviewed, mark it as finished. Don't block the user for this.
this.setRetakeFinishedInSync(lessonId, retake.retake, Number(params.pageid), siteId);

View File

@ -22,7 +22,7 @@ import { CoreFile } from '@services/file';
import { CorePlatform } from '@services/platform';
import { CoreSites, CoreSitesCommonWSOptions } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSExternalFile, CoreWSExternalWarning } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
@ -244,7 +244,7 @@ export class AddonModLtiProvider {
* @returns Promise resolved when the WS call is successful.
*/
async launch(url: string, params: AddonModLtiParam[]): Promise<void> {
if (!CoreUrlUtils.isHttpURL(url)) {
if (!CoreUrl.isHttpURL(url)) {
throw Translate.instant('addon.mod_lti.errorinvalidlaunchurl');
}

View File

@ -17,7 +17,7 @@ import { Injectable } from '@angular/core';
import { CoreCourseHelper } from '@features/course/services/course-helper';
import { CorePushNotificationsClickHandler } from '@features/pushnotifications/services/push-delegate';
import { CorePushNotificationsNotificationBasicData } from '@features/pushnotifications/services/pushnotifications';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { AddonModQuiz } from '../quiz';
@ -56,7 +56,7 @@ export class AddonModQuizPushClickHandlerService implements CorePushNotification
* @returns Promise resolved when done.
*/
async handleClick(notification: AddonModQuizPushNotificationData): Promise<void> {
const contextUrlParams = CoreUrlUtils.extractUrlParams(notification.contexturl || '');
const contextUrlParams = CoreUrl.extractUrlParams(notification.contexturl || '');
const data = notification.customdata || {};
const courseId = Number(notification.courseid);

View File

@ -23,7 +23,7 @@ import { CoreSites, CoreSitesCommonWSOptions, CoreSitesReadingStrategy } from '@
import { CoreSync } from '@services/sync';
import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreWS, CoreWSExternalFile, CoreWSExternalWarning, CoreWSFile, CoreWSPreSets } from '@services/ws';
import { makeSingleton, Translate } from '@singletons';
@ -1328,7 +1328,7 @@ export class AddonModScormProvider {
protected isExternalLink(link: string): boolean {
link = link.toLowerCase();
if (link.match(/^https?:\/\//i) && !CoreUrlUtils.isLocalFileUrl(link)) {
if (link.match(/^https?:\/\//i) && !CoreUrl.isLocalFileUrl(link)) {
return true;
} else if (link.substring(0, 4) == 'www.') {
return true;

View File

@ -26,7 +26,7 @@ import { makeSingleton } from '@singletons';
import { AddonModUrl } from '../url';
import { AddonModUrlHelper } from '../url-helper';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { ADDON_MOD_URL_ADDON_NAME, ADDON_MOD_URL_MODNAME, ADDON_MOD_URL_PAGE_NAME } from '../../constants';
@ -122,14 +122,14 @@ export class AddonModUrlModuleHandlerService extends CoreModuleHandlerBase imple
return modIcon;
}
const component = CoreUrlUtils.getThemeImageUrlParam(module.modicon, 'component');
const component = CoreUrl.getThemeImageUrlParam(module.modicon, 'component');
if (component === this.modName) {
return modIcon;
}
let icon: string | undefined;
let image = CoreUrlUtils.getThemeImageUrlParam(module.modicon, 'image');
let image = CoreUrl.getThemeImageUrlParam(module.modicon, 'image');
if (image.startsWith('f/')) {
// Remove prefix, and hyphen + numbered suffix.
image = image.substring(2).replace(/-[0-9]+$/, '');

View File

@ -26,7 +26,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreSites } from '@services/sites';
import { CoreDomUtils, ToastDuration } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEventObserver, CoreEvents } from '@singletons/events';
@ -313,7 +313,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
ws: 'core_notes_view_notes',
name: Translate.instant('addon.notes.notes'),
data: { courseid: this.courseId, userid: this.userId || 0, category: 'notes' },
url: CoreUrlUtils.addParamsToUrl('/notes/index.php', {
url: CoreUrl.addParamsToUrl('/notes/index.php', {
user: this.userId,
course: this.courseId !== CoreSites.getCurrentSiteHomeId() ? this.courseId : undefined,
}),
@ -329,7 +329,7 @@ export class AddonNotesListPage implements OnInit, OnDestroy {
ws: 'core_notes_create_notes',
name: Translate.instant('addon.notes.notes'),
data: { courseid: this.courseId, userid: this.userId || 0, category: 'notes' },
url: CoreUrlUtils.addParamsToUrl('/notes/edit.php', {
url: CoreUrl.addParamsToUrl('/notes/edit.php', {
courseid: this.courseId,
userid: this.userId,
publishstate: this.type === 'personal' ? 'draft' : (this.type === 'course' ? 'public' : 'site'),

View File

@ -39,7 +39,6 @@ import { CoreSiteError } from '@classes/errors/siteerror';
import { CoreUserAuthenticatedSupportConfig } from '@features/user/classes/support/authenticated-support-config';
import { CoreSiteInfo, CoreSiteInfoResponse, CoreSitePublicConfigResponse, CoreUnauthenticatedSite } from './unauthenticated-site';
import { Md5 } from 'ts-md5';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreSiteWSCacheRecord } from '@services/database/sites';
import { CoreErrorLogs } from '@singletons/error-logs';
import { CoreWait } from '@singletons/wait';
@ -1268,11 +1267,33 @@ export class CoreAuthenticatedSite extends CoreUnauthenticatedSite {
*
* @param page Docs page to go to.
* @returns Promise resolved with the Moodle docs URL.
*
* @deprecated since 4.5. Not needed anymore.
*/
getDocsUrl(page?: string): Promise<string> {
async getDocsUrl(page?: string): Promise<string> {
const release = this.infos?.release ? this.infos.release : undefined;
let docsUrl = 'https://docs.moodle.org/en/' + page;
return CoreUrlUtils.getDocsUrl(release, page);
if (release !== undefined) {
// Remove this part of the function if this file only uses CoreSites here.
const version = CoreSites.getMajorReleaseNumber(release).replace('.', '');
// Check is a valid number.
if (Number(version) >= 24) {
// Append release number.
docsUrl = docsUrl.replace('https://docs.moodle.org/', 'https://docs.moodle.org/' + version + '/');
}
}
try {
// Remove this part of the function if this file only uses CoreLang here.
let lang = CoreLang.getCurrentLanguageSync(CoreLangFormat.LMS);
lang = CoreLang.getParentLanguage() || lang;
return docsUrl.replace('/en/', '/' + lang + '/');
} catch {
return docsUrl;
}
}
/**

View File

@ -27,7 +27,7 @@ import {
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils, CoreUtilsOpenInBrowserOptions } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import { SQLiteDB } from '@classes/sqlitedb';
@ -380,7 +380,7 @@ export class CoreSite extends CoreAuthenticatedSite {
const accessKey = this.tokenPluginFileWorks || this.tokenPluginFileWorks === undefined ?
this.infos && this.infos.userprivateaccesskey : undefined;
return CoreUrlUtils.fixPluginfileURL(url, this.token || '', this.siteUrl, accessKey);
return CoreUrl.fixPluginfileURL(url, this.token || '', this.siteUrl, accessKey);
}
/**
@ -775,7 +775,7 @@ export class CoreSite extends CoreAuthenticatedSite {
* @returns Promise resolved with boolean: whether it works or not.
*/
checkTokenPluginFile(url: string): Promise<boolean> {
if (!CoreUrlUtils.canUseTokenPluginFile(url, this.siteUrl, this.infos && this.infos.userprivateaccesskey)) {
if (!CoreUrl.canUseTokenPluginFile(url, this.siteUrl, this.infos && this.infos.userprivateaccesskey)) {
// Cannot use tokenpluginfile.
return Promise.resolve(false);
} else if (this.tokenPluginFileWorks !== undefined) {

View File

@ -17,7 +17,7 @@ import { CoreError } from '@classes/errors/error';
import { CoreLoginHelper } from '@features/login/services/login-helper';
import { CoreSitesReadingStrategy } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreWS, CoreWSAjaxPreSets, CoreWSExternalWarning } from '@services/ws';
import { CorePath } from '@singletons/path';
@ -37,7 +37,10 @@ export class CoreUnauthenticatedSite {
* @param publicConfig Site public config.
*/
constructor(siteUrl: string, publicConfig?: CoreSitePublicConfigResponse) {
this.siteUrl = CoreUrlUtils.removeUrlParams(siteUrl); // Make sure the URL doesn't have params.
this.siteUrl = CoreUrl.removeUrlParts(
siteUrl,
[CoreUrlPartNames.Query, CoreUrlPartNames.Fragment],
); // Make sure the URL doesn't have params.
if (publicConfig) {
this.setPublicConfig(publicConfig);
}
@ -143,7 +146,7 @@ export class CoreUnauthenticatedSite {
* @returns URL with params.
*/
createSiteUrl(path: string, params?: Record<string, unknown>, anchor?: string): string {
return CoreUrlUtils.addParamsToUrl(CorePath.concatenatePaths(this.siteUrl, path), params, anchor);
return CoreUrl.addParamsToUrl(CorePath.concatenatePaths(this.siteUrl, path), params, anchor);
}
/**
@ -157,8 +160,10 @@ export class CoreUnauthenticatedSite {
return false;
}
const siteUrl = CoreTextUtils.addEndingSlash(CoreUrlUtils.removeProtocolAndWWW(this.siteUrl));
url = CoreTextUtils.addEndingSlash(CoreUrlUtils.removeProtocolAndWWW(url));
const siteUrl = CoreTextUtils.addEndingSlash(
CoreUrl.removeUrlParts(this.siteUrl, [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]),
);
url = CoreTextUtils.addEndingSlash(CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]));
return url.indexOf(siteUrl) == 0;
}
@ -244,7 +249,10 @@ export class CoreUnauthenticatedSite {
// Use the wwwroot returned by the server.
if (config.httpswwwroot) {
this.siteUrl = CoreUrlUtils.removeUrlParams(config.httpswwwroot); // Make sure the URL doesn't have params.
this.siteUrl = CoreUrl.removeUrlParts(
config.httpswwwroot,
[CoreUrlPartNames.Query, CoreUrlPartNames.Fragment],
); // Make sure the URL doesn't have params.
}
return config;
@ -268,7 +276,7 @@ export class CoreUnauthenticatedSite {
* @returns Whether it's a site file URL.
*/
isSitePluginFileUrl(url: string): boolean {
const isPluginFileUrl = CoreUrlUtils.isPluginFileUrl(url) || CoreUrlUtils.isTokenPluginFileUrl(url);
const isPluginFileUrl = CoreUrl.isPluginFileUrl(url) || CoreUrl.isTokenPluginFileUrl(url);
if (!isPluginFileUrl) {
return false;
}
@ -283,7 +291,7 @@ export class CoreUnauthenticatedSite {
* @returns Whether it's a site theme image URL.
*/
isSiteThemeImageUrl(url: string): boolean {
if (!CoreUrlUtils.isThemeImageUrl(url)) {
if (!CoreUrl.isThemeImageUrl(url)) {
return false;
}

View File

@ -20,7 +20,7 @@ import { CorePluginFileDelegate } from '@services/plugin-file-delegate';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/utils/utils';
import { CoreTextUtils } from '@services/utils/text';
import { DownloadStatus } from '@/core/constants';
@ -195,10 +195,10 @@ export class CoreFileComponent implements OnInit, OnDestroy {
if (!this.canDownload || !this.state || this.state === DownloadStatus.NOT_DOWNLOADABLE) {
// File cannot be downloaded, just open it.
if (CoreUrlUtils.isLocalFileUrl(this.fileUrl)) {
if (CoreUrl.isLocalFileUrl(this.fileUrl)) {
CoreUtils.openFile(this.fileUrl);
} else {
CoreUtils.openOnlineFile(CoreUrlUtils.unfixPluginfileURL(this.fileUrl));
CoreUtils.openOnlineFile(CoreUrl.unfixPluginfileURL(this.fileUrl));
}
return;

View File

@ -19,7 +19,7 @@ import { SafeResourceUrl } from '@angular/platform-browser';
import { CoreFile } from '@services/file';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreIframeUtils } from '@services/utils/iframe';
import { CoreUtils } from '@services/utils/utils';
import { DomSanitizer, Router, StatusBar } from '@singletons';
@ -29,7 +29,6 @@ import { Subscription } from 'rxjs';
import { filter } from 'rxjs/operators';
import { NavigationStart } from '@angular/router';
import { CoreSites } from '@services/sites';
import { CoreUrl } from '@singletons/url';
@Component({
selector: 'core-iframe',
@ -118,7 +117,7 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
}
// Show loading only with external URLs.
this.loading = !this.src || !CoreUrlUtils.isLocalFileUrl(this.src);
this.loading = !this.src || !CoreUrl.isLocalFileUrl(this.src);
if (this.loading) {
setTimeout(() => {
@ -197,8 +196,8 @@ export class CoreIframeComponent implements OnChanges, OnDestroy {
this.launchExternalLabel = undefined;
if (url && !CoreUrlUtils.isLocalFileUrl(url)) {
url = CoreUrlUtils.getYoutubeEmbedUrl(url) || url;
if (url && !CoreUrl.isLocalFileUrl(url)) {
url = CoreUrl.getYoutubeEmbedUrl(url) || url;
this.displayHelp = CoreIframeUtils.shouldDisplayHelpForUrl(url);
const currentSite = CoreSites.getCurrentSite();

View File

@ -28,7 +28,7 @@ import { CoreCourse } from '@features/course/services/course';
import { CoreCourseModuleDelegate } from '@features/course/services/module-delegate';
import { CoreSites } from '@services/sites';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
const assetsPath = 'assets/img/';
const fallbackModName = 'external-tool';
@ -155,8 +155,8 @@ export class CoreModIconComponent implements OnInit, OnChanges {
}
// If it's an Moodle Theme icon, check if filtericon is set and use it.
if (CoreUrlUtils.isThemeImageUrl(this.iconUrl())) {
const filter = CoreUrlUtils.getThemeImageUrlParam(this.iconUrl(), 'filtericon');
if (CoreUrl.isThemeImageUrl(this.iconUrl())) {
const filter = CoreUrl.getThemeImageUrlParam(this.iconUrl(), 'filtericon');
if (filter === '1') {
this.brandedClass = false;
@ -233,7 +233,7 @@ export class CoreModIconComponent implements OnInit, OnChanges {
* @returns Guessed modname.
*/
protected getComponentNameFromIconUrl(iconUrl: string): string {
const component = CoreUrlUtils.getThemeImageUrlParam(iconUrl, 'component');
const component = CoreUrl.getThemeImageUrlParam(iconUrl, 'component');
// Some invalid components (others may be added later on).
if (component === 'core' || component === 'theme') {

View File

@ -21,7 +21,7 @@ import { USER_PROFILE_PICTURE_UPDATED, CoreUserBasicData } from '@features/user/
import { CoreNavigator } from '@services/navigator';
import { CoreNetwork } from '@services/network';
import { CoreUserHelper } from '@features/user/services/user-helper';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreSiteInfo } from '@classes/sites/unauthenticated-site';
/**
@ -124,7 +124,7 @@ export class CoreUserAvatarComponent implements OnInit, OnChanges, OnDestroy {
this.fullname = this.fullname || (this.user && (this.user.fullname || this.user.userfullname));
if (this.avatarUrl && CoreUrlUtils.isThemeImageUrl(this.avatarUrl)) {
if (this.avatarUrl && CoreUrl.isThemeImageUrl(this.avatarUrl)) {
this.avatarUrl = undefined;
}

View File

@ -26,7 +26,7 @@ import {
import { CoreFile, CoreFileProvider } from '@services/file';
import { CoreFilepool, CoreFilepoolFileActions, CoreFilepoolFileEventData } from '@services/filepool';
import { CoreSites } from '@services/sites';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreLogger } from '@singletons/logger';
import { CoreError } from '@classes/errors/error';
@ -206,8 +206,8 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
const site = await CoreUtils.ignoreErrors(CoreSites.getSite(this.siteId));
const isSiteFile = site?.isSitePluginFileUrl(url);
if (!url || !url.match(/^https?:\/\//i) || CoreUrlUtils.isLocalFileUrl(url) ||
(tagName === 'A' && !(isSiteFile || site?.isSiteThemeImageUrl(url) || CoreUrlUtils.isGravatarUrl(url)))) {
if (!url || !url.match(/^https?:\/\//i) || CoreUrl.isLocalFileUrl(url) ||
(tagName === 'A' && !(isSiteFile || site?.isSiteThemeImageUrl(url) || CoreUrl.isGravatarUrl(url)))) {
this.logger.debug('Ignoring non-downloadable URL: ' + url);
@ -393,7 +393,7 @@ export class CoreExternalContentDirective implements AfterViewInit, OnChanges, O
finalUrl = CoreFile.convertFileSrc(finalUrl);
}
if (!CoreUrlUtils.isLocalFileUrl(finalUrl) && !finalUrl.includes('#') && tagName !== 'A') {
if (!CoreUrl.isLocalFileUrl(finalUrl) && !finalUrl.includes('#') && tagName !== 'A') {
/* In iOS, if we use the same URL in embedded file and background download then the download only
downloads a few bytes (cached ones). Add an anchor to the URL so both URLs are different.
Don't add this anchor if the URL already has an anchor, otherwise other anchors might not work.

View File

@ -19,7 +19,7 @@ import { IonContent } from '@ionic/angular';
import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreTextUtils } from '@services/utils/text';
import { CoreConstants } from '@/core/constants';
@ -27,7 +27,6 @@ import { CoreContentLinksHelper } from '@features/contentlinks/services/contentl
import { CoreCustomURLSchemes } from '@services/urlschemes';
import { DomSanitizer } from '@singletons';
import { CoreFilepool } from '@services/filepool';
import { CoreUrl } from '@singletons/url';
import { CoreDom } from '@singletons/dom';
/**
@ -87,7 +86,7 @@ export class CoreLinkDirective implements OnInit {
href = href || this.element.getAttribute('href') || this.element.getAttribute('xlink:href');
if (!href || CoreUrlUtils.getUrlScheme(href) === 'javascript') {
if (!href || CoreUrl.getUrlProtocol(href) === 'javascript') {
return;
}
@ -116,7 +115,7 @@ export class CoreLinkDirective implements OnInit {
*/
protected async navigate(href: string, openIn?: string | null): Promise<void> {
if (CoreUrlUtils.isLocalFileUrl(href)) {
if (CoreUrl.isLocalFileUrl(href)) {
return this.openLocalFile(href);
}

View File

@ -15,11 +15,10 @@
import { Injectable } from '@angular/core';
import { CoreLogger } from '@singletons/logger';
import { CoreSites } from '@services/sites';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton } from '@singletons';
import { CoreText } from '@singletons/text';
import { CoreUrl } from '@singletons/url';
/**
* Interface that all handlers must implement.
@ -174,7 +173,7 @@ export class CoreContentLinksDelegateService {
const linkActions: CoreContentLinksHandlerActions[] = [];
const promises: Promise<void>[] = [];
const params = CoreUrlUtils.extractUrlParams(url);
const params = CoreUrl.extractUrlParams(url);
const relativeUrl = CoreText.addStartingSlash(CoreUrl.toRelativeURL(site.getURL(), url));
for (const name in this.handlers) {

View File

@ -31,7 +31,7 @@ import { CoreCourseHelper, CoreCourseModuleData } from '../services/course-helpe
import { CoreCourseModuleDelegate, CoreCourseModuleMainComponent } from '../services/module-delegate';
import { CoreCourseModulePrefetchDelegate } from '../services/module-prefetch-delegate';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreTime } from '@singletons/time';
/**
@ -506,7 +506,7 @@ export class CoreCourseModuleMainResourceComponent implements OnInit, OnDestroy,
url = options.url;
} else if (this.pluginName) {
// Use default value.
url = CoreUrlUtils.addParamsToUrl(`/mod/${this.pluginName}/view.php?id=${this.module.id}`, options.data);
url = CoreUrl.addParamsToUrl(`/mod/${this.pluginName}/view.php?id=${this.module.id}`, options.data);
}
}

View File

@ -59,7 +59,7 @@ import { CoreFileHelper } from '@services/file-helper';
import { CoreNetwork } from '@services/network';
import { CoreSite } from '@classes/sites/site';
import { CoreFile } from '@services/file';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreFilterHelper } from '@features/filter/services/filter-helper';
@ -710,7 +710,7 @@ export class CoreCourseHelperProvider {
options,
);
if (CoreUrlUtils.isLocalFileUrl(result.path)) {
if (CoreUrl.isLocalFileUrl(result.path)) {
return CoreUtils.openFile(result.path, options);
}

View File

@ -31,7 +31,7 @@ import { Subscription } from 'rxjs';
import { CoreSites } from '@services/sites';
import { CoreFilepool } from '@services/filepool';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreEventFormActionData, CoreEventObserver, CoreEvents } from '@singletons/events';
import { CoreEditorOffline } from '../../services/editor-offline';
@ -514,7 +514,7 @@ export class CoreEditorRichTextEditorComponent implements OnInit, AfterViewInit,
const url = el.src;
if (!url || !CoreUrlUtils.isDownloadableUrl(url) || (!canDownloadFiles && site?.isSitePluginFileUrl(url))) {
if (!url || !CoreUrl.isDownloadableUrl(url) || (!canDownloadFiles && site?.isSitePluginFileUrl(url))) {
// Nothing to treat.
return;
}

View File

@ -34,7 +34,7 @@ import {
CoreGradesTableRow,
} from '@features/grades/services/grades';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreMenuItem, CoreUtils } from '@services/utils/utils';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreNavigator } from '@services/navigator';
@ -416,7 +416,7 @@ export class CoreGradesHelperProvider {
const matches = row.itemname.content.match(regex);
if (matches && matches.length) {
const hrefParams = CoreUrlUtils.extractUrlParams(matches[1]);
const hrefParams = CoreUrl.extractUrlParams(matches[1]);
return hrefParams && parseInt(hrefParams.id) === moduleId;
}

View File

@ -14,7 +14,7 @@
import { CoreFile } from '@services/file';
import { CoreSites } from '@services/sites';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreH5P } from '../services/h5p';
import { CoreH5PCore, CoreH5PDisplayOptions, CoreH5PContentData, CoreH5PDependenciesFiles } from './core';
@ -51,7 +51,7 @@ export class CoreH5PPlayer {
params.component = component;
}
return CoreUrlUtils.addParamsToUrl(CorePath.concatenatePaths(siteUrl, '/h5p/embed.php'), params);
return CoreUrl.addParamsToUrl(CorePath.concatenatePaths(siteUrl, '/h5p/embed.php'), params);
}
/**
@ -303,7 +303,7 @@ export class CoreH5PPlayer {
params.customCssUrl = customCssUrl;
}
return CoreUrlUtils.addParamsToUrl(path, params);
return CoreUrl.addParamsToUrl(path, params);
}
/**

View File

@ -22,7 +22,7 @@ import { CoreFilepool } from '@services/filepool';
import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreH5P } from '@features/h5p/services/h5p';
import { DownloadStatus } from '@/core/constants';
import { CoreSite } from '@classes/sites/site';
@ -140,7 +140,7 @@ export class CoreH5PIframeComponent implements OnChanges, OnDestroy {
);
// Add the preventredirect param so the user can authenticate.
this.iframeSrc = CoreUrlUtils.addParamsToUrl(src, { preventredirect: false });
this.iframeSrc = CoreUrl.addParamsToUrl(src, { preventredirect: false });
}
} catch (error) {
CoreDomUtils.showErrorModalDefault(error, 'Error loading H5P package.', true);

View File

@ -18,7 +18,7 @@ import { CoreNetwork } from '@services/network';
import { CoreFilepool } from '@services/filepool';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CorePluginFileDelegate } from '@services/plugin-file-delegate';
import { DownloadStatus } from '@/core/constants';
import { CoreSite } from '@classes/sites/site';
@ -168,7 +168,7 @@ export class CoreH5PPlayerComponent implements OnInit, OnChanges, OnDestroy {
*/
protected async checkCanDownload(): Promise<void> {
this.observer && this.observer.off();
this.urlParams = CoreUrlUtils.extractUrlParams(this.src || '');
this.urlParams = CoreUrl.extractUrlParams(this.src || '');
if (this.src && this.siteCanDownload && CoreH5P.canGetTrustedH5PFileInSite() && this.site.containsUrl(this.src)) {
this.calculateState();

View File

@ -16,7 +16,7 @@ import { Injectable } from '@angular/core';
import { CoreSites } from '@services/sites';
import { CoreWSExternalWarning, CoreWSExternalFile, CoreWSFile } from '@services/ws';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreQueueRunner } from '@classes/queue-runner';
import { CoreSite } from '@classes/sites/site';
@ -246,7 +246,7 @@ export class CoreH5PProvider {
url = url.replace('/webservice/pluginfile', '/pluginfile');
}
return CoreUrlUtils.removeUrlParams(url);
return CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]);
}
}

View File

@ -19,7 +19,7 @@ import { CoreFilepoolOnProgressCallback } from '@services/filepool';
import { CorePluginFileDownloadableResult, CorePluginFileHandler } from '@services/plugin-file-delegate';
import { CoreSites } from '@services/sites';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreWSFile } from '@services/ws';
import { CoreH5P } from '../h5p';
@ -80,7 +80,7 @@ export class CoreH5PPluginFileHandlerService implements CorePluginFileHandler {
const urls: string[] = [];
for (let i = 0; i < iframes.length; i++) {
const params = CoreUrlUtils.extractUrlParams(iframes[i].src);
const params = CoreUrl.extractUrlParams(iframes[i].src);
if (params.url) {
urls.push(params.url);

View File

@ -28,8 +28,7 @@ import {
import { CoreError } from '@classes/errors/error';
import { CoreConstants } from '@/core/constants';
import { Translate } from '@singletons';
import { CoreUrl } from '@singletons/url';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreNavigator } from '@services/navigator';
import { CoreCustomURLSchemes, CoreCustomURLSchemesHandleError } from '@services/urlschemes';
import { CoreTextUtils } from '@services/utils/text';
@ -206,7 +205,9 @@ export class CoreLoginSitePage implements OnInit {
*/
protected extendCoreLoginSiteInfo(sites: CoreLoginSiteInfoExtended[]): CoreLoginSiteInfoExtended[] {
return sites.map((site) => {
site.noProtocolUrl = this.siteFinderSettings.displayurl && site.url ? CoreUrl.removeProtocol(site.url) : '';
site.noProtocolUrl = this.siteFinderSettings.displayurl && site.url
? CoreUrl.removeUrlParts(site.url, CoreUrlPartNames.Protocol)
: '';
const name = this.siteFinderSettings.displaysitename ? site.name : '';
const alias = this.siteFinderSettings.displayalias && site.alias ? site.alias : '';
@ -510,7 +511,7 @@ export class CoreLoginSitePage implements OnInit {
name: 'connect',
title: '',
location: '',
noProtocolUrl: CoreUrl.removeProtocol(search),
noProtocolUrl: CoreUrl.removeUrlParts(search, CoreUrlPartNames.Protocol),
};
} else {
this.enteredSiteUrl = undefined;
@ -563,7 +564,7 @@ export class CoreLoginSitePage implements OnInit {
}
// Not a custom URL scheme, check if it's a URL scheme to another app.
const scheme = CoreUrlUtils.getUrlProtocol(text);
const scheme = CoreUrl.getUrlProtocol(text);
if (scheme && scheme != 'http' && scheme != 'https') {
CoreDomUtils.showErrorModal(Translate.instant('core.errorurlschemeinvalidscheme', { $a: text }));

View File

@ -23,7 +23,6 @@ import { CoreSites, CoreLoginSiteInfo, CoreSiteBasicInfo } from '@services/sites
import { CoreWS, CoreWSExternalWarning } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlParams, CoreUrlUtils } from '@services/utils/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import { CoreSite } from '@classes/sites/site';
@ -31,7 +30,7 @@ import { CoreError } from '@classes/errors/error';
import { CoreWSError } from '@classes/errors/wserror';
import { DomSanitizer, makeSingleton, Translate } from '@singletons';
import { CoreLogger } from '@singletons/logger';
import { CoreUrl } from '@singletons/url';
import { CoreUrl, CoreUrlParams } from '@singletons/url';
import { CoreNavigator, CoreRedirectPayload } from '@services/navigator';
import { CoreCanceledError } from '@classes/errors/cancelederror';
import { CoreCustomURLSchemes } from '@services/urlschemes';
@ -356,7 +355,7 @@ export class CoreLoginHelperProvider {
if (siteConfig.identityproviders && siteConfig.identityproviders.length) {
siteConfig.identityproviders.forEach((provider) => {
const urlParams = CoreUrlUtils.extractUrlParams(provider.url);
const urlParams = CoreUrl.extractUrlParams(provider.url);
if (
provider.url &&
@ -397,7 +396,7 @@ export class CoreLoginHelperProvider {
if (siteConfig.identityproviders && siteConfig.identityproviders.length) {
siteConfig.identityproviders.forEach((provider) => {
const urlParams = CoreUrlUtils.extractUrlParams(provider.url);
const urlParams = CoreUrl.extractUrlParams(provider.url);
if (provider.url && (provider.url.indexOf(httpsUrl) != -1 || provider.url.indexOf(httpUrl) != -1) &&
!site.isFeatureDisabled(IDENTITY_PROVIDER_FEATURE_NAME_PREFIX + urlParams.id)) {
@ -642,7 +641,7 @@ export class CoreLoginHelperProvider {
return false;
}
const params = CoreUrlUtils.extractUrlParams(provider.url);
const params = CoreUrl.extractUrlParams(provider.url);
if (!params.id) {
return false;
@ -830,7 +829,7 @@ export class CoreLoginHelperProvider {
loginUrl += '&urlscheme=' + CoreConstants.CONFIG.customurlscheme;
if (urlParams) {
loginUrl = CoreUrlUtils.addParamsToUrl(loginUrl, urlParams);
loginUrl = CoreUrl.addParamsToUrl(loginUrl, urlParams);
}
// Store the siteurl and passport in CoreConfigProvider for persistence.
@ -1334,7 +1333,7 @@ export class CoreLoginHelperProvider {
}
} else if (text) {
// Not a custom URL scheme, check if it's a URL scheme to another app.
const scheme = CoreUrlUtils.getUrlProtocol(text);
const scheme = CoreUrl.getUrlProtocol(text);
if (scheme && scheme != 'http' && scheme != 'https') {
CoreDomUtils.showErrorModal(Translate.instant('core.errorurlschemeinvalidscheme', { $a: text }));

View File

@ -25,7 +25,7 @@ import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { Translate } from '@singletons';
import { CorePolicy, CorePolicyAgreementStyle, CorePolicySitePolicy } from '@features/policy/services/policy';
import { FormControl, FormGroup, Validators } from '@angular/forms';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { IonContent } from '@ionic/angular';
import { CoreScreen } from '@services/screen';
import { Subscription } from 'rxjs';
@ -223,7 +223,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
ws: 'tool_policy_get_user_acceptances',
name: this.currentPolicy.name,
data: analyticsParams,
url: CoreUrlUtils.addParamsToUrl('/admin/tool/policy/view.php', analyticsParams),
url: CoreUrl.addParamsToUrl('/admin/tool/policy/view.php', analyticsParams),
});
}
@ -236,7 +236,7 @@ export class CorePolicySitePolicyPage implements OnInit, OnDestroy {
ws: 'tool_policy_get_user_acceptances',
name: Translate.instant('core.policy.consentpagetitle'),
data: {},
url: CoreUrlUtils.addParamsToUrl('/admin/tool/policy/index.php'),
url: CoreUrl.addParamsToUrl('/admin/tool/policy/index.php'),
});
}

View File

@ -18,7 +18,7 @@ import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreWSFile } from '@services/ws';
import { CoreIonicColorNames } from '@singletons/colors';
import { CoreLogger } from '@singletons/logger';
@ -367,7 +367,7 @@ export class CoreQuestionBaseComponent<T extends AddonModQuizQuestion = AddonMod
}
if (fileManagerUrl) {
const params = CoreUrlUtils.extractUrlParams(fileManagerUrl);
const params = CoreUrl.extractUrlParams(fileManagerUrl);
const maxBytes = Number(params.maxbytes);
const areaMaxBytes = Number(params.areamaxbytes);

View File

@ -27,7 +27,7 @@ import { makeSingleton, Translate } from '@singletons';
import { CoreQuestion, CoreQuestionProvider, CoreQuestionQuestionParsed, CoreQuestionsAnswers } from './question';
import { CoreQuestionDelegate } from './question-delegate';
import { CoreIcons } from '@singletons/icons';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { ContextLevel } from '@/core/constants';
import { CoreIonicColorNames } from '@singletons/colors';
@ -681,7 +681,7 @@ export class CoreQuestionHelperProvider {
return;
}
if (CoreUrlUtils.isThemeImageUrl(fileUrl) && fileUrl.indexOf('flagged') > -1) {
if (CoreUrl.isThemeImageUrl(fileUrl) && fileUrl.indexOf('flagged') > -1) {
// Ignore flag images.
return;
}

View File

@ -19,7 +19,7 @@ import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { Translate } from '@singletons';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreEvents, CoreEventObserver } from '@singletons/events';
import {
CoreSearchGlobalSearchResult,
@ -115,7 +115,7 @@ export class CoreSearchGlobalSearchPage implements OnInit, OnDestroy, AfterViewI
query,
filters: JSON.stringify(this.resultsSource.getFilters()),
},
url: CoreUrlUtils.addParamsToUrl('/search/index.php', {
url: CoreUrl.addParamsToUrl('/search/index.php', {
q: query,
}),
});

View File

@ -84,7 +84,7 @@ import { CoreContentLinksModuleIndexHandler } from '@features/contentlinks/class
import { CoreContentLinksDelegate } from '@features/contentlinks/services/contentlinks-delegate';
import { CoreContentLinksModuleListHandler } from '@features/contentlinks/classes/module-list-handler';
import { CoreObject } from '@singletons/object';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CorePath } from '@singletons/path';
import { CoreEnrolAction, CoreEnrolDelegate } from '@features/enrol/services/enrol-delegate';
import { CoreSitePluginsEnrolHandler } from '../classes/handlers/enrol-handler';
@ -170,7 +170,7 @@ export class CoreSitePluginsInitService {
// Make sure it's an absolute URL. Do not use toAbsoluteURL because it can change the behaviour and break plugin styles.
let url = handlerSchema.styles?.url;
if (url && !CoreUrlUtils.isAbsoluteURL(url)) {
if (url && !CoreUrl.isAbsoluteURL(url)) {
url = CorePath.concatenatePaths(site.getURL(), url);
}

View File

@ -21,7 +21,7 @@ import { CoreNavigator } from '@services/navigator';
import { CoreTime } from '@singletons/time';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { Translate } from '@singletons';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
/**
* Page that displays the tag index.
@ -62,7 +62,7 @@ export class CoreTagIndexPage implements OnInit {
ws: 'core_tag_get_tagindex_per_area',
name: this.tagName || Translate.instant('core.tag.tag'),
data: { id: this.tagId || undefined, ...params, category: 'tag' },
url: CoreUrlUtils.addParamsToUrl('/tag/index.php', params),
url: CoreUrl.addParamsToUrl('/tag/index.php', params),
});
});
}

View File

@ -32,7 +32,7 @@ import { CoreSite } from '@classes/sites/site';
import { CoreFileUploaderHelper } from '@features/fileuploader/services/fileuploader-helper';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { Translate } from '@singletons';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
/**
* Page that displays info about a user.
@ -247,7 +247,7 @@ export class CoreUserAboutPage implements OnInit, OnDestroy {
return 'undefined';
}
if (CoreUrlUtils.isThemeImageUrl(avatarUrl, this.site?.siteUrl)) {
if (CoreUrl.isThemeImageUrl(avatarUrl, this.site?.siteUrl)) {
return 'default';
}

View File

@ -26,7 +26,7 @@ import { CoreEvents, CoreEventSiteData, CoreEventUserDeletedData, CoreEventUserS
import { CoreStatusWithWarningsWSResponse, CoreWSExternalWarning } from '@services/ws';
import { CoreError } from '@classes/errors/error';
import { USERS_TABLE_NAME, CoreUserDBRecord } from './database/user';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreSiteWSPreSets } from '@classes/sites/authenticated-site';
import { CoreConstants } from '@/core/constants';
@ -668,7 +668,7 @@ export class CoreUserProvider {
}
// Do not prefetch when initials are set and image is default.
if (imageUrl && CoreUrlUtils.isThemeImageUrl(imageUrl)) {
if (imageUrl && CoreUrl.isThemeImageUrl(imageUrl)) {
return;
}

View File

@ -20,7 +20,7 @@ import { CorePlatform } from '@services/platform';
import { CoreSites } from '@services/sites';
import { CoreCustomURLSchemes } from '@services/urlschemes';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreEvents } from '@singletons/events';
@ -34,9 +34,9 @@ export default function(): void {
// Check URLs loaded in any InAppBrowser.
CoreEvents.on(CoreEvents.IAB_LOAD_START, async (event) => {
// URLs with a custom scheme can be prefixed with "http://" or "https://", we need to remove this.
const protocol = CoreUrlUtils.getUrlProtocol(event.url);
const protocol = CoreUrl.getUrlProtocol(event.url);
const url = event.url.replace(/^https?:\/\//, '');
const urlScheme = CoreUrlUtils.getUrlProtocol(url);
const urlScheme = CoreUrl.getUrlProtocol(url);
const isExternalApp = urlScheme && urlScheme !== 'file' && urlScheme !== 'cdvfile';
if (CoreCustomURLSchemes.isCustomURL(url)) {

View File

@ -20,8 +20,8 @@ import { CoreEvents } from '@singletons/events';
import { CoreSites } from './sites';
import { CoreConfig, CoreConfigProvider } from './config';
import { CoreConstants } from '../constants';
import { CoreUrlUtils } from './utils/url';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrl } from '@singletons/url';
/**
* Helper service to support analytics.
@ -107,7 +107,7 @@ export class CoreAnalyticsService extends CoreDelegate<CoreAnalyticsHandler> {
}
if ('url' in treatedEvent && treatedEvent.url) {
if (!CoreUrlUtils.isAbsoluteURL(treatedEvent.url)) {
if (!CoreUrl.isAbsoluteURL(treatedEvent.url)) {
treatedEvent.url = site.createSiteUrl(treatedEvent.url);
} else if (!site.containsUrl(treatedEvent.url)) {
// URL belongs to a different site, ignore the event.

View File

@ -21,7 +21,7 @@ import { CoreFilepool } from '@services/filepool';
import { CoreSites } from '@services/sites';
import { CoreWS, CoreWSFile } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils, CoreUtilsOpenFileOptions, OpenFileAction } from '@services/utils/utils';
import { CoreConstants, DownloadStatus } from '@/core/constants';
import { CoreError } from '@classes/errors/error';
@ -93,7 +93,7 @@ export class CoreFileHelperProvider {
return;
}
if (!CoreUrlUtils.isLocalFileUrl(url)) {
if (!CoreUrl.isLocalFileUrl(url)) {
/* In iOS, if we use the same URL in embedded browser and background download then the download only
downloads a few bytes (cached ones). Add a hash to the URL so both URLs are different. */
url = url + '#moodlemobile-embedded';

View File

@ -26,7 +26,7 @@ import { CoreDomUtils } from '@services/utils/dom';
import { CoreMimetypeUtils } from '@services/utils/mimetype';
import { CoreTextUtils } from '@services/utils/text';
import { CoreTimeUtils } from '@services/utils/time';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreUtils, CoreUtilsOpenFileOptions } from '@services/utils/utils';
import { CoreError } from '@classes/errors/error';
import { DownloadStatus } from '@/core/constants';
@ -51,7 +51,6 @@ import {
QUEUE_TABLE_PRIMARY_KEYS,
} from '@services/database/filepool';
import { CoreFileHelper } from './file-helper';
import { CoreUrl } from '@singletons/url';
import { CoreDatabaseTable } from '@classes/database/database-table';
import { CoreDatabaseCachingStrategy, CoreDatabaseTableProxy } from '@classes/database/database-table-proxy';
import { lazyMap, LazyMap } from '../utils/lazy-map';
@ -782,7 +781,7 @@ export class CoreFilepoolProvider {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.DOWNLOAD_FILE,
fileUrl: CoreUrlUtils.unfixPluginfileURL(fileUrl, site.getURL()),
fileUrl: CoreUrl.unfixPluginfileURL(fileUrl, site.getURL()),
});
// Add the anchor again to the local URL.
@ -1126,14 +1125,14 @@ export class CoreFilepoolProvider {
const element = elements[i];
const url = 'href' in element ? element.href : element.src;
if (url && CoreUrlUtils.isDownloadableUrl(url) && urls.indexOf(url) == -1) {
if (url && CoreUrl.isDownloadableUrl(url) && urls.indexOf(url) == -1) {
urls.push(url);
}
// Treat video poster.
if (element.tagName == 'VIDEO' && element.getAttribute('poster')) {
const poster = element.getAttribute('poster');
if (poster && CoreUrlUtils.isDownloadableUrl(poster) && urls.indexOf(poster) == -1) {
if (poster && CoreUrl.isDownloadableUrl(poster) && urls.indexOf(poster) == -1) {
urls.push(poster);
}
}
@ -1363,7 +1362,7 @@ export class CoreFilepoolProvider {
}
// Remove the anchor.
url = CoreUrl.removeUrlAnchor(url);
url = CoreUrl.removeUrlParts(url, CoreUrlPartNames.Fragment);
// Try to guess the filename the target file should have.
// We want to keep the original file name so people can easily identify the files after the download.
@ -1535,7 +1534,7 @@ export class CoreFilepoolProvider {
return DownloadStatus.NOT_DOWNLOADABLE;
}
fileUrl = CoreUrl.removeUrlAnchor(CoreFileHelper.getFileUrl(file));
fileUrl = CoreUrl.removeUrlParts(CoreFileHelper.getFileUrl(file), CoreUrlPartNames.Fragment);
timemodified = file.timemodified ?? timemodified;
revision = revision ?? this.getRevisionFromUrl(fileUrl);
const fileId = this.getFileIdByUrl(fileUrl);
@ -1914,7 +1913,7 @@ export class CoreFilepoolProvider {
* @returns The args found, undefined if not a pluginfile.
*/
protected getPluginFileArgs(url: string): string[] | undefined {
if (!CoreUrlUtils.isPluginFileUrl(url)) {
if (!CoreUrl.isPluginFileUrl(url)) {
// Not pluginfile, return.
return;
}
@ -2160,27 +2159,27 @@ export class CoreFilepoolProvider {
if (fileUrl.indexOf('/webservice/pluginfile') !== -1) {
// It's a pluginfile URL. Search for the 'file' param to extract the name.
const params = CoreUrlUtils.extractUrlParams(fileUrl);
const params = CoreUrl.extractUrlParams(fileUrl);
if (params.file) {
filename = params.file.substring(params.file.lastIndexOf('/') + 1);
} else {
// 'file' param not found. Extract what's after the last '/' without params.
filename = CoreUrlUtils.getLastFileWithoutParams(fileUrl);
filename = CoreUrl.getLastFileWithoutParams(fileUrl);
}
} else if (CoreUrlUtils.isGravatarUrl(fileUrl)) {
} else if (CoreUrl.isGravatarUrl(fileUrl)) {
// Extract gravatar ID.
filename = 'gravatar_' + CoreUrlUtils.getLastFileWithoutParams(fileUrl);
} else if (CoreUrlUtils.isThemeImageUrl(fileUrl)) {
filename = 'gravatar_' + CoreUrl.getLastFileWithoutParams(fileUrl);
} else if (CoreUrl.isThemeImageUrl(fileUrl)) {
// Extract user ID.
const matches = fileUrl.match(/\/core\/([^/]*)\//);
if (matches && matches[1]) {
filename = matches[1];
}
// Attach a constant and the image type.
filename = 'default_' + filename + '_' + CoreUrlUtils.getLastFileWithoutParams(fileUrl);
filename = 'default_' + filename + '_' + CoreUrl.getLastFileWithoutParams(fileUrl);
} else {
// Another URL. Just get what's after the last /.
filename = CoreUrlUtils.getLastFileWithoutParams(fileUrl);
filename = CoreUrl.getLastFileWithoutParams(fileUrl);
}
// If there are hashes in the URL, extract them.
@ -3005,7 +3004,7 @@ export class CoreFilepoolProvider {
try {
let fileUrl = absoluteUrl;
if (!CoreUrlUtils.isLocalFileUrl(absoluteUrl)) {
if (!CoreUrl.isLocalFileUrl(absoluteUrl)) {
// Not a local file, download it.
fileUrl = await this.downloadUrl(
siteId,

View File

@ -23,7 +23,7 @@ import { CoreMainMenu } from '@features/mainmenu/services/mainmenu';
import { CoreObject } from '@singletons/object';
import { CoreSites } from '@services/sites';
import { CoreUtils } from '@services/utils/utils';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreTextUtils } from '@services/utils/text';
import { makeSingleton, NavController, Router } from '@singletons';
import { CoreScreen } from './screen';
@ -262,7 +262,7 @@ export class CoreNavigatorService {
* @returns Current path.
*/
getCurrentPath(): string {
return CoreUrlUtils.removeUrlParams(Router.url);
return CoreUrl.removeUrlParts(Router.url, [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]);
}
/**

View File

@ -21,7 +21,7 @@ import { CoreEvents } from '@singletons/events';
import { CoreWS } from '@services/ws';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreTextUtils } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import {
@ -286,9 +286,9 @@ export class CoreSitesProvider {
*/
async checkSite(siteUrl: string, protocol: string = 'https://'): Promise<CoreSiteCheckResponse> {
// The formatURL function adds the protocol if is missing.
siteUrl = CoreUrlUtils.formatURL(siteUrl);
siteUrl = CoreUrl.formatURL(siteUrl);
if (!CoreUrlUtils.isHttpURL(siteUrl)) {
if (!CoreUrl.isHttpURL(siteUrl)) {
throw new CoreError(Translate.instant('core.login.invalidsite'));
}
@ -350,7 +350,7 @@ export class CoreSitesProvider {
}
// Try to add or remove 'www'.
temporarySite.setURL(CoreUrlUtils.addOrRemoveWWW(temporarySite.getURL()));
temporarySite.setURL(CoreUrl.addOrRemoveWWW(temporarySite.getURL()));
try {
config = await temporarySite.getPublicConfig();
@ -546,7 +546,7 @@ export class CoreSitesProvider {
// We only allow one retry (to avoid loops).
if (!retry && data.errorcode == 'requirecorrectaccess') {
siteUrl = CoreUrlUtils.addOrRemoveWWW(siteUrl);
siteUrl = CoreUrl.addOrRemoveWWW(siteUrl);
return this.getUserToken(siteUrl, username, password, service, true);
}
@ -1697,7 +1697,7 @@ export class CoreSitesProvider {
// Check if URL has http(s) protocol.
if (!url.match(/^https?:\/\//i)) {
// URL doesn't have http(s) protocol. Check if it has any protocol.
if (CoreUrlUtils.isAbsoluteURL(url)) {
if (CoreUrl.isAbsoluteURL(url)) {
// It has some protocol. Return empty array.
return [];
}
@ -1943,11 +1943,13 @@ export class CoreSitesProvider {
const site = await this.getSite(siteIds[0]);
const siteUrl = CoreText.removeEndingSlash(
CoreUrlUtils.removeProtocolAndWWW(site.getURL()),
CoreUrl.removeUrlParts(site.getURL(), [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]),
);
const treatedUrl = CoreText.removeEndingSlash(
CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]),
);
const treatedUrl = CoreText.removeEndingSlash(CoreUrlUtils.removeProtocolAndWWW(url));
if (siteUrl == treatedUrl) {
if (siteUrl === treatedUrl) {
result.site = site;
}

View File

@ -1,90 +0,0 @@
// (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 { CoreUrlUtilsProvider } from '@services/utils/url';
describe('CoreUrlUtilsProvider', () => {
let urlUtils: CoreUrlUtilsProvider;
beforeEach(() => {
urlUtils = new CoreUrlUtilsProvider();
});
it('adds www if missing', () => {
const originalUrl = 'https://moodle.org';
const url = urlUtils.addOrRemoveWWW(originalUrl);
expect(url).toEqual('https://www.moodle.org');
});
it('removes www if present', () => {
const originalUrl = 'https://www.moodle.org';
const url = urlUtils.addOrRemoveWWW(originalUrl);
expect(url).toEqual('https://moodle.org');
});
it('adds params to URL without params', () => {
const originalUrl = 'https://moodle.org';
const params = {
first: '1',
second: '2',
};
const url = urlUtils.addParamsToUrl(originalUrl, params);
expect(url).toEqual('https://moodle.org?first=1&second=2');
});
it('adds params to URL with existing params', () => {
const originalUrl = 'https://moodle.org?existing=1';
const params = {
first: '1',
second: '2',
};
const url = urlUtils.addParamsToUrl(originalUrl, params);
expect(url).toEqual('https://moodle.org?existing=1&first=1&second=2');
});
it('doesn\'t change URL if no params supplied', () => {
const originalUrl = 'https://moodle.org';
const url = urlUtils.addParamsToUrl(originalUrl);
expect(url).toEqual(originalUrl);
});
it('doesn\'t add undefined or null params', () => {
const originalUrl = 'https://moodle.org';
const url = urlUtils.addParamsToUrl(originalUrl, {
foo: undefined,
bar: null,
baz: 1,
});
expect(url).toEqual('https://moodle.org?baz=1');
});
it('adds anchor to URL', () => {
const originalUrl = 'https://moodle.org';
const params = {
first: '1',
second: '2',
};
const url = urlUtils.addParamsToUrl(originalUrl, params, 'myanchor');
expect(url).toEqual('https://moodle.org?first=1&second=2#myanchor');
});
});

View File

@ -28,7 +28,7 @@ import { CoreNavigator, CoreRedirectPayload } from './navigator';
import { CoreSiteCheckResponse, CoreSites } from './sites';
import { CoreDomUtils } from './utils/dom';
import { CoreTextErrorObject, CoreTextUtils } from './utils/text';
import { CoreUrlUtils } from './utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from './utils/utils';
/*
@ -240,13 +240,13 @@ export class CoreCustomURLSchemesProvider {
url = this.removeCustomURLScheme(url);
// Detect if there's a user specified.
const username = CoreUrlUtils.getUsernameFromUrl(url);
const username = CoreUrl.getUsernameFromUrl(url);
if (username) {
url = url.replace(username + '@', ''); // Remove the username from the URL.
}
// Get the params of the URL.
const params = CoreUrlUtils.extractUrlParams(url);
const params = CoreUrl.extractUrlParams(url);
// Remove the params to get the site URL.
if (url.indexOf('?') != -1) {
@ -293,7 +293,7 @@ export class CoreCustomURLSchemesProvider {
url = this.removeCustomURLLinkScheme(url);
// Detect if there's a user specified.
const username = CoreUrlUtils.getUsernameFromUrl(url);
const username = CoreUrl.getUsernameFromUrl(url);
if (username) {
url = url.replace(username + '@', ''); // Remove the username from the URL.
}

View File

@ -21,7 +21,7 @@ import { CoreConfig } from '@services/config';
import { CoreFile } from '@services/file';
import { CoreWSExternalWarning } from '@services/ws';
import { CoreTextUtils, CoreTextErrorObject } from '@services/utils/text';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { CoreConstants } from '@/core/constants';
import { CoreIonLoadingElement } from '@classes/ion-loading';
@ -710,7 +710,10 @@ export class CoreDomUtilsProvider {
media.forEach((media: HTMLElement) => {
const currentSrc = media.getAttribute('src');
const newSrc = currentSrc ?
paths[CoreUrlUtils.removeUrlParams(CoreTextUtils.decodeURIComponent(currentSrc))] :
paths[CoreUrl.removeUrlParts(
CoreTextUtils.decodeURIComponent(currentSrc),
[CoreUrlPartNames.Query, CoreUrlPartNames.Fragment],
)] :
undefined;
if (newSrc !== undefined) {
@ -732,7 +735,10 @@ export class CoreDomUtilsProvider {
anchors.forEach((anchor: HTMLElement) => {
const currentHref = anchor.getAttribute('href');
const newHref = currentHref ?
paths[CoreUrlUtils.removeUrlParams(CoreTextUtils.decodeURIComponent(currentHref))] :
paths[CoreUrl.removeUrlParts(
CoreTextUtils.decodeURIComponent(currentHref),
[CoreUrlPartNames.Query, CoreUrlPartNames.Fragment],
)] :
undefined;
if (newHref !== undefined) {

View File

@ -21,12 +21,11 @@ import { CoreFile } from '@services/file';
import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { makeSingleton, NgZone, Translate } from '@singletons';
import { CoreLogger } from '@singletons/logger';
import { CoreUrl } from '@singletons/url';
import { CoreWindow } from '@singletons/window';
import { CoreContentLinksHelper } from '@features/contentlinks/services/contentlinks-helper';
import { CorePath } from '@singletons/path';
@ -68,7 +67,7 @@ export class CoreIframeUtilsProvider {
checkOnlineFrameInOffline(element: CoreFrameElement, isSubframe?: boolean): boolean {
const src = 'src' in element ? element.src : element.data;
if (src && src != 'about:blank' && !CoreUrlUtils.isLocalFileUrl(src) && !CoreNetwork.isOnline()) {
if (src && src != 'about:blank' && !CoreUrl.isLocalFileUrl(src) && !CoreNetwork.isOnline()) {
if (element.classList.contains('core-iframe-offline-disabled')) {
// Iframe already hidden, stop.
return true;
@ -234,7 +233,7 @@ export class CoreIframeUtilsProvider {
*/
getContentWindowAndDocument(element: CoreFrameElement): { window: Window | null; document: Document | null } {
const src = 'src' in element ? element.src : element.data;
if (src !== 'about:blank' && !CoreUrlUtils.isLocalFileUrl(src)) {
if (src !== 'about:blank' && !CoreUrl.isLocalFileUrl(src)) {
// No permissions to access the iframe.
return { window: null, document: null };
}
@ -423,7 +422,7 @@ export class CoreIframeUtilsProvider {
* @returns Promise resolved when done.
*/
protected async windowOpen(url: string, name: string, element?: CoreFrameElement): Promise<void> {
const scheme = CoreUrlUtils.getUrlScheme(url);
const scheme = CoreUrl.getUrlProtocol(url);
if (!scheme) {
// It's a relative URL, use the frame src to create the full URL.
const src = element
@ -488,12 +487,12 @@ export class CoreIframeUtilsProvider {
const urlParts = CoreUrl.parse(link.href);
const originalHref = 'getAttribute' in link ? link.getAttribute('href') : link.originalHref;
if (!link.href || !originalHref || originalHref == '#' || !urlParts || urlParts.protocol == 'javascript') {
if (!link.href || !originalHref || originalHref == '#' || !urlParts || urlParts.protocol === 'javascript') {
// Links with no URL and Javascript links are ignored.
return;
}
if (urlParts.protocol && !CoreUrlUtils.isLocalFileUrlScheme(urlParts.protocol, urlParts.domain || '')) {
if (urlParts.protocol && !CoreUrl.isLocalFileUrlScheme(urlParts.protocol, urlParts.domain || '')) {
// Scheme suggests it's an external resource.
event && event.preventDefault();
@ -503,7 +502,7 @@ export class CoreIframeUtilsProvider {
if (
element &&
frameSrc &&
!CoreUrlUtils.isLocalFileUrl(frameSrc) &&
!CoreUrl.isLocalFileUrl(frameSrc) &&
(!link.target || link.target == '_self')
) {
// Load the link inside the frame itself.
@ -574,7 +573,7 @@ export class CoreIframeUtilsProvider {
* @returns Promise resolved when done.
*/
async fixIframeCookies(url: string): Promise<void> {
if (!CorePlatform.isIOS() || !url || CoreUrlUtils.isLocalFileUrl(url)) {
if (!CorePlatform.isIOS() || !url || CoreUrl.isLocalFileUrl(url)) {
// No need to fix cookies.
return;
}
@ -613,7 +612,7 @@ export class CoreIframeUtilsProvider {
* @returns Boolean.
*/
shouldDisplayHelpForUrl(url: string): boolean {
return this.shouldDisplayHelp() && !CoreUrlUtils.isLocalFileUrl(url);
return this.shouldDisplayHelp() && !CoreUrl.isLocalFileUrl(url);
}
/**

View File

@ -14,18 +14,12 @@
import { Injectable } from '@angular/core';
import { CoreLang, CoreLangFormat } from '@services/lang';
import { CoreTextUtils } from '@services/utils/text';
import { CoreConstants } from '@/core/constants';
import { makeSingleton } from '@singletons';
import { CoreUrl } from '@singletons/url';
import { CoreSites } from '@services/sites';
import { CorePath } from '@singletons/path';
import { CorePlatform } from '@services/platform';
import { CoreMedia } from '@singletons/media';
import { CoreUrl, CoreUrlParams as CoreUrlParamsNew, CoreUrlPartNames } from '@singletons/url';
/*
* "Utils" service with helper functions for URLs.
* @deprecated since 4.5. Use CoreUrl instead.
*/
@Injectable({ providedIn: 'root' })
export class CoreUrlUtilsProvider {
@ -35,19 +29,10 @@ export class CoreUrlUtilsProvider {
*
* @param url URL to modify.
* @returns Modified URL.
* @deprecated since 4.5. Use CoreUrl.addOrRemoveWWW instead.
*/
addOrRemoveWWW(url: string): string {
if (url) {
if (url.match(/http(s)?:\/\/www\./)) {
// Already has www. Remove it.
url = url.replace('www.', '');
} else {
url = url.replace('https://', 'https://www.');
url = url.replace('http://', 'http://www.');
}
}
return url;
return CoreUrl.addOrRemoveWWW(url);
}
/**
@ -58,43 +43,10 @@ export class CoreUrlUtilsProvider {
* @param anchor Anchor text if needed.
* @param boolToNumber Whether to convert bools to 1 or 0.
* @returns URL with params.
* @deprecated since 4.5. Use CoreUrl.addParamsToUrl instead.
*/
addParamsToUrl(url: string, params?: Record<string, unknown>, anchor?: string, boolToNumber?: boolean): string {
// Remove any existing anchor to add the params before it.
const urlAndAnchor = url.split('#');
url = urlAndAnchor[0];
let separator = url.indexOf('?') !== -1 ? '&' : '?';
for (const key in params) {
let value = params[key];
if (boolToNumber && typeof value === 'boolean') {
// Convert booleans to 1 or 0.
value = value ? '1' : '0';
}
// Ignore objects and undefined.
if (typeof value !== 'object' && value !== undefined) {
url += separator + key + '=' + value;
separator = '&';
}
}
// Re-add the anchor if any.
if (urlAndAnchor.length > 1) {
// Remove the URL from the array.
urlAndAnchor.shift();
// Use a join in case there is more than one #.
url += '#' + urlAndAnchor.join('#');
}
if (anchor) {
url += '#' + anchor;
}
return url;
return CoreUrl.addParamsToUrl(url, params, anchor, boolToNumber);
}
/**
@ -103,9 +55,10 @@ export class CoreUrlUtilsProvider {
* @param url URL.
* @param text Text of the link.
* @returns Link.
* @deprecated since 4.5. Use CoreUrl.buildLink instead.
*/
buildLink(url: string, text: string): string {
return '<a href="' + url + '">' + text + '</a>';
return CoreUrl.buildLink(url, text);
}
/**
@ -115,14 +68,10 @@ export class CoreUrlUtilsProvider {
* @param siteUrl The URL of the site the URL belongs to.
* @param accessKey User access key for tokenpluginfile.
* @returns Whether tokenpluginfile.php can be used.
* @deprecated since 4.5. Use CoreUrl.canUseTokenPluginFile instead.
*/
canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean {
// Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work.
// Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert).
return !CoreConstants.CONFIG.disableTokenFile && !!accessKey && !url.match(/[&?]file=/) && (
url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 ||
url.indexOf(CorePath.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0) &&
!CoreMedia.sourceUsesJavascriptPlayer({ src: url });
return CoreUrl.canUseTokenPluginFile(url, siteUrl, accessKey);
}
/**
@ -130,43 +79,10 @@ export class CoreUrlUtilsProvider {
*
* @param url URL to treat.
* @returns Object with the params.
* @deprecated since 4.5. Use CoreUrl.extractUrlParams instead.
*/
extractUrlParams(url: string): CoreUrlParams {
const regex = /[?&]+([^=&]+)=?([^&]*)?/gi;
const subParamsPlaceholder = '@@@SUBPARAMS@@@';
const params: CoreUrlParams = {};
const urlAndHash = url.split('#');
const questionMarkSplit = urlAndHash[0].split('?');
let subParams: string;
if (questionMarkSplit.length > 2) {
// There is more than one question mark in the URL. This can happen if any of the params is a URL with params.
// We only want to treat the first level of params, so we'll remove this second list of params and restore it later.
questionMarkSplit.splice(0, 2);
subParams = '?' + questionMarkSplit.join('?');
urlAndHash[0] = urlAndHash[0].replace(subParams, subParamsPlaceholder);
}
urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => {
params[key] = value !== undefined ? CoreTextUtils.decodeURIComponent(value) : '';
if (subParams) {
params[key] = params[key].replace(subParamsPlaceholder, subParams);
}
return match;
});
if (urlAndHash.length > 1) {
// Remove the URL from the array.
urlAndHash.shift();
// Add the hash as a param with a special name. Use a join in case there is more than one #.
params.urlHash = urlAndHash.join('#');
}
return params;
extractUrlParams(url: string): CoreUrlParamsNew {
return CoreUrl.extractUrlParams(url);
}
/**
@ -179,40 +95,10 @@ export class CoreUrlUtilsProvider {
* @param siteUrl The URL of the site the URL belongs to.
* @param accessKey User access key for tokenpluginfile.
* @returns Fixed URL.
* @deprecated since 4.5. Use CoreUrl.fixPluginfileURL instead.
*/
fixPluginfileURL(url: string, token: string, siteUrl: string, accessKey?: string): string {
if (!url) {
return '';
}
url = url.replace(/&amp;/g, '&');
const canUseTokenPluginFile = accessKey && this.canUseTokenPluginFile(url, siteUrl, accessKey);
// First check if we need to fix this url or is already fixed.
if (!canUseTokenPluginFile && url.indexOf('token=') != -1) {
return url;
}
// Check if is a valid URL (contains the pluginfile endpoint) and belongs to the site.
if (!this.isPluginFileUrl(url) || url.indexOf(CoreTextUtils.addEndingSlash(siteUrl)) !== 0) {
return url;
}
if (canUseTokenPluginFile) {
// Use tokenpluginfile.php.
url = url.replace(/(\/webservice)?\/pluginfile\.php/, '/tokenpluginfile.php/' + accessKey);
} else {
// Use pluginfile.php. Some webservices returns directly the correct download url, others not.
if (url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0) {
url = url.replace('/pluginfile', '/webservice/pluginfile');
}
url = this.addParamsToUrl(url, { token });
}
// Always send offline=1 (it's for external repositories).
return this.addParamsToUrl(url, { offline: '1', lang: CoreLang.getCurrentLanguageSync(CoreLangFormat.LMS) });
return CoreUrl.fixPluginfileURL(url, token, siteUrl, accessKey);
}
/**
@ -220,54 +106,26 @@ export class CoreUrlUtilsProvider {
*
* @param url The url to be formatted.
* @returns Fromatted url.
* @deprecated since 4.5. Use CoreUrl.formatURL instead.
*/
formatURL(url: string): string {
url = url.trim();
// Check if the URL starts by http or https.
if (! /^http(s)?:\/\/.*/i.test(url)) {
// Test first allways https.
url = 'https://' + url;
}
// http always in lowercase.
url = url.replace(/^http/i, 'http');
url = url.replace(/^https/i, 'https');
// Replace last slash.
url = url.replace(/\/$/, '');
return url;
return CoreUrl.formatURL(url);
}
/**
* Returns the URL to the documentation of the app, based on Moodle version and current language.
*
* @param release Moodle release.
* The URL has been simplified and always returns the English version of the latest version of Moodle
* to simplify the circular dependencies.
*
* @param release Moodle release. Unused.
* @param page Docs page to go to.
* @returns Promise resolved with the Moodle docs URL.
*
* @deprecated since 4.5. You can use CoreAuthenticatedSite.getDocsUrl but is also deprecated.
*/
async getDocsUrl(release?: string, page: string = 'Mobile_app'): Promise<string> {
let docsUrl = 'https://docs.moodle.org/en/' + page;
if (release !== undefined) {
const version = CoreSites.getMajorReleaseNumber(release).replace('.', '');
// Check is a valid number.
if (Number(version) >= 24) {
// Append release number.
docsUrl = docsUrl.replace('https://docs.moodle.org/', 'https://docs.moodle.org/' + version + '/');
}
}
try {
let lang = await CoreLang.getCurrentLanguage(CoreLangFormat.LMS);
lang = CoreLang.getParentLanguage() || lang;
return docsUrl.replace('/en/', '/' + lang + '/');
} catch (error) {
return docsUrl;
}
return 'https://docs.moodle.org/en/' + page;
}
/**
@ -275,53 +133,10 @@ export class CoreUrlUtilsProvider {
*
* @param url URL
* @returns Youtube Embed Video URL or undefined if not found.
* @deprecated since 4.5. Use CoreUrl.getYoutubeEmbedUrl instead.
*/
getYoutubeEmbedUrl(url?: string): string | void {
if (!url) {
return;
}
let videoId = '';
const params: CoreUrlParams = {};
url = CoreTextUtils.decodeHTML(url);
// Get the video ID.
let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/);
if (match && match[2].length === 11) {
videoId = match[2];
}
// No videoId, do not continue.
if (!videoId) {
return;
}
// Now get the playlist (if any).
match = url.match(/[?&]list=([^#&?]+)/);
if (match && match[1]) {
params.list = match[1];
}
// Now get the start time (if any).
match = url.match(/[?&]start=(\d+)/);
if (match && match[1]) {
params.start = parseInt(match[1], 10).toString();
} else {
// No start param, but it could have a time param.
match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/);
if (match) {
const start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) +
(match[2] ? parseInt(match[2], 10) * 60 : 0) +
(match[3] ? parseInt(match[3], 10) : 0);
params.start = start.toString();
}
}
return this.addParamsToUrl('https://www.youtube.com/embed/' + videoId, params);
return CoreUrl.getYoutubeEmbedUrl(url);
}
/**
@ -331,15 +146,10 @@ export class CoreUrlUtilsProvider {
*
* @param url URL to treat.
* @returns Last file without params.
* @deprecated since 4.5. Use CoreUrl.getLastFileWithoutParams instead.
*/
getLastFileWithoutParams(url: string): string {
const parsedUrl = CoreUrl.parse(url);
if (!parsedUrl) {
return '';
}
const path = parsedUrl.path ?? '';
return path.split('/').pop() ?? '';
return CoreUrl.getLastFileWithoutParams(url);
}
/**
@ -348,17 +158,10 @@ export class CoreUrlUtilsProvider {
*
* @param url URL to treat.
* @returns Protocol, undefined if no protocol found.
* @todo Use CoreUrl.parse
* @deprecated since 4.5. Use CoreUrl.getUrlProtocol instead.
*/
getUrlProtocol(url: string): string | void {
if (!url) {
return;
}
const matches = url.match(/^([^/:.?]*):\/\//);
if (matches && matches[1]) {
return matches[1];
}
return CoreUrl.getUrlProtocol(url);
}
/**
@ -367,36 +170,21 @@ export class CoreUrlUtilsProvider {
*
* @param url URL to treat.
* @returns Scheme, undefined if no scheme found.
* @deprecated since 4.5. Use CoreUrl.getUrlProtocol instead.
*/
getUrlScheme(url: string): string | void {
if (!url) {
return;
}
const matches = url.match(/^([a-z][a-z0-9+\-.]*):/);
if (matches && matches[1]) {
return matches[1];
}
return CoreUrl.getUrlProtocol(url);
}
/*
/**
* Gets a username from a URL like: user@mysite.com.
*
* @param url URL to treat.
* @returns Username. Undefined if no username found.
* @todo Use CoreUrl.parse
* @deprecated since 4.5. Use CoreUrl.getUsernameFromUrl instead.
*/
getUsernameFromUrl(url: string): string | undefined {
if (url.indexOf('@') > -1) {
// Get URL without protocol.
const withoutProtocol = url.replace(/^[^?@/]*:\/\//, '');
const matches = withoutProtocol.match(/[^@]*/);
// Make sure that @ is at the start of the URL, not in a param at the end.
if (matches && matches.length && !matches[0].match(/[/|?]/)) {
return matches[0];
}
}
return CoreUrl.getUsernameFromUrl(url);
}
/**
@ -404,9 +192,10 @@ export class CoreUrlUtilsProvider {
*
* @param url The url to test against the pattern.
* @returns Whether the url is absolute.
* @deprecated since 4.5. Use CoreUrl.isAbsoluteURL instead.
*/
isAbsoluteURL(url: string): boolean {
return /^[^:]{2,}:\/\//i.test(url) || /^(tel:|mailto:|geo:)/.test(url);
return CoreUrl.isAbsoluteURL(url);
}
/**
@ -414,9 +203,10 @@ export class CoreUrlUtilsProvider {
*
* @param url The URL to test.
* @returns Whether the URL is downloadable.
* @deprecated since 4.5. Use CoreUrl.isDownloadableUrl instead.
*/
isDownloadableUrl(url: string): boolean {
return this.isPluginFileUrl(url) || this.isTokenPluginFileUrl(url) || this.isThemeImageUrl(url) || this.isGravatarUrl(url);
return CoreUrl.isDownloadableUrl(url);
}
/**
@ -424,9 +214,10 @@ export class CoreUrlUtilsProvider {
*
* @param url The URL to test.
* @returns Whether the URL is a gravatar URL.
* @deprecated since 4.5. Use CoreUrl.isGravatarUrl instead.
*/
isGravatarUrl(url: string): boolean {
return url?.indexOf('gravatar.com/avatar') !== -1;
return CoreUrl.isGravatarUrl(url);
}
/**
@ -434,10 +225,10 @@ export class CoreUrlUtilsProvider {
*
* @param url The url to test.
* @returns Whether the url uses http or https protocol.
* @todo Use CoreUrl.parse
* @deprecated since 4.5. Use CoreUrl.isHttpURL instead.
*/
isHttpURL(url: string): boolean {
return /^https?:\/\/.+/i.test(url);
return CoreUrl.isHttpURL(url);
}
/**
@ -445,11 +236,10 @@ export class CoreUrlUtilsProvider {
*
* @param url URL to check.
* @returns Whether the URL belongs to a local file.
* @deprecated since 4.5. Use CoreUrl.isLocalFileUrl instead.
*/
isLocalFileUrl(url: string): boolean {
const urlParts = CoreUrl.parse(url);
return this.isLocalFileUrlScheme(urlParts?.protocol || '', urlParts?.domain || '');
return CoreUrl.isLocalFileUrl(url);
}
/**
@ -457,18 +247,10 @@ export class CoreUrlUtilsProvider {
*
* @param scheme Scheme to check.
* @returns Whether the scheme belongs to a local file.
* @deprecated since 4.5. Use CoreUrl.isLocalFileUrlScheme instead.
*/
isLocalFileUrlScheme(scheme: string, domain: string): boolean {
if (!scheme) {
return false;
}
scheme = scheme.toLowerCase();
return scheme == 'cdvfile' ||
scheme == 'file' ||
scheme == 'filesystem' ||
scheme == CoreConstants.CONFIG.ioswebviewscheme ||
(CorePlatform.isMobile() && scheme === 'http' && domain === 'localhost'); // @todo Get served domain from ENV.
return CoreUrl.isLocalFileUrlScheme(scheme, domain);
}
/**
@ -476,9 +258,10 @@ export class CoreUrlUtilsProvider {
*
* @param url The URL to test.
* @returns Whether the URL is a pluginfile URL.
* @deprecated since 4.5. Use CoreUrl.isPluginFileUrl instead.
*/
isPluginFileUrl(url: string): boolean {
return url.indexOf('/pluginfile.php') !== -1;
return CoreUrl.isPluginFileUrl(url);
}
/**
@ -486,9 +269,10 @@ export class CoreUrlUtilsProvider {
*
* @param url The URL to test.
* @returns Whether the URL is a tokenpluginfile URL.
* @deprecated since 4.5. Use CoreUrl.isTokenPluginFileUrl instead.
*/
isTokenPluginFileUrl(url: string): boolean {
return url.indexOf('/tokenpluginfile.php') !== -1;
return CoreUrl.isTokenPluginFileUrl(url);
}
/**
@ -497,13 +281,10 @@ export class CoreUrlUtilsProvider {
* @param imageUrl The URL to test.
* @param siteUrl The Site Url.
* @returns Whether the URL is a theme image URL.
* @deprecated since 4.5. Use CoreUrl.isThemeImageUrl instead.
*/
isThemeImageUrl(imageUrl: string, siteUrl?: string): boolean {
if (siteUrl) {
return imageUrl.startsWith(`${siteUrl}/theme/image.php`);
}
return imageUrl?.indexOf('/theme/image.php') !== -1;
return CoreUrl.isThemeImageUrl(imageUrl, siteUrl);
}
/**
@ -513,57 +294,10 @@ export class CoreUrlUtilsProvider {
* @param param Param to get from the URL.
* @param siteUrl Site URL.
* @returns Param from the URL.
* @deprecated since 4.5. Use CoreUrl.getThemeImageUrlParam instead.
*/
getThemeImageUrlParam(imageUrl: string, param: string, siteUrl?: string): string {
if (!this.isThemeImageUrl(imageUrl, siteUrl)) {
// Cannot be guessed.
return '';
}
const matches = imageUrl.match('/theme/image.php/(.*)');
if (matches?.[1]) {
// Slash arguments found.
const slasharguments = matches[1].split('/');
if (slasharguments.length < 4) {
// Image not found, malformed URL.
return '';
}
// Join from the third element to the end.
const image = slasharguments.slice(3).join('/');
switch (param) {
case 'theme':
return slasharguments[0];
case 'component':
return slasharguments[1];
case 'rev':
return slasharguments[2];
case 'image':
// Remove possible url params.
return CoreUrlUtils.removeUrlParams(image);
default:
return CoreUrlUtils.extractUrlParams(image)[param] || '';
}
}
// URL arguments found.
const iconParams = CoreUrlUtils.extractUrlParams(imageUrl);
switch (param) {
case 'theme':
return iconParams[param] || 'standard';
case 'component':
return iconParams[param] || 'core';
case 'rev':
return iconParams[param] || '-1';
case 'svg':
return iconParams[param] || '1';
case 'image':
default:
return iconParams[param] || '';
}
return CoreUrl.getThemeImageUrlParam(imageUrl, param, siteUrl);
}
/**
@ -571,14 +305,10 @@ export class CoreUrlUtilsProvider {
*
* @param url URL to treat.
* @returns Treated URL.
* @deprecated since 4.5. Use CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]) instead.
*/
removeProtocolAndWWW(url: string): string {
// Remove protocol.
url = url.replace(/^.*?:\/\//, '');
// Remove www.
url = url.replace(/^www./, '');
return url;
return CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]);
}
/**
@ -586,11 +316,10 @@ export class CoreUrlUtilsProvider {
*
* @param url URL to treat.
* @returns URL without params.
* @deprecated since 4.5. Use CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]) instead.
*/
removeUrlParams(url: string): string {
const matches = url.match(/^[^?]+/);
return matches ? matches[0] : '';
return CoreUrl.removeUrlParts(url, [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]);
}
/**
@ -599,33 +328,16 @@ export class CoreUrlUtilsProvider {
* @param url The url to be fixed.
* @param siteUrl The URL of the site the URL belongs to.
* @returns Modified URL.
* @deprecated since 4.5. Use CoreUrl.unfixPluginfileURL instead.
*/
unfixPluginfileURL(url: string, siteUrl?: string): string {
if (!url) {
return '';
}
url = url.replace(/&amp;/g, '&');
// It site URL is supplied, check if the URL belongs to the site.
if (siteUrl && url.indexOf(CoreTextUtils.addEndingSlash(siteUrl)) !== 0) {
return url;
}
// Check tokenpluginfile first.
url = url.replace(/\/tokenpluginfile\.php\/[^/]+\//, '/pluginfile.php/');
// Treat webservice/pluginfile case.
url = url.replace(/\/webservice\/pluginfile\.php\//, '/pluginfile.php/');
// Make sure the URL doesn't contain the token.
url = url.replace(/([?&])token=[^&]*&?/, '$1');
return url;
return CoreUrl.unfixPluginfileURL(url, siteUrl);
}
}
export const CoreUrlUtils = makeSingleton(CoreUrlUtilsProvider);
export type CoreUrlParams = {[key: string]: string};
/**
* @deprecated since 4.5. Use CoreUrlParams on CoreUrl instead.
*/
export type CoreUrlParams = CoreUrlParamsNew;

View File

@ -37,7 +37,7 @@ import { CoreFilepool } from '@services/filepool';
import { CoreSites } from '@services/sites';
import { CoreCancellablePromise } from '@classes/cancellable-promise';
import { CoreAnalytics, CoreAnalyticsEventType } from '@services/analytics';
import { CoreUrlUtils } from './url';
import { CoreUrl } from '@singletons/url';
import { QRScanner } from '@features/native/plugins';
import { CoreArray } from '@singletons/array';
import { CoreText } from '@singletons/text';
@ -1114,7 +1114,7 @@ export class CoreUtilsProvider {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.OPEN_LINK,
link: CoreUrlUtils.unfixPluginfileURL(options.originalUrl ?? url),
link: CoreUrl.unfixPluginfileURL(options.originalUrl ?? url),
});
return this.iabInstance;
@ -1172,7 +1172,7 @@ export class CoreUtilsProvider {
*/
async openInBrowser(url: string, options: CoreUtilsOpenInBrowserOptions = {}): Promise<void> {
// eslint-disable-next-line deprecation/deprecation
const originaUrl = CoreUrlUtils.unfixPluginfileURL(options.originalUrl ?? options.browserWarningUrl ?? url);
const originaUrl = CoreUrl.unfixPluginfileURL(options.originalUrl ?? options.browserWarningUrl ?? url);
if (options.showBrowserWarning || options.showBrowserWarning === undefined) {
try {
await CoreWindow.confirmOpenBrowserIfNeeded(originaUrl);
@ -1217,7 +1217,7 @@ export class CoreUtilsProvider {
CoreAnalytics.logEvent({
type: CoreAnalyticsEventType.OPEN_LINK,
link: CoreUrlUtils.unfixPluginfileURL(url),
link: CoreUrl.unfixPluginfileURL(url),
});
return;

View File

@ -14,10 +14,75 @@
import { mock } from '@/testing/utils';
import { CoreSite } from '@classes/sites/site';
import { CoreUrl } from '@singletons/url';
import { CoreUrl, CoreUrlPartNames } from '@singletons/url';
describe('CoreUrl singleton', () => {
it('adds www if missing', () => {
const originalUrl = 'https://moodle.org';
const url = CoreUrl.addOrRemoveWWW(originalUrl);
expect(url).toEqual('https://www.moodle.org');
});
it('removes www if present', () => {
const originalUrl = 'https://www.moodle.org';
const url = CoreUrl.addOrRemoveWWW(originalUrl);
expect(url).toEqual('https://moodle.org');
});
it('adds params to URL without params', () => {
const originalUrl = 'https://moodle.org';
const params = {
first: '1',
second: '2',
};
const url = CoreUrl.addParamsToUrl(originalUrl, params);
expect(url).toEqual('https://moodle.org?first=1&second=2');
});
it('adds params to URL with existing params', () => {
const originalUrl = 'https://moodle.org?existing=1';
const params = {
first: '1',
second: '2',
};
const url = CoreUrl.addParamsToUrl(originalUrl, params);
expect(url).toEqual('https://moodle.org?existing=1&first=1&second=2');
});
it('doesn\'t change URL if no params supplied', () => {
const originalUrl = 'https://moodle.org';
const url = CoreUrl.addParamsToUrl(originalUrl);
expect(url).toEqual(originalUrl);
});
it('doesn\'t add undefined or null params', () => {
const originalUrl = 'https://moodle.org';
const url = CoreUrl.addParamsToUrl(originalUrl, {
foo: undefined,
bar: null,
baz: 1,
});
expect(url).toEqual('https://moodle.org?baz=1');
});
it('adds anchor to URL', () => {
const originalUrl = 'https://moodle.org';
const params = {
first: '1',
second: '2',
};
const url = CoreUrl.addParamsToUrl(originalUrl, params, 'myanchor');
expect(url).toEqual('https://moodle.org?first=1&second=2#myanchor');
});
it('parses standard urls', () => {
expect(CoreUrl.parse('https://u1:pw1@my.subdomain.com/path/?query=search#hash')).toEqual({
protocol: 'https',
@ -83,9 +148,34 @@ describe('CoreUrl singleton', () => {
});
it('removes protocol', () => {
expect(CoreUrl.removeProtocol('https://school.edu')).toEqual('school.edu');
expect(CoreUrl.removeProtocol('ftp://school.edu')).toEqual('school.edu');
expect(CoreUrl.removeProtocol('school.edu')).toEqual('school.edu');
expect(CoreUrl.removeUrlParts('https://school.edu', CoreUrlPartNames.Protocol)).toEqual('school.edu');
expect(CoreUrl.removeUrlParts('ftp://school.edu', CoreUrlPartNames.Protocol)).toEqual('school.edu');
expect(CoreUrl.removeUrlParts('school.edu', CoreUrlPartNames.Protocol)).toEqual('school.edu');
});
it('removes protocol and www', () => {
expect(CoreUrl.removeUrlParts('https://www.school.edu', [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]))
.toEqual('school.edu');
expect(CoreUrl.removeUrlParts('ftp://school.edu', [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]))
.toEqual('school.edu');
expect(CoreUrl.removeUrlParts('www.school.edu', [CoreUrlPartNames.Protocol, CoreUrlPartNames.WWWInDomain]))
.toEqual('school.edu');
// Test that it works in a different order.
expect(CoreUrl.removeUrlParts('https://www.school.edu', [CoreUrlPartNames.WWWInDomain, CoreUrlPartNames.Protocol]))
.toEqual('school.edu');
expect(CoreUrl.removeUrlParts('ftp://school.edu', [CoreUrlPartNames.WWWInDomain, CoreUrlPartNames.Protocol]))
.toEqual('school.edu');
expect(CoreUrl.removeUrlParts('www.school.edu', [CoreUrlPartNames.WWWInDomain, CoreUrlPartNames.Protocol]))
.toEqual('school.edu');
});
it('removes params', () => {
expect(CoreUrl.removeUrlParts('https://www.school.edu?blabla#a', [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]))
.toEqual('https://www.school.edu');
expect(CoreUrl.removeUrlParts('ftp://school.edu?blabla=r#a', [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]))
.toEqual('ftp://school.edu');
expect(CoreUrl.removeUrlParts('www.school.edu?blabla=5&gg=3', [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]))
.toEqual('www.school.edu');
});
it('compares domains and paths', () => {
@ -108,9 +198,21 @@ describe('CoreUrl singleton', () => {
});
it('removes the anchor of a URL', () => {
expect(CoreUrl.removeUrlAnchor('https://school.edu#foo')).toEqual('https://school.edu');
expect(CoreUrl.removeUrlAnchor('https://school.edu#foo#bar')).toEqual('https://school.edu');
expect(CoreUrl.removeUrlAnchor('https://school.edu')).toEqual('https://school.edu');
expect(CoreUrl.removeUrlParts('https://school.edu#foo', CoreUrlPartNames.Fragment)).toEqual('https://school.edu');
expect(CoreUrl.removeUrlParts('https://school.edu#foo#bar', CoreUrlPartNames.Fragment)).toEqual('https://school.edu');
expect(CoreUrl.removeUrlParts('https://school.edu', CoreUrlPartNames.Fragment)).toEqual('https://school.edu');
});
it('gets the username from a URL', () => {
expect(CoreUrl.getUsernameFromUrl(
'https://username@domain.com?token=TOKEN&privatetoken=PRIVATETOKEN&redirect=http://domain.com/course/view.php?id=2',
)).toEqual('username');
expect(CoreUrl.getUsernameFromUrl(
'https://username:password@domain.com?token=TOKEN&privatetoken=PRIVATETOKEN&redirect=http://domain.com/course/',
)).toEqual('username');
expect(CoreUrl.getUsernameFromUrl(
'https://domain.com?token=TOKEN&privatetoken=PRIVATETOKEN&redirect=http://domain.com/course/view.php?id=2',
)).toEqual(undefined);
});
it('converts to absolute URLs', () => {

View File

@ -16,6 +16,12 @@ import { CoreSite } from '@classes/sites/site';
import { CorePath } from './path';
import { CoreText } from './text';
import { CorePlatform } from '@services/platform';
import { CoreTextUtils } from '@services/utils/text';
import { CoreConstants } from '../constants';
import { CoreMedia } from './media';
import { CoreLang, CoreLangFormat } from '@services/lang';
/**
* Parts contained within a url.
*/
@ -68,6 +74,13 @@ interface UrlParts {
}
export const enum CoreUrlPartNames {
Protocol = 'protocol',
WWWInDomain = 'www', // Will remove starting www from domain.
Query = 'query',
Fragment = 'fragment',
}
/**
* Singleton with helper functions for urls.
*/
@ -85,8 +98,9 @@ export class CoreUrl {
* @returns Url parts.
*/
static parse(url: string): UrlParts | null {
url = url.trim();
// Parse url with regular expression taken from RFC 3986: https://tools.ietf.org/html/rfc3986#appendix-B.
const match = url.trim().match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
const match = url.match(/^(([^:/?#]+):)?(\/\/([^/?#]*))?([^?#]*)(\?([^#]*))?(#(.*))?/);
if (!match) {
return null;
@ -120,8 +134,12 @@ export class CoreUrl {
* @returns Assembled URL.
*/
static assemble(parts: UrlParts): string {
return (parts.protocol ? `${parts.protocol}://` : '') +
(parts.credentials ? `${parts.credentials}@` : '') +
const protocol = parts.protocol;
const credentials = parts.credentials ||
(parts.password ? `${parts.username}:${parts.password}` : parts.username);
return (protocol ? `${protocol}://` : '') +
(credentials ? `${credentials}@` : '') +
(parts.domain ?? '') +
(parts.port ? `:${parts.port}` : '') +
(parts.path ?? '') +
@ -190,9 +208,10 @@ export class CoreUrl {
*
* @param url Site url.
* @returns Url without protocol.
* @deprecated since 4.5. Use CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol) instead.
*/
static removeProtocol(url: string): string {
return url.replace(/^[a-zA-Z]+:\/\//i, '');
return CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol);
}
/**
@ -229,12 +248,9 @@ export class CoreUrl {
* @returns Anchor, undefined if no anchor.
*/
static getUrlAnchor(url: string): string | undefined {
const firstAnchorIndex = url.indexOf('#');
if (firstAnchorIndex === -1) {
return;
}
const urlParts = CoreUrl.parse(url);
return url.substring(firstAnchorIndex);
return urlParts?.fragment ? `#${urlParts.fragment}` : undefined;
}
/**
@ -242,11 +258,11 @@ export class CoreUrl {
*
* @param url URL.
* @returns URL without anchor if any.
*
* @deprecated since 4.5. Use CoreUrl.removeUrlParts(url, CoreUrlPartNames.Fragment) instead.
*/
static removeUrlAnchor(url: string): string {
const urlAndAnchor = url.split('#');
return urlAndAnchor[0];
return CoreUrl.removeUrlParts(url, CoreUrlPartNames.Fragment);
}
/**
@ -290,13 +306,13 @@ export class CoreUrl {
* @returns Relative URL.
*/
static toRelativeURL(parentUrl: string, url: string): string {
parentUrl = CoreUrl.removeProtocol(parentUrl);
parentUrl = CoreUrl.removeUrlParts(parentUrl, CoreUrlPartNames.Protocol);
if (!url.includes(parentUrl)) {
return url; // Already relative URL.
}
return CoreText.removeStartingSlash(CoreUrl.removeProtocol(url).replace(parentUrl, ''));
return CoreText.removeStartingSlash(CoreUrl.removeUrlParts(url, CoreUrlPartNames.Protocol).replace(parentUrl, ''));
}
/**
@ -344,4 +360,564 @@ export class CoreUrl {
return newUrl;
}
/**
* Add or remove 'www' from a URL. The url needs to have http or https protocol.
*
* @param url URL to modify.
* @returns Modified URL.
*/
static addOrRemoveWWW(url: string): string {
if (url) {
if (url.match(/http(s)?:\/\/www\./)) {
// Already has www. Remove it.
url = url.replace('www.', '');
} else {
url = url.replace('https://', 'https://www.');
url = url.replace('http://', 'http://www.');
}
}
return url;
}
/**
* Add params to a URL.
*
* @param url URL to add the params to.
* @param params Object with the params to add.
* @param anchor Anchor text if needed.
* @param boolToNumber Whether to convert bools to 1 or 0.
* @returns URL with params.
*/
static addParamsToUrl(url: string, params?: Record<string, unknown>, anchor?: string, boolToNumber?: boolean): string {
// Remove any existing anchor to add the params before it.
const urlAndAnchor = url.split('#');
url = urlAndAnchor[0];
let separator = url.indexOf('?') !== -1 ? '&' : '?';
for (const key in params) {
let value = params[key];
if (boolToNumber && typeof value === 'boolean') {
// Convert booleans to 1 or 0.
value = value ? '1' : '0';
}
// Ignore objects and undefined.
if (typeof value !== 'object' && value !== undefined) {
url += separator + key + '=' + value;
separator = '&';
}
}
// Re-add the anchor if any.
if (urlAndAnchor.length > 1) {
// Remove the URL from the array.
urlAndAnchor.shift();
// Use a join in case there is more than one #.
url += '#' + urlAndAnchor.join('#');
}
if (anchor) {
url += '#' + anchor;
}
return url;
}
/**
* Given a URL and a text, return an HTML link.
*
* @param url URL.
* @param text Text of the link.
* @returns Link.
*/
static buildLink(url: string, text: string): string {
return '<a href="' + url + '">' + text + '</a>';
}
/**
* Check whether we can use tokenpluginfile.php endpoint for a certain URL.
*
* @param url URL to check.
* @param siteUrl The URL of the site the URL belongs to.
* @param accessKey User access key for tokenpluginfile.
* @returns Whether tokenpluginfile.php can be used.
*/
static canUseTokenPluginFile(url: string, siteUrl: string, accessKey?: string): boolean {
// Do not use tokenpluginfile if site doesn't use slash params, the URL doesn't work.
// Also, only use it for "core" pluginfile endpoints. Some plugins can implement their own endpoint (like customcert).
return !CoreConstants.CONFIG.disableTokenFile && !!accessKey && !url.match(/[&?]file=/) && (
url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0 ||
url.indexOf(CorePath.concatenatePaths(siteUrl, 'webservice/pluginfile.php')) === 0) &&
!CoreMedia.sourceUsesJavascriptPlayer({ src: url });
}
/**
* Extracts the parameters from a URL and stores them in an object.
*
* @param url URL to treat.
* @returns Object with the params.
*/
static extractUrlParams(url: string): CoreUrlParams {
const regex = /[?&]+([^=&]+)=?([^&]*)?/gi;
const subParamsPlaceholder = '@@@SUBPARAMS@@@';
const params: CoreUrlParams = {};
const urlAndHash = url.split('#');
const questionMarkSplit = urlAndHash[0].split('?');
let subParams: string;
if (questionMarkSplit.length > 2) {
// There is more than one question mark in the URL. This can happen if any of the params is a URL with params.
// We only want to treat the first level of params, so we'll remove this second list of params and restore it later.
questionMarkSplit.splice(0, 2);
subParams = '?' + questionMarkSplit.join('?');
urlAndHash[0] = urlAndHash[0].replace(subParams, subParamsPlaceholder);
}
urlAndHash[0].replace(regex, (match: string, key: string, value: string): string => {
params[key] = value !== undefined ? CoreTextUtils.decodeURIComponent(value) : '';
if (subParams) {
params[key] = params[key].replace(subParamsPlaceholder, subParams);
}
return match;
});
if (urlAndHash.length > 1) {
// Remove the URL from the array.
urlAndHash.shift();
// Add the hash as a param with a special name. Use a join in case there is more than one #.
params.urlHash = urlAndHash.join('#');
}
return params;
}
/**
* Generic function for adding the wstoken to Moodle urls and for pointing to the correct script.
* For download remote files from Moodle we need to use the special /webservice/pluginfile passing
* the ws token as a get parameter.
*
* @param url The url to be fixed.
* @param token Token to use.
* @param siteUrl The URL of the site the URL belongs to.
* @param accessKey User access key for tokenpluginfile.
* @returns Fixed URL.
*/
static fixPluginfileURL(url: string, token: string, siteUrl: string, accessKey?: string): string {
if (!url) {
return '';
}
url = url.replace(/&amp;/g, '&');
const canUseTokenPluginFile = accessKey && CoreUrl.canUseTokenPluginFile(url, siteUrl, accessKey);
// First check if we need to fix this url or is already fixed.
if (!canUseTokenPluginFile && url.indexOf('token=') != -1) {
return url;
}
// Check if is a valid URL (contains the pluginfile endpoint) and belongs to the site.
if (!CoreUrl.isPluginFileUrl(url) || url.indexOf(CoreTextUtils.addEndingSlash(siteUrl)) !== 0) {
return url;
}
if (canUseTokenPluginFile) {
// Use tokenpluginfile.php.
url = url.replace(/(\/webservice)?\/pluginfile\.php/, '/tokenpluginfile.php/' + accessKey);
} else {
// Use pluginfile.php. Some webservices returns directly the correct download url, others not.
if (url.indexOf(CorePath.concatenatePaths(siteUrl, 'pluginfile.php')) === 0) {
url = url.replace('/pluginfile', '/webservice/pluginfile');
}
url = CoreUrl.addParamsToUrl(url, { token });
}
// Always send offline=1 (it's for external repositories).
return CoreUrl.addParamsToUrl(url, { offline: '1', lang: CoreLang.getCurrentLanguageSync(CoreLangFormat.LMS) });
}
/**
* Formats a URL, trim, lowercase, etc...
*
* @param url The url to be formatted.
* @returns Fromatted url.
*/
static formatURL(url: string): string {
url = url.trim();
// Check if the URL starts by http or https.
if (! /^http(s)?:\/\/.*/i.test(url)) {
// Test first allways https.
url = 'https://' + url;
}
// http always in lowercase.
url = url.replace(/^http/i, 'http');
url = url.replace(/^https/i, 'https');
// Replace last slash.
url = url.replace(/\/$/, '');
return url;
}
/**
* Returns the Youtube Embed Video URL or undefined if not found.
*
* @param url URL
* @returns Youtube Embed Video URL or undefined if not found.
*/
static getYoutubeEmbedUrl(url?: string): string | void {
if (!url) {
return;
}
let videoId = '';
const params: CoreUrlParams = {};
url = CoreTextUtils.decodeHTML(url);
// Get the video ID.
let match = url.match(/^.*(youtu.be\/|v\/|u\/\w\/|embed\/|watch\?v=|&v=)([^#&?]*).*/);
if (match && match[2].length === 11) {
videoId = match[2];
}
// No videoId, do not continue.
if (!videoId) {
return;
}
// Now get the playlist (if any).
match = url.match(/[?&]list=([^#&?]+)/);
if (match && match[1]) {
params.list = match[1];
}
// Now get the start time (if any).
match = url.match(/[?&]start=(\d+)/);
if (match && match[1]) {
params.start = parseInt(match[1], 10).toString();
} else {
// No start param, but it could have a time param.
match = url.match(/[?&]t=(\d+h)?(\d+m)?(\d+s)?/);
if (match) {
const start = (match[1] ? parseInt(match[1], 10) * 3600 : 0) +
(match[2] ? parseInt(match[2], 10) * 60 : 0) +
(match[3] ? parseInt(match[3], 10) : 0);
params.start = start.toString();
}
}
return CoreUrl.addParamsToUrl('https://www.youtube.com/embed/' + videoId, params);
}
/**
* Given a URL, returns what's after the last '/' without params.
* Example:
* http://mysite.com/a/course.html?id=1 -> course.html
*
* @param url URL to treat.
* @returns Last file without params.
*/
static getLastFileWithoutParams(url: string): string {
const parsedUrl = CoreUrl.parse(url);
if (!parsedUrl) {
return '';
}
const path = parsedUrl.path ?? '';
return path.split('/').pop() ?? '';
}
/**
* Get the protocol from a URL.
* E.g. http://www.google.com returns 'http'.
*
* @param url URL to treat.
* @returns Protocol, undefined if no protocol found.
*/
static getUrlProtocol(url: string): string | void {
return CoreUrl.parse(url)?.protocol;
}
/**
* Gets a username from a URL like: user@mysite.com.
*
* @param url URL to treat.
* @returns Username. Undefined if no username found.
* @todo Use CoreUrl.parse. It cannot use it right now because it won't detect username on custom URL with double protocol.
*/
static getUsernameFromUrl(url: string): string | undefined {
if (url.indexOf('@') < 0) {
return;
}
// Get URL without protocol.
const withoutProtocol = url.replace(/^[^?@/]*:\/\//, '');
const matches = withoutProtocol.match(/[^@]*/);
// Make sure that @ is at the start of the URL, not in a param at the end.
if (matches && matches.length && !matches[0].match(/[/|?]/)) {
const credentials = matches[0];
return credentials.split(':')[0];
}
}
/**
* Returns if a URL has any protocol (not a relative URL).
*
* @param url The url to test against the pattern.
* @returns Whether the url is absolute.
*/
static isAbsoluteURL(url: string): boolean {
return /^[^:]{2,}:\/\//i.test(url) || /^(tel:|mailto:|geo:)/.test(url);
}
/**
* Returns if a URL is downloadable: plugin file OR theme/image.php OR gravatar.
*
* @param url The URL to test.
* @returns Whether the URL is downloadable.
*/
static isDownloadableUrl(url: string): boolean {
return CoreUrl.isPluginFileUrl(url) ||
CoreUrl.isTokenPluginFileUrl(url) ||
CoreUrl.isThemeImageUrl(url) ||
CoreUrl.isGravatarUrl(url);
}
/**
* Returns if a URL is a gravatar URL.
*
* @param url The URL to test.
* @returns Whether the URL is a gravatar URL.
*/
static isGravatarUrl(url: string): boolean {
return url?.indexOf('gravatar.com/avatar') !== -1;
}
/**
* Check if a URL uses http or https protocol.
*
* @param url The url to test.
* @returns Whether the url uses http or https protocol.
* @todo Use CoreUrl.parse
*/
static isHttpURL(url: string): boolean {
return /^https?:\/\/.+/i.test(url);
}
/**
* Check whether an URL belongs to a local file.
*
* @param url URL to check.
* @returns Whether the URL belongs to a local file.
*/
static isLocalFileUrl(url: string): boolean {
const urlParts = CoreUrl.parse(url);
return CoreUrl.isLocalFileUrlScheme(urlParts?.protocol || '', urlParts?.domain || '');
}
/**
* Check whether a URL scheme belongs to a local file.
*
* @param scheme Scheme to check.
* @returns Whether the scheme belongs to a local file.
*/
static isLocalFileUrlScheme(scheme: string, domain: string): boolean {
if (!scheme) {
return false;
}
scheme = scheme.toLowerCase();
return scheme === 'cdvfile' ||
scheme === 'file' ||
scheme === 'filesystem' ||
scheme === CoreConstants.CONFIG.ioswebviewscheme ||
(CorePlatform.isMobile() && scheme === 'http' && domain === 'localhost'); // @todo Get served domain from ENV.
}
/**
* Returns if a URL is a pluginfile URL.
*
* @param url The URL to test.
* @returns Whether the URL is a pluginfile URL.
*/
static isPluginFileUrl(url: string): boolean {
return url.indexOf('/pluginfile.php') !== -1;
}
/**
* Returns if a URL is a tokenpluginfile URL.
*
* @param url The URL to test.
* @returns Whether the URL is a tokenpluginfile URL.
*/
static isTokenPluginFileUrl(url: string): boolean {
return url.indexOf('/tokenpluginfile.php') !== -1;
}
/**
* Returns if a URL is a theme image URL.
*
* @param imageUrl The URL to test.
* @param siteUrl The Site Url.
* @returns Whether the URL is a theme image URL.
*/
static isThemeImageUrl(imageUrl: string, siteUrl?: string): boolean {
if (siteUrl) {
return imageUrl.startsWith(`${siteUrl}/theme/image.php`);
}
return imageUrl?.indexOf('/theme/image.php') !== -1;
}
/**
* Returns an specific param from an image URL.
*
* @param imageUrl Image Url
* @param param Param to get from the URL.
* @param siteUrl Site URL.
* @returns Param from the URL.
*/
static getThemeImageUrlParam(imageUrl: string, param: string, siteUrl?: string): string {
if (!CoreUrl.isThemeImageUrl(imageUrl, siteUrl)) {
// Cannot be guessed.
return '';
}
const matches = imageUrl.match('/theme/image.php/(.*)');
if (matches?.[1]) {
// Slash arguments found.
const slasharguments = matches[1].split('/');
if (slasharguments.length < 4) {
// Image not found, malformed URL.
return '';
}
// Join from the third element to the end.
const image = slasharguments.slice(3).join('/');
switch (param) {
case 'theme':
return slasharguments[0];
case 'component':
return slasharguments[1];
case 'rev':
return slasharguments[2];
case 'image':
// Remove possible url params.
return CoreUrl.removeUrlParts(image, [CoreUrlPartNames.Query, CoreUrlPartNames.Fragment]);
default:
return CoreUrl.extractUrlParams(image)[param] || '';
}
}
// URL arguments found.
const iconParams = CoreUrl.extractUrlParams(imageUrl);
switch (param) {
case 'theme':
return iconParams[param] || 'standard';
case 'component':
return iconParams[param] || 'core';
case 'rev':
return iconParams[param] || '-1';
case 'svg':
return iconParams[param] || '1';
case 'image':
default:
return iconParams[param] || '';
}
}
/**
* Returns the URL without the desired parts.
*
* @param url URL to treat.
* @param parts Parts to remove.
* @returns URL without the parts.
*/
static removeUrlParts(url: string, parts: CoreUrlPartNames | CoreUrlPartNames[]): string {
if (!url) {
return url;
}
if (!Array.isArray(parts)) {
parts = [parts];
}
parts.forEach((part) => {
switch (part) {
case CoreUrlPartNames.WWWInDomain:
// Remove www, no protocol.
url = url.replace(/^www./, '');
// Remove www, with protocol.
url = url.replace(/\/\/www./, '//');
break;
case CoreUrlPartNames.Protocol:
// Remove the protocol from url
url = url.replace(/^.*?:\/\//, '');
break;
case CoreUrlPartNames.Query:
url = url.match(/^[^?]+/)?.[0] || '';
break;
case CoreUrlPartNames.Fragment:
url = url.split('#')[0];
break;
}
});
return url;
}
/**
* Modifies a pluginfile URL to use the default pluginfile script instead of the webservice one.
*
* @param url The url to be fixed.
* @param siteUrl The URL of the site the URL belongs to.
* @returns Modified URL.
*/
static unfixPluginfileURL(url: string, siteUrl?: string): string {
if (!url) {
return '';
}
url = url.replace(/&amp;/g, '&');
// It site URL is supplied, check if the URL belongs to the site.
if (siteUrl && url.indexOf(CoreTextUtils.addEndingSlash(siteUrl)) !== 0) {
return url;
}
// Check tokenpluginfile first.
url = url.replace(/\/tokenpluginfile\.php\/[^/]+\//, '/pluginfile.php/');
// Treat webservice/pluginfile case.
url = url.replace(/\/webservice\/pluginfile\.php\//, '/pluginfile.php/');
// Make sure the URL doesn't contain the token.
url = url.replace(/([?&])token=[^&]*&?/, '$1');
return url;
}
}
export type CoreUrlParams = {[key: string]: string};

View File

@ -18,7 +18,7 @@ import { CoreConfig } from '@services/config';
import { CoreFileHelper } from '@services/file-helper';
import { CoreSites } from '@services/sites';
import { CoreDomUtils } from '@services/utils/dom';
import { CoreUrlUtils } from '@services/utils/url';
import { CoreUrl } from '@singletons/url';
import { CoreUtils } from '@services/utils/utils';
import { Translate } from '@singletons';
import { CoreConstants } from '../constants';
@ -40,7 +40,7 @@ export class CoreWindow {
* @returns Promise resolved if confirmed, rejected if rejected.
*/
static async confirmOpenBrowserIfNeeded(url: string): Promise<void> {
if (!CoreUrlUtils.isHttpURL(url)) {
if (!CoreUrl.isHttpURL(url)) {
// Only ask confirm for http(s), other cases usually launch external apps.
return;
}
@ -76,7 +76,7 @@ export class CoreWindow {
* @returns Promise resolved when done.
*/
static async open(url: string, name?: string): Promise<void> {
if (CoreUrlUtils.isLocalFileUrl(url)) {
if (CoreUrl.isLocalFileUrl(url)) {
const filename = url.substring(url.lastIndexOf('/') + 1);
if (!CoreFileHelper.isOpenableInApp({ filename })) {