/
githubmirror
/
wp-calypso
Обзор
Документация
Войти
/
githubmirror
/
wp-calypso
Код
Задачи
Вики
Пакеты
0
Релизы
0
Аналитика
Безопасность
trunk
client/lib/plugins/utils.js
454 строки
13 KB
Griffith Chen
Lodash: replace map() with native Array.prototype.map (#112102)
30 июн 2026, 06:44
Не верифицирован
30 июн 2026, 06:44
0dc1083
Код
Авторство
О чём код?
import { isEnabled } from '@automattic/calypso-config'; import { PLAN_BUSINESS_MONTHLY, PLAN_BUSINESS, PLAN_PREMIUM, PLAN_PERSONAL, PLAN_PERSONAL_MONTHLY, PLAN_BLOGGER, PLAN_PREMIUM_2_YEARS, PLAN_BUSINESS_2_YEARS, PLAN_BLOGGER_2_YEARS, PLAN_PERSONAL_2_YEARS, } from '@automattic/calypso-products'; import { pick, sortBy } from '@automattic/js-utils'; import isA8CForAgencies from 'calypso/lib/a8c-for-agencies/is-a8c-for-agencies'; import { decodeEntities, parseHtml } from 'calypso/lib/formatting'; import isJetpackCloud from 'calypso/lib/jetpack/is-jetpack-cloud'; import { IntervalLength } from 'calypso/my-sites/marketplace/components/billing-interval-switcher/constants'; import { PREINSTALLED_PREMIUM_PLUGINS } from 'calypso/my-sites/plugins/constants'; import { sanitizeSectionContent } from './sanitize-section-content'; /** * @param {number} siteId Site Object * @param {Object} log Notice log Object * @returns {boolean} True if notice matches criteria */ function isSameSiteNotice( siteId, log ) { return siteId && log.siteId && parseInt( log.siteId ) === siteId; } /** * @param {string} pluginId Plugin ID * @param {Object} log Notice log Object * @returns {boolean} True if notice matches criteria */ function isSamePluginNotice( pluginId, log ) { if ( ! pluginId || ! log.pluginId ) { return false; } return isSamePluginIdSlug( log.pluginId, pluginId ); } /** * @param {string} idOrSlug First plugin ID or slug for comparison * @param {string} slugOrId Second plugin ID or slug for comparison * @returns {boolean} True if the plugin ID and slug match */ export function isSamePluginIdSlug( idOrSlug, slugOrId ) { const firstIdOrSlug = idOrSlug.toString(); const secondIdOrSlug = slugOrId.toString(); return ( firstIdOrSlug === secondIdOrSlug || firstIdOrSlug.startsWith( secondIdOrSlug + '/' ) || firstIdOrSlug.endsWith( '/' + secondIdOrSlug ) || secondIdOrSlug.startsWith( firstIdOrSlug + '/' ) || secondIdOrSlug.endsWith( '/' + firstIdOrSlug ) ); } /** * Filter function that return notices that fit a certain criteria. * @param {number} siteId Site Object * @param {string} pluginId Plugin Id * @param {Object} log Notice log Object * @returns {boolean} True if notice matches criteria */ function filterNoticesBy( siteId, pluginId, log ) { if ( ! siteId && ! pluginId ) { return true; } if ( isSameSiteNotice( siteId, log ) && isSamePluginNotice( pluginId, log ) ) { return true; } else if ( ! pluginId && isSameSiteNotice( siteId, log ) ) { return true; } else if ( ! siteId && isSamePluginNotice( pluginId, log ) ) { return true; } return false; } export function getAllowedPluginData( plugin ) { return pick( plugin, 'action_links', 'active', 'author', 'author_profile', 'author_url', 'autoupdate', 'banners', 'compatibility', 'contributors', 'description', 'active_installs', 'short_description', 'detailsFetched', 'downloaded', 'documentation_url', 'homepage', 'icons', 'id', 'last_updated', 'name', 'network', 'num_ratings', 'org_slug', 'plugin_url', 'product_video', 'rating', 'ratings', 'requirements', 'sections', 'setup_url', 'slug', 'software_slug', 'support_URL', 'software_slug', 'tags', 'tested', 'update', 'updating', 'variations', 'version', 'wp_admin_settings_page_url', 'saas_landing_page', 'categories' ); } export function extractAuthorName( authorElementSource ) { if ( ! authorElementSource ) { return ''; } return decodeEntities( authorElementSource.replace( /(<([^>]+)>)/gi, '' ) ); } export function extractAuthorUrl( authorElementSource ) { const match = /<a\s+(?:[^>]*?\s+)?href="([^"]*)"/.exec( authorElementSource ); return match && match[ 1 ] ? match[ 1 ] : ''; } export function extractScreenshots( screenshotsHtml ) { if ( 'undefined' === typeof window ) { return null; } const screenshotsDom = parseHtml( screenshotsHtml ); const list = screenshotsDom && screenshotsDom.querySelectorAll( 'li' ); if ( ! list ) { return null; } let screenshots = Array.from( list ).map( function ( li ) { const img = li.querySelectorAll( 'img' ); const captionP = li.querySelectorAll( 'p' ); if ( img[ 0 ] && img[ 0 ].src ) { return { url: img[ 0 ].src, caption: captionP[ 0 ] ? captionP[ 0 ].textContent : null, }; } } ); screenshots = screenshots.filter( ( screenshot ) => screenshot ); return screenshots.length ? screenshots : null; } export function normalizeCompatibilityList( compatibilityList ) { function splitInNumbers( version ) { const splittedVersion = version.split( '.' ).map( function ( versionComponent ) { return Number.parseInt( versionComponent, 10 ); } ); while ( splittedVersion.length < 3 ) { splittedVersion.push( 0 ); } return splittedVersion; } const sortedCompatibility = sortBy( Object.keys( compatibilityList ).map( splitInNumbers ), [ 0, 1, 2 ] ); return sortedCompatibility.map( function ( version ) { if ( version.length && version[ version.length - 1 ] === 0 ) { version.pop(); } return version.join( '.' ); } ); } export function mapStarRatingToPercent( starRating ) { return ( ( starRating ?? 0 ) / 5 ) * 100; } export function normalizePluginData( plugin, pluginData ) { plugin = getAllowedPluginData( { ...plugin, ...pluginData } ); plugin.variations = getPreinstalledPremiumPluginsVariations( plugin ); return Object.entries( plugin ).reduce( ( returnData, [ key, item ] ) => { switch ( key ) { case 'short_description': case 'description': case 'name': case 'slug': returnData[ key ] = decodeEntities( item ); break; case 'author': returnData.author = item; returnData.author_name = extractAuthorName( item ); returnData.author_url = plugin.author_url || extractAuthorUrl( item ); break; case 'sections': { const cleanItem = {}; for ( const sectionKey of Object.keys( item ) ) { if ( ! item[ sectionKey ] ) { // The current section hasn't value or is empty. continue; } cleanItem[ sectionKey ] = sanitizeSectionContent( item[ sectionKey ] ); } returnData.sections = cleanItem; returnData.screenshots = cleanItem.screenshots ? extractScreenshots( cleanItem.screenshots ) : null; break; } case 'num_ratings': case 'rating': returnData[ key ] = parseInt( item, 10 ); break; case 'ratings': for ( const prop in item ) { item[ prop ] = parseInt( item[ prop ], 10 ); } returnData[ key ] = item; break; case 'icons': if ( item ) { returnData.icon = item[ '256x256' ] || item[ '128x128' ] || item[ '2x' ] || item[ '1x' ] || item.svg || item.default || item; } break; case 'homepage': case 'plugin_url': returnData.plugin_url = item; break; case 'compatibility': returnData[ key ] = normalizeCompatibilityList( item ); break; case 'product_video': returnData.banner_video_src = item; break; default: returnData[ key ] = item; } return returnData; }, {} ); } export function normalizePluginsList( pluginsList ) { if ( ! pluginsList ) { return []; } return Object.values( pluginsList ).map( ( pluginData ) => normalizePluginData( pluginData ) ); } /** * Return logs that match a certain critia. * @param {Array} logs List of all notices * @param {number} siteId Site Object * @param {string} pluginId Plugin ID * @returns {Array} Array of filtered logs that match the criteria */ export function filterNotices( logs, siteId, pluginId ) { return ( logs ?? [] ).filter( filterNoticesBy.bind( this, siteId, pluginId ) ); } /** * Regex to extract the author from the search */ export const DEVELOPER_PATTERN = /developer:(?:\s)*"(.*)"/; /** * Extract author and search params from the plugin search query * @param {string} searchTerm The full plugin search query * @returns {Array<string | undefined>} The first item will be the search and the second will be the author if exists */ export function extractSearchInformation( searchTerm = '' ) { const author = searchTerm.match( DEVELOPER_PATTERN )?.[ 1 ]; const search = searchTerm.replace( DEVELOPER_PATTERN, '' ).trim(); return [ search, author ]; } export const WPORG_PROFILE_URL = 'https://profiles.wordpress.org/'; /** * Get the author keyword from author_profile property * @param plugin * @returns {string|null} the author keyword */ export function getPluginAuthorProfileKeyword( plugin ) { if ( ! plugin?.author_profile?.startsWith( WPORG_PROFILE_URL ) ) { return null; } return plugin.author_profile.replace( WPORG_PROFILE_URL, '' ).replaceAll( '/', '' ); } /** * @param currentPlan * @param pluginBillingPeriod * @returns the correct plan slug depending on current plan and pluginBillingPeriod */ export function marketplacePlanToAdd( currentPlan, pluginBillingPeriod ) { if ( isEnabled( 'marketplace-personal-premium' ) ) { // Site is free - doesn't have a plan. return pluginBillingPeriod === IntervalLength.ANNUALLY ? PLAN_PERSONAL : PLAN_PERSONAL_MONTHLY; } // Legacy plans always upgrade to business. switch ( currentPlan.product_slug ) { case PLAN_PERSONAL_2_YEARS: case PLAN_PREMIUM_2_YEARS: case PLAN_BLOGGER_2_YEARS: return PLAN_BUSINESS_2_YEARS; case PLAN_PERSONAL: case PLAN_PREMIUM: case PLAN_BLOGGER: return PLAN_BUSINESS; default: // Return annual plan if selected, monthly otherwise. return pluginBillingPeriod === IntervalLength.ANNUALLY ? PLAN_BUSINESS : PLAN_BUSINESS_MONTHLY; } } /** * Determines the URL to use for managing a connection. * @param {string} siteSlug The site slug to use in the URL. * @returns The URL to use for managing a connection. */ export const getManageConnectionHref = ( siteSlug ) => { return isJetpackCloud() || isA8CForAgencies() ? `https://wordpress.com/settings/manage-connection/${ siteSlug }` : `/settings/manage-connection/${ siteSlug }`; }; /** * Some plugins can be preinstalled on WPCOM and available as standalone on WPORG, * but require a paid upgrade to function. * @typedef {Object} PluginVariations * @property {Object} monthly The plugin's monthly variation * @property {string} monthly.product_slug The plugin's monthly variation's product slug * @property {Object} yearly The plugin's yearly variation * @property {string} yearly.product_slug The plugin's yearly variation's product slug * @param {Object} plugin * @returns {PluginVariations} */ export function getPreinstalledPremiumPluginsVariations( plugin ) { if ( ! PREINSTALLED_PREMIUM_PLUGINS[ plugin.slug ] || !! plugin.variations ) { return plugin?.variations; } const { monthly, yearly } = PREINSTALLED_PREMIUM_PLUGINS[ plugin.slug ].products; return { monthly: { product_slug: monthly }, yearly: { product_slug: yearly }, }; } /** * Returns the product slug of periodVariation passed filtering the productsList passed only if required * @param {{ product_slug?: string; product_id?: number } | undefined} periodVariation The variation object with the shape { product_slug: string; product_id: number; } * @param {Record<string, Object>} productsList The list of products * @returns The product slug if it exists in the periodVariation, if it does not exist in periodVariation * it will find the product slug in the productsList filtering by the variation.product_id. * It additionally returns: * - null|undefined if periodVariation is null|undefined * - null|undefined if variation.product_id is null|undefined * - undefined product is not found by productId in productsList */ export function getProductSlugByPeriodVariation( periodVariation, productsList ) { if ( ! periodVariation ) { return periodVariation; } const productSlug = periodVariation.product_slug; if ( productSlug ) { return productSlug; } const productId = periodVariation.product_id; if ( productId === undefined || productId === null ) { return productId; } return Object.values( productsList ).find( ( product ) => product.product_id === productId ) ?.product_slug; } /** * @param {Object} plugin The plugin object * @param {boolean} isMarketplaceProduct Is this part of WP.com Marketplace or WP.org * @returns {string} The software slug string */ export const getSoftwareSlug = ( plugin, isMarketplaceProduct ) => isMarketplaceProduct ? plugin.software_slug || plugin.org_slug : plugin.slug; /** * @typedef {import('calypso/lib/purchases/types').Purchase} Purchase * @param {Object} plugin The plugin object * @param {Array} purchases An array of site purchases * @returns {Purchase} The purchase object, if found. */ export const getPluginPurchased = ( plugin, purchases ) => { return ( plugin?.variations && purchases.find( ( purchase ) => Object.values( plugin.variations ).some( ( variation ) => variation.product_id === purchase.productId ) ) ); }; /** * Gets the SaaS redirect URL of a plugin if it exits and is valid * @param {plugin} plugin The plugin object to read the SaaS redirect url from * @param {number} userId The user id * @param {number} siteId The site id * @returns The URL of the SaaS redirect page or null if it doesn't exist or is an invalid URL */ export function getSaasRedirectUrl( plugin, userId, siteId ) { if ( ! plugin?.saas_landing_page ) { return null; } try { const saasRedirectUrl = new URL( plugin.saas_landing_page ); saasRedirectUrl.searchParams.append( 'uuid', `${ userId }+${ siteId }` ); return saasRedirectUrl.toString(); } catch ( error ) { return null; } }