Support installing live branches from the manifest (#36072)
This commit is contained in:
parent
400ace67a3
commit
4877e4b36e
|
@ -17,8 +17,8 @@ function register_woocommerce_admin_test_helper_rest_route( $route, $callback, $
|
|||
'rest_api_init',
|
||||
function() use ( $route, $callback, $additional_options ) {
|
||||
$default_options = array(
|
||||
'methods' => 'POST',
|
||||
'callback' => $callback,
|
||||
'methods' => 'POST',
|
||||
'callback' => $callback,
|
||||
'permission_callback' => function( $request ) {
|
||||
if ( ! wc_rest_check_manager_permissions( 'settings', 'edit' ) ) {
|
||||
return new \WP_Error(
|
||||
|
@ -55,3 +55,4 @@ require 'features/features.php';
|
|||
require 'rest-api-filters/rest-api-filters.php';
|
||||
require 'rest-api-filters/hook.php';
|
||||
require 'live-branches/manifest.php';
|
||||
require 'live-branches/install.php';
|
||||
|
|
|
@ -0,0 +1,95 @@
|
|||
<?php // @codingStandardsIgnoreLine I don't know why it thinks the doc comment is missing.
|
||||
/**
|
||||
* REST API endpoints for live branches installation.
|
||||
*
|
||||
* @package WC_Beta_Tester
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../includes/class-wc-beta-tester-live-branches-installer.php';
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/live-branches/install/v1',
|
||||
'install_version',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
)
|
||||
);
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/live-branches/deactivate/v1',
|
||||
'deactivate_woocommerce',
|
||||
array(
|
||||
'methods' => 'GET',
|
||||
)
|
||||
);
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/live-branches/activate/v1',
|
||||
'activate_version',
|
||||
array(
|
||||
'methods' => 'POST',
|
||||
'permission_callback' => function( $request ) {
|
||||
// Avoid using WC functions as core will be deactivated during this request.
|
||||
$user = wp_get_current_user();
|
||||
$allowed_roles = array( 'administrator' );
|
||||
if ( array_intersect( $allowed_roles, $user->roles ) ) {
|
||||
return true;
|
||||
} else {
|
||||
return new \WP_Error(
|
||||
'woocommerce_rest_cannot_edit',
|
||||
__( 'Sorry, you cannot perform this action', 'woocommerce' )
|
||||
);
|
||||
}
|
||||
},
|
||||
)
|
||||
);
|
||||
|
||||
/**
|
||||
* Respond to POST request to install a plugin by download url.
|
||||
*
|
||||
* @param Object $request - The request parameter.
|
||||
*/
|
||||
function install_version( $request ) {
|
||||
$params = json_decode( $request->get_body() );
|
||||
$download_url = $params->download_url;
|
||||
$pr_name = $params->pr_name;
|
||||
$version = $params->version;
|
||||
|
||||
$installer = new WC_Beta_Tester_Live_Branches_Installer();
|
||||
$result = $installer->install( $download_url, $pr_name, $version );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return new WP_Error( 400, "Could not install $pr_name with error {$result->get_error_message()}", '' );
|
||||
} else {
|
||||
return new WP_REST_Response( wp_json_encode( array( 'ok' => true ) ), 200 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to POST request to activate a plugin by version.
|
||||
*
|
||||
* @param Object $request - The request parameter.
|
||||
*/
|
||||
function activate_version( $request ) {
|
||||
$params = json_decode( $request->get_body() );
|
||||
$version = $params->version;
|
||||
|
||||
$installer = new WC_Beta_Tester_Live_Branches_Installer();
|
||||
$result = $installer->activate( $version );
|
||||
|
||||
if ( is_wp_error( $result ) ) {
|
||||
return new WP_Error( 400, "Could not activate version: $version with error {$result->get_error_message()}", '' );
|
||||
} else {
|
||||
return new WP_REST_Response( wp_json_encode( array( 'ok' => true ) ), 200 );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Respond to GET request to deactivate WooCommerce.
|
||||
*/
|
||||
function deactivate_woocommerce() {
|
||||
$installer = new WC_Beta_Tester_Live_Branches_Installer();
|
||||
$installer->deactivate_woocommerce();
|
||||
|
||||
return new WP_REST_Response( wp_json_encode( array( 'ok' => true ) ), 200 );
|
||||
}
|
|
@ -1,10 +1,12 @@
|
|||
<?php
|
||||
<?php // @codingStandardsIgnoreLine
|
||||
/**
|
||||
* Register REST endpoint for fetching live branches manifest.
|
||||
*
|
||||
* @package WC_Beta_Tester
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/../../includes/class-wc-beta-tester-live-branches-installer.php';
|
||||
|
||||
register_woocommerce_admin_test_helper_rest_route(
|
||||
'/live-branches/manifest/v1',
|
||||
'fetch_live_branches_manifest',
|
||||
|
@ -17,8 +19,15 @@ register_woocommerce_admin_test_helper_rest_route(
|
|||
* API endpoint to fetch the manifest of live branches.
|
||||
*/
|
||||
function fetch_live_branches_manifest() {
|
||||
$response = wp_remote_get( 'https://betadownload.jetpack.me/woocommerce-branches.json' );
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$response = wp_remote_get( 'https://betadownload.jetpack.me/woocommerce-branches.json' );
|
||||
$body = wp_remote_retrieve_body( $response );
|
||||
$installer = new WC_Beta_Tester_Live_Branches_Installer();
|
||||
|
||||
return new WP_REST_Response( json_decode( $body ), 200 );
|
||||
$obj = json_decode( $body );
|
||||
|
||||
foreach ( $obj->pr as $key => $value ) {
|
||||
$value->install_status = $installer->check_install_status( $value->version );
|
||||
}
|
||||
|
||||
return new WP_REST_Response( $obj, 200 );
|
||||
}
|
||||
|
|
|
@ -0,0 +1,4 @@
|
|||
Significance: minor
|
||||
Type: add
|
||||
|
||||
Add feature to install and activate live branches.
|
|
@ -0,0 +1,159 @@
|
|||
<?php
|
||||
/**
|
||||
* Beta Tester Plugin Live Branches feature class.
|
||||
*
|
||||
* @package WC_Beta_Tester
|
||||
*/
|
||||
|
||||
defined( 'ABSPATH' ) || exit;
|
||||
|
||||
require_once ABSPATH . 'wp-admin/includes/plugin.php';
|
||||
|
||||
const LIVE_BRANCH_PLUGIN_PREFIX = 'wc_beta_tester_live_branch';
|
||||
|
||||
/**
|
||||
* WC_Beta_Tester Live Branches Installer Class.
|
||||
*/
|
||||
class WC_Beta_Tester_Live_Branches_Installer {
|
||||
|
||||
/**
|
||||
* Keep an instance of the WP Filesystem API.
|
||||
*
|
||||
* @var Object The WP_Filesystem API instance
|
||||
*/
|
||||
private $file_system;
|
||||
|
||||
/**
|
||||
* Constructor.
|
||||
*/
|
||||
public function __construct() {
|
||||
$this->file_system = $this->init_filesystem();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the WP_Filesystem API
|
||||
*/
|
||||
private function init_filesystem() {
|
||||
require_once ABSPATH . 'wp-admin/includes/file.php';
|
||||
$creds = request_filesystem_credentials( site_url() . '/wp-admin/', '', false, false, array() );
|
||||
|
||||
if ( ! WP_Filesystem( $creds ) ) {
|
||||
return new WP_Error( 'fs_api_error', __( 'WooCommerce Beta Tester: No File System access', 'woocommerce-beta-tester' ) ); // @codingStandardsIgnoreLine.
|
||||
}
|
||||
|
||||
global $wp_filesystem;
|
||||
|
||||
return $wp_filesystem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Install a WooCommerce plugin version by download url.
|
||||
*
|
||||
* @param string $download_url The download url of the plugin version.
|
||||
* @param string $pr_name The name of the associated PR.
|
||||
* @param string $version The version of the plugin.
|
||||
*/
|
||||
public function install( $download_url, $pr_name, $version ) {
|
||||
// Download the plugin.
|
||||
$tmp_dir = download_url( $download_url );
|
||||
|
||||
if ( is_wp_error( $tmp_dir ) ) {
|
||||
return new WP_Error(
|
||||
'download_error',
|
||||
sprintf( __( 'Error Downloading: <a href="%1$s">%1$s</a> - Error: %2$s', 'woocommerce-beta-tester' ), $download_url, $tmp_dir->get_error_message() ) // @codingStandardsIgnoreLine.
|
||||
);
|
||||
}
|
||||
|
||||
// Unzip the plugin.
|
||||
$plugin_dir = str_replace( ABSPATH, $this->file_system->abspath(), WP_PLUGIN_DIR );
|
||||
$plugin_path = $plugin_dir . '/' . LIVE_BRANCH_PLUGIN_PREFIX . "_$version";
|
||||
$unzip_path = $plugin_dir . "/woocommerce-$version";
|
||||
|
||||
$unzip = unzip_file( $tmp_dir, $unzip_path );
|
||||
|
||||
// The plugin is nested under woocommerce-dev, so we need to move it up one level.
|
||||
$this->file_system->mkdir( $plugin_path );
|
||||
$this->move( $unzip_path . '/woocommerce-dev', $plugin_path );
|
||||
|
||||
if ( is_wp_error( $unzip ) ) {
|
||||
return new WP_Error( 'unzip_error', sprintf( __( 'Error Unzipping file: Error: %1$s', 'woocommerce-beta-tester' ), $result->get_error_message() ) ); // @codingStandardsIgnoreLine.
|
||||
}
|
||||
|
||||
// Delete the downloaded zip file.
|
||||
unlink( $tmp_dir );
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Move all files from one folder to another.
|
||||
*
|
||||
* @param string $from The folder to move files from.
|
||||
* @param string $to The folder to move files to.
|
||||
*/
|
||||
private function move( $from, $to ) {
|
||||
$files = scandir( $from );
|
||||
$oldfolder = "$from/";
|
||||
$newfolder = "$to/";
|
||||
|
||||
foreach ( $files as $fname ) {
|
||||
if ( '.' !== $fname && '..' !== $fname ) {
|
||||
$this->file_system->move( $oldfolder . $fname, $newfolder . $fname );
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Deactivate all currently active WooCommerce plugins.
|
||||
*/
|
||||
public function deactivate_woocommerce() {
|
||||
// First check is the regular woo plugin active.
|
||||
if ( is_plugin_active( 'woocommerce/woocommerce.php' ) ) {
|
||||
deactivate_plugins( 'woocommerce/woocommerce.php' );
|
||||
}
|
||||
|
||||
// Check if any beta tester installed plugins are active.
|
||||
$active_plugins = get_option( 'active_plugins' );
|
||||
|
||||
$active_woo_plugins = array_filter(
|
||||
$active_plugins,
|
||||
function( $plugin ) {
|
||||
return str_contains( $plugin, LIVE_BRANCH_PLUGIN_PREFIX );
|
||||
}
|
||||
);
|
||||
|
||||
if ( ! empty( $active_woo_plugins ) ) {
|
||||
deactivate_plugins( $active_woo_plugins );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Activate a beta tester installed WooCommerce plugin
|
||||
*
|
||||
* @param string $version The version of the plugin to activate.
|
||||
*/
|
||||
public function activate( $version ) {
|
||||
if ( ! is_plugin_active( LIVE_BRANCH_PLUGIN_PREFIX . "_$version/woocommerce.php" ) ) {
|
||||
activate_plugin( LIVE_BRANCH_PLUGIN_PREFIX . "_$version/woocommerce.php" );
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the install status of a plugin version.
|
||||
*
|
||||
* @param string $version The version of the plugin to check.
|
||||
*/
|
||||
public function check_install_status( $version ) {
|
||||
$plugin_path = WP_PLUGIN_DIR . '/' . LIVE_BRANCH_PLUGIN_PREFIX . "_$version/woocommerce.php";
|
||||
|
||||
if ( ! file_exists( $plugin_path ) ) {
|
||||
return 'not-installed';
|
||||
}
|
||||
|
||||
if ( is_plugin_active( LIVE_BRANCH_PLUGIN_PREFIX . "_$version/woocommerce.php" ) ) {
|
||||
return 'active';
|
||||
}
|
||||
|
||||
return 'installed';
|
||||
}
|
||||
}
|
|
@ -7,14 +7,42 @@ import {
|
|||
// @ts-ignore
|
||||
__experimentalItem as Item,
|
||||
Button,
|
||||
Spinner,
|
||||
} from '@wordpress/components';
|
||||
import { useState } from 'react';
|
||||
|
||||
/**
|
||||
* Internal dependencies
|
||||
*/
|
||||
import { Branch } from '../hooks/live-branches';
|
||||
import { Branch, useLiveBranchInstall } from '../hooks/live-branches';
|
||||
|
||||
const BranchListItem = ( { branch }: { branch: Branch } ) => {
|
||||
const { isError, isInProgress, installAndActivate, activate, status } =
|
||||
useLiveBranchInstall(
|
||||
branch.download_url,
|
||||
`https://github.com/woocommerce/woocommerce/pull/${ branch.pr }`,
|
||||
branch.version,
|
||||
branch.install_status
|
||||
);
|
||||
|
||||
const ActionButton = {
|
||||
'not-installed': () => (
|
||||
<Button variant="primary" onClick={ installAndActivate }>
|
||||
Install and Activate
|
||||
</Button>
|
||||
),
|
||||
installed: () => (
|
||||
<Button variant="primary" onClick={ activate }>
|
||||
Activate
|
||||
</Button>
|
||||
),
|
||||
active: () => (
|
||||
<Button variant="secondary" disabled>
|
||||
Activated
|
||||
</Button>
|
||||
),
|
||||
}[ status ];
|
||||
|
||||
return (
|
||||
<Item>
|
||||
<p>
|
||||
|
@ -29,21 +57,27 @@ const BranchListItem = ( { branch }: { branch: Branch } ) => {
|
|||
{ branch.branch }
|
||||
</a>
|
||||
</p>
|
||||
<Button
|
||||
variant="primary"
|
||||
onClick={ () => console.log( 'Do install stuffs' ) }
|
||||
>
|
||||
Install
|
||||
</Button>
|
||||
{ isError && <p>Something Went Wrong!</p> }
|
||||
{ isInProgress && <Spinner /> }
|
||||
{ ! isError && ! isInProgress && <ActionButton /> }
|
||||
</Item>
|
||||
);
|
||||
};
|
||||
|
||||
export const BranchList = ( { branches }: { branches: Branch[] } ) => {
|
||||
const activeBranch = branches.find(
|
||||
( branch ) => branch.install_status === 'active'
|
||||
);
|
||||
|
||||
const nonActiveBranches = branches.filter(
|
||||
( branch ) => branch.install_status !== 'active'
|
||||
);
|
||||
|
||||
return (
|
||||
<ItemGroup isSeparated>
|
||||
{ /* @ts-ignore */ }
|
||||
{ branches.map( ( branch ) => (
|
||||
{ /* Sort the active branch if it exists to the top of the list */ }
|
||||
{ activeBranch && <BranchListItem branch={ activeBranch } /> }
|
||||
{ nonActiveBranches.map( ( branch ) => (
|
||||
<BranchListItem key={ branch.commit } branch={ branch } />
|
||||
) ) }
|
||||
</ItemGroup>
|
||||
|
|
|
@ -4,6 +4,8 @@ import { useEffect, useState } from 'react';
|
|||
// @ts-ignore
|
||||
import { API_NAMESPACE } from '../../features/data/constants';
|
||||
|
||||
type PluginStatus = 'not-installed' | 'installed' | 'active';
|
||||
|
||||
export type Branch = {
|
||||
branch: string;
|
||||
commit: string;
|
||||
|
@ -11,6 +13,7 @@ export type Branch = {
|
|||
update_date: string;
|
||||
version: string;
|
||||
pr: number;
|
||||
install_status: PluginStatus;
|
||||
};
|
||||
|
||||
export const useLiveBranchesData = () => {
|
||||
|
@ -39,3 +42,101 @@ export const useLiveBranchesData = () => {
|
|||
|
||||
return { branches, isLoading: loading };
|
||||
};
|
||||
|
||||
export const useLiveBranchInstall = (
|
||||
downloadUrl: string,
|
||||
prName: string,
|
||||
version: string,
|
||||
status: PluginStatus
|
||||
) => {
|
||||
const [ isInProgress, setIsInProgress ] = useState( false );
|
||||
const [ isError, setIsError ] = useState( false );
|
||||
const [ pluginStatus, setPluginStatus ] = useState( status );
|
||||
|
||||
const activate = async () => {
|
||||
setIsInProgress( true );
|
||||
|
||||
try {
|
||||
const deactivateResult = await apiFetch< Response >( {
|
||||
path: `${ API_NAMESPACE }/live-branches/deactivate/v1`,
|
||||
} );
|
||||
|
||||
if ( deactivateResult.status >= 400 ) {
|
||||
throw new Error( 'Could not deactivate' );
|
||||
}
|
||||
|
||||
const activateResult = await apiFetch< Response >( {
|
||||
path: `${ API_NAMESPACE }/live-branches/activate/v1`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify( {
|
||||
version,
|
||||
} ),
|
||||
} );
|
||||
|
||||
if ( activateResult.status >= 400 ) {
|
||||
throw new Error( 'Could not activate' );
|
||||
}
|
||||
} catch ( e ) {
|
||||
setIsError( true );
|
||||
}
|
||||
|
||||
setPluginStatus( 'active' );
|
||||
setIsInProgress( false );
|
||||
};
|
||||
|
||||
const installAndActivate = async () => {
|
||||
setIsInProgress( true );
|
||||
|
||||
try {
|
||||
const installResult = await apiFetch< Response >( {
|
||||
path: `${ API_NAMESPACE }/live-branches/install/v1`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify( {
|
||||
pr_name: prName,
|
||||
download_url: downloadUrl,
|
||||
version,
|
||||
} ),
|
||||
} );
|
||||
|
||||
if ( installResult.status >= 400 ) {
|
||||
throw new Error( 'Could not install' );
|
||||
}
|
||||
|
||||
setPluginStatus( 'installed' );
|
||||
|
||||
const deactivateResult = await apiFetch< Response >( {
|
||||
path: `${ API_NAMESPACE }/live-branches/deactivate/v1`,
|
||||
} );
|
||||
|
||||
if ( deactivateResult.status >= 400 ) {
|
||||
throw new Error( 'Could not deactivate' );
|
||||
}
|
||||
|
||||
const activateResult = await apiFetch< Response >( {
|
||||
path: `${ API_NAMESPACE }/live-branches/activate/v1`,
|
||||
method: 'POST',
|
||||
body: JSON.stringify( {
|
||||
version,
|
||||
} ),
|
||||
} );
|
||||
|
||||
if ( activateResult.status >= 400 ) {
|
||||
throw new Error( 'Could not activate' );
|
||||
}
|
||||
|
||||
setPluginStatus( 'active' );
|
||||
} catch ( e ) {
|
||||
setIsError( true );
|
||||
}
|
||||
|
||||
setIsInProgress( false );
|
||||
};
|
||||
|
||||
return {
|
||||
installAndActivate,
|
||||
activate,
|
||||
isError,
|
||||
isInProgress,
|
||||
status: pluginStatus,
|
||||
};
|
||||
};
|
||||
|
|
Loading…
Reference in New Issue