initial
@@ -0,0 +1,51 @@
|
||||
<?php
|
||||
/**
|
||||
* Action Hooks for Jetpack Backup module.
|
||||
*
|
||||
* @package automattic/jetpack-backup
|
||||
*/
|
||||
|
||||
// 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';
|
||||
$add_action = 'add_action';
|
||||
} else {
|
||||
$add_filter = function ( $name, $cb, $priority = 10, $accepted_args = 1 ) {
|
||||
global $wp_filter;
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$wp_filter[ $name ][ $priority ][] = array(
|
||||
'accepted_args' => $accepted_args,
|
||||
'function' => $cb,
|
||||
);
|
||||
};
|
||||
$add_action = $add_filter;
|
||||
}
|
||||
|
||||
// Clean up expired Helper Scripts from a scheduled event.
|
||||
$add_action( 'jetpack_backup_cleanup_helper_scripts', array( 'Automattic\\Jetpack\\Backup\\V0005\\Helper_Script_Manager', 'cleanup_expired_helper_scripts' ) );
|
||||
|
||||
// Register REST routes.
|
||||
$add_action( 'rest_api_init', array( 'Automattic\\Jetpack\\Backup\\V0005\\REST_Controller', 'register_rest_routes' ) );
|
||||
|
||||
// Set up package version hook.
|
||||
$add_filter( 'jetpack_package_versions', 'Automattic\\Jetpack\\Backup\\Package_Version::send_package_version_to_tracker' );
|
||||
|
||||
// Register Jetpack Backup abilities with the WordPress Abilities API at autoload
|
||||
// time so the surface is available in any consumer that loads this package
|
||||
// (both the standalone Jetpack Backup plugin and the Jetpack plugin). The
|
||||
// `jetpack_wp_abilities_enabled` filter (default false) gates registration,
|
||||
// so this is a no-op until a site opts in.
|
||||
$add_action(
|
||||
'plugins_loaded',
|
||||
static function () {
|
||||
if ( ! apply_filters( 'jetpack_wp_abilities_enabled', false ) ) {
|
||||
return;
|
||||
}
|
||||
if ( ! class_exists( \Automattic\Jetpack\Backup\V0005\Abilities\Backup_Abilities::class ) ) {
|
||||
return;
|
||||
}
|
||||
\Automattic\Jetpack\Backup\V0005\Abilities\Backup_Abilities::init();
|
||||
},
|
||||
20
|
||||
);
|
||||
@@ -0,0 +1,43 @@
|
||||
<?php
|
||||
/**
|
||||
* Main entry point for auto-generated asset registration.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
// Load script module registration.
|
||||
$modules_file = __DIR__ . '/modules.php';
|
||||
if ( file_exists( $modules_file ) ) {
|
||||
require_once $modules_file;
|
||||
}
|
||||
|
||||
// Load script registration.
|
||||
$scripts_file = __DIR__ . '/scripts.php';
|
||||
if ( file_exists( $scripts_file ) ) {
|
||||
require_once $scripts_file;
|
||||
}
|
||||
|
||||
// Load style registration.
|
||||
$styles_file = __DIR__ . '/styles.php';
|
||||
if ( file_exists( $styles_file ) ) {
|
||||
require_once $styles_file;
|
||||
}
|
||||
|
||||
// Load routes registration.
|
||||
$routes_file = __DIR__ . '/routes.php';
|
||||
if ( file_exists( $routes_file ) ) {
|
||||
require_once $routes_file;
|
||||
}
|
||||
|
||||
// Load widgets registration.
|
||||
$widgets_file = __DIR__ . '/widgets.php';
|
||||
if ( file_exists( $widgets_file ) ) {
|
||||
require_once $widgets_file;
|
||||
}
|
||||
|
||||
// Load pages registration.
|
||||
$pages_file = __DIR__ . '/pages.php';
|
||||
if ( file_exists( $pages_file ) ) {
|
||||
require_once $pages_file;
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
<?php
|
||||
/**
|
||||
* Plugin constants - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* Returns an array of constants for use in other templates.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
return array(
|
||||
'version' => 'undefined',
|
||||
'build_url' => plugin_dir_url( __FILE__ ),
|
||||
);
|
||||
@@ -0,0 +1,9 @@
|
||||
<svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" width="32" height="32" rx="4" fill="#008710"/>
|
||||
<mask id="mask0_3026_4949" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="9" y="6" width="15" height="20">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.5002 13.5002V11.0002C13.7418 11.0002 11.5002 13.2418 11.5002 16.0002C11.5002 16.8418 11.7085 17.6418 12.0835 18.3335L10.8668 19.5502C10.2168 18.5252 9.8335 17.3085 9.8335 16.0002C9.8335 12.3168 12.8168 9.3335 16.5002 9.3335V6.8335L19.8335 10.1668L16.5002 13.5002ZM20.9168 13.6668L22.1335 12.4502C22.7835 13.4752 23.1668 14.6918 23.1668 16.0002C23.1668 19.6835 20.1835 22.6668 16.5002 22.6668V25.1668L13.1668 21.8335L16.5002 18.5002V21.0002C19.2585 21.0002 21.5002 18.7585 21.5002 16.0002C21.5002 15.1585 21.2835 14.3668 20.9168 13.6668Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3026_4949)">
|
||||
<rect x="6.5" y="6" width="20" height="20" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 992 B |
@@ -0,0 +1,33 @@
|
||||
<svg
|
||||
class="backup__animation-el-1"
|
||||
width="176"
|
||||
height="212"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter1_d)">
|
||||
<rect x="40" y="40" width="96" height="132" rx="3" fill="#98C6D9"></rect>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter1_d"
|
||||
x="0"
|
||||
y="0"
|
||||
width="176"
|
||||
height="212"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
></feColorMatrix>
|
||||
<feOffset></feOffset>
|
||||
<feGaussianBlur stdDeviation="20"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"></feColorMatrix>
|
||||
<feBlend in2="BackgroundImageFix" result="effect1_dropShadow"></feBlend>
|
||||
<feBlend in="SourceGraphic" in2="effect1_dropShadow" result="shape"></feBlend>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 922 B |
@@ -0,0 +1,33 @@
|
||||
<svg
|
||||
class="backup__animation-el-2"
|
||||
width="248"
|
||||
height="200"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter2_d)">
|
||||
<rect x="40" y="40" width="168" height="120" rx="3" fill="#F2D76B"></rect>
|
||||
</g>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter2_d"
|
||||
x="0"
|
||||
y="0"
|
||||
width="248"
|
||||
height="200"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
></feColorMatrix>
|
||||
<feOffset></feOffset>
|
||||
<feGaussianBlur stdDeviation="20"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"></feColorMatrix>
|
||||
<feBlend in2="BackgroundImageFix" result="effect2_dropShadow"></feBlend>
|
||||
<feBlend in="SourceGraphic" in2="effect2_dropShadow" result="shape"></feBlend>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 924 B |
@@ -0,0 +1,42 @@
|
||||
<svg
|
||||
class="backup__animation-el-3"
|
||||
width="536"
|
||||
height="196"
|
||||
fill="none"
|
||||
xmlns="http://www.w3.org/2000/svg"
|
||||
>
|
||||
<g filter="url(#filter3_d)">
|
||||
<rect x="40" y="40" width="456" height="116" rx="8" fill="#fff"></rect>
|
||||
</g>
|
||||
<path
|
||||
d="M475.35 62.04A7.49 7.49 0 00468 56c-2.89 0-5.4 1.64-6.65 4.04A5.994 5.994 0 00456 66c0 3.31 2.69 6 6 6h13c2.76 0 5-2.24 5-5 0-2.64-2.05-4.78-4.65-4.96z"
|
||||
fill="#E9EFF5"
|
||||
></path>
|
||||
<circle cx="100" cy="98" r="36" fill="#F7A8C3"></circle>
|
||||
<path
|
||||
d="M160 84a6 6 0 016-6h174a6 6 0 110 12H166a6 6 0 01-6-6zM160 112a6 6 0 016-6h276a6 6 0 110 12H166a6 6 0 01-6-6z"
|
||||
fill="#E9EFF5"
|
||||
></path>
|
||||
<defs>
|
||||
<filter
|
||||
id="filter3_d"
|
||||
x="0"
|
||||
y="0"
|
||||
width="536"
|
||||
height="196"
|
||||
filterUnits="userSpaceOnUse"
|
||||
color-interpolation-filters="sRGB"
|
||||
>
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"></feFlood>
|
||||
<feColorMatrix
|
||||
in="SourceAlpha"
|
||||
values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 127 0"
|
||||
></feColorMatrix>
|
||||
<feOffset></feOffset>
|
||||
<feGaussianBlur stdDeviation="20"></feGaussianBlur>
|
||||
<feColorMatrix values="0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0 0.08 0"></feColorMatrix>
|
||||
<feBlend in2="BackgroundImageFix" result="effect3_dropShadow"></feBlend>
|
||||
<feBlend in="SourceGraphic" in2="effect3_dropShadow" result="shape"></feBlend>
|
||||
</filter>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.3 KiB |
|
After Width: | Height: | Size: 22 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="32" height="32" fill="none" xmlns="http://www.w3.org/2000/svg"><mask id="a" maskUnits="userSpaceOnUse" x="0" y="5" width="32" height="22"><path fill-rule="evenodd" clip-rule="evenodd" d="M16 5.333c4.853 0 8.893 3.453 9.8 8.053 3.467.24 6.2 3.094 6.2 6.614a6.67 6.67 0 01-6.667 6.666H8c-4.413 0-8-3.586-8-8 0-4.12 3.12-7.52 7.133-7.946A9.994 9.994 0 0116 5.333zM8.667 18l4.666 4.666 8.787-8.786L20.24 12l-6.907 6.906-2.786-2.786L8.667 18z" fill="#fff"></path></mask><g mask="url(#a)"><path fill="#069E08" d="M0 0h32v32H0z"></path></g></svg>
|
||||
|
After Width: | Height: | Size: 551 B |
@@ -0,0 +1 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?><!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd"><svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" width="32" height="32" viewBox="0 0 24 24"><path fill="#d63639" d="M19 20H6C2.7 20 0 17.3 0 14C0 10.9 2.3 8.4 5.3 8C6.6 5.6 9.1 4 12 4C15.6 4 18.7 6.6 19.4 10C22 10.2 24 12.3 24 15C24 17.7 21.7 20 19 20M11 15V17H13V15H11M11 13H13V7H11V13Z" /></svg>
|
||||
|
After Width: | Height: | Size: 481 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="4" fill="#008710"/>
|
||||
<mask id="mask0_3026_4944" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="6" y="9" width="20" height="14">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M22.125 14.3668C21.5583 11.4918 19.0333 9.3335 16 9.3335C13.5917 9.3335 11.5 10.7002 10.4583 12.7002C7.95 12.9668 6 15.0918 6 17.6668C6 20.4252 8.24167 22.6668 11 22.6668H21.8333C24.1333 22.6668 26 20.8002 26 18.5002C26 16.3002 24.2917 14.5168 22.125 14.3668ZM21.8333 21.0002H11C9.15833 21.0002 7.66667 19.5085 7.66667 17.6668C7.66667 15.9585 8.94167 14.5335 10.6333 14.3585L11.525 14.2668L11.9417 13.4752C12.7333 11.9502 14.2833 11.0002 16 11.0002C18.1833 11.0002 20.0667 12.5502 20.4917 14.6918L20.7417 15.9418L22.0167 16.0335C23.3167 16.1168 24.3333 17.2085 24.3333 18.5002C24.3333 19.8752 23.2083 21.0002 21.8333 21.0002ZM12.5917 16.0752L14.3333 17.8168L18.1667 13.9835L19.3417 15.1585L14.3333 20.1668L11.4167 17.2502L12.5917 16.0752Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3026_4944)">
|
||||
<rect x="6" y="6" width="20" height="20" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 6.1 KiB |
@@ -0,0 +1,17 @@
|
||||
<svg width="380" height="183" viewBox="0 0 380 183" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<g opacity="0.75" filter="url(#filter0_f_5953_11924)">
|
||||
<path d="M360 -60C360 7.93102 304.931 63 237 63C169.069 63 114 7.93102 114 -60C114 -127.931 169.069 -183 237 -183C304.931 -183 360 -127.931 360 -60Z" fill="url(#paint0_linear_5953_11924)"/>
|
||||
</g>
|
||||
<defs>
|
||||
<filter id="filter0_f_5953_11924" x="-6" y="-303" width="486" height="486" filterUnits="userSpaceOnUse" color-interpolation-filters="sRGB">
|
||||
<feFlood flood-opacity="0" result="BackgroundImageFix"/>
|
||||
<feBlend mode="normal" in="SourceGraphic" in2="BackgroundImageFix" result="shape"/>
|
||||
<feGaussianBlur stdDeviation="60" result="effect1_foregroundBlur_5953_11924"/>
|
||||
</filter>
|
||||
<linearGradient id="paint0_linear_5953_11924" x1="360" y1="-60" x2="114" y2="-60" gradientUnits="userSpaceOnUse">
|
||||
<stop stop-color="#48FF50"/>
|
||||
<stop offset="0.471846" stop-color="#108642"/>
|
||||
<stop offset="1" stop-color="#2E38FA"/>
|
||||
</linearGradient>
|
||||
</defs>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 990 B |
@@ -0,0 +1,9 @@
|
||||
<svg width="33" height="32" viewBox="0 0 33 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect x="0.5" width="32" height="32" rx="4" fill="#008710"/>
|
||||
<mask id="mask0_3026_4960" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="8" y="7" width="17" height="18">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M16.4998 7.6665C11.8998 7.6665 8.1665 11.3998 8.1665 15.9998C8.1665 20.5998 11.8998 24.3332 16.4998 24.3332C21.0998 24.3332 24.8332 20.5998 24.8332 15.9998C24.8332 11.3998 21.0998 7.6665 16.4998 7.6665ZM16.4998 22.6665C12.8248 22.6665 9.83317 19.6748 9.83317 15.9998C9.83317 12.3248 12.8248 9.33317 16.4998 9.33317C20.1748 9.33317 23.1665 12.3248 23.1665 15.9998C23.1665 19.6748 20.1748 22.6665 16.4998 22.6665ZM11.4998 17.6665C11.4998 16.746 12.246 15.9998 13.1665 15.9998C14.087 15.9998 14.8332 16.746 14.8332 17.6665C14.8332 18.587 14.087 19.3332 13.1665 19.3332C12.246 19.3332 11.4998 18.587 11.4998 17.6665ZM16.4998 10.9998C15.5794 10.9998 14.8332 11.746 14.8332 12.6665C14.8332 13.587 15.5794 14.3332 16.4998 14.3332C17.4203 14.3332 18.1665 13.587 18.1665 12.6665C18.1665 11.746 17.4203 10.9998 16.4998 10.9998ZM18.1665 17.6665C18.1665 16.746 18.9127 15.9998 19.8332 15.9998C20.7536 15.9998 21.4998 16.746 21.4998 17.6665C21.4998 18.587 20.7536 19.3332 19.8332 19.3332C18.9127 19.3332 18.1665 18.587 18.1665 17.6665Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3026_4960)">
|
||||
<rect x="6.5" y="6" width="20" height="20" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.4 KiB |
@@ -0,0 +1,9 @@
|
||||
<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg">
|
||||
<rect width="32" height="32" rx="4" fill="#008710"/>
|
||||
<mask id="mask0_3026_4955" style="mask-type:luminance" maskUnits="userSpaceOnUse" x="6" y="8" width="18" height="16">
|
||||
<path fill-rule="evenodd" clip-rule="evenodd" d="M8.75 16C8.75 11.8583 12.1083 8.5 16.25 8.5C20.3917 8.5 23.75 11.8583 23.75 16C23.75 20.1417 20.3917 23.5 16.25 23.5C14.175 23.5 12.3083 22.6583 10.95 21.3L12.1333 20.1167C13.1833 21.175 14.6417 21.8333 16.25 21.8333C19.475 21.8333 22.0833 19.225 22.0833 16C22.0833 12.775 19.475 10.1667 16.25 10.1667C13.025 10.1667 10.4167 12.775 10.4167 16H12.9167L9.55 19.3583L9.49167 19.2417L6.25 16H8.75ZM15.4167 16.8333V12.6667H16.6667V16.125L19.6 17.8667L18.9583 18.9333L15.4167 16.8333Z" fill="white"/>
|
||||
</mask>
|
||||
<g mask="url(#mask0_3026_4955)">
|
||||
<rect x="5" y="6" width="20" height="20" fill="white"/>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 920 B |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="4.75" y="7.75" width="14.5" height="10.5" rx="1.25" stroke="#069E08" stroke-width="1.5"></rect><path fill="#069E08" d="M6 5h5v3H6zM13 5h5v3h-5z"></path></svg>
|
||||
|
After Width: | Height: | Size: 242 B |
@@ -0,0 +1 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M14.5 7L8 13.5c-.167-1.167 1-5.167 1.5-6 .365-.608 1-1.5 2.5-2.5s5-2 5-2c0 1-1 2.5-2.5 4z" fill="#069E08"></path><path d="M7 16l1-2.5m0 0L14.5 7C16 5.5 17 4 17 3c0 0-3.5 1-5 2S9.865 6.892 9.5 7.5c-.5.833-1.667 4.833-1.5 6z" stroke="#069E08" stroke-width="1.5" stroke-linecap="round"></path><path d="M17.743 3.1a.75.75 0 10-1.486-.2l1.486.2zm-1.486-.2c-.089.662-.131 1.443-.17 2.235-.04.804-.075 1.63-.146 2.423-.072.795-.176 1.525-.344 2.134-.17.62-.386 1.036-.627 1.278l1.06 1.06c.509-.508.814-1.216 1.013-1.94.203-.735.317-1.568.392-2.398.074-.831.111-1.694.15-2.483.04-.802.08-1.52.158-2.11l-1.486-.198zm-1.287 8.07c-.276.275-.767.574-1.435.866-.653.287-1.415.541-2.175.754-.758.213-1.502.38-2.114.493-.63.117-1.071.167-1.246.167v1.5c.325 0 .885-.075 1.52-.192a26.28 26.28 0 002.245-.523 18.402 18.402 0 002.372-.825c.722-.316 1.419-.705 1.893-1.18l-1.06-1.06z" fill="#069E08"></path><path stroke="#069E08" stroke-width="1.5" d="M6 19.25h8"></path></svg>
|
||||
|
After Width: | Height: | Size: 1.0 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M17.44 13.905a5.345 5.345 0 01-10.69 0c0-.652.307-1.557.866-2.619.547-1.039 1.284-2.137 2.034-3.15a47.06 47.06 0 012.445-3.014 48.194 48.194 0 012.445 3.015c.75 1.012 1.488 2.11 2.034 3.15.56 1.061.866 1.966.866 2.618z" stroke="#069E08" stroke-width="1.5"></path></svg>
|
||||
|
After Width: | Height: | Size: 353 B |
|
After Width: | Height: | Size: 18 KiB |
@@ -0,0 +1 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path fill-rule="evenodd" clip-rule="evenodd" d="M19.293 13.758a7.503 7.503 0 01-14.168 1.243l3.67-2.177 3.456 1.217a.75.75 0 00.686-.098l2.887-2.073.08.04c.296.145.703.349 1.15.58.78.406 1.64.877 2.239 1.268zm.206-1.635a37.63 37.63 0 00-1.754-.964 63.44 63.44 0 00-1.538-.771l-.099-.048-.026-.012-.01-.005a.75.75 0 00-.76.068l-2.932 2.105-3.417-1.203a.75.75 0 00-.631.062l-3.675 2.18A7.5 7.5 0 1119.5 12.123zM21 12a9 9 0 11-18 0 9 9 0 0118 0z" fill="#069E08"></path></svg>
|
||||
|
After Width: | Height: | Size: 548 B |
@@ -0,0 +1 @@
|
||||
<svg width="24" height="24" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M12 4C16.42 4 20 7.58 20 12C20 16.42 16.42 20 12 20C7.58 20 4 16.42 4 12C4 7.58 7.58 4 12 4ZM13.13 13.38L13.48 6.92H10.52L10.87 13.38H13.13ZM13.04 16.74C13.28 16.51 13.41 16.19 13.41 15.78C13.41 15.36 13.29 15.04 13.05 14.81C12.81 14.58 12.46 14.46 11.99 14.46C11.52 14.46 11.17 14.58 10.92 14.81C10.67 15.04 10.55 15.36 10.55 15.78C10.55 16.19 10.68 16.51 10.93 16.74C11.19 16.97 11.54 17.08 11.99 17.08C12.44 17.08 12.79 16.97 13.04 16.74V16.74Z" fill="#D63638"/></svg>
|
||||
|
After Width: | Height: | Size: 556 B |
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('jetpack-connection', 'jetpack-script-data', 'moment', 'react', 'react-jsx-runtime', 'wp-a11y', 'wp-api-fetch', 'wp-components', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-polyfill', 'wp-primitives', 'wp-url'), 'version' => '7d9184e6ce035d060c7d');
|
||||
@@ -0,0 +1,63 @@
|
||||
<?php
|
||||
/**
|
||||
* Script module registration - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
/**
|
||||
* Register all script modules.
|
||||
*/
|
||||
function jetpack_backup_register_script_modules() {
|
||||
// Ensure this only runs once. wp_default_scripts can fire multiple times,
|
||||
// and each wp_deregister_script_module() call also dequeues the module.
|
||||
// If a module was enqueued between calls, repeated deregister/register
|
||||
// cycles would lose the enqueue state.
|
||||
static $already_registered = false;
|
||||
if ( $already_registered ) {
|
||||
return;
|
||||
}
|
||||
$already_registered = true;
|
||||
|
||||
// Load build constants
|
||||
$constants_file = __DIR__ . '/constants.php';
|
||||
if ( ! file_exists( $constants_file ) ) {
|
||||
return;
|
||||
}
|
||||
$build_constants = require $constants_file;
|
||||
$modules_dir = __DIR__ . '/modules';
|
||||
$modules_file = $modules_dir . '/registry.php';
|
||||
|
||||
if ( ! file_exists( $modules_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$modules = require $modules_file;
|
||||
$base_url = $build_constants['build_url'] . 'modules/';
|
||||
|
||||
foreach ( $modules as $module ) {
|
||||
// WASM-only modules (e.g., vips worker) only have .min.js; use it even when SCRIPT_DEBUG is `true`.
|
||||
$extension = '.min.js';
|
||||
|
||||
$asset_path = $modules_dir . '/' . $module['asset'];
|
||||
$asset = file_exists( $asset_path ) ? require $asset_path : array();
|
||||
|
||||
// Deregister first to override any previously registered version
|
||||
// (e.g., Core's default modules when running as a plugin).
|
||||
wp_deregister_script_module( $module['id'] );
|
||||
|
||||
wp_register_script_module(
|
||||
$module['id'],
|
||||
$base_url . $module['path'] . $extension,
|
||||
$asset['module_dependencies'] ?? array(),
|
||||
$asset['version'] ?? false,
|
||||
array(
|
||||
'fetchpriority' => 'low',
|
||||
'in_footer' => true,
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'wp_default_scripts', 'jetpack_backup_register_script_modules' );
|
||||
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-commands', 'wp-components', 'wp-compose', 'wp-core-data', 'wp-data', 'wp-editor', 'wp-element', 'wp-html-entities', 'wp-i18n', 'wp-keyboard-shortcuts', 'wp-keycodes', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/lazy-editor', 'import' => 'dynamic'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => '188faf6c64fdd1b4');
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Script module registry - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
return array(
|
||||
|
||||
);
|
||||
@@ -0,0 +1,16 @@
|
||||
<?php
|
||||
/**
|
||||
* Pages loader - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
foreach ( [
|
||||
__DIR__ . '/pages/jetpack-backup-dashboard/page.php',
|
||||
__DIR__ . '/pages/jetpack-backup-dashboard/page-wp-admin.php',
|
||||
] as $file ) {
|
||||
if ( file_exists( $file ) ) {
|
||||
require_once $file;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1 @@
|
||||
// Empty module loader for page dependencies
|
||||
@@ -0,0 +1,317 @@
|
||||
<?php
|
||||
/**
|
||||
* Page: jetpack-backup-dashboard (wp-admin integrated)
|
||||
* Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* This version integrates with the standard WordPress admin interface,
|
||||
* keeping the wp-admin sidebar and scripts/styles intact.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
// Global storage for jetpack-backup-dashboard routes and menu items
|
||||
global $jetpack_backup_jetpack_backup_dashboard_wp_admin_routes, $jetpack_backup_jetpack_backup_dashboard_wp_admin_menu_items;
|
||||
$jetpack_backup_jetpack_backup_dashboard_wp_admin_routes = array();
|
||||
$jetpack_backup_jetpack_backup_dashboard_wp_admin_menu_items = array();
|
||||
|
||||
/**
|
||||
* Register a route for the jetpack-backup-dashboard-wp-admin page.
|
||||
*
|
||||
* @param string $path Route path (e.g., '/types/$type/edit/$id').
|
||||
* @param string|null $content_module Script module ID for content (stage/inspector).
|
||||
* @param string|null $route_module Script module ID for route lifecycle hooks.
|
||||
*/
|
||||
function jetpack_backup_register_jetpack_backup_dashboard_wp_admin_route( $path, $content_module = null, $route_module = null ) {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_wp_admin_routes;
|
||||
|
||||
$route = array( 'path' => $path );
|
||||
if ( ! empty( $content_module ) ) {
|
||||
$route['content_module'] = $content_module;
|
||||
}
|
||||
if ( ! empty( $route_module ) ) {
|
||||
$route['route_module'] = $route_module;
|
||||
}
|
||||
|
||||
$jetpack_backup_jetpack_backup_dashboard_wp_admin_routes[] = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a menu item for the jetpack-backup-dashboard-wp-admin page.
|
||||
* Note: Menu items are registered but not displayed in single-page mode.
|
||||
*
|
||||
* @param string $id Menu item ID.
|
||||
* @param string $label Display label.
|
||||
* @param string $to Route path to navigate to.
|
||||
* @param string $parent_id Optional. Parent menu item ID.
|
||||
*/
|
||||
function jetpack_backup_register_jetpack_backup_dashboard_wp_admin_menu_item( $id, $label, $to, $parent_id = '' ) {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_wp_admin_menu_items;
|
||||
|
||||
$menu_item = array(
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'to' => $to,
|
||||
);
|
||||
|
||||
if ( ! empty( $parent_id ) ) {
|
||||
$menu_item['parent'] = $parent_id;
|
||||
}
|
||||
|
||||
$jetpack_backup_jetpack_backup_dashboard_wp_admin_menu_items[] = $menu_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered routes for the jetpack-backup-dashboard-wp-admin page.
|
||||
*
|
||||
* @return array Array of route objects.
|
||||
*/
|
||||
function jetpack_backup_get_jetpack_backup_dashboard_wp_admin_routes() {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_wp_admin_routes;
|
||||
return $jetpack_backup_jetpack_backup_dashboard_wp_admin_routes ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered menu items for the jetpack-backup-dashboard-wp-admin page.
|
||||
*
|
||||
* @return array Array of menu item objects.
|
||||
*/
|
||||
function jetpack_backup_get_jetpack_backup_dashboard_wp_admin_menu_items() {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_wp_admin_menu_items;
|
||||
return $jetpack_backup_jetpack_backup_dashboard_wp_admin_menu_items ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload REST API data for the jetpack-backup-dashboard-wp-admin page.
|
||||
* Automatically called during page rendering.
|
||||
*/
|
||||
function jetpack_backup_jetpack_backup_dashboard_wp_admin_preload_data() {
|
||||
// Define paths to preload - same for all pages
|
||||
// Please also change packages/core-data/src/entities.js when changing this.
|
||||
$preload_paths = array(
|
||||
'/?_fields=description,gmt_offset,home,image_sizes,image_size_threshold,name,site_icon,site_icon_url,site_logo,timezone_string,url,page_for_posts,page_on_front,show_on_front',
|
||||
array( '/wp/v2/settings', 'OPTIONS' ),
|
||||
);
|
||||
|
||||
// Use rest_preload_api_request to gather the preloaded data
|
||||
$preload_data = array_reduce(
|
||||
$preload_paths,
|
||||
'rest_preload_api_request',
|
||||
array()
|
||||
);
|
||||
|
||||
// Register the preloading middleware with wp-api-fetch
|
||||
wp_add_inline_script(
|
||||
'wp-api-fetch',
|
||||
sprintf(
|
||||
'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );',
|
||||
wp_json_encode( $preload_data )
|
||||
),
|
||||
'after'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue scripts and styles for the jetpack-backup-dashboard-wp-admin page.
|
||||
* Hooked to admin_enqueue_scripts.
|
||||
*
|
||||
* @param string $hook_suffix The current admin page.
|
||||
*/
|
||||
function jetpack_backup_jetpack_backup_dashboard_wp_admin_enqueue_scripts( $hook_suffix ) {
|
||||
// Check all possible ways this page can be accessed:
|
||||
// 1. Menu page via admin.php?page=jetpack-backup-dashboard-wp-admin (plugin)
|
||||
// 2. Direct file via jetpack-backup-dashboard.php (Core) - screen ID will be 'jetpack-backup-dashboard'
|
||||
$current_screen = get_current_screen();
|
||||
$is_our_page = (
|
||||
( isset( $_GET['page'] ) && 'jetpack-backup-dashboard-wp-admin' === $_GET['page'] ) || // phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
( $current_screen && 'jetpack-backup-dashboard' === $current_screen->id )
|
||||
);
|
||||
|
||||
if ( ! $is_our_page ) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/../../constants.php';
|
||||
|
||||
/**
|
||||
* Fires when the jetpack-backup-dashboard admin page is initialized so extensions can register routes and menu items.
|
||||
*/
|
||||
do_action( 'jetpack-backup-dashboard-wp-admin_init' );
|
||||
|
||||
// Preload REST API data
|
||||
jetpack_backup_jetpack_backup_dashboard_wp_admin_preload_data();
|
||||
|
||||
// Get all registered routes
|
||||
$routes = jetpack_backup_get_jetpack_backup_dashboard_wp_admin_routes();
|
||||
|
||||
// Get boot module asset file for dependencies
|
||||
$asset_file = __DIR__ . '/../../modules/boot/index.min.asset.php';
|
||||
if ( file_exists( $asset_file ) ) {
|
||||
$asset = require $asset_file;
|
||||
|
||||
// This script serves two purposes:
|
||||
// 1. It ensures all the globals that are made available to the modules are loaded.
|
||||
// 2. It initializes the boot module as an inline script.
|
||||
wp_register_script( 'jetpack-backup-dashboard-wp-admin-prerequisites', '', $asset['dependencies'], $asset['version'], true );
|
||||
|
||||
/*
|
||||
* Add inline script to initialize the app using initSinglePage (no menuItems).
|
||||
* The dynamic import is deferred until DOMContentLoaded so that all classic
|
||||
* script dependencies of @wordpress/boot (wp-private-apis, wp-components,
|
||||
* wp-theme, etc.) have finished parsing and executing before the boot module
|
||||
* evaluates. Otherwise, a modulepreloaded @wordpress/boot can win the race
|
||||
* against the classic-script-printing pass on fast CDN-fronted hosts in
|
||||
* Chrome, evaluating before wp.theme.privateApis is defined and throwing
|
||||
* "Cannot unlock an undefined object". See <https://core.trac.wordpress.org/ticket/65103>.
|
||||
*/
|
||||
$init_js_function = <<<'JS'
|
||||
( mountId, routes ) => {
|
||||
const run = async () => {
|
||||
const mod = await import( "@wordpress/boot" );
|
||||
mod.initSinglePage( { mountId, routes } );
|
||||
};
|
||||
if ( document.readyState === "loading" ) {
|
||||
document.addEventListener( "DOMContentLoaded", run );
|
||||
} else {
|
||||
run();
|
||||
}
|
||||
}
|
||||
JS;
|
||||
wp_add_inline_script(
|
||||
'jetpack-backup-dashboard-wp-admin-prerequisites',
|
||||
sprintf(
|
||||
'( %s )( %s, %s );',
|
||||
$init_js_function,
|
||||
wp_json_encode( 'jetpack-backup-dashboard-wp-admin-app', JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
|
||||
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES )
|
||||
)
|
||||
);
|
||||
|
||||
// Register prerequisites style by filtering script dependencies to find registered styles
|
||||
$style_dependencies = array_filter(
|
||||
$asset['dependencies'],
|
||||
function ( $handle ) {
|
||||
return wp_style_is( $handle, 'registered' );
|
||||
}
|
||||
);
|
||||
wp_register_style( 'jetpack-backup-dashboard-wp-admin-prerequisites', false, $style_dependencies, $asset['version'] );
|
||||
|
||||
// Build dependencies for jetpack-backup-dashboard-wp-admin module
|
||||
$boot_dependencies = array(
|
||||
array(
|
||||
'import' => 'static',
|
||||
'id' => '@wordpress/boot',
|
||||
),
|
||||
);
|
||||
|
||||
// Add all registered routes as dependencies
|
||||
foreach ( $routes as $route ) {
|
||||
if ( isset( $route['route_module'] ) ) {
|
||||
$boot_dependencies[] = array(
|
||||
'import' => 'static',
|
||||
'id' => $route['route_module'],
|
||||
);
|
||||
}
|
||||
if ( isset( $route['content_module'] ) ) {
|
||||
$boot_dependencies[] = array(
|
||||
'import' => 'dynamic',
|
||||
'id' => $route['content_module'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the boot script-module dependencies for the
|
||||
* jetpack-backup-dashboard-wp-admin page.
|
||||
*
|
||||
* Surfaces extending this page can append entries to the boot
|
||||
* dependency list. Each entry is an array with 'import' (string
|
||||
* 'static' or 'dynamic') and 'id' (script-module handle) keys.
|
||||
*
|
||||
* @param array $boot_dependencies Boot dependencies for the page.
|
||||
*/
|
||||
$boot_dependencies = apply_filters(
|
||||
'jetpack-backup-dashboard-wp-admin_boot_dependencies',
|
||||
$boot_dependencies
|
||||
);
|
||||
|
||||
// Dummy script module to ensure dependencies are loaded
|
||||
wp_register_script_module(
|
||||
'jetpack-backup-dashboard-wp-admin',
|
||||
$build_constants['build_url'] . 'pages/jetpack-backup-dashboard/loader.js',
|
||||
$boot_dependencies
|
||||
);
|
||||
|
||||
// Enqueue the boot scripts and styles
|
||||
wp_enqueue_script( 'jetpack-backup-dashboard-wp-admin-prerequisites' );
|
||||
wp_enqueue_script_module( 'jetpack-backup-dashboard-wp-admin' );
|
||||
wp_enqueue_style( 'jetpack-backup-dashboard-wp-admin-prerequisites' );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the jetpack-backup-dashboard-wp-admin page.
|
||||
* Call this function from add_menu_page or add_submenu_page.
|
||||
* This renders within the normal WordPress admin interface.
|
||||
*/
|
||||
function jetpack_backup_jetpack_backup_dashboard_wp_admin_render_page() {
|
||||
?>
|
||||
<style>
|
||||
/* Critical styles to prevent layout shifts - inlined for immediate application */
|
||||
|
||||
#wpwrap {
|
||||
overflow-y: auto;
|
||||
}
|
||||
body {
|
||||
background: #fff;
|
||||
}
|
||||
|
||||
/* Reset wp-admin padding */
|
||||
#wpcontent {
|
||||
padding-inline-start: 0;
|
||||
}
|
||||
#wpbody-content {
|
||||
padding-bottom: 0;
|
||||
}
|
||||
|
||||
/* Hide legacy admin elements */
|
||||
#wpbody-content > div:not(.boot-layout-container):not(#screen-meta) {
|
||||
display: none;
|
||||
}
|
||||
#wpfooter {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Accessibility regions */
|
||||
.a11y-speak-region {
|
||||
inset-inline-start: -1px;
|
||||
top: -1px;
|
||||
}
|
||||
|
||||
/* Admin menu indicators */
|
||||
ul#adminmenu a.wp-has-current-submenu::after,
|
||||
ul#adminmenu > li.current > a.current::after {
|
||||
border-inline-end-color: #fff;
|
||||
}
|
||||
|
||||
/* Media frame fix */
|
||||
.media-frame select.attachment-filters:last-of-type {
|
||||
width: auto;
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
/* Responsive overflow fix for #wpwrap */
|
||||
@media (min-width: 782px) {
|
||||
#wpwrap {
|
||||
overflow-y: initial;
|
||||
}
|
||||
}
|
||||
</style>
|
||||
<div id="jetpack-backup-dashboard-wp-admin-app" class="boot-layout-container"></div>
|
||||
<?php
|
||||
}
|
||||
|
||||
// Hook the enqueue function to admin_enqueue_scripts
|
||||
add_action( 'admin_enqueue_scripts', 'jetpack_backup_jetpack_backup_dashboard_wp_admin_enqueue_scripts' );
|
||||
|
||||
@@ -0,0 +1,318 @@
|
||||
<?php
|
||||
/**
|
||||
* Page: jetpack-backup-dashboard
|
||||
* Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
// Global storage for jetpack-backup-dashboard routes and menu items
|
||||
global $jetpack_backup_jetpack_backup_dashboard_routes, $jetpack_backup_jetpack_backup_dashboard_menu_items;
|
||||
$jetpack_backup_jetpack_backup_dashboard_routes = array();
|
||||
$jetpack_backup_jetpack_backup_dashboard_menu_items = array();
|
||||
|
||||
/**
|
||||
* Register a route for the jetpack-backup-dashboard page.
|
||||
*
|
||||
* @param string $path Route path (e.g., '/types/$type/edit/$id').
|
||||
* @param string|null $content_module Script module ID for content (stage/inspector).
|
||||
* @param string|null $route_module Script module ID for route lifecycle hooks.
|
||||
*/
|
||||
function jetpack_backup_register_jetpack_backup_dashboard_route( $path, $content_module = null, $route_module = null ) {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_routes;
|
||||
|
||||
$route = array( 'path' => $path );
|
||||
if ( ! empty( $content_module ) ) {
|
||||
$route['content_module'] = $content_module;
|
||||
}
|
||||
if ( ! empty( $route_module ) ) {
|
||||
$route['route_module'] = $route_module;
|
||||
}
|
||||
|
||||
$jetpack_backup_jetpack_backup_dashboard_routes[] = $route;
|
||||
}
|
||||
|
||||
/**
|
||||
* Register a menu item for the jetpack-backup-dashboard page.
|
||||
*
|
||||
* @param string $id Menu item ID.
|
||||
* @param string $label Display label.
|
||||
* @param string $to Route path to navigate to.
|
||||
* @param string $parent_id Optional. Parent menu item ID.
|
||||
* @param string $parent_type Optional. Parent type: 'drilldown' or 'dropdown'.
|
||||
*/
|
||||
function jetpack_backup_register_jetpack_backup_dashboard_menu_item( $id, $label, $to, $parent_id = '', $parent_type = '' ) {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_menu_items;
|
||||
|
||||
$menu_item = array(
|
||||
'id' => $id,
|
||||
'label' => $label,
|
||||
'to' => $to,
|
||||
);
|
||||
|
||||
if ( ! empty( $parent_id ) ) {
|
||||
$menu_item['parent'] = $parent_id;
|
||||
}
|
||||
|
||||
if ( ! empty( $parent_type ) && in_array( $parent_type, array( 'drilldown', 'dropdown' ), true ) ) {
|
||||
$menu_item['parent_type'] = $parent_type;
|
||||
}
|
||||
|
||||
$jetpack_backup_jetpack_backup_dashboard_menu_items[] = $menu_item;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered routes for the jetpack-backup-dashboard page.
|
||||
*
|
||||
* @return array Array of route objects.
|
||||
*/
|
||||
function jetpack_backup_get_jetpack_backup_dashboard_routes() {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_routes;
|
||||
return $jetpack_backup_jetpack_backup_dashboard_routes ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Get all registered menu items for the jetpack-backup-dashboard page.
|
||||
*
|
||||
* @return array Array of menu item objects.
|
||||
*/
|
||||
function jetpack_backup_get_jetpack_backup_dashboard_menu_items() {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_menu_items;
|
||||
return $jetpack_backup_jetpack_backup_dashboard_menu_items ?? array();
|
||||
}
|
||||
|
||||
/**
|
||||
* Preload REST API data for the jetpack-backup-dashboard page.
|
||||
* Automatically called during page rendering.
|
||||
*/
|
||||
function jetpack_backup_jetpack_backup_dashboard_preload_data() {
|
||||
// Define paths to preload - same for all pages
|
||||
// Please also change packages/core-data/src/entities.js when changing this.
|
||||
$preload_paths = array(
|
||||
'/?_fields=description,gmt_offset,home,image_sizes,image_size_threshold,name,site_icon,site_icon_url,site_logo,timezone_string,url,page_for_posts,page_on_front,show_on_front',
|
||||
array( '/wp/v2/settings', 'OPTIONS' ),
|
||||
);
|
||||
|
||||
// Use rest_preload_api_request to gather the preloaded data
|
||||
$preload_data = array_reduce(
|
||||
$preload_paths,
|
||||
'rest_preload_api_request',
|
||||
array()
|
||||
);
|
||||
|
||||
// Register the preloading middleware with wp-api-fetch
|
||||
wp_add_inline_script(
|
||||
'wp-api-fetch',
|
||||
sprintf(
|
||||
'wp.apiFetch.use( wp.apiFetch.createPreloadingMiddleware( %s ) );',
|
||||
wp_json_encode( $preload_data )
|
||||
),
|
||||
'after'
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the jetpack-backup-dashboard page.
|
||||
* Call this function from add_menu_page or add_submenu_page.
|
||||
*/
|
||||
function jetpack_backup_jetpack_backup_dashboard_render_page() {
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/../../constants.php';
|
||||
|
||||
// Set current screen
|
||||
set_current_screen();
|
||||
|
||||
// Remove unwanted deprecated handler
|
||||
remove_action( 'admin_head', 'wp_admin_bar_header' );
|
||||
|
||||
// Remove unwanted scripts and styles that were enqueued during `admin_init`
|
||||
foreach ( wp_scripts()->queue as $script ) {
|
||||
wp_dequeue_script( $script );
|
||||
}
|
||||
foreach ( wp_styles()->queue as $style ) {
|
||||
wp_dequeue_style( $style );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fires when the jetpack-backup-dashboard page is initialized so extensions can register routes and menu items.
|
||||
*/
|
||||
do_action( 'jetpack-backup-dashboard_init' );
|
||||
|
||||
// Enqueue command palette assets for boot-based pages
|
||||
if ( function_exists( 'wp_enqueue_command_palette_assets' ) ) {
|
||||
wp_enqueue_command_palette_assets();
|
||||
}
|
||||
|
||||
// Preload REST API data
|
||||
jetpack_backup_jetpack_backup_dashboard_preload_data();
|
||||
|
||||
// Get all registered routes and menu items
|
||||
$menu_items = jetpack_backup_get_jetpack_backup_dashboard_menu_items();
|
||||
$routes = jetpack_backup_get_jetpack_backup_dashboard_routes();
|
||||
|
||||
// Get boot module asset file for dependencies
|
||||
$asset_file = __DIR__ . '/../../modules/boot/index.min.asset.php';
|
||||
if ( file_exists( $asset_file ) ) {
|
||||
$asset = require $asset_file;
|
||||
|
||||
// This script serves two purposes:
|
||||
// 1. It ensures all the globals that are made available to the modules are loaded.
|
||||
// 2. It initializes the boot module as an inline script.
|
||||
wp_register_script( 'jetpack-backup-dashboard-prerequisites', '', $asset['dependencies'], $asset['version'], true );
|
||||
|
||||
// Add inline script to initialize the app
|
||||
$init_modules = [];
|
||||
wp_add_inline_script(
|
||||
'jetpack-backup-dashboard-prerequisites',
|
||||
sprintf(
|
||||
'import("@wordpress/boot").then(mod => mod.init({mountId: "%s", menuItems: %s, routes: %s, initModules: %s, dashboardLink: "%s"}));',
|
||||
'jetpack-backup-dashboard-app',
|
||||
wp_json_encode( $menu_items, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
|
||||
wp_json_encode( $routes, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
|
||||
wp_json_encode( $init_modules, JSON_HEX_TAG | JSON_UNESCAPED_SLASHES ),
|
||||
esc_url( admin_url( '/' ) )
|
||||
)
|
||||
);
|
||||
|
||||
// Register prerequisites style by filtering script dependencies to find registered styles
|
||||
$style_dependencies = array_filter(
|
||||
$asset['dependencies'],
|
||||
function ( $handle ) {
|
||||
return wp_style_is( $handle, 'registered' );
|
||||
}
|
||||
);
|
||||
wp_register_style( 'jetpack-backup-dashboard-prerequisites', false, $style_dependencies, $asset['version'] );
|
||||
|
||||
// Build dependencies for jetpack-backup-dashboard module
|
||||
$boot_dependencies = array(
|
||||
array(
|
||||
'import' => 'static',
|
||||
'id' => '@wordpress/boot',
|
||||
),
|
||||
);
|
||||
|
||||
// Add init modules as static dependencies
|
||||
// No init modules configured
|
||||
|
||||
// Add all registered routes as dependencies
|
||||
foreach ( $routes as $route ) {
|
||||
if ( isset( $route['route_module'] ) ) {
|
||||
$boot_dependencies[] = array(
|
||||
'import' => 'static',
|
||||
'id' => $route['route_module'],
|
||||
);
|
||||
}
|
||||
if ( isset( $route['content_module'] ) ) {
|
||||
$boot_dependencies[] = array(
|
||||
'import' => 'dynamic',
|
||||
'id' => $route['content_module'],
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Filters the boot script-module dependencies for the
|
||||
* jetpack-backup-dashboard page.
|
||||
*
|
||||
* Surfaces extending this page can append entries to the boot
|
||||
* dependency list. Each entry is an array with 'import' (string
|
||||
* 'static' or 'dynamic') and 'id' (script-module handle) keys.
|
||||
*
|
||||
* @param array $boot_dependencies Boot dependencies for the page.
|
||||
*/
|
||||
$boot_dependencies = apply_filters(
|
||||
'jetpack-backup-dashboard_boot_dependencies',
|
||||
$boot_dependencies
|
||||
);
|
||||
|
||||
// Dummy script module to ensure dependencies are loaded
|
||||
wp_register_script_module(
|
||||
'jetpack-backup-dashboard',
|
||||
$build_constants['build_url'] . 'pages/jetpack-backup-dashboard/loader.js',
|
||||
$boot_dependencies
|
||||
);
|
||||
|
||||
// Enqueue the boot scripts and styles
|
||||
wp_enqueue_script( 'jetpack-backup-dashboard-prerequisites' );
|
||||
wp_enqueue_script_module( 'jetpack-backup-dashboard' );
|
||||
wp_enqueue_style( 'jetpack-backup-dashboard-prerequisites' );
|
||||
}
|
||||
|
||||
// Output the HTML
|
||||
?>
|
||||
<!DOCTYPE html>
|
||||
<html <?php language_attributes(); ?>>
|
||||
<head>
|
||||
<meta charset="<?php bloginfo( 'charset' ); ?>">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<title><?php echo esc_html( get_admin_page_title() ); ?></title>
|
||||
<style>
|
||||
html {
|
||||
background: #f1f1f1;
|
||||
color: #444;
|
||||
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
|
||||
font-size: 13px;
|
||||
line-height: 1.4em;
|
||||
}
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
#wpadminbar { display: none; }
|
||||
</style>
|
||||
<?php
|
||||
global $hook_suffix;
|
||||
// phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
|
||||
$hook_suffix = 'jetpack-backup-dashboard';
|
||||
|
||||
// BEGIN see wp-admin/admin-header.php
|
||||
print_admin_styles();
|
||||
print_head_scripts();
|
||||
|
||||
/** This action is documented in wp-admin/admin-header.php */
|
||||
do_action( "admin_head-{$hook_suffix}" ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
|
||||
/** This action is documented in wp-admin/admin-header.php */
|
||||
do_action( 'admin_head' );
|
||||
// END see wp-admin/admin-header.php
|
||||
?>
|
||||
</head>
|
||||
<body class="jetpack-backup-dashboard">
|
||||
<div id="jetpack-backup-dashboard-app" style="height: 100vh; box-sizing: border-box;"></div>
|
||||
<?php
|
||||
// BEGIN see wp-admin/admin-footer.php
|
||||
|
||||
/** This action is documented in wp-admin/admin-footer.php */
|
||||
do_action( 'admin_footer', '' );
|
||||
|
||||
// Print import map first so it's available for inline scripts
|
||||
wp_script_modules()->print_import_map();
|
||||
print_footer_scripts();
|
||||
wp_script_modules()->print_enqueued_script_modules();
|
||||
wp_script_modules()->print_script_module_preloads();
|
||||
wp_script_modules()->print_script_module_data();
|
||||
|
||||
/** This action is documented in wp-admin/admin-footer.php */
|
||||
do_action( "admin_footer-{$hook_suffix}" ); // phpcs:ignore WordPress.NamingConventions.ValidHookName.UseUnderscores
|
||||
// END see wp-admin/admin-footer.php
|
||||
?>
|
||||
</body>
|
||||
</html>
|
||||
<?php
|
||||
exit;
|
||||
}
|
||||
|
||||
/**
|
||||
* Intercept admin_init to render the page early.
|
||||
* This bypasses the default WordPress admin template.
|
||||
*/
|
||||
function jetpack_backup_jetpack_backup_dashboard_intercept_render() {
|
||||
// phpcs:ignore WordPress.Security.NonceVerification.Recommended
|
||||
if ( isset( $_GET['page'] ) && 'jetpack-backup-dashboard' === $_GET['page'] ) {
|
||||
jetpack_backup_jetpack_backup_dashboard_render_page();
|
||||
exit;
|
||||
}
|
||||
}
|
||||
|
||||
// Hook the interceptor to admin_init
|
||||
add_action( 'admin_init', 'jetpack_backup_jetpack_backup_dashboard_intercept_render' );
|
||||
@@ -0,0 +1,113 @@
|
||||
<?php
|
||||
/**
|
||||
* Routes registration - Auto-generated by build process.
|
||||
* Registers all routes on their respective page init hooks.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
// Load routes registry
|
||||
$routes_file = __DIR__ . '/routes/registry.php';
|
||||
if ( ! file_exists( $routes_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$routes = require $routes_file;
|
||||
|
||||
// Group routes by page and store in globals for page-specific functions
|
||||
$routes_by_page = array();
|
||||
foreach ( $routes as $route ) {
|
||||
$page_slug = $route['page'];
|
||||
if ( ! isset( $routes_by_page[ $page_slug ] ) ) {
|
||||
$routes_by_page[ $page_slug ] = array();
|
||||
}
|
||||
$routes_by_page[ $page_slug ][] = $route;
|
||||
}
|
||||
|
||||
// Store routes data in globals for each page
|
||||
foreach ( $routes_by_page as $page_slug => $page_routes ) {
|
||||
$page_slug_underscore = str_replace( '-', '_', $page_slug );
|
||||
$global_name = 'jetpack_backup_' . $page_slug_underscore . '_routes_data';
|
||||
$GLOBALS[ $global_name ] = $page_routes;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generic helper function to register routes for a page.
|
||||
*
|
||||
* @param array $page_routes Array of route data for the page.
|
||||
* @param string $register_function_name Name of the function to call for registering each route.
|
||||
*/
|
||||
function jetpack_backup_register_page_routes( $page_routes, $register_function_name ) {
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/constants.php';
|
||||
|
||||
foreach ( $page_routes as $route ) {
|
||||
$content_handle = null;
|
||||
$route_handle = null;
|
||||
|
||||
// Register content module if exists
|
||||
if ( $route['has_content'] ) {
|
||||
$content_asset_path = __DIR__ . "/routes/{$route['name']}/content.min.asset.php";
|
||||
if ( file_exists( $content_asset_path ) ) {
|
||||
$content_asset = require $content_asset_path;
|
||||
$content_handle = 'jetpack-backup/routes/' . $route['name'] . '/content';
|
||||
$extension = '.min.js';
|
||||
// Deregister first to override any previously registered version
|
||||
// (e.g., Core's default modules when running as a plugin).
|
||||
wp_deregister_script_module( $content_handle );
|
||||
wp_register_script_module(
|
||||
$content_handle,
|
||||
$build_constants['build_url'] . 'routes/' . $route['name'] . '/content' . $extension,
|
||||
$content_asset['module_dependencies'] ?? array(),
|
||||
$content_asset['version'] ?? false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Register route module if exists
|
||||
if ( $route['has_route'] ) {
|
||||
$route_asset_path = __DIR__ . "/routes/{$route['name']}/route.min.asset.php";
|
||||
if ( file_exists( $route_asset_path ) ) {
|
||||
$route_asset = require $route_asset_path;
|
||||
$route_handle = 'jetpack-backup/routes/' . $route['name'] . '/route';
|
||||
$extension = '.min.js';
|
||||
// Deregister first to override any previously registered version
|
||||
// (e.g., Core's default modules when running as a plugin).
|
||||
wp_deregister_script_module( $route_handle );
|
||||
wp_register_script_module(
|
||||
$route_handle,
|
||||
$build_constants['build_url'] . 'routes/' . $route['name'] . '/route' . $extension,
|
||||
$route_asset['module_dependencies'] ?? array(),
|
||||
$route_asset['version'] ?? false
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Register route with page
|
||||
if ( function_exists( $register_function_name ) ) {
|
||||
call_user_func( $register_function_name, $route['path'], $content_handle, $route_handle );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Page-specific route registration functions
|
||||
// Page-specific route registration functions for jetpack-backup-dashboard
|
||||
/**
|
||||
* Register routes for jetpack-backup-dashboard page (full-page mode).
|
||||
*/
|
||||
function jetpack_backup_register_jetpack_backup_dashboard_page_routes() {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_routes_data;
|
||||
jetpack_backup_register_page_routes( $jetpack_backup_jetpack_backup_dashboard_routes_data, 'jetpack_backup_register_jetpack_backup_dashboard_route' );
|
||||
}
|
||||
add_action( 'jetpack-backup-dashboard_init', 'jetpack_backup_register_jetpack_backup_dashboard_page_routes' );
|
||||
|
||||
/**
|
||||
* Register routes for jetpack-backup-dashboard page (wp-admin mode).
|
||||
*/
|
||||
function jetpack_backup_register_jetpack_backup_dashboard_wp_admin_page_routes() {
|
||||
global $jetpack_backup_jetpack_backup_dashboard_routes_data;
|
||||
jetpack_backup_register_page_routes( $jetpack_backup_jetpack_backup_dashboard_routes_data, 'jetpack_backup_register_jetpack_backup_dashboard_wp_admin_route' );
|
||||
}
|
||||
add_action( 'jetpack-backup-dashboard-wp-admin_init', 'jetpack_backup_register_jetpack_backup_dashboard_wp_admin_page_routes' );
|
||||
|
||||
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-deprecated', 'wp-element', 'wp-i18n', 'wp-keycodes', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url', 'wp-warning'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => '2265a8c314eb50f1e452');
|
||||
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'bdca920315a6e5637299');
|
||||
@@ -0,0 +1 @@
|
||||
var o={};export{o as route};
|
||||
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => 'd5b51fbdaedc42e22931');
|
||||
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'bdca920315a6e5637299');
|
||||
@@ -0,0 +1 @@
|
||||
var o={};export{o as route};
|
||||
@@ -0,0 +1,31 @@
|
||||
<?php
|
||||
/**
|
||||
* Route registry - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
return array(
|
||||
array(
|
||||
'name' => 'dashboard',
|
||||
'path' => '/',
|
||||
'page' => 'jetpack-backup-dashboard',
|
||||
'has_route' => true,
|
||||
'has_content' => true,
|
||||
),
|
||||
array(
|
||||
'name' => 'download',
|
||||
'path' => '/download/$rewindId',
|
||||
'page' => 'jetpack-backup-dashboard',
|
||||
'has_route' => true,
|
||||
'has_content' => true,
|
||||
),
|
||||
array(
|
||||
'name' => 'restore',
|
||||
'path' => '/restore/$rewindId',
|
||||
'page' => 'jetpack-backup-dashboard',
|
||||
'has_route' => true,
|
||||
'has_content' => true,
|
||||
)
|
||||
);
|
||||
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array('react', 'react-dom', 'react-jsx-runtime', 'wp-components', 'wp-compose', 'wp-data', 'wp-date', 'wp-element', 'wp-i18n', 'wp-notices', 'wp-primitives', 'wp-private-apis', 'wp-theme', 'wp-url'), 'module_dependencies' => array(array('id' => '@wordpress/a11y', 'import' => 'static'), array('id' => '@wordpress/route', 'import' => 'static')), 'version' => '868a35ed1bba54131d12');
|
||||
@@ -0,0 +1 @@
|
||||
<?php return array('dependencies' => array(), 'version' => 'bdca920315a6e5637299');
|
||||
@@ -0,0 +1 @@
|
||||
var o={};export{o as route};
|
||||
@@ -0,0 +1,85 @@
|
||||
<?php
|
||||
/**
|
||||
* Script registration - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers a script according to `wp_register_script`. Honors this request by
|
||||
* reassigning internal dependency properties of any script handle already
|
||||
* registered by that name. It does not deregister the original script, to
|
||||
* avoid losing inline scripts which may have been attached.
|
||||
*
|
||||
* @param WP_Scripts $scripts WP_Scripts instance.
|
||||
* @param string $handle Name of the script. Should be unique.
|
||||
* @param string $src Full URL of the script, or path of the script relative to the WordPress root directory.
|
||||
* @param array $deps Optional. An array of registered script handles this script depends on. Default empty array.
|
||||
* @param string|bool|null $ver Optional. String specifying script version number, if it has one, which is added to the URL
|
||||
* as a query string for cache busting purposes. If version is set to false, a version
|
||||
* number is automatically added equal to current installed WordPress version.
|
||||
* If set to null, no version is added.
|
||||
* @param bool $in_footer Optional. Whether to enqueue the script before </body> instead of in the <head>.
|
||||
* Default 'false'.
|
||||
*/
|
||||
function jetpack_backup_override_script( $scripts, $handle, $src, $deps = array(), $ver = false, $in_footer = false ) {
|
||||
$script = $scripts->query( $handle, 'registered' );
|
||||
if ( $script ) {
|
||||
/*
|
||||
* In many ways, this is a reimplementation of `wp_register_script` but
|
||||
* bypassing consideration of whether a script by the given handle had
|
||||
* already been registered.
|
||||
*/
|
||||
|
||||
// See: `_WP_Dependency::__construct` .
|
||||
$script->src = $src;
|
||||
$script->deps = $deps;
|
||||
$script->ver = $ver;
|
||||
$script->args = $in_footer ? 1 : null;
|
||||
} else {
|
||||
$scripts->add( $handle, $src, $deps, $ver, ( $in_footer ? 1 : null ) );
|
||||
}
|
||||
|
||||
if ( in_array( 'wp-i18n', $deps, true ) ) {
|
||||
$scripts->set_translations( $handle );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all package scripts.
|
||||
*/
|
||||
function jetpack_backup_register_package_scripts( $scripts ) {
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/constants.php';
|
||||
$default_version = ! SCRIPT_DEBUG ? $build_constants['version'] : time();
|
||||
$extension = '.min.js';
|
||||
|
||||
$scripts_dir = __DIR__ . '/scripts';
|
||||
$scripts_file = $scripts_dir . '/registry.php';
|
||||
|
||||
if ( ! file_exists( $scripts_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$scripts_data = require $scripts_file;
|
||||
$plugin_dir = dirname( __FILE__ );
|
||||
|
||||
foreach ( $scripts_data as $script_data ) {
|
||||
$asset_file = $scripts_dir . '/' . $script_data['asset'];
|
||||
$asset = file_exists( $asset_file ) ? require $asset_file : array();
|
||||
$dependencies = $asset['dependencies'] ?? array();
|
||||
$version = $asset['version'] ?? $default_version;
|
||||
|
||||
jetpack_backup_override_script(
|
||||
$scripts,
|
||||
$script_data['handle'],
|
||||
$build_constants['build_url'] . 'scripts/' . $script_data['path'] . $extension,
|
||||
$dependencies,
|
||||
$version,
|
||||
true
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'wp_default_scripts', 'jetpack_backup_register_package_scripts' );
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Script registry - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
return array(
|
||||
|
||||
);
|
||||
@@ -0,0 +1,73 @@
|
||||
<?php
|
||||
/**
|
||||
* Style registration - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* Note: These styles can be overridden by calling jetpack_backup_override_style()
|
||||
* in lib/client-assets.php for complex cases (multiple files, conditional deps, etc.)
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
/**
|
||||
* Registers a style according to `wp_register_style`. Honors this request by
|
||||
* deregistering any style by the same handler before registration.
|
||||
*
|
||||
* @param WP_Styles $styles WP_Styles instance.
|
||||
* @param string $handle Name of the stylesheet. Should be unique.
|
||||
* @param string $src Full URL of the stylesheet, or path of the stylesheet relative to the WordPress root directory.
|
||||
* @param array $deps Optional. An array of registered stylesheet handles this stylesheet depends on. Default empty array.
|
||||
* @param string|bool|null $ver Optional. String specifying stylesheet version number, if it has one, which is added to the URL
|
||||
* as a query string for cache busting purposes. If version is set to false, a version
|
||||
* number is automatically added equal to current installed WordPress version.
|
||||
* If set to null, no version is added.
|
||||
* @param string $media Optional. The media for which this stylesheet has been defined.
|
||||
* Default 'all'. Accepts media types like 'all', 'print' and 'screen', or media queries like
|
||||
* '(orientation: portrait)' and '(max-width: 640px)'.
|
||||
*/
|
||||
function jetpack_backup_override_style( $styles, $handle, $src, $deps = array(), $ver = false, $media = 'all' ) {
|
||||
$style = $styles->query( $handle, 'registered' );
|
||||
if ( $style ) {
|
||||
$styles->remove( $handle );
|
||||
}
|
||||
$styles->add( $handle, $src, $deps, $ver, $media );
|
||||
}
|
||||
|
||||
/**
|
||||
* Register all package styles (simple cases only).
|
||||
* Complex cases should be manually registered in lib/client-assets.php.
|
||||
*
|
||||
* @param WP_Styles $styles WP_Styles instance.
|
||||
*/
|
||||
function jetpack_backup_register_package_styles( $styles ) {
|
||||
// Load build constants
|
||||
$build_constants = require __DIR__ . '/constants.php';
|
||||
$default_version = ! SCRIPT_DEBUG ? $build_constants['version'] : time();
|
||||
$suffix = '.min';
|
||||
|
||||
$styles_dir = __DIR__ . '/styles';
|
||||
$styles_file = $styles_dir . '/registry.php';
|
||||
|
||||
if ( ! file_exists( $styles_file ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
$styles_data = require $styles_file;
|
||||
$plugin_dir = dirname( __FILE__ );
|
||||
|
||||
foreach ( $styles_data as $style_data ) {
|
||||
jetpack_backup_override_style(
|
||||
$styles,
|
||||
$style_data['handle'],
|
||||
$build_constants['build_url'] . 'styles/' . $style_data['path'] . $suffix . '.css',
|
||||
$style_data['dependencies'],
|
||||
$default_version
|
||||
);
|
||||
|
||||
// Enable RTL support (WordPress automatically loads -rtl.css variant)
|
||||
$styles->add_data( $style_data['handle'], 'rtl', 'replace' );
|
||||
$styles->add_data( $style_data['handle'], 'suffix', $suffix );
|
||||
}
|
||||
}
|
||||
|
||||
add_action( 'wp_default_styles', 'jetpack_backup_register_package_styles' );
|
||||
@@ -0,0 +1,11 @@
|
||||
<?php
|
||||
/**
|
||||
* Style registry - Auto-generated by build process.
|
||||
* Do not edit this file manually.
|
||||
*
|
||||
* @package jetpack_backup
|
||||
*/
|
||||
|
||||
return array(
|
||||
|
||||
);
|
||||
@@ -0,0 +1,899 @@
|
||||
<?php
|
||||
/**
|
||||
* Jetpack Backup Abilities Registration.
|
||||
*
|
||||
* Registers Jetpack Backup abilities with the WordPress Abilities API so AI
|
||||
* agents can read backup status and trigger on-demand backups through the
|
||||
* standard `wp-abilities/v1` REST surface.
|
||||
*
|
||||
* @package automattic/jetpack-backup
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Backup\V0005\Abilities;
|
||||
|
||||
use Automattic\Jetpack\Backup\V0005\Jetpack_Backup;
|
||||
use Automattic\Jetpack\My_Jetpack\Products\Backup as My_Jetpack_Backup;
|
||||
use Automattic\Jetpack\WP_Abilities\Registrar;
|
||||
use WP_Error;
|
||||
use WP_REST_Response;
|
||||
|
||||
/**
|
||||
* Registers Jetpack Backup abilities with the WordPress Abilities API.
|
||||
*
|
||||
* Exposes a small, agent-friendly surface for site backups:
|
||||
*
|
||||
* - `jetpack-backup/get-backup-overview` — single-call site backup health snapshot.
|
||||
* - `jetpack-backup/list-backups` — recent backups with optional id/pagination filters.
|
||||
* - `jetpack-backup/list-restores` — recent restores with optional id/pagination filters.
|
||||
* - `jetpack-backup/request-backup` — enqueue an on-demand backup.
|
||||
*/
|
||||
class Backup_Abilities extends Registrar {
|
||||
|
||||
const PER_PAGE_DEFAULT = 20;
|
||||
const PER_PAGE_MAX = 100;
|
||||
|
||||
/**
|
||||
* Return the ability category slug.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public static function get_category_slug(): string {
|
||||
return 'site';
|
||||
}
|
||||
|
||||
/**
|
||||
* Required by the abstract parent, but unused: the `site` category is
|
||||
* already registered upstream (WordPress core / wpcom), so we don't
|
||||
* re-declare it. Kept so the contract holds if a consumer ever asks for
|
||||
* the definition we *would* use.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_category_definition(): array {
|
||||
return array(
|
||||
'label' => __( 'Site', 'jetpack-backup-pkg' ),
|
||||
'description' => __( 'Site-wide management abilities (registered upstream).', 'jetpack-backup-pkg' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Override the Registrar lifecycle so the backup abilities only register
|
||||
* on sites that actually have a Jetpack Backup product provisioned.
|
||||
* Mirrors the gating done in the Jetpack dashboard / My Jetpack — there's
|
||||
* no point exposing tool surfaces an agent can never use, and on free
|
||||
* sites the upstream wpcom endpoints either silently accept writes (e.g.
|
||||
* `request-backup` reported `enqueued: true`) or return null payloads
|
||||
* that confuse callers.
|
||||
*
|
||||
* The `site` category is registered upstream by WordPress core / wpcom,
|
||||
* so this class never tries to register a category — `register_category`
|
||||
* is a no-op even though the parent hooks it.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_category() {
|
||||
// No-op: `site` is registered upstream; re-registering would either
|
||||
// no-op or trigger "already registered" notices.
|
||||
}
|
||||
|
||||
/**
|
||||
* Register every ability returned by `get_abilities()`, gated on the
|
||||
* Backup product being loaded. See `register_category()` for why we
|
||||
* never register a category from here.
|
||||
*
|
||||
* @return void
|
||||
*/
|
||||
public static function register_abilities() {
|
||||
if ( ! self::backup_is_loaded() ) {
|
||||
return;
|
||||
}
|
||||
parent::register_abilities();
|
||||
}
|
||||
|
||||
/**
|
||||
* Is the Jetpack Backup product actually loaded on this site?
|
||||
*
|
||||
* Defaults to `My_Jetpack\Products\Backup::is_active()` — the same
|
||||
* boolean the Jetpack dashboard uses to decide whether the Backup
|
||||
* product is usable. That returns true when the plugin is active and
|
||||
* the site has a Backup plan (covering `STATUS_ACTIVE`,
|
||||
* `STATUS_EXPIRING_SOON`, and the `STATUS_NEEDS_ATTENTION__*` states),
|
||||
* and false for `STATUS_EXPIRED`, `STATUS_NEEDS_PLAN`,
|
||||
* `STATUS_MODULE_DISABLED`, and the connection-error states. The plan
|
||||
* lookup is cached for 15s in `MY_JETPACK_SITE_FEATURES_TRANSIENT_KEY`,
|
||||
* so the cost on a real wpcom call is paid at most once per 15 seconds
|
||||
* across the whole My Jetpack surface.
|
||||
*
|
||||
* The `jetpack_backup_abilities_should_load` filter lets consumers and
|
||||
* tests override the answer without round-tripping through the My
|
||||
* Jetpack product class.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
private static function backup_is_loaded(): bool {
|
||||
$default = class_exists( My_Jetpack_Backup::class ) && My_Jetpack_Backup::is_active();
|
||||
|
||||
/**
|
||||
* Filters whether the Jetpack Backup abilities should register on
|
||||
* this site. Defaults to `My_Jetpack\Products\Backup::is_active()`.
|
||||
*
|
||||
* @since 0.1.0
|
||||
*
|
||||
* @param bool $should_load Whether to register the backup abilities.
|
||||
*/
|
||||
return (bool) apply_filters( 'jetpack_backup_abilities_should_load', $default );
|
||||
}
|
||||
|
||||
/**
|
||||
* Return the abilities this Registrar exposes, keyed by slug.
|
||||
*
|
||||
* @return array<string, array<string, mixed>>
|
||||
*/
|
||||
public static function get_abilities(): array {
|
||||
// `id` is the rewind_id (a timestamp-fractional string like
|
||||
// "1752860369.781") — the single cross-system identifier exposed by
|
||||
// the wpcom rewind, restore, and activity-log APIs. Use it whenever
|
||||
// referring to a specific backup across abilities.
|
||||
//
|
||||
// For in-progress backup attempts the rewind_id isn't assigned yet,
|
||||
// in which case `id` is null. Such backups can't be looked up by id
|
||||
// anywhere — wait for completion and re-query.
|
||||
$backup_item_schema = array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'started' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'last_updated' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'status' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'period' => array( 'type' => array( 'string', 'integer', 'null' ) ),
|
||||
'is_rewindable' => array( 'type' => array( 'boolean', 'null' ) ),
|
||||
'has_warnings' => array( 'type' => array( 'boolean', 'null' ) ),
|
||||
),
|
||||
);
|
||||
|
||||
// Same convention applies to restores: `id` is the rewind_id of the
|
||||
// backup being restored to.
|
||||
$restore_item_schema = array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'id' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'started' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'last_updated' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'status' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'progress' => array( 'type' => array( 'integer', 'null' ) ),
|
||||
),
|
||||
);
|
||||
|
||||
return array(
|
||||
'jetpack-backup/get-backup-overview' => array(
|
||||
'label' => __( 'Get backup overview', 'jetpack-backup-pkg' ),
|
||||
'description' => __(
|
||||
'Return a single-call snapshot of the site backup state: { last_backup, recent_backup_count, schedule, storage }. Use this to answer "is my site protected?" before deciding whether to call list-backups, list-restores, or request-backup. Read-only and idempotent. Fields whose backing service is unreachable come back as null rather than failing the call. Requires the manage_options capability.',
|
||||
'jetpack-backup-pkg'
|
||||
),
|
||||
'input_schema' => array(
|
||||
'type' => 'object',
|
||||
'default' => array(),
|
||||
'properties' => array(),
|
||||
'additionalProperties' => false,
|
||||
),
|
||||
'output_schema' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'recent_backup_count' => array( 'type' => array( 'integer', 'null' ) ),
|
||||
'last_backup' => array(
|
||||
'type' => array( 'object', 'null' ),
|
||||
'properties' => array(
|
||||
'id' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'last_updated' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'status' => array( 'type' => array( 'string', 'null' ) ),
|
||||
'is_rewindable' => array( 'type' => array( 'boolean', 'null' ) ),
|
||||
'has_warnings' => array( 'type' => array( 'boolean', 'null' ) ),
|
||||
),
|
||||
),
|
||||
'schedule' => array(
|
||||
'type' => array( 'object', 'null' ),
|
||||
'properties' => array(
|
||||
'hour' => array( 'type' => array( 'integer', 'null' ) ),
|
||||
'minute' => array( 'type' => array( 'integer', 'null' ) ),
|
||||
),
|
||||
),
|
||||
'storage' => array(
|
||||
'type' => array( 'object', 'null' ),
|
||||
'properties' => array(
|
||||
'used_bytes' => array( 'type' => array( 'integer', 'null' ) ),
|
||||
'limit_bytes' => array( 'type' => array( 'integer', 'null' ) ),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
'execute_callback' => array( __CLASS__, 'execute_get_backup_overview' ),
|
||||
'permission_callback' => array( __CLASS__, 'can_view_backups' ),
|
||||
'meta' => array(
|
||||
'annotations' => array(
|
||||
'readonly' => true,
|
||||
'destructive' => false,
|
||||
'idempotent' => true,
|
||||
),
|
||||
'mcp' => array(
|
||||
'public' => true,
|
||||
'type' => 'tool',
|
||||
),
|
||||
'show_in_rest' => true,
|
||||
),
|
||||
),
|
||||
|
||||
'jetpack-backup/list-backups' => array(
|
||||
'label' => __( 'List backups', 'jetpack-backup-pkg' ),
|
||||
'description' => __(
|
||||
'Return zero or more backups as an array. Each item summarises one backup: { id, rewind_id, started, last_updated, status, period, is_rewindable, has_warnings }. Combine filters to narrow the result without making multiple calls. `id` returns a 0- or 1-element array for a single rewind_id. `date_from` and `date_to` window the results (ISO 8601 datetimes; server-side filter). `date` + `match` ("on_or_before" default, "on_or_after", "closest") pick a single backup near a target datetime — useful for "find a restore point near this incident"; the response stays a 0- or 1-element array. `status` filters by mapped status (e.g. "finished", "error"). `page` + `per_page` paginate the result; iterate `page=1,2,...` until you get an empty array. A page may come back with fewer than `per_page` items even when more pages exist — `status` is applied client-side, and non-backup events are filtered out — so only an empty page reliably signals end of history. Read-only and idempotent. Backed by the wpcom activity-log feed.',
|
||||
'jetpack-backup-pkg'
|
||||
),
|
||||
'input_schema' => array(
|
||||
'type' => 'object',
|
||||
'default' => array(),
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Return only the backup with this rewind_id. Unknown ids yield an empty array.', 'jetpack-backup-pkg' ),
|
||||
'minLength' => 1,
|
||||
),
|
||||
'date_from' => array(
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'description' => __( 'Lower bound (inclusive) on backup `started` time. ISO 8601 datetime, e.g. "2026-04-01T00:00:00Z".', 'jetpack-backup-pkg' ),
|
||||
'minLength' => 1,
|
||||
),
|
||||
'date_to' => array(
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'description' => __( 'Upper bound (inclusive) on backup `started` time. ISO 8601 datetime, e.g. "2026-04-30T23:59:59Z".', 'jetpack-backup-pkg' ),
|
||||
'minLength' => 1,
|
||||
),
|
||||
'date' => array(
|
||||
'type' => 'string',
|
||||
'format' => 'date-time',
|
||||
'description' => __( 'Target datetime to find a single matching backup. When set, the response is a 0- or 1-element array. Pair with `match` to choose direction.', 'jetpack-backup-pkg' ),
|
||||
'minLength' => 1,
|
||||
),
|
||||
'match' => array(
|
||||
'type' => 'string',
|
||||
'enum' => array( 'on_or_before', 'on_or_after', 'closest' ),
|
||||
'default' => 'on_or_before',
|
||||
'description' => __( 'How to interpret `date`. "on_or_before" (default; the latest backup at or before the target — typical for restores), "on_or_after" (earliest backup at or after), or "closest" (smallest absolute time difference). Ignored when `date` is not set.', 'jetpack-backup-pkg' ),
|
||||
),
|
||||
'status' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Filter by mapped status string (e.g. "finished", "error"). Applied client-side after the server query.', 'jetpack-backup-pkg' ),
|
||||
'minLength' => 1,
|
||||
),
|
||||
'page' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( '1-based page number. Ignored when `id` or `date` is set (those are single-result lookups).', 'jetpack-backup-pkg' ),
|
||||
'default' => 1,
|
||||
'minimum' => 1,
|
||||
),
|
||||
'per_page' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Cap on items returned per page (default 20, max 100). Also bounds the server-side query window the date filters are evaluated against.', 'jetpack-backup-pkg' ),
|
||||
'default' => self::PER_PAGE_DEFAULT,
|
||||
'minimum' => 1,
|
||||
'maximum' => self::PER_PAGE_MAX,
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
),
|
||||
'output_schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => $backup_item_schema,
|
||||
),
|
||||
'execute_callback' => array( __CLASS__, 'execute_list_backups' ),
|
||||
'permission_callback' => array( __CLASS__, 'can_view_backups' ),
|
||||
'meta' => array(
|
||||
'annotations' => array(
|
||||
'readonly' => true,
|
||||
'destructive' => false,
|
||||
'idempotent' => true,
|
||||
),
|
||||
'mcp' => array(
|
||||
'public' => true,
|
||||
'type' => 'tool',
|
||||
),
|
||||
'show_in_rest' => true,
|
||||
),
|
||||
),
|
||||
|
||||
'jetpack-backup/list-restores' => array(
|
||||
'label' => __( 'List restores', 'jetpack-backup-pkg' ),
|
||||
'description' => __(
|
||||
'Return zero or more recent restore operations as an array. Each item: { id, rewind_id, started, last_updated, status, progress }. Pass id to fetch a single restore (returns 0- or 1-element array). Otherwise paginate with page and per_page (default 20, max 100). Read-only and idempotent.',
|
||||
'jetpack-backup-pkg'
|
||||
),
|
||||
'input_schema' => array(
|
||||
'type' => 'object',
|
||||
'default' => array(),
|
||||
'properties' => array(
|
||||
'id' => array(
|
||||
'type' => 'string',
|
||||
'description' => __( 'Return only the restore with this id. Unknown ids yield an empty array.', 'jetpack-backup-pkg' ),
|
||||
'minLength' => 1,
|
||||
),
|
||||
'page' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Page number, 1-based.', 'jetpack-backup-pkg' ),
|
||||
'default' => 1,
|
||||
'minimum' => 1,
|
||||
),
|
||||
'per_page' => array(
|
||||
'type' => 'integer',
|
||||
'description' => __( 'Items per page (default 20, max 100).', 'jetpack-backup-pkg' ),
|
||||
'default' => self::PER_PAGE_DEFAULT,
|
||||
'minimum' => 1,
|
||||
'maximum' => self::PER_PAGE_MAX,
|
||||
),
|
||||
),
|
||||
'additionalProperties' => false,
|
||||
),
|
||||
'output_schema' => array(
|
||||
'type' => 'array',
|
||||
'items' => $restore_item_schema,
|
||||
),
|
||||
'execute_callback' => array( __CLASS__, 'execute_list_restores' ),
|
||||
'permission_callback' => array( __CLASS__, 'can_view_backups' ),
|
||||
'meta' => array(
|
||||
'annotations' => array(
|
||||
'readonly' => true,
|
||||
'destructive' => false,
|
||||
'idempotent' => true,
|
||||
),
|
||||
'mcp' => array(
|
||||
'public' => true,
|
||||
'type' => 'tool',
|
||||
),
|
||||
'show_in_rest' => true,
|
||||
),
|
||||
),
|
||||
|
||||
'jetpack-backup/request-backup' => array(
|
||||
'label' => __( 'Request a backup', 'jetpack-backup-pkg' ),
|
||||
'description' => __(
|
||||
'Enqueue an on-demand backup of this site. Returns { enqueued: bool, message: string }. Each successful call queues a new backup job; this is a state-changing write, not idempotent. Use get-backup-overview or list-backups afterwards to track progress. Requires the manage_options capability. Returns jetpack_backup_data_unavailable when the upstream service rejects the request.',
|
||||
'jetpack-backup-pkg'
|
||||
),
|
||||
'input_schema' => array(
|
||||
'type' => 'object',
|
||||
'default' => array(),
|
||||
'properties' => array(),
|
||||
'additionalProperties' => false,
|
||||
),
|
||||
'output_schema' => array(
|
||||
'type' => 'object',
|
||||
'properties' => array(
|
||||
'enqueued' => array( 'type' => 'boolean' ),
|
||||
'message' => array( 'type' => 'string' ),
|
||||
),
|
||||
),
|
||||
'execute_callback' => array( __CLASS__, 'execute_request_backup' ),
|
||||
'permission_callback' => array( __CLASS__, 'can_manage_backups' ),
|
||||
'meta' => array(
|
||||
'annotations' => array(
|
||||
'readonly' => false,
|
||||
'destructive' => false,
|
||||
'idempotent' => false,
|
||||
),
|
||||
'mcp' => array(
|
||||
'public' => true,
|
||||
'type' => 'tool',
|
||||
),
|
||||
'show_in_rest' => true,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check for read abilities. Gates on `manage_options` to
|
||||
* match the existing REST controller (see
|
||||
* Jetpack_Backup::backups_permissions_callback). Kept separate from
|
||||
* `can_manage_backups()` so the read and write surfaces can diverge
|
||||
* later without touching every spec.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function can_view_backups(): bool {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Permission check for write abilities. See `can_view_backups()`.
|
||||
*
|
||||
* @return bool
|
||||
*/
|
||||
public static function can_manage_backups(): bool {
|
||||
return current_user_can( 'manage_options' );
|
||||
}
|
||||
|
||||
/**
|
||||
* Composite read: each subfield is null on upstream failure rather than
|
||||
* failing the whole call, so a partial wpcom outage degrades to "missing
|
||||
* pieces" instead of "no data." Registration is gated on a Backup product
|
||||
* being loaded (see register_abilities), so this callback assumes the
|
||||
* site has one and only reports on the data it can fetch.
|
||||
*
|
||||
* @param mixed $input Unused; ability accepts no input. Typed `mixed` because
|
||||
* the Abilities API may pass the raw caller-supplied value
|
||||
* (string/null/array) before our `additionalProperties:false`
|
||||
* schema runs — a strict array type would fatal on garbage input.
|
||||
* @return array
|
||||
*/
|
||||
public static function execute_get_backup_overview( $input = null ): array {
|
||||
unset( $input );
|
||||
|
||||
$backups = self::unwrap_response( Jetpack_Backup::get_recent_backups() );
|
||||
$schedule_data = self::unwrap_response( Jetpack_Backup::get_site_backup_schedule_time() );
|
||||
$size_data = self::unwrap_response( Jetpack_Backup::get_site_backup_size() );
|
||||
|
||||
return array(
|
||||
'recent_backup_count' => is_array( $backups ) ? count( $backups ) : null,
|
||||
'last_backup' => self::summarize_last_backup( is_array( $backups ) ? ( $backups[0] ?? null ) : null ),
|
||||
'schedule' => self::summarize_schedule( $schedule_data ),
|
||||
'storage' => self::summarize_storage( $size_data ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Consolidated read: queries the wpcom activity-log rewindable feed
|
||||
* (server-side date filtering, up to 1000 items/page) and reshapes
|
||||
* activity events back into the backup-item schema. All input filters
|
||||
* land here; the picker is invoked when a `date` + `match` is set.
|
||||
*
|
||||
* @param mixed $input See input_schema on `jetpack-backup/list-backups`.
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function execute_list_backups( $input = null ) {
|
||||
$input = is_array( $input ) ? $input : array();
|
||||
|
||||
// Validate `date` / `date_from` / `date_to` ahead of the round-trip so
|
||||
// agents get a specific error rather than a 200 with mysterious empty
|
||||
// results. Schema's `format: date-time` is advisory in WP REST.
|
||||
foreach ( array( 'date', 'date_from', 'date_to' ) as $key ) {
|
||||
if ( isset( $input[ $key ] ) && '' !== $input[ $key ] && null === self::parse_timestamp( $input[ $key ] ) ) {
|
||||
return new WP_Error(
|
||||
'jetpack_backup_invalid_date',
|
||||
/* translators: %s is an input parameter name. */
|
||||
sprintf( __( 'The `%s` parameter must be a valid ISO 8601 datetime (e.g. "2026-05-13T14:30:00Z").', 'jetpack-backup-pkg' ), $key )
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
$per_page = min(
|
||||
self::PER_PAGE_MAX,
|
||||
max( 1, isset( $input['per_page'] ) ? (int) $input['per_page'] : self::PER_PAGE_DEFAULT )
|
||||
);
|
||||
$page = max( 1, isset( $input['page'] ) ? (int) $input['page'] : 1 );
|
||||
|
||||
// `page` is suppressed on the single-result lookups (id, date+match)
|
||||
// — those resolve from page 1 and walking later pages would skip
|
||||
// candidates without an obvious benefit.
|
||||
$is_single_lookup = ( isset( $input['id'] ) && '' !== $input['id'] )
|
||||
|| ( isset( $input['date'] ) && '' !== $input['date'] );
|
||||
|
||||
$query = array(
|
||||
'number' => $per_page,
|
||||
'page' => $is_single_lookup ? 1 : $page,
|
||||
'sort_order' => 'desc',
|
||||
);
|
||||
if ( isset( $input['date_from'] ) && '' !== $input['date_from'] ) {
|
||||
$query['after'] = (string) $input['date_from'];
|
||||
}
|
||||
if ( isset( $input['date_to'] ) && '' !== $input['date_to'] ) {
|
||||
$query['before'] = (string) $input['date_to'];
|
||||
}
|
||||
|
||||
$envelope = self::unwrap_response( Jetpack_Backup::list_backup_events( $query ) );
|
||||
$events = self::extract_rewindable_items( $envelope );
|
||||
if ( ! is_array( $events ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$items = array_values( array_filter( array_map( array( __CLASS__, 'summarize_backup_event' ), $events ) ) );
|
||||
|
||||
// Single-id filter — same convention as the old endpoint: 0/1-element array.
|
||||
if ( isset( $input['id'] ) && is_string( $input['id'] ) && '' !== $input['id'] ) {
|
||||
foreach ( $items as $item ) {
|
||||
if ( isset( $item['id'] ) && (string) $item['id'] === $input['id'] ) {
|
||||
return array( $item );
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
// Client-side status filter (server-side filters by event name, not status).
|
||||
if ( isset( $input['status'] ) && is_string( $input['status'] ) && '' !== $input['status'] ) {
|
||||
$want = $input['status'];
|
||||
$items = array_values(
|
||||
array_filter(
|
||||
$items,
|
||||
static function ( $i ) use ( $want ) {
|
||||
return ( $i['status'] ?? null ) === $want;
|
||||
}
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
// Single-match shortcut.
|
||||
if ( isset( $input['date'] ) && '' !== $input['date'] ) {
|
||||
$target = self::parse_timestamp( $input['date'] );
|
||||
$match = isset( $input['match'] ) && is_string( $input['match'] ) ? $input['match'] : 'on_or_before';
|
||||
if ( ! in_array( $match, array( 'on_or_before', 'on_or_after', 'closest' ), true ) ) {
|
||||
$match = 'on_or_before';
|
||||
}
|
||||
$pick = self::pick_backup_near_timestamp( $items, (int) $target, $match );
|
||||
return null === $pick ? array() : array( $pick );
|
||||
}
|
||||
|
||||
return array_slice( $items, 0, $per_page );
|
||||
}
|
||||
|
||||
/**
|
||||
* Execute callback for `jetpack-backup/list-restores`.
|
||||
*
|
||||
* @param mixed $input See input_schema on the ability.
|
||||
* @return array
|
||||
*/
|
||||
public static function execute_list_restores( $input = null ): array {
|
||||
$restores = self::unwrap_response( Jetpack_Backup::get_recent_restores() );
|
||||
if ( ! is_array( $restores ) ) {
|
||||
return array();
|
||||
}
|
||||
|
||||
$summarized = array_map( array( __CLASS__, 'summarize_restore' ), $restores );
|
||||
return self::apply_id_or_pagination( $summarized, is_array( $input ) ? $input : array() );
|
||||
}
|
||||
|
||||
/**
|
||||
* Pure picker for the `date` + `match` shortcut. Operates on already-
|
||||
* summarized backup items (so it works regardless of which upstream
|
||||
* helper produced them) and uses `started` as the comparison timestamp.
|
||||
*
|
||||
* @param array $items Summarized backup items.
|
||||
* @param int $target_ts Unix timestamp the caller is searching around.
|
||||
* @param string $match 'on_or_before' | 'on_or_after' | 'closest'.
|
||||
* @return array|null The winning item or null when nothing matches.
|
||||
*/
|
||||
private static function pick_backup_near_timestamp( array $items, int $target_ts, string $match ): ?array {
|
||||
$best = null;
|
||||
$best_score = null;
|
||||
|
||||
foreach ( $items as $item ) {
|
||||
$ts = self::parse_timestamp( $item['started'] ?? null );
|
||||
if ( null === $ts ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$diff = $ts - $target_ts;
|
||||
$score = 0;
|
||||
switch ( $match ) {
|
||||
case 'on_or_after':
|
||||
if ( $diff < 0 ) {
|
||||
continue 2;
|
||||
}
|
||||
$score = $diff;
|
||||
break;
|
||||
case 'closest':
|
||||
$score = abs( $diff );
|
||||
break;
|
||||
case 'on_or_before':
|
||||
default:
|
||||
if ( $diff > 0 ) {
|
||||
continue 2;
|
||||
}
|
||||
$score = -$diff;
|
||||
break;
|
||||
}
|
||||
|
||||
if ( null === $best_score || $score < $best_score ) {
|
||||
$best = $item;
|
||||
$best_score = $score;
|
||||
}
|
||||
}
|
||||
|
||||
return $best;
|
||||
}
|
||||
|
||||
/**
|
||||
* Pull the activity-event array out of the W3C ActivityStreams envelope
|
||||
* that `/activity/rewindable` returns. The endpoint puts the items in
|
||||
* `current.orderedItems`; older proxy shapes used `orderedItems` at the
|
||||
* top level, so check both before giving up.
|
||||
*
|
||||
* @param mixed $envelope Raw decoded response body.
|
||||
* @return array|null
|
||||
*/
|
||||
private static function extract_rewindable_items( $envelope ): ?array {
|
||||
if ( ! is_array( $envelope ) && ! is_object( $envelope ) ) {
|
||||
return null;
|
||||
}
|
||||
$envelope = (array) $envelope;
|
||||
if ( isset( $envelope['current'] ) ) {
|
||||
$current = (array) $envelope['current'];
|
||||
if ( isset( $current['orderedItems'] ) && is_array( $current['orderedItems'] ) ) {
|
||||
return $current['orderedItems'];
|
||||
}
|
||||
}
|
||||
if ( isset( $envelope['orderedItems'] ) && is_array( $envelope['orderedItems'] ) ) {
|
||||
return $envelope['orderedItems'];
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Translate one /activity/rewindable event into the same backup-item
|
||||
* shape `summarize_backup()` produces, so the ability's output schema
|
||||
* stays stable across the upstream switch. Returns null for events that
|
||||
* don't look like backups (no `rewind_id`).
|
||||
*
|
||||
* @param mixed $raw One element from `current.orderedItems`.
|
||||
* @return array|null
|
||||
*/
|
||||
private static function summarize_backup_event( $raw ): ?array {
|
||||
if ( ! is_array( $raw ) && ! is_object( $raw ) ) {
|
||||
return null;
|
||||
}
|
||||
$raw = (array) $raw;
|
||||
|
||||
$rewind_id = $raw['rewind_id'] ?? null;
|
||||
if ( null === $rewind_id || '' === $rewind_id ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$published = isset( $raw['published'] ) && is_string( $raw['published'] ) ? $raw['published'] : null;
|
||||
$status_raw = isset( $raw['status'] ) && is_string( $raw['status'] ) ? $raw['status'] : null;
|
||||
$name = isset( $raw['name'] ) && is_string( $raw['name'] ) ? $raw['name'] : '';
|
||||
$is_rewindable = isset( $raw['is_rewindable'] ) ? (bool) $raw['is_rewindable'] : null;
|
||||
|
||||
return array(
|
||||
'id' => (string) $rewind_id,
|
||||
'started' => $published,
|
||||
'last_updated' => $published,
|
||||
'status' => self::map_event_status( $status_raw, $name ),
|
||||
'period' => self::parse_timestamp( $rewind_id ),
|
||||
'is_rewindable' => $is_rewindable,
|
||||
'has_warnings' => self::event_has_warnings( $status_raw, $name ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Map activity-event status / action-name to the status vocabulary the
|
||||
* ability's output schema uses (the same labels as the old
|
||||
* `/rewind/backups` endpoint: "finished", "error", ...). Falls back to
|
||||
* the raw status when no mapping fits so the caller still sees signal.
|
||||
*
|
||||
* @param string|null $status_raw Activity event status (e.g. "success", "warning", "error").
|
||||
* @param string $name Activity name, e.g. "rewind__backup_complete_full".
|
||||
* @return string|null
|
||||
*/
|
||||
private static function map_event_status( ?string $status_raw, string $name ): ?string {
|
||||
if ( 'success' === $status_raw || false !== strpos( $name, 'backup_complete' ) ) {
|
||||
return 'finished';
|
||||
}
|
||||
return $status_raw;
|
||||
}
|
||||
|
||||
/**
|
||||
* Derive `has_warnings` from an activity event's status / name.
|
||||
*
|
||||
* @param string|null $status_raw Activity event status.
|
||||
* @param string $name Activity name.
|
||||
* @return bool|null
|
||||
*/
|
||||
private static function event_has_warnings( ?string $status_raw, string $name ): ?bool {
|
||||
if ( 'warning' === $status_raw ) {
|
||||
return true;
|
||||
}
|
||||
if ( 'success' === $status_raw || false !== strpos( $name, 'backup_complete' ) ) {
|
||||
return false;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Coerce an ISO 8601 string, RFC-style date string, or numeric unix
|
||||
* timestamp to an int unix timestamp. Returns null for anything that
|
||||
* can't be unambiguously parsed (instead of strtotime's `false`, which
|
||||
* is also a valid timestamp for 1969-12-31).
|
||||
*
|
||||
* Fractional numeric strings (e.g. rewind_id "1778804242.107") are
|
||||
* accepted — the fractional part is truncated.
|
||||
*
|
||||
* @param mixed $value Source value (string, int, float, or anything else).
|
||||
* @return int|null
|
||||
*/
|
||||
private static function parse_timestamp( $value ): ?int {
|
||||
if ( is_int( $value ) ) {
|
||||
return $value;
|
||||
}
|
||||
if ( is_float( $value ) ) {
|
||||
return (int) $value;
|
||||
}
|
||||
if ( is_string( $value ) && '' !== $value ) {
|
||||
if ( is_numeric( $value ) ) {
|
||||
return (int) $value;
|
||||
}
|
||||
$ts = strtotime( $value );
|
||||
return false === $ts ? null : $ts;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Enqueue an on-demand backup. Registration is gated on a Backup product
|
||||
* being loaded so we assume one exists by the time this runs. Returns
|
||||
* WP_Error only when the upstream connection itself fails so agents can
|
||||
* retry strategically.
|
||||
*
|
||||
* @param mixed $input Unused; see note on execute_get_backup_overview().
|
||||
* @return array|WP_Error
|
||||
*/
|
||||
public static function execute_request_backup( $input = null ) {
|
||||
unset( $input );
|
||||
|
||||
// wpcom can return HTTP 200 with `{ success: false, error: ... }`; treat
|
||||
// that as a failure rather than reporting the backup was enqueued.
|
||||
$result = self::unwrap_response( Jetpack_Backup::enqueue_backup() );
|
||||
if ( ! is_array( $result ) || empty( $result['success'] ) ) {
|
||||
return new WP_Error(
|
||||
'jetpack_backup_data_unavailable',
|
||||
__( 'The backup service did not accept the request. The connection to WordPress.com may be temporarily unavailable; retry shortly.', 'jetpack-backup-pkg' )
|
||||
);
|
||||
}
|
||||
|
||||
return array(
|
||||
'enqueued' => true,
|
||||
'message' => __( 'Backup enqueued. Use jetpack-backup/list-backups to monitor progress.', 'jetpack-backup-pkg' ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize a Jetpack_Backup helper result (WP_REST_Response, array, null,
|
||||
* or WP_Error) to a plain value or null. Jetpack_Backup uses
|
||||
* `rest_ensure_response()` on success and returns null on http failure, so
|
||||
* abilities need both shapes flattened before summarising.
|
||||
*
|
||||
* @param mixed $maybe_response Result of a Jetpack_Backup helper call.
|
||||
* @return mixed
|
||||
*/
|
||||
private static function unwrap_response( $maybe_response ) {
|
||||
if ( null === $maybe_response || is_wp_error( $maybe_response ) ) {
|
||||
return null;
|
||||
}
|
||||
if ( $maybe_response instanceof WP_REST_Response ) {
|
||||
return $maybe_response->get_data();
|
||||
}
|
||||
return $maybe_response;
|
||||
}
|
||||
|
||||
/**
|
||||
* Slice the (already-summarized) list down to a single id, or apply
|
||||
* page/per_page pagination. Always returns the same item shape.
|
||||
*
|
||||
* @param array $items Summarized items.
|
||||
* @param array $input Sanitized input.
|
||||
* @return array
|
||||
*/
|
||||
private static function apply_id_or_pagination( array $items, array $input ): array {
|
||||
if ( isset( $input['id'] ) && is_string( $input['id'] ) && '' !== $input['id'] ) {
|
||||
foreach ( $items as $item ) {
|
||||
if ( isset( $item['id'] ) && (string) $item['id'] === $input['id'] ) {
|
||||
return array( $item );
|
||||
}
|
||||
}
|
||||
return array();
|
||||
}
|
||||
|
||||
$page = max( 1, (int) ( $input['page'] ?? 1 ) );
|
||||
$per_page = min( self::PER_PAGE_MAX, max( 1, (int) ( $input['per_page'] ?? self::PER_PAGE_DEFAULT ) ) );
|
||||
|
||||
return array_slice( $items, ( $page - 1 ) * $per_page, $per_page );
|
||||
}
|
||||
|
||||
/**
|
||||
* High-signal summary used inside `last_backup` for the overview. Same as
|
||||
* `summarize_backup` minus the `started`/`period` fields which the agent
|
||||
* doesn't need at a glance.
|
||||
*
|
||||
* @param mixed $raw One element from the upstream backups list.
|
||||
* @return array|null
|
||||
*/
|
||||
private static function summarize_last_backup( $raw ): ?array {
|
||||
if ( ! is_array( $raw ) && ! is_object( $raw ) ) {
|
||||
return null;
|
||||
}
|
||||
return array_diff_key(
|
||||
self::summarize_backup( $raw ),
|
||||
array_flip( array( 'started', 'period' ) )
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize a `/rewind/backups` payload item using `rewind_id` as the
|
||||
* canonical `id`. The numeric attempt id wpcom also exposes is
|
||||
* internal to VaultPress and can't be looked up via any other endpoint,
|
||||
* so it's intentionally dropped from the agent-facing shape — see the
|
||||
* note on `$backup_item_schema` in `get_abilities()`.
|
||||
*
|
||||
* @param mixed $raw Upstream backup item.
|
||||
* @return array
|
||||
*/
|
||||
private static function summarize_backup( $raw ): array {
|
||||
$raw = (array) $raw;
|
||||
$rewind_id = $raw['rewind_id'] ?? null;
|
||||
return array(
|
||||
'id' => ( null === $rewind_id || '' === $rewind_id ) ? null : (string) $rewind_id,
|
||||
'started' => $raw['started'] ?? null,
|
||||
'last_updated' => $raw['last_updated'] ?? null,
|
||||
'status' => $raw['status'] ?? null,
|
||||
'period' => $raw['period'] ?? null,
|
||||
'is_rewindable' => isset( $raw['is_rewindable'] ) ? (bool) $raw['is_rewindable'] : null,
|
||||
'has_warnings' => isset( $raw['has_warnings'] ) ? (bool) $raw['has_warnings'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize a `/rewind/restores` payload item. `id` is the rewind_id
|
||||
* of the backup being restored to — same canonical id system as
|
||||
* `summarize_backup()`.
|
||||
*
|
||||
* @param mixed $raw Upstream restore item.
|
||||
* @return array
|
||||
*/
|
||||
private static function summarize_restore( $raw ): array {
|
||||
$raw = (array) $raw;
|
||||
$rewind_id = $raw['rewind_id'] ?? null;
|
||||
return array(
|
||||
'id' => ( null === $rewind_id || '' === $rewind_id ) ? null : (string) $rewind_id,
|
||||
'started' => $raw['started'] ?? null,
|
||||
'last_updated' => $raw['last_updated'] ?? null,
|
||||
'status' => $raw['status'] ?? null,
|
||||
'progress' => isset( $raw['progress'] ) ? (int) $raw['progress'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Summarize the wpcom schedule payload to `{ hour, minute }`.
|
||||
*
|
||||
* @param mixed $raw Upstream schedule payload.
|
||||
* @return array|null
|
||||
*/
|
||||
private static function summarize_schedule( $raw ): ?array {
|
||||
if ( ! is_array( $raw ) && ! is_object( $raw ) ) {
|
||||
return null;
|
||||
}
|
||||
$raw = (array) $raw;
|
||||
return array(
|
||||
'hour' => isset( $raw['hour'] ) ? (int) $raw['hour'] : null,
|
||||
'minute' => isset( $raw['minute'] ) ? (int) $raw['minute'] : null,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps both the production wpcom field names (`size_in_bytes`, `storage_limit_bytes`)
|
||||
* and shorter aliases (`used_bytes`, `limit_bytes`) so the ability stays stable
|
||||
* if the upstream payload is renamed.
|
||||
*
|
||||
* @param mixed $raw Upstream storage payload.
|
||||
* @return array|null
|
||||
*/
|
||||
private static function summarize_storage( $raw ): ?array {
|
||||
if ( ! is_array( $raw ) && ! is_object( $raw ) ) {
|
||||
return null;
|
||||
}
|
||||
$raw = (array) $raw;
|
||||
$used_bytes = $raw['size_in_bytes'] ?? ( $raw['used_bytes'] ?? null );
|
||||
$limit_bytes = $raw['storage_limit_bytes'] ?? ( $raw['limit_bytes'] ?? null );
|
||||
return array(
|
||||
'used_bytes' => null === $used_bytes ? null : (int) $used_bytes,
|
||||
'limit_bytes' => null === $limit_bytes ? null : (int) $limit_bytes,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
<?php
|
||||
/**
|
||||
* The React initial state.
|
||||
*
|
||||
* @package automattic/jetpack-backup-plugin
|
||||
*/
|
||||
|
||||
// After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in
|
||||
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
|
||||
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
|
||||
// are installed, or in some other cases).
|
||||
namespace Automattic\Jetpack\Backup\V0005;
|
||||
|
||||
use Automattic\Jetpack\Connection\Plugin_Storage as Connection_Plugin_Storage;
|
||||
use Automattic\Jetpack\Status;
|
||||
use Jetpack_Options;
|
||||
use function add_action;
|
||||
use function admin_url;
|
||||
use function esc_url;
|
||||
use function esc_url_raw;
|
||||
use function get_bloginfo;
|
||||
use function get_site_url;
|
||||
use function plugins_url;
|
||||
use function rest_url;
|
||||
use function wp_create_nonce;
|
||||
use function wp_json_encode;
|
||||
|
||||
/**
|
||||
* The React initial state.
|
||||
*/
|
||||
class Initial_State {
|
||||
/**
|
||||
* Get the initial state data.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private function get_data() {
|
||||
return array(
|
||||
'API' => array(
|
||||
'WP_API_root' => esc_url_raw( rest_url() ),
|
||||
'WP_API_nonce' => wp_create_nonce( 'wp_rest' ),
|
||||
'registrationNonce' => wp_create_nonce( 'jetpack-registration-nonce' ),
|
||||
),
|
||||
'jetpackStatus' => array(
|
||||
'calypsoSlug' => ( new Status() )->get_site_suffix(),
|
||||
),
|
||||
'connectedPlugins' => Connection_Plugin_Storage::get_all(),
|
||||
'siteData' => array(
|
||||
'id' => Jetpack_Options::get_option( 'id' ),
|
||||
'title' => get_bloginfo( 'name' ) ? get_bloginfo( 'name' ) : get_site_url(),
|
||||
'adminUrl' => esc_url( admin_url() ),
|
||||
),
|
||||
'assets' => array(
|
||||
'buildUrl' => plugins_url( '../build/', __FILE__ ),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render the initial state into a JavaScript variable.
|
||||
*
|
||||
* @return string
|
||||
*/
|
||||
public function render() {
|
||||
add_action( 'jetpack_use_iframe_authorization_flow', '__return_true' );
|
||||
|
||||
return 'var JPBACKUP_INITIAL_STATE=' . wp_json_encode( $this->get_data(), JSON_UNESCAPED_SLASHES | JSON_HEX_TAG | JSON_HEX_AMP ) . ';';
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,48 @@
|
||||
<?php
|
||||
/**
|
||||
* Handle Backup plugin upgrades
|
||||
*
|
||||
* @package automattic/jetpack-backup-plugin
|
||||
*/
|
||||
|
||||
// After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in
|
||||
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
|
||||
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
|
||||
// are installed, or in some other cases).
|
||||
namespace Automattic\Jetpack\Backup\V0005;
|
||||
|
||||
use function get_option;
|
||||
use function update_option;
|
||||
|
||||
/**
|
||||
* The Upgrades class.
|
||||
*/
|
||||
class Jetpack_Backup_Upgrades {
|
||||
|
||||
/**
|
||||
* Run all methods only once and store an option to make sure it never runs again
|
||||
*/
|
||||
public static function upgrade() {
|
||||
|
||||
$upgrades = get_class_methods( __CLASS__ );
|
||||
|
||||
foreach ( $upgrades as $upgrade ) {
|
||||
$option_name = '_upgrade_' . $upgrade;
|
||||
if ( 'upgrade' === $upgrade || get_option( $option_name ) ) {
|
||||
continue;
|
||||
}
|
||||
|
||||
update_option( $option_name, 1 );
|
||||
|
||||
call_user_func( array( __CLASS__, $upgrade ) );
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* The plugin is not checking if it was disabled and reactivating it when we reconnect, therefore we need to clear this information from DB so other plugins know we are still using the connection
|
||||
*
|
||||
* @deprecated since 1.7.0 No longer required after removing soft disconnect functionality.
|
||||
*/
|
||||
public static function clear_disabled_plugin() {}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
<?php // phpcs:ignore WordPress.Files.FileName.InvalidClassFileName
|
||||
/**
|
||||
* The Package_Version class's compatibility shim.
|
||||
*
|
||||
* @package automattic/jetpack-backup
|
||||
*/
|
||||
|
||||
// Do *not* update the "V0001" namespace version on changes.
|
||||
namespace Automattic\Jetpack\Backup\V0001;
|
||||
|
||||
/**
|
||||
* Package_Version proxy class to accommodate upgrades from plugin version 2.4.
|
||||
*
|
||||
* Backup plugin version 2.4 had a versioned class defined ("Automattic\Jetpack\Backup\V0001\Package_Version"), so
|
||||
* the "jetpack_package_versions" filter will try to look for the class with this namespace + name in the newer
|
||||
* plugin's code.
|
||||
*/
|
||||
class Package_Version {
|
||||
// phpcs:ignore Squiz.Commenting.FunctionComment.Missing
|
||||
public static function send_package_version_to_tracker( $package_versions ) {
|
||||
return \Automattic\Jetpack\Backup\Package_Version::send_package_version_to_tracker( $package_versions );
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
<?php
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*
|
||||
* @package automattic/jetpack-backup
|
||||
*/
|
||||
|
||||
namespace Automattic\Jetpack\Backup;
|
||||
|
||||
/**
|
||||
* The Package_Version class.
|
||||
*
|
||||
* Does *not* use namespaced versioning ("VXXXX") because send_package_version_to_tracker() is used as a
|
||||
* "jetpack_package_versions" filter, and said filter gets run during a plugin upgrade, so it always expects to
|
||||
* find the "Package_Version" class with the same namespace, name, and interface.
|
||||
*/
|
||||
class Package_Version {
|
||||
|
||||
const PACKAGE_VERSION = '4.3.6';
|
||||
|
||||
const PACKAGE_SLUG = 'backup';
|
||||
|
||||
/**
|
||||
* Adds the package slug and version to the package version tracker's data.
|
||||
*
|
||||
* @param array $package_versions The package version array.
|
||||
*
|
||||
* @return array The package version array.
|
||||
*/
|
||||
public static function send_package_version_to_tracker( $package_versions ) {
|
||||
$package_versions[ self::PACKAGE_SLUG ] = self::PACKAGE_VERSION;
|
||||
|
||||
return $package_versions;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,751 @@
|
||||
<?php
|
||||
/**
|
||||
* The Backup Rest Controller class.
|
||||
* Registers the REST routes for Backup.
|
||||
*
|
||||
* @package automattic/jetpack-backup
|
||||
*/
|
||||
|
||||
// After changing this file, consider increasing the version number ("VXXX") in all the files using this namespace, in
|
||||
// order to ensure that the specific version of this file always get loaded. Otherwise, Jetpack autoloader might decide
|
||||
// to load an older/newer version of the class (if, for example, both the standalone and bundled versions of the plugin
|
||||
// are installed, or in some other cases).
|
||||
namespace Automattic\Jetpack\Backup\V0005;
|
||||
|
||||
use Automattic\Jetpack\Connection\Client;
|
||||
use Automattic\Jetpack\Connection\Rest_Authentication;
|
||||
use Automattic\Jetpack\Sync\Actions as Sync_Actions;
|
||||
use Automattic\WooCommerce\Internal\DataStores\Orders\OrdersTableDataStore;
|
||||
use Jetpack_Options;
|
||||
use WP_Error;
|
||||
use WP_REST_Request;
|
||||
use WP_REST_Server;
|
||||
use function esc_html__;
|
||||
use function get_comment;
|
||||
use function get_comment_meta;
|
||||
use function get_metadata;
|
||||
use function get_post;
|
||||
use function get_post_meta;
|
||||
use function get_term;
|
||||
use function get_term_meta;
|
||||
use function get_user_by;
|
||||
use function get_user_meta;
|
||||
use function is_wp_error;
|
||||
use function register_rest_route;
|
||||
use function rest_authorization_required_code;
|
||||
use function rest_ensure_response;
|
||||
use function wp_remote_retrieve_response_code;
|
||||
|
||||
/**
|
||||
* Registers the REST routes for Backup.
|
||||
*/
|
||||
class REST_Controller {
|
||||
/**
|
||||
* Registers the REST routes for Backup.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*/
|
||||
public static function register_rest_routes() {
|
||||
// Install a Helper Script to assist Jetpack Backup fetch data.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/backup-helper-script',
|
||||
array(
|
||||
'methods' => WP_REST_Server::CREATABLE,
|
||||
'callback' => __CLASS__ . '::install_backup_helper_script',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
'args' => array(
|
||||
'helper' => array(
|
||||
'description' => __( 'base64 encoded Backup Helper Script body.', 'jetpack-backup-pkg' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Delete a Backup Helper Script.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/backup-helper-script',
|
||||
array(
|
||||
'methods' => WP_REST_Server::DELETABLE,
|
||||
'callback' => __CLASS__ . '::delete_backup_helper_script',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
'args' => array(
|
||||
'path' => array(
|
||||
'description' => __( 'Path to Backup Helper Script', 'jetpack-backup-pkg' ),
|
||||
'type' => 'string',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch a backup of a database object, along with all of its metadata.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/database-object/backup',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::fetch_database_object_backup',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
'args' => array(
|
||||
'object_type' => array(
|
||||
'description' => __( 'Type of object to fetch from the database', 'jetpack-backup-pkg' ),
|
||||
'required' => true,
|
||||
'validate_callback' => function ( $value ) {
|
||||
if ( ! is_string( $value ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
__( 'The object_type argument must be a non-empty string.', 'jetpack-backup-pkg' ),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
$allowed_object_types = array_keys( self::get_allowed_object_types() );
|
||||
|
||||
if ( ! in_array( $value, $allowed_object_types, true ) ) {
|
||||
return new WP_Error(
|
||||
'rest_invalid_param',
|
||||
sprintf(
|
||||
/* translators: %s: comma-separated list of allowed object types */
|
||||
__( 'The object_type argument should be one of %s', 'jetpack-backup-pkg' ),
|
||||
implode( ', ', $allowed_object_types )
|
||||
),
|
||||
array( 'status' => 400 )
|
||||
);
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
),
|
||||
'object_id' => array(
|
||||
'description' => __( 'ID of the database object to fetch', 'jetpack-backup-pkg' ),
|
||||
'type' => 'integer',
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch a backup of an option.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/options/backup',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::fetch_options_backup',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
'args' => array(
|
||||
'name' => array(
|
||||
'description' => __( 'One or more option names to include in the backup', 'jetpack-backup-pkg' ),
|
||||
'validate_callback' => function ( $value ) {
|
||||
$is_valid = is_array( $value ) || is_string( $value );
|
||||
if ( ! $is_valid ) {
|
||||
return new WP_Error( 'rest_invalid_param', __( 'The name argument should be an option name or an array of option names', 'jetpack-backup-pkg' ), array( 'status' => 400 ) );
|
||||
}
|
||||
|
||||
return true;
|
||||
},
|
||||
'required' => true,
|
||||
),
|
||||
),
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch a backup of a comment, along with all of its metadata.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/comments/(?P<id>\d+)/backup',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::fetch_comment_backup',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch a backup of a post, along with all of its metadata.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/posts/(?P<id>\d+)/backup',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::fetch_post_backup',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch a backup of a term, along with all of its metadata.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/terms/(?P<id>\d+)/backup',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::fetch_term_backup',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch a backup of a user, along with all of its metadata.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/users/(?P<id>\d+)/backup',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::fetch_user_backup',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
// Get backup undo event
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/site/backup/undo-event',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_site_backup_undo_event',
|
||||
'permission_callback' => __NAMESPACE__ . '\Jetpack_Backup::backups_permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch a backup of a wc_order along with all of its data.
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/orders/(?P<id>\d+)/backup',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::fetch_wc_orders_backup',
|
||||
'permission_callback' => __CLASS__ . '::backup_permissions_callback',
|
||||
)
|
||||
);
|
||||
|
||||
// Fetch backup preflight status
|
||||
register_rest_route(
|
||||
'jetpack/v4',
|
||||
'/site/backup/preflight',
|
||||
array(
|
||||
'methods' => WP_REST_Server::READABLE,
|
||||
'callback' => __CLASS__ . '::get_site_backup_preflight',
|
||||
'permission_callback' => __NAMESPACE__ . '\Jetpack_Backup::backups_permissions_callback',
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* The Backup endpoints should only be available via site-level authentication.
|
||||
* This means that the corresponding endpoints can only be accessible from WPCOM.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @return bool|WP_Error True if a blog token was used to sign the request, WP_Error otherwise.
|
||||
*/
|
||||
public static function backup_permissions_callback() {
|
||||
if ( Rest_Authentication::is_signed_with_blog_token() ) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$error_msg = esc_html__(
|
||||
'You are not allowed to perform this action.',
|
||||
'jetpack-backup-pkg'
|
||||
);
|
||||
|
||||
return new WP_Error( 'rest_forbidden', $error_msg, array( 'status' => rest_authorization_required_code() ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Install the Backup Helper Script.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array|WP_Error Array with installation info on success:
|
||||
*
|
||||
* 'path' (string) Helper script installation path on the filesystem.
|
||||
* 'url' (string) URL to the helper script.
|
||||
* 'abspath' (string) WordPress root.
|
||||
*
|
||||
* or an instance of WP_Error on failure.
|
||||
*/
|
||||
public static function install_backup_helper_script( $request ) {
|
||||
$helper_script = $request->get_param( 'helper' );
|
||||
|
||||
// phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.obfuscation_base64_decode
|
||||
$helper_script = base64_decode( $helper_script );
|
||||
if ( ! $helper_script ) {
|
||||
return new WP_Error( 'invalid_args', __( 'Helper script body must be base64 encoded', 'jetpack-backup-pkg' ), 400 );
|
||||
}
|
||||
|
||||
$installation_info = Helper_Script_Manager::install_helper_script( $helper_script );
|
||||
Helper_Script_Manager::cleanup_expired_helper_scripts();
|
||||
|
||||
return rest_ensure_response( $installation_info );
|
||||
}
|
||||
|
||||
/**
|
||||
* Delete a Backup Helper Script.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array|WP_Error An array with 'success' key, or an instance of WP_Error on failure.
|
||||
*/
|
||||
public static function delete_backup_helper_script( $request ) {
|
||||
$path_to_helper_script = $request->get_param( 'path' );
|
||||
|
||||
$delete_result = Helper_Script_Manager::delete_helper_script( $path_to_helper_script );
|
||||
Helper_Script_Manager::cleanup_expired_helper_scripts();
|
||||
|
||||
if ( is_wp_error( $delete_result ) ) {
|
||||
return $delete_result;
|
||||
}
|
||||
|
||||
return rest_ensure_response( array( 'success' => true ) );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a backup of a database object, along with all of its metadata.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_database_object_backup( $request ) {
|
||||
global $wpdb;
|
||||
|
||||
// Disable Sync as this is a read-only operation and triggered by sync activity.
|
||||
Sync_Actions::mark_sync_read_only();
|
||||
|
||||
$allowed_object_types = self::get_allowed_object_types();
|
||||
// Safe to do this as we have already validated the object_type key exists in self::get_allowed_object_types().
|
||||
$object_type = $allowed_object_types[ $request->get_param( 'object_type' ) ];
|
||||
$object_id = $request->get_param( 'object_id' );
|
||||
$table = $wpdb->prefix . $object_type['table'];
|
||||
$id_field = $object_type['id_field'];
|
||||
|
||||
// Fetch the requested object.
|
||||
$object = $wpdb->get_row(
|
||||
$wpdb->prepare(
|
||||
'SELECT * FROM `' . $table . '` WHERE `' . $id_field . '` = %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$object_id
|
||||
)
|
||||
);
|
||||
|
||||
if ( empty( $object ) ) {
|
||||
return new WP_Error( 'object_not_found', __( 'Object not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$result = array( 'object' => $object );
|
||||
|
||||
// Fetch associated metadata (if this object type has any).
|
||||
if ( ! empty( $object_type['meta_type'] ) ) {
|
||||
$result['meta'] = get_metadata( $object_type['meta_type'], $object_id );
|
||||
}
|
||||
|
||||
// If there is a child linked table (eg: woocommerce_tax_rate_locations), fetch linked records.
|
||||
if ( ! empty( $object_type['child_table'] ) ) {
|
||||
$child_table = $wpdb->prefix . $object_type['child_table'];
|
||||
$child_id_field = $object_type['child_id_field'];
|
||||
|
||||
$result['children'] = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
'SELECT * FROM `' . $child_table . '` where `' . $child_id_field . '` = %d', // phpcs:ignore WordPress.DB.PreparedSQL.NotPrepared
|
||||
$object_id
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a backup of an option.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_options_backup( $request ) {
|
||||
// Disable Sync as this is a read-only operation and triggered by sync activity.
|
||||
Sync_Actions::mark_sync_read_only();
|
||||
|
||||
$option_names = (array) $request->get_param( 'name' );
|
||||
|
||||
$options = array_map( self::class . '::get_option_row', $option_names );
|
||||
return array( 'options' => $options );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a backup of a comment, along with all of its metadata.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_comment_backup( $request ) {
|
||||
// Disable Sync as this is a read-only operation and triggered by sync activity.
|
||||
Sync_Actions::mark_sync_read_only();
|
||||
|
||||
$comment_id = $request['id'];
|
||||
$comment = get_comment( $comment_id );
|
||||
|
||||
if ( empty( $comment ) ) {
|
||||
return new WP_Error( 'comment_not_found', __( 'Comment not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
$allowed_keys = array(
|
||||
'comment_ID',
|
||||
'comment_post_ID',
|
||||
'comment_author',
|
||||
'comment_author_email',
|
||||
'comment_author_url',
|
||||
'comment_author_IP',
|
||||
'comment_date',
|
||||
'comment_date_gmt',
|
||||
'comment_content',
|
||||
'comment_karma',
|
||||
'comment_approved',
|
||||
'comment_agent',
|
||||
'comment_type',
|
||||
'comment_parent',
|
||||
'user_id',
|
||||
);
|
||||
|
||||
$comment = array_intersect_key( $comment->to_array(), array_flip( $allowed_keys ) );
|
||||
|
||||
$comment_meta = get_comment_meta( $comment['comment_ID'] );
|
||||
|
||||
return array(
|
||||
'comment' => $comment,
|
||||
'meta' => is_array( $comment_meta ) ? $comment_meta : array(),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a backup of a post, along with all of its metadata.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_post_backup( $request ) {
|
||||
global $wpdb;
|
||||
|
||||
// Disable Sync as this is a read-only operation and triggered by sync activity.
|
||||
Sync_Actions::mark_sync_read_only();
|
||||
|
||||
$post_id = $request['id'];
|
||||
$post = get_post( $post_id );
|
||||
|
||||
if ( empty( $post ) ) {
|
||||
return new WP_Error( 'post_not_found', __( 'Post not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
// Fetch terms associated with this post object.
|
||||
$terms = $wpdb->get_results(
|
||||
$wpdb->prepare(
|
||||
"SELECT term_taxonomy_id, term_order FROM {$wpdb->term_relationships} WHERE object_id = %d;",
|
||||
$post->ID
|
||||
)
|
||||
);
|
||||
|
||||
return array(
|
||||
'post' => (array) $post,
|
||||
'meta' => get_post_meta( $post->ID ),
|
||||
'terms' => (array) $terms,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a backup of a term, along with all of its metadata.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_term_backup( $request ) {
|
||||
// Disable Sync as this is a read-only operation and triggered by sync activity.
|
||||
Sync_Actions::mark_sync_read_only();
|
||||
|
||||
$term_id = $request['id'];
|
||||
$term = get_term( $term_id );
|
||||
|
||||
if ( empty( $term ) ) {
|
||||
return new WP_Error( 'term_not_found', __( 'Term not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return array(
|
||||
'term' => (array) $term,
|
||||
'meta' => get_term_meta( $term_id ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a backup of a user, along with all of its metadata.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_user_backup( $request ) {
|
||||
// Disable Sync as this is a read-only operation and triggered by sync activity.
|
||||
Sync_Actions::mark_sync_read_only();
|
||||
|
||||
$user_id = $request['id'];
|
||||
$user = get_user_by( 'id', $user_id );
|
||||
|
||||
if ( empty( $user ) ) {
|
||||
return new WP_Error( 'user_not_found', __( 'User not found', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
return array(
|
||||
'user' => $user->to_array(),
|
||||
'meta' => get_user_meta( $user->ID ),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Get allowed object types for the '/database-object/backup' endpoint.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
private static function get_allowed_object_types() {
|
||||
return array(
|
||||
'woocommerce_attribute' => array(
|
||||
'table' => 'woocommerce_attribute_taxonomies',
|
||||
'id_field' => 'attribute_id',
|
||||
),
|
||||
'woocommerce_downloadable_product_permission' => array(
|
||||
'table' => 'woocommerce_downloadable_product_permissions',
|
||||
'id_field' => 'permission_id',
|
||||
),
|
||||
'woocommerce_order_item' => array(
|
||||
'table' => 'woocommerce_order_items',
|
||||
'id_field' => 'order_item_id',
|
||||
'meta_type' => 'order_item',
|
||||
),
|
||||
'woocommerce_payment_token' => array(
|
||||
'table' => 'woocommerce_payment_tokens',
|
||||
'id_field' => 'token_id',
|
||||
'meta_type' => 'payment_token',
|
||||
),
|
||||
'woocommerce_tax_rate' => array(
|
||||
'table' => 'woocommerce_tax_rates',
|
||||
'id_field' => 'tax_rate_id',
|
||||
'child_table' => 'woocommerce_tax_rate_locations',
|
||||
'child_id_field' => 'tax_rate_id',
|
||||
),
|
||||
'woocommerce_webhook' => array(
|
||||
'table' => 'wc_webhooks',
|
||||
'id_field' => 'webhook_id',
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* This will fetch the last rewindable event from the Activity Log and
|
||||
* the last rewind_id prior to that.
|
||||
*/
|
||||
public static function get_site_backup_undo_event() {
|
||||
$blog_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_user(
|
||||
'/sites/' . $blog_id . '/activity?force=wpcom',
|
||||
'v2',
|
||||
array(),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( 200 !== wp_remote_retrieve_response_code( $response ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$body = json_decode( $response['body'], true );
|
||||
|
||||
if ( ! isset( $body['current'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if ( ! isset( $body['current']['orderedItems'] ) ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// Preparing the response structure
|
||||
$undo_event = array(
|
||||
'last_rewindable_event' => null,
|
||||
'undo_backup_id' => null,
|
||||
);
|
||||
|
||||
// List of events that will not be considered to be undo.
|
||||
// Basically we should not `undo` a full backup event, but we could
|
||||
// use them to undo any other action like plugin updates.
|
||||
$last_event_exceptions = array(
|
||||
'rewind__backup_only_complete_full',
|
||||
'rewind__backup_only_complete_initial',
|
||||
'rewind__backup_only_complete',
|
||||
'rewind__backup_complete_full',
|
||||
'rewind__backup_complete_initial',
|
||||
'rewind__backup_complete',
|
||||
);
|
||||
|
||||
// Looping through the events to find the last rewindable event and the last backup_id.
|
||||
// The idea is to find the last rewindable event and then the last rewind_id before that.
|
||||
$found_last_event = false;
|
||||
foreach ( $body['current']['orderedItems'] as $event ) {
|
||||
if ( $event['is_rewindable'] ) {
|
||||
if ( ! $found_last_event && ! in_array( $event['name'], $last_event_exceptions, true ) ) {
|
||||
$undo_event['last_rewindable_event'] = $event;
|
||||
$found_last_event = true;
|
||||
} elseif ( $found_last_event ) {
|
||||
$undo_event['undo_backup_id'] = $event['rewind_id'];
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Ensure that we have a rewindable event and a backup_id to undo.
|
||||
if ( $undo_event['last_rewindable_event'] === null || $undo_event['undo_backup_id'] === null ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return rest_ensure_response( $undo_event );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch a backup of a order, along with all of its data.
|
||||
*
|
||||
* @access public
|
||||
* @static
|
||||
*
|
||||
* @param WP_REST_Request $request The request sent to the WP REST API.
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function fetch_wc_orders_backup( $request ) {
|
||||
global $wpdb;
|
||||
|
||||
// Disable Sync as this is a read-only operation and triggered by sync activity.
|
||||
Sync_Actions::mark_sync_read_only();
|
||||
|
||||
$order_id = $request['id'];
|
||||
|
||||
$order = array();
|
||||
$order_addresses = array();
|
||||
$order_operational_data = array();
|
||||
$order_meta = array();
|
||||
|
||||
if ( ! class_exists( OrdersTableDataStore::class ) ) {
|
||||
return new WP_Error( 'order_not_allowed', __( 'Not allowed to get the order with current configuration', 'jetpack-backup-pkg' ), array( 'status' => 403 ) );
|
||||
}
|
||||
|
||||
if ( method_exists( OrdersTableDataStore::class, 'get_orders_table_name' ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
|
||||
$order = $wpdb->get_row( $wpdb->prepare( 'SELECT * FROM `' . OrdersTableDataStore::get_orders_table_name() . '` WHERE id = %s', $order_id ) );
|
||||
}
|
||||
|
||||
if ( empty( $order ) ) {
|
||||
// No order in HPOS
|
||||
return new WP_Error( 'order_not_found', __( 'Order not found ', 'jetpack-backup-pkg' ), array( 'status' => 404 ) );
|
||||
}
|
||||
|
||||
if ( method_exists( OrdersTableDataStore::class, 'get_addresses_table_name' ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
|
||||
$order_addresses = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM `' . OrdersTableDataStore::get_addresses_table_name() . '` WHERE order_id = %s', $order_id ) );
|
||||
}
|
||||
|
||||
if ( method_exists( OrdersTableDataStore::class, 'get_operational_data_table_name' ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
|
||||
$order_operational_data = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM `' . OrdersTableDataStore::get_operational_data_table_name() . '` WHERE order_id = %s', $order_id ) );
|
||||
}
|
||||
|
||||
if ( method_exists( OrdersTableDataStore::class, 'get_meta_table_name' ) ) {
|
||||
// phpcs:ignore WordPress.DB.DirectDatabaseQuery.DirectQuery,WordPress.DB.DirectDatabaseQuery.NoCaching,WordPress.DB.PreparedSQL.NotPrepared
|
||||
$order_meta = $wpdb->get_results( $wpdb->prepare( 'SELECT * FROM `' . OrdersTableDataStore::get_meta_table_name() . '` WHERE order_id = %s', $order_id ) );
|
||||
}
|
||||
|
||||
return array(
|
||||
'order' => (array) $order,
|
||||
'order_addresses' => (array) $order_addresses,
|
||||
'order_operational_data' => (array) $order_operational_data,
|
||||
'order_meta' => (array) $order_meta,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch backup preflight status
|
||||
*
|
||||
* @return array
|
||||
*/
|
||||
public static function get_site_backup_preflight() {
|
||||
$blog_id = Jetpack_Options::get_option( 'id' );
|
||||
|
||||
$response = Client::wpcom_json_api_request_as_user(
|
||||
'/sites/' . $blog_id . '/rewind/preflight?force=wpcom',
|
||||
'v2',
|
||||
array(),
|
||||
null,
|
||||
'wpcom'
|
||||
);
|
||||
|
||||
if ( is_wp_error( $response ) ) {
|
||||
return new WP_Error(
|
||||
'wp_error_fetch_preflight',
|
||||
$response->get_error_message(),
|
||||
array( 'status' => 500 )
|
||||
);
|
||||
}
|
||||
|
||||
$response_code = wp_remote_retrieve_response_code( $response );
|
||||
if ( 200 !== $response_code ) {
|
||||
return new WP_Error(
|
||||
'http_error_fetch_preflight',
|
||||
wp_remote_retrieve_response_message( $response ),
|
||||
array( 'status' => $response_code )
|
||||
);
|
||||
}
|
||||
|
||||
$body = json_decode( $response['body'], true );
|
||||
return rest_ensure_response( $body );
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch option row by option name.
|
||||
*
|
||||
* @access private
|
||||
* @static
|
||||
*
|
||||
* @param string $name The option name.
|
||||
* @return object|null Database query result as object format specified or null on failure.
|
||||
*/
|
||||
private static function get_option_row( $name ) {
|
||||
global $wpdb;
|
||||
return $wpdb->get_row( $wpdb->prepare( "select * from `{$wpdb->options}` where option_name = %s", $name ) );
|
||||
}
|
||||
}
|
||||