This commit is contained in:
2026-07-02 15:54:39 -06:00
commit 9883323161
17470 changed files with 4470592 additions and 0 deletions
@@ -0,0 +1,52 @@
<?php
namespace Automattic\Jetpack_Boost\Modules\Image_Guide;
use Automattic\Jetpack\Image_CDN\Image_CDN_Core;
/**
* Add an ajax endpoint to proxy external CSS files.
*/
class Image_Guide_Proxy {
const NONCE_ACTION = 'jb-ig-proxy-nonce';
public static function init() {
add_action( 'wp_ajax_boost_proxy_ig', array( __CLASS__, 'handle_proxy' ) );
}
/**
* AJAX handler to handle proxying of external image resources.
*
* @return never
*/
public static function handle_proxy() {
// Verify valid nonce.
if ( empty( $_POST['nonce'] ) || ! wp_verify_nonce( sanitize_key( $_POST['nonce'] ), self::NONCE_ACTION ) ) {
wp_send_json_error( 'bad nonce', 400, JSON_UNESCAPED_SLASHES );
}
// Make sure currently logged in as admin.
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( 'not admin', 400, JSON_UNESCAPED_SLASHES );
}
// Validate URL and fetch.
$proxy_url = filter_var( wp_unslash( $_POST['proxy_url'] ?? '' ), FILTER_VALIDATE_URL );
if ( ! wp_http_validate_url( $proxy_url ) ) {
wp_send_json_error( 'Invalid URL', 400, JSON_UNESCAPED_SLASHES );
}
$photon_url = Image_CDN_Core::cdn_url( $proxy_url );
if ( ! Image_CDN_Core::is_cdn_url( $proxy_url ) ) {
wp_send_json_error( 'Failed to proxy the image.', 400, JSON_UNESCAPED_SLASHES );
}
$response = wp_safe_remote_get( $photon_url );
if ( is_wp_error( $response ) ) {
wp_send_json_error( 'error', 400, JSON_UNESCAPED_SLASHES );
}
// @phan-suppress-next-line PhanTypeMismatchArgumentProbablyReal -- It takes null, but its phpdoc only says int.
wp_send_json_success( iterator_to_array( wp_remote_retrieve_headers( $response ) ), null, JSON_UNESCAPED_SLASHES );
}
}
@@ -0,0 +1,84 @@
<?php
namespace Automattic\Jetpack_Boost\Modules\Image_Guide;
use Automattic\Jetpack_Boost\Admin\Admin;
use Automattic\Jetpack_Boost\Contracts\Feature;
use Automattic\Jetpack_Boost\Lib\Analytics;
class Image_Guide implements Feature {
public function setup() {
if ( is_user_logged_in() && current_user_can( 'manage_options' ) ) {
Image_Guide_Proxy::init();
}
// Show the UI only when the user is logged in, with sufficient permissions and isn't looking at the dashboard.
if ( is_admin() || ! is_user_logged_in() || ! current_user_can( 'manage_options' ) ) {
return;
}
// Enqueue the tracks library.
add_action( 'wp_enqueue_scripts', array( Analytics::class, 'init_tracks_scripts' ) );
add_action( 'wp_enqueue_scripts', array( $this, 'enqueue_assets' ) );
/**
* The priority determines where the admin bar menu item is placed.
*/
add_action( 'admin_bar_menu', array( $this, 'add_to_adminbar' ), 500 );
}
public static function get_slug() {
return 'image_guide';
}
public function enqueue_assets() {
wp_enqueue_script( 'jetpack-boost-guide', plugins_url( 'dist/guide.min.js', __FILE__ ), array( 'wp-i18n' ), JETPACK_BOOST_VERSION, true );
wp_enqueue_style( 'jetpack-boost-guide', plugins_url( 'dist/guide.min.css', __FILE__ ), array(), JETPACK_BOOST_VERSION, 'screen' );
wp_localize_script(
'jetpack-boost-guide',
'jetpackBoostAnalytics',
array(
'tracksData' => Analytics::get_tracking_data(),
)
);
wp_localize_script(
'jetpack-boost-guide',
'jbImageGuide',
array(
'proxyNonce' => wp_create_nonce( Image_Guide_Proxy::NONCE_ACTION ),
'ajax_url' => admin_url( 'admin-ajax.php' ),
)
);
}
/**
* @param \WP_Admin_Bar $bar
*/
public function add_to_adminbar( $bar ) {
// Disable in Admin Dashboard
if ( is_admin() ) {
return;
}
$bar->add_menu(
array(
'id' => 'jetpack-boost-guide',
'parent' => null,
'group' => null,
'title' => 'Jetpack Boost', // "Jetpack Boost" is a product name, do not translate.
'href' => admin_url( 'admin.php?page=' . Admin::MENU_SLUG ),
'meta' => array(
'target' => '_self',
'class' => 'jetpack-boost-guide',
),
)
);
}
public static function is_available() {
return true;
}
}
@@ -0,0 +1 @@
<?php return array('dependencies' => array('wp-i18n', 'wp-polyfill'), 'version' => '6c102965235b0a1f6156');
@@ -0,0 +1 @@
div.svelte-1yjr9q3{align-items:center;display:flex;justify-content:center}.spinner.svelte-q7ue1b{animation:svelte-q7ue1b-pulse 1.25s ease-in-out infinite reverse}@keyframes svelte-q7ue1b-pulse{0%{transform:scale(1) rotate(0deg)}70%{transform:scale(1.4) rotate(30deg)}to{transform:scale(.9) rotate(-190deg)}}svg.svelte-qy5udj{transform:translateY(2px)}.interaction-area.svelte-1vido64{margin-bottom:-15px;margin-left:-15px;margin-top:-15px;padding:15px}.bubble.svelte-1vido64{background-color:#069e08;border:1px solid #ffffff0d;border-radius:50%;color:#fff;cursor:default;font-family:sans-serif;font-size:.8em;font-weight:600;height:32px;position:relative;text-align:center;text-shadow:0 0 1px #00000026;transition:background-color .3s ease;width:32px}.high.svelte-1vido64 .bubble:where(.svelte-1vido64){background-color:#e52e00}.medium.svelte-1vido64 .bubble:where(.svelte-1vido64){background-color:#e98800}.bubble-inner.svelte-1vido64{align-items:center;bottom:0;display:flex;height:100%;justify-content:center;left:0;margin:auto;position:absolute;right:0;top:0;width:100%}.guide.small .interaction-area.svelte-1vido64{padding:15px}.guide.small .bubble.svelte-1vido64{font-size:10px;height:28px;width:28px}.guide.micro .interaction-area.svelte-1vido64{margin-left:-10px;margin-top:-10px;padding:10px}.guide.micro .bubble.svelte-1vido64{border:1px solid #ffffff4d;font-size:0;height:10px;width:10px}.guide.micro .bubble.svelte-1vido64 svg{display:none}svg.svelte-1b220d7{fill:currentcolor;height:1em;margin:0;vertical-align:middle;width:1em}a.svelte-1okxgd8{color:#3c434a!important;font-weight:600!important}a.documentation.svelte-1okxgd8{color:#3c434a!important;font-weight:500!important}.preview.svelte-1okxgd8{align-items:flex-start;display:flex;gap:15px;margin-bottom:15px;max-width:360px;width:100%}.preview.svelte-1okxgd8 img:where(.svelte-1okxgd8){border-radius:3px;box-shadow:0 0 2px 1px #f2f2f2}.info.svelte-1okxgd8{font-size:.9em;margin-top:15px}.jetpack-boost-guide-popup.svelte-1okxgd8{background-color:#fff;background:linear-gradient(159.87deg,#f6f6f4 7.24%,#f7f4ea 64.73%,#ddedd5 116.53%);border-radius:6px;color:#3c434a;font-family:sans-serif;margin-bottom:10px;min-width:320px;overflow:hidden;padding:25px;position:fixed;text-align:left;width:fit-content;z-index:90000}.title.svelte-1okxgd8{font-weight:600;overflow-wrap:anywhere}.explanation.svelte-1okxgd8{margin-top:5px}.description.svelte-1okxgd8{font-size:.9em}.row.svelte-1okxgd8{border-bottom:1px dotted #d9d9d9;display:flex;font-size:14px;gap:10px;justify-content:space-between;margin-bottom:5px}.row.svelte-1okxgd8:last-child{border-bottom:none}.logo.svelte-1okxgd8{bottom:-25px;opacity:.04;pointer-events:none;position:absolute;right:-50px;transform:rotate(15deg)}.guide.small .preview.svelte-1okxgd8{gap:8px}.jetpack-boost-guide:not(.relative){left:0;position:absolute;top:0}.jetpack-boost-guide.relative{position:relative}.guide.svelte-6tsow1{left:0;line-height:1.55;padding:20px;position:absolute;top:0;z-index:8000}.guide.small.svelte-6tsow1{font-size:13px}.guide.micro.svelte-6tsow1{font-size:13px;padding:10px}.guide.show.svelte-6tsow1{z-index:9000}.guide.svelte-6tsow1{font-family:Inter,-apple-system,BlinkMacSystemFont,Segoe UI,Roboto,Oxygen-Sans,Ubuntu,Cantarell,Helvetica Neue,sans-serif!important;font-size:15px!important}.previews.svelte-6tsow1{display:flex;flex-wrap:wrap;gap:15px;margin-bottom:15px;width:100%}.jetpack-boost-guide__backdrop{filter:brightness(.3);transition:opacity .2s ease-in-out,filter .2s ease-in-out}#jetpack-boost-guide-bar.ab-item.svelte-1km5e3e{align-items:center;display:flex;gap:10px}#jetpack-boost-guide-bar.ab-item.paused.svelte-1km5e3e svg{filter:grayscale(100%) contrast(1.7)}
File diff suppressed because one or more lines are too long
@@ -0,0 +1,80 @@
import { setupImageGuideUI } from '@automattic/jetpack-image-guide';
import { isSameOrigin } from '../../../assets/src/js/lib/utils/is-same-origin';
import { prepareAdminAjaxRequest } from '../../../assets/src/js/lib/utils/make-admin-ajax-request';
import { recordBoostPixelEvent } from '$lib/utils/analytics';
import analytics from '@automattic/jetpack-analytics';
/**
* Fetches the weight of a resource using a proxy if the resource is not on the same origin.
* This function is passed to MeasurableImageStore to be used when fetching the file size of images.
*
* @param url The URL of the resource to fetch.
* @return A Promise that resolves to a Response object.
*/
async function fetchWeightUsingProxy( url: string ): Promise< Response > {
if ( ! isSameOrigin( url ) ) {
const response = await prepareAdminAjaxRequest(
{
action: 'boost_proxy_ig',
proxy_url: url,
nonce: jbImageGuide.proxyNonce,
},
jbImageGuide.ajax_url
);
const contentType = response.headers.get( 'content-type' );
if ( contentType && contentType.indexOf( 'application/json' ) !== -1 ) {
const json = await response.clone().json();
if ( json && json.data[ 'content-length' ] ) {
// If the JSON data contains the content length, create a new response object with the JSON headers and the original response body.
const headers = new Headers();
for ( const key in json.data ) {
if ( Object.hasOwn( json.data, key ) ) {
headers.set( key, json.data[ key ] );
}
}
const newResponse = new Response( response.body, {
status: response.status,
statusText: response.statusText,
headers,
} );
return newResponse;
}
}
}
// If the resource is on the same origin or the response is not JSON, fetch the resource using a HEAD request with no-cors mode.
return await fetch( url, { method: 'HEAD', mode: 'no-cors' } );
}
/**
* Initialize the AdminBarToggle component when the DOM is ready.
*/
document.addEventListener( 'DOMContentLoaded', () => {
analytics.initialize(
jetpackBoostAnalytics.tracksData.userData.userid,
jetpackBoostAnalytics.tracksData.userData.username,
{
blog_id: jetpackBoostAnalytics.tracksData.blogId,
}
);
const tracksCallback = ( action: string, props: { [ key: string ]: number | string } ) => {
recordBoostPixelEvent( action, props );
};
const adminBarToggle = document.getElementById( 'wp-admin-bar-jetpack-boost-guide' );
const link = adminBarToggle?.querySelector( 'a' );
if ( adminBarToggle && link ) {
const href = link.getAttribute( 'href' );
link.remove();
setupImageGuideUI( adminBarToggle, {
href: href!,
tracksCallback,
fetchFunction: fetchWeightUsingProxy,
} );
}
} );