From ca7398eb5e22e975353216d0f5a6806add3efa59 Mon Sep 17 00:00:00 2001 From: Victor Fernandez de Alba Date: Fri, 23 Feb 2024 16:46:13 +0100 Subject: [PATCH] Add temporary customization for flattenToAppUrl --- .../customizations/volto/helpers/Url/Url.js | 364 ++++++++++++++++++ 1 file changed, 364 insertions(+) create mode 100644 frontend/src/customizations/volto/helpers/Url/Url.js diff --git a/frontend/src/customizations/volto/helpers/Url/Url.js b/frontend/src/customizations/volto/helpers/Url/Url.js new file mode 100644 index 0000000..214600e --- /dev/null +++ b/frontend/src/customizations/volto/helpers/Url/Url.js @@ -0,0 +1,364 @@ +/** + * Url helper. + * @module helpers/Url + */ + +import { last, memoize, isArray, isObject, isString } from 'lodash'; +import { + urlRegex, + telRegex, + mailRegex, +} from '@plone/volto/helpers/Url/urlRegex'; +import prependHttp from 'prepend-http'; +import config from '@plone/volto/registry'; +import { matchPath } from 'react-router'; + +/** + * Get base url. + * @function getBaseUrl + * @param {string} url Url to be parsed. + * @return {string} Base url of content object. + */ +export const getBaseUrl = memoize((url) => { + const { settings } = config; + if (url === undefined) return; + + // We allow settings.nonContentRoutes to have strings (that are supposed to match + // ending strings of pathnames, so we are converting them to RegEx to match also + const normalized_nonContentRoutes = settings.nonContentRoutes.map((item) => { + if (item.test) { + return item; + } else { + return new RegExp(item + '$'); + } + }); + + let adjustedUrl = normalized_nonContentRoutes.reduce( + (acc, item) => acc.replace(item, ''), + url, + ); + + adjustedUrl = adjustedUrl || '/'; + return adjustedUrl === '/' ? '' : adjustedUrl; +}); + +/** + * Get parent url. + * @function getParentUrl + * @param {string} url Url to be parsed. + * @return {string} Parent url of content object. + */ +export const getParentUrl = memoize((url) => { + return url.substring(0, url.lastIndexOf('/')); +}); + +/** + * Get id from url. + * @function getId + * @param {string} url Url to be parsed. + * @return {string} Id of content object. + */ +export function getId(url) { + return last(url.replace(/\?.*$/, '').split('/')); +} + +/** + * Get view of an url. + * @function getView + * @param {string} url Url to be parsed. + * @return {string} View of content object. + */ +export function getView(url) { + const view = last(url.replace(/\?.*$/, '').split('/')); + if ( + [ + 'add', + 'layout', + 'contents', + 'edit', + 'delete', + 'diff', + 'history', + 'sharing', + 'controlpanel', + ].indexOf(view) === -1 + ) { + return 'view'; + } + return view === 'layout' ? 'edit' : view; +} + +/** + * Flatten to app server URL - Given a URL if it starts with the API server URL + * this method flattens it (removes) the server part + * TODO: Update it when implementing non-root based app location (on a + * directory other than /, eg. /myapp) + * @method flattenToAppURL + * @param {string} url URL of the object + * @returns {string} Flattened URL to the app server + */ +export function flattenToAppURL(url) { + const { settings } = config; + // START CUSTOMIZATION + return ( + url && + url + .replace('http://backend:8080/Plone', '') + .replace(settings.internalApiPath, '') + .replace(settings.apiPath, '') + .replace(settings.publicURL, '') + ); + // END CUSTOMIZATION +} +/** + * Given a URL it remove the querystring from the URL. + * @method stripQuerystring + * @param {string} url URL of the object + * @returns {string} URL without querystring + */ +export function stripQuerystring(url) { + return url.replace(/\?.*$/, ''); +} + +/** + * Given a URL if it starts with the API server URL + * this method removes the /api or the /Plone part. + * @method toPublicURL + * @param {string} url URL of the object + * @returns {string} public URL + */ +export function toPublicURL(url) { + const { settings } = config; + return settings.publicURL.concat(flattenToAppURL(url)); +} + +/** + * Returns true if the current view is a cms ui view + * @method isCmsUi + * @param {string} currentPathname pathname of the current view + * @returns {boolean} true if the current view is a cms ui view + */ +export const isCmsUi = memoize((currentPathname) => { + const { settings } = config; + const fullPath = currentPathname.replace(/\?.*$/, ''); + // WARNING: + // not working properly for paths like /editors or similar + // because the regexp test does not take that into account + // https://github.com/plone/volto/issues/870 + return settings.nonContentRoutes.reduce( + (acc, route) => acc || new RegExp(route).test(`/${fullPath}`), + false, + ); +}); + +/** + * Flatten to app server HTML - Given a text if it contains some urls that starts + * with the API server URL this method flattens it (removes) the server part. + * TODO: Update it when implementing non-root based app location (on a + * directory other than /, eg. /myapp) + * @method flattenHTMLToAppURL + * @param {string} html Some html snippet + * @returns {string} Same HTML with Flattened URLs to the app server + */ +export function flattenHTMLToAppURL(html) { + const { settings } = config; + return settings.internalApiPath + ? html + .replace(new RegExp(settings.internalApiPath, 'g'), '') + .replace(new RegExp(settings.apiPath, 'g'), '') + : html.replace(new RegExp(settings.apiPath, 'g'), ''); +} + +/** + * Add the app url + * @method addAppURL + * @param {string} url URL of the object + * @returns {string} New URL with app + */ +export function addAppURL(url) { + const { settings } = config; + return url.indexOf(settings.apiPath) === 0 + ? url + : `${settings.apiPath}${url}`; +} + +/** + * Given a URL expands it to the backend URL + * Useful when you have to actually call the backend from the + * frontend code (eg. ICS calendar download) + * It is seamless mode aware + * @method expandToBackendURL + * @param {string} url URL or path of the object + * @returns {string} New URL with the backend URL + */ +export function expandToBackendURL(path) { + const { settings } = config; + const APISUFIX = settings.legacyTraverse ? '' : '/++api++'; + let adjustedPath; + if (path.startsWith('http://') || path.startsWith('https://')) { + // flattenToAppURL first if we get a full URL + adjustedPath = flattenToAppURL(path); + } else { + // Next adds a / in front if not a full URL to make sure it's a valid relative path + adjustedPath = path[0] !== '/' ? `/${path}` : path; + } + + let apiPath = ''; + if (settings.internalApiPath && __SERVER__) { + apiPath = settings.internalApiPath; + } else if (settings.apiPath) { + apiPath = settings.apiPath; + } + + return `${apiPath}${APISUFIX}${adjustedPath}`; +} + +/** + * Check if internal url + * @method isInternalURL + * @param {string} url URL of the object + * @returns {boolean} True if internal url + */ +export function isInternalURL(url) { + const { settings } = config; + + const isMatch = (config.settings.externalRoutes ?? []).find((route) => { + if (typeof route === 'object') { + return matchPath(flattenToAppURL(url), route.match); + } + return matchPath(flattenToAppURL(url), route); + }); + + const isExcluded = isMatch && Object.keys(isMatch)?.length > 0; + + const internalURL = + url && + (url.indexOf(settings.publicURL) !== -1 || + (settings.internalApiPath && + url.indexOf(settings.internalApiPath) !== -1) || + url.indexOf(settings.apiPath) !== -1 || + url.charAt(0) === '/' || + url.charAt(0) === '.' || + url.startsWith('#')); + + if (internalURL && isExcluded) { + return false; + } + + return internalURL; +} + +/** + * Check if it's a valid url + * @method isUrl + * @param {string} url URL of the object + * @returns {boolean} True if is a valid url + */ +export function isUrl(url) { + return urlRegex().test(url); +} + +/** + * Get field url + * @method getFieldURL + * @param {object} data + * @returns {string | any} URL string value if field is of url type or any. + */ +export const getFieldURL = (data) => { + let url = data; + const _isObject = data && isObject(data) && !isArray(data); + if (_isObject && data['@type'] === 'URL') { + url = data['value'] ?? data['url'] ?? data['href'] ?? data; + } else if (_isObject) { + url = data['@id'] ?? data['url'] ?? data['href'] ?? data; + } + if (isArray(data)) { + url = data.map((item) => getFieldURL(item)); + } + if (isString(url) && isInternalURL(url)) return flattenToAppURL(url); + return url; +}; + +/** + * Normalize URL, adds protocol (if required eg. user has not entered the protocol) + * @method normalizeUrl + * @param {string} url URL of the object + * @returns {boolean} URL with the protocol + */ +export function normalizeUrl(url) { + return prependHttp(url); +} + +/** + * Removes protocol from URL (for display) + * @method removeProtocol + * @param {string} url URL of the object + * @returns {string} URL without the protocol part + */ +export function removeProtocol(url) { + return url.replace('https://', '').replace('http://', ''); +} + +export function isMail(text) { + return mailRegex().test(text); +} + +export function isTelephone(text) { + return telRegex().test(text); +} + +export function normaliseMail(email) { + if (email?.toLowerCase()?.startsWith('mailto:')) { + return email; + } + return `mailto:${email}`; +} + +export function normalizeTelephone(tel) { + if (tel?.toLowerCase()?.startsWith('tel:')) { + return tel; + } + return `tel:${tel}`; +} + +export function checkAndNormalizeUrl(url) { + let res = { + isMail: false, + isTelephone: false, + url: url, + isValid: true, + }; + if (URLUtils.isMail(URLUtils.normaliseMail(url))) { + //Mail + res.isMail = true; + res.url = URLUtils.normaliseMail(url); + } else if (URLUtils.isTelephone(url)) { + //Phone + res.isTelephone = true; + res.url = URLUtils.normalizeTelephone(url); + } else { + //url + if ( + res.url?.length >= 0 && + !res.url.startsWith('/') && + !res.url.startsWith('#') + ) { + res.url = URLUtils.normalizeUrl(url); + if (!URLUtils.isUrl(res.url)) { + res.isValid = false; + } + } + if (res.url === undefined || res.url === null) res.isValid = false; + } + return res; +} + +export const URLUtils = { + normalizeTelephone, + normaliseMail, + normalizeUrl, + isTelephone, + isMail, + isUrl, + checkAndNormalizeUrl, +};