This commit is contained in:
zbatte committed 2026-07-02 15:54:39 -06:00
commit 9883323161
17470 files changed
+4470592

No files matched your search

File diff suppressed because it is too large. Load diff
@@ -0,0 +1,31 @@
<?php
/**
* Action Hooks for Jetpack Assets module.
*
* @package automattic/jetpack-assets
*/
// If WordPress's plugin API is available already, use it. If not,
// drop data into `$wp_filter` for `WP_Hook::build_preinitialized_hooks()`.
if ( function_exists( 'add_action' ) ) {
add_action( 'wp_default_scripts', array( Automattic\Jetpack\Assets::class, 'wp_default_scripts_hook' ) );
add_action( 'plugins_loaded', array( Automattic\Jetpack\Assets\Script_Data::class, 'configure' ), 1 );
add_action( 'plugins_loaded', array( Automattic\Jetpack\Assets\Shared_Stores_Assets::class, 'configure' ), 1 );
} else {
global $wp_filter;
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_filter['wp_default_scripts'][10][] = array(
'accepted_args' => 1,
'function' => array( Automattic\Jetpack\Assets::class, 'wp_default_scripts_hook' ),
);
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_filter['plugins_loaded'][1][] = array(
'accepted_args' => 0,
'function' => array( Automattic\Jetpack\Assets\Script_Data::class, 'configure' ),
);
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_filter['plugins_loaded'][1][] = array(
'accepted_args' => 0,
'function' => array( Automattic\Jetpack\Assets\Shared_Stores_Assets::class, 'configure' ),
);
}
@@ -0,0 +1 @@
<?php return array('dependencies' => array('wp-i18n'), 'version' => '5ba5dddc04be2306aaf0');
@@ -0,0 +1 @@
(()=>{var e={477(e){e.exports=function(e){function r(n){if(t[n])return t[n].exports;var a=t[n]={i:n,l:!1,exports:{}};return e[n].call(a.exports,a,a.exports,r),a.l=!0,a.exports}var t={};return r.m=e,r.c=t,r.i=function(e){return e},r.d=function(e,t,n){r.o(e,t)||Object.defineProperty(e,t,{configurable:!1,enumerable:!0,get:n})},r.n=function(e){var t=e&&e.__esModule?function(){return e.default}:function(){return e};return r.d(t,"a",t),t},r.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},r.p="",r(r.s=1)}([function(e,r,t){"use strict";function n(e,r){if(!(e instanceof r))throw new TypeError("Cannot call a class as a function")}Object.defineProperty(r,"__esModule",{value:!0});var a=function(){function e(e,r){for(var t=0;t<r.length;t++){var n=r[t];n.enumerable=n.enumerable||!1,n.configurable=!0,"value"in n&&(n.writable=!0),Object.defineProperty(e,n.key,n)}}return function(r,t,n){return t&&e(r.prototype,t),n&&e(r,n),r}}(),o=function(){function e(){n(this,e)}return a(e,null,[{key:"hash",value:function(r){return e.hex(e.md51(r))}},{key:"md5cycle",value:function(r,t){var n=r[0],a=r[1],o=r[2],i=r[3];n=e.ff(n,a,o,i,t[0],7,-680876936),i=e.ff(i,n,a,o,t[1],12,-389564586),o=e.ff(o,i,n,a,t[2],17,606105819),a=e.ff(a,o,i,n,t[3],22,-1044525330),n=e.ff(n,a,o,i,t[4],7,-176418897),i=e.ff(i,n,a,o,t[5],12,1200080426),o=e.ff(o,i,n,a,t[6],17,-1473231341),a=e.ff(a,o,i,n,t[7],22,-45705983),n=e.ff(n,a,o,i,t[8],7,1770035416),i=e.ff(i,n,a,o,t[9],12,-1958414417),o=e.ff(o,i,n,a,t[10],17,-42063),a=e.ff(a,o,i,n,t[11],22,-1990404162),n=e.ff(n,a,o,i,t[12],7,1804603682),i=e.ff(i,n,a,o,t[13],12,-40341101),o=e.ff(o,i,n,a,t[14],17,-1502002290),a=e.ff(a,o,i,n,t[15],22,1236535329),n=e.gg(n,a,o,i,t[1],5,-165796510),i=e.gg(i,n,a,o,t[6],9,-1069501632),o=e.gg(o,i,n,a,t[11],14,643717713),a=e.gg(a,o,i,n,t[0],20,-373897302),n=e.gg(n,a,o,i,t[5],5,-701558691),i=e.gg(i,n,a,o,t[10],9,38016083),o=e.gg(o,i,n,a,t[15],14,-660478335),a=e.gg(a,o,i,n,t[4],20,-405537848),n=e.gg(n,a,o,i,t[9],5,568446438),i=e.gg(i,n,a,o,t[14],9,-1019803690),o=e.gg(o,i,n,a,t[3],14,-187363961),a=e.gg(a,o,i,n,t[8],20,1163531501),n=e.gg(n,a,o,i,t[13],5,-1444681467),i=e.gg(i,n,a,o,t[2],9,-51403784),o=e.gg(o,i,n,a,t[7],14,1735328473),a=e.gg(a,o,i,n,t[12],20,-1926607734),n=e.hh(n,a,o,i,t[5],4,-378558),i=e.hh(i,n,a,o,t[8],11,-2022574463),o=e.hh(o,i,n,a,t[11],16,1839030562),a=e.hh(a,o,i,n,t[14],23,-35309556),n=e.hh(n,a,o,i,t[1],4,-1530992060),i=e.hh(i,n,a,o,t[4],11,1272893353),o=e.hh(o,i,n,a,t[7],16,-155497632),a=e.hh(a,o,i,n,t[10],23,-1094730640),n=e.hh(n,a,o,i,t[13],4,681279174),i=e.hh(i,n,a,o,t[0],11,-358537222),o=e.hh(o,i,n,a,t[3],16,-722521979),a=e.hh(a,o,i,n,t[6],23,76029189),n=e.hh(n,a,o,i,t[9],4,-640364487),i=e.hh(i,n,a,o,t[12],11,-421815835),o=e.hh(o,i,n,a,t[15],16,530742520),a=e.hh(a,o,i,n,t[2],23,-995338651),n=e.ii(n,a,o,i,t[0],6,-198630844),i=e.ii(i,n,a,o,t[7],10,1126891415),o=e.ii(o,i,n,a,t[14],15,-1416354905),a=e.ii(a,o,i,n,t[5],21,-57434055),n=e.ii(n,a,o,i,t[12],6,1700485571),i=e.ii(i,n,a,o,t[3],10,-1894986606),o=e.ii(o,i,n,a,t[10],15,-1051523),a=e.ii(a,o,i,n,t[1],21,-2054922799),n=e.ii(n,a,o,i,t[8],6,1873313359),i=e.ii(i,n,a,o,t[15],10,-30611744),o=e.ii(o,i,n,a,t[6],15,-1560198380),a=e.ii(a,o,i,n,t[13],21,1309151649),n=e.ii(n,a,o,i,t[4],6,-145523070),i=e.ii(i,n,a,o,t[11],10,-1120210379),o=e.ii(o,i,n,a,t[2],15,718787259),a=e.ii(a,o,i,n,t[9],21,-343485551),r[0]=n+r[0]&4294967295,r[1]=a+r[1]&4294967295,r[2]=o+r[2]&4294967295,r[3]=i+r[3]&4294967295}},{key:"cmn",value:function(e,r,t,n,a,o){return((r=(r+e&4294967295)+(n+o&4294967295)&4294967295)<<a|r>>>32-a)+t&4294967295}},{key:"ff",value:function(r,t,n,a,o,i,f){return e.cmn(t&n|~t&a,r,t,o,i,f)}},{key:"gg",value:function(r,t,n,a,o,i,f){return e.cmn(t&a|n&~a,r,t,o,i,f)}},{key:"hh",value:function(r,t,n,a,o,i,f){return e.cmn(t^n^a,r,t,o,i,f)}},{key:"ii",value:function(r,t,n,a,o,i,f){return e.cmn(n^(t|~a),r,t,o,i,f)}},{key:"md51",value:function(r){for(var t=r.length,n=[1732584193,-271733879,-1732584194,271733878],a=[0,0,0,0,0,0,0,0,0,0,0,0,0,0,0,0],o=0,i=64;i<=t;i+=64)e.md5cycle(n,e.md5blk(r.substring(i-64,i)));for(r=r.substring(i-64),i=0,o=r.length;i<o;i++)a[i>>2]|=r.charCodeAt(i)<<(i%4<<3);if(a[i>>2]|=128<<(i%4<<3),i>55)for(e.md5cycle(n,a),i=0;i<16;i++)a[i]=0;return a[14]=8*t,e.md5cycle(n,a),n}},{key:"md5blk",value:function(e){for(var r=[],t=0;t<64;t+=4)r[t>>2]=e.charCodeAt(t)+(e.charCodeAt(t+1)<<8)+(e.charCodeAt(t+2)<<16)+(e.charCodeAt(t+3)<<24);return r}},{key:"rhex",value:function(r){var t="";return t+=e.hexArray[r>>4&15]+e.hexArray[15&r],t+=e.hexArray[r>>12&15]+e.hexArray[r>>8&15],(t+=e.hexArray[r>>20&15]+e.hexArray[r>>16&15])+(e.hexArray[r>>28&15]+e.hexArray[r>>24&15])}},{key:"hex",value:function(r){for(var t=r.length,n=0;n<t;n++)r[n]=e.rhex(r[n]);return r.join("")}}]),e}();o.hexArray=["0","1","2","3","4","5","6","7","8","9","a","b","c","d","e","f"],r.default=o},function(e,r,t){e.exports=t(0)}])},305(e,r,t){const n=t(723),{default:a}=t(477),o={plugin:"plugins/",theme:"themes/",core:""},i=(e,r)=>Object.prototype.hasOwnLine truncated
@@ -0,0 +1 @@
<?php return array('dependencies' => array(), 'version' => '063e99e8f3d8166cfb17');
@@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.JetpackScriptDataModule=t():e.JetpackScriptDataModule=t()}(globalThis,()=>(()=>{"use strict";var e={729(e,t,r){r.d(t,{$8:()=>n.$8,IT:()=>n.IT,KS:()=>n.KS,L2:()=>n.L2,Sy:()=>n.Sy,au:()=>n.au,d_:()=>n.d_,e5:()=>n.e5,hT:()=>n.hT,lI:()=>n.lI,mH:()=>n.mH,oQ:()=>n.oQ,sV:()=>n.sV});var n=r(336)},336(e,t,r){function n(){return window.JetpackScriptData}function o(){return n()?.site}function i(e=""){return`${n()?.site.admin_url}${e}`}function a(e=""){return i(`admin.php?page=jetpack${e}`)}function u(e=""){return i(`admin.php?page=my-jetpack${e}`)}function p(){return n()?.site.plan?.features?.active??[]}function c(e){return-1!==p().indexOf(e)}function s(){return"wpcom"===n()?.site?.host}function f(){return"woa"===n()?.site?.host}function d(){return n()?.site?.is_wpcom_platform}function l(){return"unknown"===n()?.site?.host}function S(){return s()?"simple":f()?"woa":"jetpack"}function m(e){return n()?.user.current_user.capabilities[e]}r.d(t,{$8:()=>d,IT:()=>c,KS:()=>S,L2:()=>l,Sy:()=>s,au:()=>n,d_:()=>m,e5:()=>u,hT:()=>i,lI:()=>f,mH:()=>p,oQ:()=>a,sV:()=>o})}},t={};function r(n){var o=t[n];if(void 0!==o)return o.exports;var i=t[n]={exports:{}};return e[n](i,i.exports,r),i.exports}r.d=(e,t)=>{for(var n in t)r.o(t,n)&&!r.o(e,n)&&Object.defineProperty(e,n,{enumerable:!0,get:t[n]})},r.o=(e,t)=>Object.prototype.hasOwnProperty.call(e,t),r.r=e=>{"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})};var n={};r.r(n),r.d(n,{currentUserCan:()=>o.d_,getActiveFeatures:()=>o.mH,getAdminUrl:()=>o.hT,getJetpackAdminPageUrl:()=>o.oQ,getMyJetpackUrl:()=>o.e5,getScriptData:()=>o.au,getSiteData:()=>o.sV,getSiteType:()=>o.KS,isJetpackSelfHostedSite:()=>o.L2,isSimpleSite:()=>o.Sy,isWoASite:()=>o.lI,isWpcomPlatformSite:()=>o.$8,siteHasFeature:()=>o.IT});var o=r(729);return n})());
@@ -0,0 +1 @@
<?php return array('dependencies' => array('jetpack-script-data', 'wp-api-fetch', 'wp-data', 'wp-polyfill'), 'version' => '09c0b5848292ccbc06d4');
@@ -0,0 +1 @@
!function(e,t){"object"==typeof exports&&"object"==typeof module?module.exports=t():"function"==typeof define&&define.amd?define([],t):"object"==typeof exports?exports.JetpackSharedStores=t():e.JetpackSharedStores=t()}(globalThis,()=>(()=>{"use strict";var e={441(e,t,s){s.d(t,{Ft:()=>a.F,M_:()=>a.M,Tp:()=>r.T,j5:()=>r.j});var a=s(362),r=s(547)},905(e,t,s){s.d(t,{Ay:()=>d,E9:()=>c,F6:()=>u});var a=s(999),r=s(143),n=s(142),i=s(362);function u(e){return c({isLoading:e})}function o(e,t){return{type:"SET_MODULE_UPDATING",name:e,isUpdating:t}}function c(e){return{type:"SET_JETPACK_MODULES",options:e}}const d={updateJetpackModuleStatus:function*(e){try{yield o(e.name,!0),yield(0,n.sB)(e);const t=yield(0,n.wz)();return yield c({data:t}),!0}catch{const e=(0,r.select)(i.F).getJetpackModules();return yield c(e),!1}finally{yield o(e.name,!1)}},setJetpackModules:c,fetchModules:function*(){if((0,a.isSimpleSite)())return!0;try{yield u(!0);const e=yield(0,n.wz)();return yield c({data:e}),!0}catch{const e=(0,r.select)(i.F).getJetpackModules();return yield c(e),!1}finally{yield u(!1)}}}},142(e,t,s){s.d(t,{Ay:()=>c,sB:()=>o,wz:()=>u});var a=s(455),r=s.n(a);const n="FETCH_JETPACK_MODULES",i="UPDATE_JETPACK_MODULE_STATUS",u=()=>({type:n}),o=e=>({type:i,settings:e}),c={[n]:function(){return r()({path:"/jetpack/v4/module/all",method:"GET"})},[i]:function({settings:e}){return r()({path:`/jetpack/v4/module/${e.name}/active`,method:"POST",data:{active:e.active}})}}},362(e,t,s){s.d(t,{F:()=>c,M:()=>d});var a=s(143),r=s(905),n=s(142),i=s(198),u=s(517),o=s(928);const c="jetpack-modules",d=(0,a.createReduxStore)(c,{reducer:i.A,actions:r.Ay,controls:n.Ay,resolvers:u.A,selectors:o.A});(0,a.register)(d);const l=window?.Initial_State?.getModules||window?.Jetpack_Editor_Initial_State?.modules||null;null!==l&&(0,a.dispatch)(c).setJetpackModules({data:{...l}})},198(e,t,s){s.d(t,{A:()=>r});const a={isLoading:!1,isUpdating:{},data:{}},r=(e=a,t)=>{switch(t.type){case"SET_JETPACK_MODULES":return{...e,...t.options};case"SET_MODULE_UPDATING":return{...e,isUpdating:{...e.isUpdating,[t.name]:t.isUpdating}}}return e}},517(e,t,s){s.d(t,{A:()=>n});var a=s(905),r=s(142);const n={getJetpackModules:function*(){try{yield(0,a.F6)(!0);const e=yield(0,r.wz)();if(e)return(0,a.E9)({data:e})}catch(e){console.error(e)}finally{yield(0,a.F6)(!1)}}}},928(e,t,s){s.d(t,{A:()=>r});var a=s(999);const r={getJetpackModules:e=>e.data,isModuleActive:(e,t)=>(0,a.isSimpleSite)()||(e?.data?.[t]?.activated??!1),areModulesLoading:e=>e.isLoading??!1,isModuleUpdating:(e,t)=>e?.isUpdating?.[t]??!1}},879(e,t,s){s.d(t,{A:()=>u});var a=s(455),r=s.n(a),n=s(677);const i={setPlans:e=>({type:n.PC,plans:e}),fetchFromAPI:e=>({type:n.iH,url:e}),storeAiAssistantFeature:e=>({type:n.UU,feature:e}),fetchAiAssistantFeature:()=>async({dispatch:e})=>{e({type:n.ql});try{const t=await r()({path:n.J8});e(i.storeAiAssistantFeature(function(e){return{hasFeature:!!e["has-feature"],isOverLimit:!!e["is-over-limit"],requestsCount:e["requests-count"],requestsLimit:e["requests-limit"],requireUpgrade:!!e["site-require-upgrade"],errorMessage:e["error-message"],errorCode:e["error-code"],upgradeType:e["upgrade-type"],usagePeriod:{currentStart:e["usage-period"]?.["current-start"],nextStart:e["usage-period"]?.["next-start"],requestsCount:e["usage-period"]?.["requests-count"]||0},currentTier:e["current-tier"],nextTier:e["next-tier"],tierPlansEnabled:!!e["tier-plans-enabled"],costs:e.costs,featuresControl:e["features-control"]}}(t)))}catch(e){console.error(e)}},increaseAiAssistantRequestsCount:(e=1)=>({dispatch:t})=>{t({type:n.Lg,count:e}),t(i.decreaseAsyncRequestCountdownValue())},decreaseAsyncRequestCountdownValue:()=>async({dispatch:e,select:t})=>{e({type:n.yO});t.getAsyncRequestCountdownValue()<=0&&e(i.enqueueAiAssistantFeatureAsyncRequest())},enqueueAiAssistantFeatureAsyncRequest:()=>({dispatch:e})=>{e.dequeueAiAssistantFeatureAsyncRequest();const t=setTimeout(()=>{e(i.fetchAiAssistantFeature())},n.gW);e({type:n.jM,timerId:t})},dequeueAiAssistantFeatureAsyncRequest:()=>({dispatch:e,select:t})=>{e({type:n.js,timerId:0});const s=t.getAsyncRequestCountdownTimerId();s&&window?.clearTimeout(s)},setAiAssistantFeatureRequireUpgrade:(e=!0)=>({type:n.Kt,requireUpgrade:e}),setTierPlansEnabled:(e=!0)=>({type:n.c1,tierPlansEnabled:e})},u=i},677(e,t,s){s.d(t,{GB:()=>l,J8:()=>d,Kt:()=>o,Lg:()=>u,PC:()=>a,UU:()=>n,c1:()=>c,gW:()=>f,iH:()=>r,jM:()=>_,js:()=>g,mx:()=>p,nb:()=>A,ql:()=>i,yO:()=>y});const a="SET_PLANS",r="FETCH_FROM_API",n="STORE_AI_ASSISTANT_FEATURE",i="REQUEST_AI_ASSISTANT_FEATURE",u="INCREASE_AI_ASSISTANT_REQUESTS_COUNT",o="SET_AI_ASSISTANT_FEATURE_REQUIRE_UPGRADE",c="SET_TIER_PLANS_ENABLED",d="/wpcom/v2/jetpack-ai/ai-assistant-feature",l=20,A=3e3,p=3,f=5e3,y="DECREASE_NEW_ASYNC_REQUEST_COUNTDOWN",_="ENQUEUE_ASYNC_COUNTDOWN_REQUEST",g="DEQUEUE_ASYNC_COUNTDOWN_REQUEST"},547(e,t,s){s.d(t,{T:()=>i,j:()=>u});var a=s(143),r=s(879),n=s(732);const i={getPlan:(e,t)=>e.plans.find(e=>e.product_slug===t),getAiAssistantFeatLine truncated
@@ -0,0 +1,763 @@
<?php
/**
* Jetpack Assets package.
*
* @package automattic/jetpack-assets
*/
namespace Automattic\Jetpack;
use Automattic\Jetpack\Assets\Semver;
use Automattic\Jetpack\Constants as Jetpack_Constants;
use InvalidArgumentException;
/**
* Class Assets
*/
class Assets {
/**
* Holds all the scripts handles that should be loaded in a deferred fashion.
*
* @var array
*/
private $defer_script_handles = array();
/**
* The singleton instance of this class.
*
* @var Assets
*/
protected static $instance;
/**
* The registered textdomain mappings.
*
* @var array `array( mapped_domain => array( string target_domain, string target_type, string semver, string path_prefix ) )`.
*/
private static $domain_map = array();
/**
* Constructor.
*
* Static-only class, so nothing here.
*/
private function __construct() {}
// ////////////////////
// region Async script loading
/**
* Get the singleton instance of the class.
*
* @return Assets
*/
public static function instance() {
if ( ! isset( self::$instance ) ) {
self::$instance = new Assets();
}
return self::$instance;
}
/**
* A public method for adding the async script.
*
* @deprecated Since 2.1.0, the `strategy` feature should be used instead, with the "defer" setting.
*
* @param string $script_handle Script handle.
*/
public static function add_async_script( $script_handle ) {
_deprecated_function( __METHOD__, '2.1.0' );
wp_script_add_data( $script_handle, 'strategy', 'defer' );
}
/**
* Add an async attribute to scripts that can be loaded deferred.
* https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script
*
* @deprecated Since 2.1.0, the `strategy` feature should be used instead.
*
* @param string $tag The <script> tag for the enqueued script.
* @param string $handle The script's registered handle.
*/
public function script_add_async( $tag, $handle ) {
_deprecated_function( __METHOD__, '2.1.0' );
if ( empty( $this->defer_script_handles ) ) {
return $tag;
}
if ( in_array( $handle, $this->defer_script_handles, true ) ) {
// phpcs:ignore WordPress.WP.EnqueuedResources.NonEnqueuedScript
return preg_replace( '/<script( [^>]*)? src=/i', '<script defer$1 src=', $tag );
}
return $tag;
}
/**
* A helper function that lets you enqueue scripts in an async fashion.
*
* @deprecated Since 2.1.0 - use the strategy feature instead.
*
* @param string $handle Name of the script. Should be unique.
* @param string $min_path Minimized script path.
* @param string $non_min_path Full Script path.
* @param array $deps Array of script dependencies.
* @param bool $ver The script version.
* @param bool $in_footer Should the script be included in the footer.
*/
public static function enqueue_async_script( $handle, $min_path, $non_min_path, $deps = array(), $ver = false, $in_footer = true ) {
_deprecated_function( __METHOD__, '2.1.0' );
wp_enqueue_script( $handle, self::get_file_url_for_environment( $min_path, $non_min_path ), $deps, $ver, $in_footer );
wp_script_add_data( $handle, 'strategy', 'defer' );
}
// endregion .
// ////////////////////
// region Utils
/**
* Given a minified path, and a non-minified path, will return
* a minified or non-minified file URL based on whether SCRIPT_DEBUG is set and truthy.
*
* If $package_path is provided, then the minified or non-minified file URL will be generated
* relative to the root package directory.
*
* Both `$min_base` and `$non_min_base` can be either full URLs, or are expected to be relative to the
* root Jetpack directory.
*
* @param string $min_path minified path.
* @param string $non_min_path non-minified path.
* @param string $package_path Optional. A full path to a file inside a package directory
* The URL will be relative to its directory. Default empty.
* Typically this is done by passing __FILE__ as the argument.
*
* @return string The URL to the file
* @since 1.0.3
* @since-jetpack 5.6.0
*/
public static function get_file_url_for_environment( $min_path, $non_min_path, $package_path = '' ) {
$path = ( Jetpack_Constants::is_defined( 'SCRIPT_DEBUG' ) && Jetpack_Constants::get_constant( 'SCRIPT_DEBUG' ) )
? $non_min_path
: $min_path;
/*
* If the path is actually a full URL, keep that.
* We look for a host value, since enqueues are sometimes without a scheme.
*/
$file_parts = wp_parse_url( $path );
if ( ! empty( $file_parts['host'] ) ) {
$url = $path;
} else {
$plugin_path = empty( $package_path ) ? Jetpack_Constants::get_constant( 'JETPACK__PLUGIN_FILE' ) : $package_path;
$url = plugins_url( $path, $plugin_path );
}
/**
* Filters the URL for a file passed through the get_file_url_for_environment function.
*
* @since 1.0.3
*
* @package assets
*
* @param string $url The URL to the file.
* @param string $min_path The minified path.
* @param string $non_min_path The non-minified path.
*/
return apply_filters( 'jetpack_get_file_for_environment', $url, $min_path, $non_min_path );
}
/**
* Passes an array of URLs to wp_resource_hints.
*
* @since 1.5.0
*
* @param string|array $urls URLs to hint.
* @param string $type One of the supported resource types: dns-prefetch (default), preconnect, prefetch, or prerender.
*/
public static function add_resource_hint( $urls, $type = 'dns-prefetch' ) {
add_filter(
'wp_resource_hints',
function ( $hints, $resource_type ) use ( $urls, $type ) {
if ( $resource_type === $type ) {
// Type casting to array required since the function accepts a single string.
foreach ( (array) $urls as $url ) {
$hints[] = $url;
}
}
return $hints;
},
10,
2
);
}
/**
* Serve a WordPress.com static resource via a randomized wp.com subdomain.
*
* @since 1.9.0
*
* @param string $url WordPress.com static resource URL.
*
* @return string $url
*/
public static function staticize_subdomain( $url ) {
// Extract hostname from URL.
$host = wp_parse_url( $url, PHP_URL_HOST );
// Explode hostname on '.'.
$exploded_host = explode( '.', $host );
// Retrieve the name and TLD.
if ( count( $exploded_host ) > 1 ) {
$name = $exploded_host[ count( $exploded_host ) - 2 ];
$tld = $exploded_host[ count( $exploded_host ) - 1 ];
// Rebuild domain excluding subdomains.
$domain = $name . '.' . $tld;
} else {
$domain = $host;
}
// Array of Automattic domains.
$domains_allowed = array( 'wordpress.com', 'wp.com' );
// Return $url if not an Automattic domain.
if ( ! in_array( $domain, $domains_allowed, true ) ) {
return $url;
}
if ( \is_ssl() ) {
return preg_replace( '|https?://[^/]++/|', 'https://s-ssl.wordpress.com/', $url );
}
/*
* Generate a random subdomain id by taking the modulus of the crc32 value of the URL.
* Valid values are 0, 1, and 2.
*/
$static_counter = abs( crc32( basename( $url ) ) % 3 );
return preg_replace( '|://[^/]+?/|', "://s$static_counter.wp.com/", $url );
}
/**
* Resolve '.' and '..' components in a path or URL.
*
* @since 1.12.0
* @param string $path Path or URL.
* @return string Normalized path or URL.
*/
public static function normalize_path( $path ) {
$parts = wp_parse_url( $path );
if ( ! isset( $parts['path'] ) ) {
return $path;
}
$ret = '';
$ret .= isset( $parts['scheme'] ) ? $parts['scheme'] . '://' : '';
if ( isset( $parts['user'] ) || isset( $parts['pass'] ) ) {
$ret .= $parts['user'] ?? '';
$ret .= isset( $parts['pass'] ) ? ':' . $parts['pass'] : '';
$ret .= '@';
}
$ret .= $parts['host'] ?? '';
$ret .= isset( $parts['port'] ) ? ':' . $parts['port'] : '';
$pp = explode( '/', $parts['path'] );
if ( '' === $pp[0] ) {
$ret .= '/';
array_shift( $pp );
}
$i = 0;
while ( $i < count( $pp ) ) { // phpcs:ignore Squiz.PHP.DisallowSizeFunctionsInLoops.Found
if ( '' === $pp[ $i ] || '.' === $pp[ $i ] || 0 === $i && '..' === $pp[ $i ] ) {
array_splice( $pp, $i, 1 );
} elseif ( '..' === $pp[ $i ] ) {
array_splice( $pp, --$i, 2 );
} else {
++$i;
}
}
$ret .= implode( '/', $pp );
$ret .= isset( $parts['query'] ) ? '?' . $parts['query'] : '';
$ret .= isset( $parts['fragment'] ) ? '#' . $parts['fragment'] : '';
return $ret;
}
// endregion .
// ////////////////////
// region Webpack-built script registration
/**
* Register a Webpack-built script.
*
* Our Webpack-built scripts tend to need a bunch of boilerplate:
* - A call to `Assets::get_file_url_for_environment()` for possible debugging.
* - A call to `wp_register_style()` for extracted CSS, possibly with detection of RTL.
* - Loading of dependencies and version provided by `@wordpress/dependency-extraction-webpack-plugin`.
* - Avoiding WPCom's broken minifier.
*
* This wrapper handles all of that.
*
* @since 1.12.0
* @since 2.1.0 Add a new `strategy` option to leverage WP >= 6.3 script strategy feature. The `async` option is deprecated.
* @param string $handle Name of the script. Should be unique across both scripts and styles.
* @param string $path Minimized script path.
* @param string $relative_to File that `$path` is relative to. Pass `__FILE__`.
* @param array $options Additional options:
* - `asset_path`: (string|null) `.asset.php` to load. Default is to base it on `$path`.
* - `async`: (bool) Set true to register the script as deferred, like `Assets::enqueue_async_script()`. Deprecated in favor of `strategy`.
* - `css_dependencies`: (string[]) Additional style dependencies to queue.
* - `css_path`: (string|null) `.css` to load. Default is to base it on `$path`.
* - `dependencies`: (string[]) Additional script dependencies to queue.
* - `enqueue`: (bool) Set true to enqueue the script immediately.
* - `in_footer`: (bool) Set true to register script for the footer.
* - `media`: (string) Media for the css file. Default 'all'.
* - `minify`: (bool|null) Set true to pass `minify=true` in the query string, or `null` to suppress the normal `minify=false`.
* - `nonmin_path`: (string) Non-minified script path.
* - `strategy`: (string) Specify a script strategy to use, eg. `defer` or `async`. Default is `""`.
* - `textdomain`: (string) Text domain for the script. Required if the script depends on wp-i18n.
* - `version`: (string) Override the version from the `asset_path` file.
* @phan-param array{asset_path?:?string,async?:bool,css_dependencies?:string[],css_path?:?string,dependencies?:string[],enqueue?:bool,in_footer?:bool,media?:string,minify?:?bool,nonmin_path?:string,strategy?:string,textdomain?:string,version?:string} $options
* @throws \InvalidArgumentException If arguments are invalid.
*/
public static function register_script( $handle, $path, $relative_to, array $options = array() ) {
if ( substr( $path, -3 ) !== '.js' ) {
throw new \InvalidArgumentException( '$path must end in ".js"' );
}
if ( isset( $options['async'] ) ) {
_deprecated_argument( __METHOD__, '2.1.0', 'The `async` option is deprecated in favor of `strategy`' );
}
$dir = dirname( $relative_to );
$base = substr( $path, 0, -3 );
$options += array(
'asset_path' => "$base.asset.php",
'async' => false,
'css_dependencies' => array(),
'css_path' => "$base.css",
'dependencies' => array(),
'enqueue' => false,
'in_footer' => false,
'media' => 'all',
'minify' => false,
'strategy' => '',
'textdomain' => null,
);
'@phan-var array{asset_path:?string,async:bool,css_dependencies:string[],css_path:?string,dependencies:string[],enqueue:bool,in_footer:bool,media:string,minify:?bool,nonmin_path?:string,strategy:string,textdomain:string,version?:string} $options'; // Phan gets confused by the array addition.
if ( is_string( $options['css_path'] ) && $options['css_path'] !== '' && substr( $options['css_path'], -4 ) !== '.css' ) {
throw new \InvalidArgumentException( '$options[\'css_path\'] must end in ".css"' );
}
if ( isset( $options['nonmin_path'] ) ) {
$url = self::get_file_url_for_environment( $path, $options['nonmin_path'], $relative_to );
} else {
$url = plugins_url( $path, $relative_to );
}
$url = self::normalize_path( $url );
if ( null !== $options['minify'] ) {
$url = add_query_arg( 'minify', $options['minify'] ? 'true' : 'false', $url );
}
if ( $options['asset_path'] && file_exists( "$dir/{$options['asset_path']}" ) ) {
$asset = require "$dir/{$options['asset_path']}"; // phpcs:ignore WordPressVIPMinimum.Files.IncludingFile.NotAbsolutePath
$options['dependencies'] = array_merge( $asset['dependencies'], $options['dependencies'] );
$options['css_dependencies'] = array_merge(
array_filter(
$asset['dependencies'],
function ( $d ) {
return wp_style_is( $d, 'registered' );
}
),
$options['css_dependencies']
);
$ver = $options['version'] ?? $asset['version'];
} else {
// phpcs:ignore WordPress.PHP.NoSilencedErrors.Discouraged
$ver = $options['version'] ?? @filemtime( "$dir/$path" );
}
if ( $options['async'] && '' === $options['strategy'] ) { // Handle the deprecated `async` option
$options['strategy'] = 'defer';
}
wp_register_script(
$handle,
$url,
$options['dependencies'],
$ver,
array(
'in_footer' => $options['in_footer'],
'strategy' => $options['strategy'],
)
);
if ( $options['textdomain'] ) {
// phpcs:ignore Jetpack.Functions.I18n.DomainNotLiteral
wp_set_script_translations( $handle, $options['textdomain'] );
} elseif ( in_array( 'wp-i18n', $options['dependencies'], true ) ) {
_doing_it_wrong(
__METHOD__,
/* translators: %s is the script handle. */
esc_html( sprintf( __( 'Script "%s" depends on wp-i18n but does not specify "textdomain"', 'jetpack-assets' ), $handle ) ),
''
);
}
if ( is_string( $options['css_path'] ) && $options['css_path'] !== '' && file_exists( "$dir/{$options['css_path']}" ) ) {
$csspath = $options['css_path'];
if ( is_rtl() ) {
$rtlcsspath = substr( $csspath, 0, -4 ) . '.rtl.css';
if ( file_exists( "$dir/$rtlcsspath" ) ) {
$csspath = $rtlcsspath;
}
}
$url = self::normalize_path( plugins_url( $csspath, $relative_to ) );
if ( null !== $options['minify'] ) {
$url = add_query_arg( 'minify', $options['minify'] ? 'true' : 'false', $url );
}
wp_register_style( $handle, $url, $options['css_dependencies'], $ver, $options['media'] );
wp_script_add_data( $handle, 'Jetpack::Assets::hascss', true );
} else {
wp_script_add_data( $handle, 'Jetpack::Assets::hascss', false );
}
if ( $options['enqueue'] ) {
self::enqueue_script( $handle );
}
}
/**
* Enqueue a script registered with `Assets::register_script`.
*
* @since 1.12.0
* @param string $handle Name of the script. Should be unique across both scripts and styles.
*/
public static function enqueue_script( $handle ) {
wp_enqueue_script( $handle );
if ( wp_scripts()->get_data( $handle, 'Jetpack::Assets::hascss' ) ) {
wp_enqueue_style( $handle );
}
}
/**
* 'wp_default_scripts' action handler.
*
* This registers the `wp-jp-i18n-loader` script for use by Webpack bundles built with
* `@automattic/i18n-loader-webpack-plugin`.
*
* @since 1.14.0
* @param \WP_Scripts $wp_scripts WP_Scripts instance.
*/
public static function wp_default_scripts_hook( $wp_scripts ) {
$data = array(
'baseUrl' => false,
'locale' => determine_locale(),
'domainMap' => array(),
'domainPaths' => array(),
);
$lang_dir = Jetpack_Constants::get_constant( 'WP_LANG_DIR' );
$content_dir = Jetpack_Constants::get_constant( 'WP_CONTENT_DIR' );
$abspath = Jetpack_Constants::get_constant( 'ABSPATH' );
// Note: str_starts_with() is not used here, as wp-includes/compat.php may not be loaded at this point.
if ( strpos( $lang_dir, $content_dir ) === 0 ) {
$data['baseUrl'] = content_url( substr( trailingslashit( $lang_dir ), strlen( trailingslashit( $content_dir ) ) ) );
} elseif ( strpos( $lang_dir, $abspath ) === 0 ) {
$data['baseUrl'] = site_url( substr( trailingslashit( $lang_dir ), strlen( untrailingslashit( $abspath ) ) ) );
}
foreach ( self::$domain_map as $from => list( $to, $type, , $path ) ) {
$data['domainMap'][ $from ] = ( 'core' === $type ? '' : "{$type}/" ) . $to;
if ( '' !== $path ) {
$data['domainPaths'][ $from ] = trailingslashit( $path );
}
}
/**
* Filters the i18n state data for use by Webpack bundles built with
* `@automattic/i18n-loader-webpack-plugin`.
*
* @since 1.14.0
* @package assets
* @param array $data The state data to generate. Expected fields are:
* - `baseUrl`: (string|false) The URL to the languages directory. False if no URL could be determined.
* - `locale`: (string) The locale for the page.
* - `domainMap`: (string[]) A mapping from Composer package textdomains to the corresponding
* `plugins/textdomain` or `themes/textdomain` (or core `textdomain`, but that's unlikely).
* - `domainPaths`: (string[]) A mapping from Composer package textdomains to the corresponding package
* paths.
*/
$data = apply_filters( 'jetpack_i18n_state', $data );
// Can't use self::register_script(), this action is called too early.
if ( file_exists( __DIR__ . '/../build/i18n-loader.asset.php' ) ) {
$path = '../build/i18n-loader.js';
$asset = require __DIR__ . '/../build/i18n-loader.asset.php';
} else {
$path = 'js/i18n-loader.js';
$asset = array(
'dependencies' => array( 'wp-i18n' ),
'version' => filemtime( __DIR__ . "/$path" ),
);
}
$url = self::normalize_path( plugins_url( $path, __FILE__ ) );
$url = add_query_arg( 'minify', 'true', $url );
$handle = 'wp-jp-i18n-loader';
$wp_scripts->add( $handle, $url, $asset['dependencies'], $asset['version'] );
// Ensure the script is loaded in the footer and deferred.
$wp_scripts->add_data( $handle, 'group', 1 );
if ( ! is_array( $data ) ||
! isset( $data['baseUrl'] ) || ! ( is_string( $data['baseUrl'] ) || false === $data['baseUrl'] ) ||
! isset( $data['locale'] ) || ! is_string( $data['locale'] ) ||
! isset( $data['domainMap'] ) || ! is_array( $data['domainMap'] ) ||
! isset( $data['domainPaths'] ) || ! is_array( $data['domainPaths'] )
) {
$wp_scripts->add_inline_script( $handle, 'console.warn( "I18n state deleted by jetpack_i18n_state hook" );' );
} elseif ( ! $data['baseUrl'] ) {
$wp_scripts->add_inline_script( $handle, 'console.warn( "Failed to determine languages base URL. Is WP_LANG_DIR in the WordPress root?" );' );
} else {
$data['domainMap'] = (object) $data['domainMap']; // Ensure it becomes a json object.
$data['domainPaths'] = (object) $data['domainPaths']; // Ensure it becomes a json object.
$wp_scripts->add_inline_script( $handle, 'wp.jpI18nLoader.state = ' . wp_json_encode( $data, JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';' );
}
// Deprecated state module: Depend on wp-i18n to ensure global `wp` exists and because anything needing this will need that too.
$wp_scripts->add( 'wp-jp-i18n-state', false, array( 'wp-deprecated', $handle ) );
$wp_scripts->add_inline_script( 'wp-jp-i18n-state', 'wp.deprecated( "wp-jp-i18n-state", { alternative: "wp-jp-i18n-loader" } );' );
$wp_scripts->add_inline_script( 'wp-jp-i18n-state', 'wp.jpI18nState = wp.jpI18nLoader.state;' );
}
// endregion .
// ////////////////////
// region Textdomain aliasing
/**
* Register a textdomain alias.
*
* Composer packages included in plugins will likely not use the textdomain of the plugin, while
* WordPress's i18n infrastructure will include the translations in the plugin's domain. This
* allows for mapping the package's domain to the plugin's.
*
* Since multiple plugins may use the same package, we include the package's version here so
* as to choose the most recent translations (which are most likely to match the package
* selected by jetpack-autoloader).
*
* @since 1.15.0
* @param string $from Domain to alias.
* @param string $to Domain to alias it to.
* @param string $totype What is the target of the alias: 'plugins', 'themes', or 'core'.
* @param string $ver Version of the `$from` domain.
* @param string $path Path to prepend when lazy-loading from JavaScript.
* @throws InvalidArgumentException If arguments are invalid.
*/
public static function alias_textdomain( $from, $to, $totype, $ver, $path = '' ) {
if ( ! in_array( $totype, array( 'plugins', 'themes', 'core' ), true ) ) {
throw new InvalidArgumentException( 'Type must be "plugins", "themes", or "core"' );
}
if (
did_action( 'wp_default_scripts' ) &&
// Don't complain during plugin activation.
! defined( 'WP_SANDBOX_SCRAPING' )
) {
_doing_it_wrong(
__METHOD__,
sprintf(
/* translators: 1: wp_default_scripts. 2: Name of the domain being aliased. */
esc_html__( 'Textdomain aliases should be registered before the %1$s hook. This notice was triggered by the %2$s domain.', 'jetpack-assets' ),
'<code>wp_default_scripts</code>',
'<code>' . esc_html( $from ) . '</code>'
),
''
);
}
if ( empty( self::$domain_map[ $from ] ) ) {
self::init_domain_map_hooks( $from, array() === self::$domain_map );
self::$domain_map[ $from ] = array( $to, $totype, $ver, $path );
} elseif ( Semver::compare( $ver, self::$domain_map[ $from ][2] ) > 0 ) {
self::$domain_map[ $from ] = array( $to, $totype, $ver, $path );
}
}
/**
* Register textdomain aliases from a mapping file.
*
* The mapping file is simply a PHP file that returns an array
* with the following properties:
* - 'domain': String, `$to`
* - 'type': String, `$totype`
* - 'packages': Array, mapping `$from` to `array( 'path' => $path, 'ver' => $ver )` (or to the string `$ver` for back compat).
*
* @since 1.15.0
* @param string $file Mapping file.
*/
public static function alias_textdomains_from_file( $file ) {
$data = require $file;
foreach ( $data['packages'] as $from => $fromdata ) {
if ( ! is_array( $fromdata ) ) {
$fromdata = array(
'path' => '',
'ver' => $fromdata,
);
}
self::alias_textdomain( $from, $data['domain'], $data['type'], $fromdata['ver'], $fromdata['path'] );
}
}
/**
* Register the hooks for textdomain aliasing.
*
* @param string $domain Domain to alias.
* @param bool $firstcall If this is the first call.
*/
private static function init_domain_map_hooks( $domain, $firstcall ) {
// If WordPress's plugin API is available already, use it. If not,
// drop data into `$wp_filter` for `WP_Hook::build_preinitialized_hooks()`.
if ( function_exists( 'add_filter' ) ) {
$add_filter = 'add_filter';
} else {
$add_filter = function ( $hook_name, $callback, $priority = 10, $accepted_args = 1 ) {
global $wp_filter;
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
$wp_filter[ $hook_name ][ $priority ][] = array(
'accepted_args' => $accepted_args,
'function' => $callback,
);
};
}
$add_filter( "gettext_{$domain}", array( self::class, 'filter_gettext' ), 10, 3 );
$add_filter( "ngettext_{$domain}", array( self::class, 'filter_ngettext' ), 10, 5 );
$add_filter( "gettext_with_context_{$domain}", array( self::class, 'filter_gettext_with_context' ), 10, 4 );
$add_filter( "ngettext_with_context_{$domain}", array( self::class, 'filter_ngettext_with_context' ), 10, 6 );
if ( $firstcall ) {
$add_filter( 'load_script_translation_file', array( self::class, 'filter_load_script_translation_file' ), 10, 3 );
}
}
/**
* Filter for `gettext`.
*
* @since 1.15.0
* @param string $translation Translated text.
* @param string $text Text to translate.
* @param string $domain Text domain.
* @return string Translated text.
*/
public static function filter_gettext( $translation, $text, $domain ) {
if ( $translation === $text ) {
// phpcs:ignore WordPress.WP.I18n -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
$newtext = __( $text, self::$domain_map[ $domain ][0] );
if ( $newtext !== $text ) {
return $newtext;
}
}
return $translation;
}
/**
* Filter for `ngettext`.
*
* @since 1.15.0
* @param string $translation Translated text.
* @param string $single The text to be used if the number is singular.
* @param string $plural The text to be used if the number is plural.
* @param int $number The number to compare against to use either the singular or plural form.
* @param string $domain Text domain.
* @return string Translated text.
*/
public static function filter_ngettext( $translation, $single, $plural, $number, $domain ) {
if ( $translation === $single || $translation === $plural ) {
// phpcs:ignore WordPress.WP.I18n -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
$translation = _n( $single, $plural, $number, self::$domain_map[ $domain ][0] );
}
return $translation;
}
/**
* Filter for `gettext_with_context`.
*
* @since 1.15.0
* @param string $translation Translated text.
* @param string $text Text to translate.
* @param string $context Context information for the translators.
* @param string $domain Text domain.
* @return string Translated text.
*/
public static function filter_gettext_with_context( $translation, $text, $context, $domain ) {
if ( $translation === $text ) {
// phpcs:ignore WordPress.WP.I18n -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
$translation = _x( $text, $context, self::$domain_map[ $domain ][0] );
}
return $translation;
}
/**
* Filter for `ngettext_with_context`.
*
* @since 1.15.0
* @param string $translation Translated text.
* @param string $single The text to be used if the number is singular.
* @param string $plural The text to be used if the number is plural.
* @param int $number The number to compare against to use either the singular or plural form.
* @param string $context Context information for the translators.
* @param string $domain Text domain.
* @return string Translated text.
*/
public static function filter_ngettext_with_context( $translation, $single, $plural, $number, $context, $domain ) {
if ( $translation === $single || $translation === $plural ) {
// phpcs:ignore WordPress.WP.I18n -- This is a filter hook to map the text domains from our Composer packages to the domain for a containing plugin. See https://wp.me/p2gHKz-oRh#problem-6-text-domains-in-composer-packages
$translation = _nx( $single, $plural, $number, $context, self::$domain_map[ $domain ][0] );
}
return $translation;
}
/**
* Filter for `load_script_translation_file`.
*
* @since 1.15.0
* @param string|false $file Path to the translation file to load. False if there isn't one.
* @param string $handle Name of the script to register a translation domain to.
* @param string $domain The text domain.
*/
public static function filter_load_script_translation_file( $file, $handle, $domain ) {
if ( false !== $file && isset( self::$domain_map[ $domain ] ) && ! is_readable( $file ) ) {
// Determine the part of the filename after the domain.
$suffix = basename( $file );
$l = strlen( $domain );
if ( substr( $suffix, 0, $l ) !== $domain || '-' !== $suffix[ $l ] ) {
return $file;
}
$suffix = substr( $suffix, $l );
$lang_dir = Jetpack_Constants::get_constant( 'WP_LANG_DIR' );
// Look for replacement files.
list( $newdomain, $type ) = self::$domain_map[ $domain ];
$newfile = $lang_dir . ( 'core' === $type ? '/' : "/{$type}/" ) . $newdomain . $suffix;
if ( is_readable( $newfile ) ) {
return $newfile;
}
}
return $file;
}
// endregion .
}
// Enable section folding in vim:
// vim: foldmarker=//\ region,//\ endregion foldmethod=marker
// .
@@ -0,0 +1,238 @@
<?php
/**
* Jetpack script data.
*
* @package automattic/jetpack-assets
*/
namespace Automattic\Jetpack\Assets;
use Automattic\Jetpack\Assets;
use Automattic\Jetpack\Status;
use Automattic\Jetpack\Status\Host;
/**
* Class script data
*/
class Script_Data {
const SCRIPT_HANDLE = 'jetpack-script-data';
/**
* Whether the script data has been rendered.
*
* @var bool
*/
private static $did_render_script_data = false;
/**
* Configure.
*/
public static function configure() {
/**
* Ensure that assets are registered on wp_loaded,
* which is fired before *_enqueue_scripts actions.
* It means that when the dependent scripts are registered,
* the scripts here are already registered.
*/
add_action( 'wp_loaded', array( self::class, 'register_assets' ) );
/**
* Notes:
* 1. wp_print_scripts action is fired on both admin and public pages.
* On admin pages, it's fired before admin_enqueue_scripts action,
* which can be a problem if the consumer package uses admin_enqueue_scripts
* to hook into the script data. Thus, we prefer to use admin_print_scripts on admin pages.
* 2. We want to render the script data on print, instead of init or enqueue actions,
* so that the hook callbacks have enough time and information
* to decide whether to update the script data or not.
*/
$hook = is_admin() ? 'admin_print_scripts' : 'wp_print_scripts';
add_action( $hook, array( self::class, 'render_script_data' ), 1 );
add_action( 'enqueue_block_editor_assets', array( self::class, 'render_script_data' ), 1 );
}
/**
* Register assets.
*
* @access private
*/
public static function register_assets() {
Assets::register_script(
self::SCRIPT_HANDLE,
'../build/jetpack-script-data.js',
__FILE__,
array(
'in_footer' => true,
'textdomain' => 'jetpack-assets',
)
);
}
/**
* Render the script data using an inline script.
*
* @access private
*
* @return void
*/
public static function render_script_data() {
// In case of 'enqueue_block_editor_assets' action, this can be called multiple times.
if ( self::$did_render_script_data ) {
return;
}
self::$did_render_script_data = true;
$script_data = is_admin() || self::is_authenticated_rest_request()
? self::get_admin_script_data()
: self::get_public_script_data();
if ( ! empty( $script_data ) ) {
$script_data = wp_json_encode(
$script_data,
JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP | JSON_UNESCAPED_UNICODE
);
wp_add_inline_script(
self::SCRIPT_HANDLE,
sprintf( 'window.JetpackScriptData = %s;', $script_data ),
'before'
);
Assets::enqueue_script( self::SCRIPT_HANDLE );
}
}
/**
* Whether the current request is an authenticated REST API request.
*
* @return bool
*/
protected static function is_authenticated_rest_request() {
return wp_is_serving_rest_request() && current_user_can( 'read' );
}
/**
* Get the admin script data.
*
* @return array
*/
protected static function get_admin_script_data() {
global $wp_version;
$data = array(
'site' => array(
'admin_url' => esc_url_raw( admin_url() ),
'date_format' => get_option( 'date_format' ),
'icon' => self::get_site_icon(),
'is_multisite' => is_multisite(),
'host' => ( new Host() )->get_known_host_guess(),
'is_wpcom_platform' => ( new Host() )->is_wpcom_platform(),
'plan' => array(
// The properties here should be updated by the consumer package/plugin.
// It includes properties like 'product_slug', 'features', etc.
'product_slug' => '',
),
'rest_nonce' => wp_create_nonce( 'wp_rest' ),
'rest_root' => esc_url_raw( rest_url() ),
'suffix' => ( new Status() )->get_site_suffix(),
'title' => self::get_site_title(),
'wp_version' => $wp_version,
'wpcom' => array(
// This should contain the connected site details like blog_id, is_atomic etc.
'blog_id' => 0,
),
),
'user' => array(
'current_user' => self::get_current_user_data(),
),
);
/**
* Filter the admin script data.
*
* When using this filter, ensure that the data is added only if it is used by some script.
* This filter may be called on almost every admin page load. So, one should check if the data is needed/used on that page.
* For example, the social (publicize) data is used only on Social admin page, Jetpack settings page and the post editor.
* So, the social data should be added only on those pages.
*
* @since 2.3.0
*
* @param array $data The script data.
*/
return apply_filters( 'jetpack_admin_js_script_data', $data );
}
/**
* Get the public script data.
*
* @return array
*/
protected static function get_public_script_data() {
$data = array();
/**
* Filter the public script data.
*
* See the docs for `jetpack_admin_js_script_data` filter for more information.
*
* @since 2.3.0
*
* @param array $data The script data.
*/
return apply_filters( 'jetpack_public_js_script_data', $data );
}
/**
* Get the site title.
*
* @return string
*/
protected static function get_site_title() {
$title = get_bloginfo( 'name' );
return $title ? $title : esc_url_raw( ( get_site_url() ) );
}
/**
* Get the site icon.
*
* @return string
*/
protected static function get_site_icon() {
if ( ! has_site_icon() ) {
return '';
}
/**
* Filters the site icon using Photon.
*
* @see https://developer.wordpress.com/docs/photon/
*
* @param string $url The URL of the site icon.
* @param array|string $args An array of arguments, e.g. array( 'w' => '300', 'resize' => array( 123, 456 ) ), or in string form (w=123&h=456).
*/
return apply_filters( 'jetpack_photon_url', get_site_icon_url(), array( 'w' => 64 ) );
}
/**
* Get the current user data.
*
* @return array
*/
protected static function get_current_user_data() {
$current_user = wp_get_current_user();
return array(
'display_name' => $current_user->display_name,
'id' => $current_user->ID,
'capabilities' => array(
'manage_options' => current_user_can( 'manage_options' ),
'manage_modules' => current_user_can( 'jetpack_manage_modules' ),
),
);
}
}
@@ -0,0 +1,121 @@
<?php
/**
* Simple semver version handling.
*
* We use this instead of something like `composer/semver` to avoid
* plugins needing to include yet-another dependency package. The
* amount of code we need here is pretty small.
*
* We use this instead of PHP's `version_compare()` because that doesn't
* handle prerelease versions in the way anyone other than PHP devs would
* expect, and silently breaks on various unexpected input.
*
* @package automattic/jetpack-assets
*/
namespace Automattic\Jetpack\Assets;
use InvalidArgumentException;
/**
* Simple semver version handling.
*/
class Semver {
/**
* Parse a semver version.
*
* @param string $version Version.
* @return array With components:
* - major: (int) Major version.
* - minor: (int) Minor version.
* - patch: (int) Patch version.
* - version: (string) Major.minor.patch.
* - prerelease: (string|null) Pre-release string.
* - buildinfo: (string|null) Build metadata string.
* @throws InvalidArgumentException If the version number is not in a recognized format.
*/
public static function parse( $version ) {
// This is slightly looser than the official version from semver.org, in that leading zeros are allowed.
if ( ! preg_match( '/^(?P<major>\d+)\.(?P<minor>\d+)\.(?P<patch>\d+)(?:-(?P<prerelease>(?:[0-9a-zA-Z-]+)(?:\.(?:[0-9a-zA-Z-]+))*))?(?:\+(?P<buildinfo>[0-9a-zA-Z-]+(?:\.[0-9a-zA-Z-]+)*))?$/', $version, $m ) ) {
throw new InvalidArgumentException( "Version number \"$version\" is not in a recognized format." );
}
$info = array(
'major' => (int) $m['major'],
'minor' => (int) $m['minor'],
'patch' => (int) $m['patch'],
'version' => sprintf( '%d.%d.%d', $m['major'], $m['minor'], $m['patch'] ),
'prerelease' => isset( $m['prerelease'] ) && '' !== $m['prerelease'] ? $m['prerelease'] : null,
'buildinfo' => isset( $m['buildinfo'] ) && '' !== $m['buildinfo'] ? $m['buildinfo'] : null,
);
if ( null !== $info['prerelease'] ) {
$sep = '';
$prerelease = '';
foreach ( explode( '.', $info['prerelease'] ) as $part ) {
if ( ctype_digit( $part ) ) {
$part = (int) $part;
}
$prerelease .= $sep . $part;
$sep = '.';
}
$info['prerelease'] = $prerelease;
}
return $info;
}
/**
* Compare two version numbers.
*
* @param string $a First version.
* @param string $b Second version.
* @return int Less than, equal to, or greater than 0 depending on whether `$a` is less than, equal to, or greater than `$b`.
* @throws InvalidArgumentException If the version numbers are not in a recognized format.
*/
public static function compare( $a, $b ) {
$aa = self::parse( $a );
$bb = self::parse( $b );
if ( $aa['major'] !== $bb['major'] ) {
return $aa['major'] - $bb['major'];
}
if ( $aa['minor'] !== $bb['minor'] ) {
return $aa['minor'] - $bb['minor'];
}
if ( $aa['patch'] !== $bb['patch'] ) {
return $aa['patch'] - $bb['patch'];
}
if ( null === $aa['prerelease'] ) {
return null === $bb['prerelease'] ? 0 : 1;
}
if ( null === $bb['prerelease'] ) {
return -1;
}
$aaa = explode( '.', $aa['prerelease'] );
$bbb = explode( '.', $bb['prerelease'] );
$al = count( $aaa );
$bl = count( $bbb );
for ( $i = 0; $i < $al && $i < $bl; $i++ ) {
$a = $aaa[ $i ];
$b = $bbb[ $i ];
if ( ctype_digit( $a ) ) {
if ( ctype_digit( $b ) ) {
if ( (int) $a !== (int) $b ) {
return (int) $a - (int) $b;
}
} else {
return -1;
}
} elseif ( ctype_digit( $b ) ) {
return 1;
} else {
$tmp = strcmp( $a, $b );
if ( 0 !== $tmp ) {
return $tmp;
}
}
}
return $al - $bl;
}
}
@@ -0,0 +1,41 @@
<?php
/**
* Jetpack shared stores assets.
*
* @package automattic/jetpack-assets
*/
namespace Automattic\Jetpack\Assets;
use Automattic\Jetpack\Assets;
/**
* Registers the externalized jetpack-shared-stores bundle so that consuming
* packages can declare it as a WordPress script dependency. Loading the bundle
* once ensures the contained data stores register exactly once per page.
*/
class Shared_Stores_Assets {
const SCRIPT_HANDLE = 'jetpack-shared-stores';
/**
* Configure.
*/
public static function configure() {
add_action( 'wp_loaded', array( self::class, 'register_assets' ) );
}
/**
* Register assets.
*/
public static function register_assets() {
Assets::register_script(
self::SCRIPT_HANDLE,
'../build/jetpack-shared-stores.js',
__FILE__,
array(
'in_footer' => true,
)
);
}
}
@@ -0,0 +1,76 @@
const i18n = require( '@wordpress/i18n' );
const { default: md5 } = require( 'md5-es' );
const locationMap = {
plugin: 'plugins/',
theme: 'themes/',
core: '',
};
const hasOwn = ( obj, prop ) => Object.prototype.hasOwnProperty.call( obj, prop );
module.exports = {
state: {
baseUrl: null,
locale: null,
domainMap: {},
domainPaths: {},
},
/**
* Download and register translations for a bundle.
*
* @param {string} path - Bundle path being fetched. May have a query part.
* @param {string} domain - Text domain to register into.
* @param {string} location - Location for the translation: 'plugin', 'theme', or 'core'.
* @return {Promise} Resolved when the translations are registered, or rejected with an `Error`.
*/
async downloadI18n( path, domain, location ) {
const state = this.state;
if ( ! state || typeof state.baseUrl !== 'string' ) {
throw new Error( 'wp.jpI18nLoader.state is not set' );
}
// "en_US" is the default, no translations are needed.
if ( state.locale === 'en_US' ) {
return;
}
// Check that fetch is available.
if ( typeof fetch === 'undefined' ) {
throw new Error( 'Fetch API is not available.' );
}
// Extract any query part and hash the script name like WordPress does.
const pathPrefix = hasOwn( state.domainPaths, domain ) ? state.domainPaths[ domain ] : '';
let hash, query;
const i = path.indexOf( '?' );
if ( i >= 0 ) {
hash = md5.hash( pathPrefix + path.substring( 0, i ) );
query = path.substring( i );
} else {
hash = md5.hash( pathPrefix + path );
query = '';
}
// Download.
const locationAndDomain = hasOwn( state.domainMap, domain )
? state.domainMap[ domain ]
: locationMap[ location ] + domain;
const res = await fetch(
// prettier-ignore
`${ state.baseUrl }${ locationAndDomain }-${ state.locale }-${ hash }.json${ query }`
);
if ( ! res.ok ) {
throw new Error( `HTTP request failed: ${ res.status } ${ res.statusText }` );
}
const data = await res.json();
// Extract the messages from the file and register them.
const localeData = hasOwn( data.locale_data, domain )
? data.locale_data[ domain ]
: data.locale_data.messages;
localeData[ '' ].domain = domain;
i18n.setLocaleData( localeData, domain );
},
};
@@ -0,0 +1 @@
export * from '@automattic/jetpack-script-data';
@@ -0,0 +1 @@
export * from '@automattic/jetpack-shared-stores';