From 7556aae9796dc267381f9a61e65728fed86c74f7 Mon Sep 17 00:00:00 2001 From: Maciej Kwiatkowski Date: Tue, 10 Sep 2024 22:35:44 +0200 Subject: [PATCH] SP-940 Adding BitPay Support Package download button --- .gitignore | 5 +- BitPayLib/class-bitpaylogger.php | 8 +- BitPayLib/class-bitpaypluginsetup.php | 16 ++ BitPayLib/class-bitpaysupportpackage.php | 198 +++++++++++++++++++++++ BitPayLib/class-wcgatewaybitpay.php | 20 +++ js/wc_gateway_bitpay.js | 25 +++ phpcs.xml | 9 +- scoper.inc.php | 2 + 8 files changed, 279 insertions(+), 4 deletions(-) create mode 100644 BitPayLib/class-bitpaysupportpackage.php diff --git a/.gitignore b/.gitignore index 9e22776a..5cd457b4 100644 --- a/.gitignore +++ b/.gitignore @@ -4,4 +4,7 @@ codeception.yml logs/*.* /.idea/ /vendor -/build \ No newline at end of file +/build +.phpunit.result.cache +tests/.env +tests/Unit/wp-config.php diff --git a/BitPayLib/class-bitpaylogger.php b/BitPayLib/class-bitpaylogger.php index eff9bb61..1fce6a9b 100644 --- a/BitPayLib/class-bitpaylogger.php +++ b/BitPayLib/class-bitpaylogger.php @@ -16,8 +16,7 @@ class BitPayLogger { public function execute( $msg, string $type, bool $is_array = false, $error = false ): void { $bitpay_checkout_options = get_option( 'woocommerce_bitpay_checkout_gateway_settings' ); - $log_directory = plugin_dir_path( __FILE__ ) . '..' . DIRECTORY_SEPARATOR . '..' - . DIRECTORY_SEPARATOR . 'logs/'; + $log_directory = $this->get_log_directory(); if ( ! file_exists( $log_directory ) && ! mkdir( $log_directory ) && ! is_dir( $log_directory ) ) { throw new \RuntimeException( sprintf( 'Directory "%s" was not created', esc_html( $log_directory ) ) ); } @@ -47,4 +46,9 @@ public function execute( $msg, string $type, bool $is_array = false, $error = fa } // @codingStandardsIgnoreEnd } + + public function get_log_directory(): string { + return plugin_dir_path( __FILE__ ) . '..' . DIRECTORY_SEPARATOR . '..' + . DIRECTORY_SEPARATOR . 'logs/'; + } } diff --git a/BitPayLib/class-bitpaypluginsetup.php b/BitPayLib/class-bitpaypluginsetup.php index 7a610abd..0510074d 100644 --- a/BitPayLib/class-bitpaypluginsetup.php +++ b/BitPayLib/class-bitpaypluginsetup.php @@ -25,6 +25,7 @@ class BitPayPluginSetup { private BitPayPaymentSettings $bitpay_payment_settings; private BitPayInvoiceCreate $bitpay_invoice_create; private BitPayCheckoutTransactions $bitpay_checkout_transactions; + private BitPaySupportPackage $bitpay_support_package; public function __construct() { $this->bitpay_payment_settings = new BitPayPaymentSettings(); @@ -42,6 +43,10 @@ public function __construct() { $wordpress_helper, $logger ); + $this->bitpay_support_package = new BitPaySupportPackage( + $wordpress_helper, + $logger + ); } public function execute(): void { @@ -82,6 +87,17 @@ function () { 'permission_callback' => '__return_true', ) ); + register_rest_route( + 'bitpay/site', + '/health-status', + array( + 'methods' => 'GET', + 'callback' => array( $this->bitpay_support_package, 'get_zip' ), + 'permission_callback' => function () { + return current_user_can( 'manage_woocommerce' ); + }, + ) + ); } ); } diff --git a/BitPayLib/class-bitpaysupportpackage.php b/BitPayLib/class-bitpaysupportpackage.php new file mode 100644 index 00000000..25d9382d --- /dev/null +++ b/BitPayLib/class-bitpaysupportpackage.php @@ -0,0 +1,198 @@ +bitpay_wordpress = $bitpay_wordpress; + $this->bitpay_logger = $bitpay_logger; + } + + public function get_zip(): WP_REST_Response { + $zipfile_string = $this->create_site_info_zip(); + + return $this->get_zip_rest_response( $zipfile_string ); + } + + private function create_site_info_zip(): string { + $json_data = $this->get_site_data_as_json(); + $tmp_file = tmpfile(); + $tmp_location = stream_get_meta_data( $tmp_file )['uri']; + + $zip = new \ZipArchive(); + + if ( true !== $zip->open( $tmp_location, \ZipArchive::CREATE ) ) { + throw new \RuntimeException( 'Could not create zip file' ); + } + + $zip->addFromString( 'site-info.json', $json_data ); + $log_directory = $this->bitpay_logger->get_log_directory(); + + if ( is_readable( $log_directory ) ) { + $zip->addGlob( + $log_directory . '*.log', + 0, + array( + 'remove_all_path' => true, + ) + ); + } + + $zip->close(); + $file_contents = file_get_contents( $tmp_location ); + + // This removes the file. + fclose( $tmp_file ); + + return $file_contents; + } + + private function get_site_data_as_json(): bool|string { + $active_plugins = get_plugins(); + $active_plugin_data = array(); + + foreach ( $active_plugins as $plugin_path => $plugin_data ) { + if ( is_plugin_active( $plugin_path ) ) { + $active_plugin_data[] = array( + 'name' => $plugin_data['Name'], + 'version' => $plugin_data['Version'], + ); + } + } + + $wpdb = $this->bitpay_wordpress->get_wpdb(); + $extension = null; + + // Populate the database debug fields. + if ( is_object( $wpdb->dbh ) ) { + // mysqli or PDO. + $extension = get_class( $wpdb->dbh ); + } + + $json_data = array( + 'bitpay_plugin_version' => BitPayPluginSetup::VERSION, + 'plugins' => $active_plugin_data, + 'database' => array( + array( + 'dbms' => $extension, + 'dbms_version' => $wpdb->get_var( 'SELECT VERSION()' ), // phpcs:ignore + 'char_set' => $wpdb->charset, + 'collation' => $wpdb->collate, + 'tables' => array( + array( + 'table' => '_bitpay_checkout_transactions', + 'exists' => $wpdb->get_var( "SHOW TABLES LIKE '{$wpdb->prefix}_bitpay_checkout_transactions'" ) ? 'yes' : 'no', + ), + ), + ), + ), + 'wordpress' => array( + array( + 'url' => home_url(), + 'version' => get_bloginfo( 'version' ), + ), + ), + 'server' => array( + array( + 'software' => $_SERVER['SERVER_SOFTWARE'], // phpcs:ignore + 'document_root' => $_SERVER['DOCUMENT_ROOT'], // phpcs:ignore + ), + ), + 'php' => array( + 'version' => phpversion(), + 'memory_limit' => ini_get( 'memory_limit' ), + 'max_execution_time' => ini_get( 'max_execution_time' ), + 'max_file_upload_size' => ini_get( 'upload_max_filesize' ), + 'max_post_size' => ini_get( 'post_max_size' ), + 'max_input_variables' => ini_get( 'max_input_vars' ), + 'curl_enabled' => function_exists( 'curl_version' ) ? 'yes' : 'no', + 'curl_version' => function_exists( 'curl_version' ) ? curl_version()['version'] : '', + 'openssl_version' => defined( 'OPENSSL_VERSION_TEXT' ) ? OPENSSL_VERSION_TEXT : '', + 'mcrypt_enabled' => function_exists( 'mcrypt_encrypt' ) ? 'yes' : 'no', + 'mbstring_enabled' => function_exists( 'mb_detect_encoding' ) ? 'yes' : 'no', + 'extensions' => get_loaded_extensions(), + ), + ); + + return json_encode( $json_data, JSON_PRETTY_PRINT ); + } + + /** + * Serves a zip via the REST endpoint. + * + * By default, every REST response is passed through json_encode(), as the + * typical REST response contains JSON data. + * + * This method hooks into the REST server to return a binary zip. + * + * @param string $data Data of the ZIP to serve. + * + * @return WP_REST_Response The REST response object to serve the zip. + */ + private function get_zip_rest_response( string $data ): WP_REST_Response { + $response = new WP_REST_Response(); + + $response->set_data( $data ); + $response->set_headers( + array( + 'Content-Type' => 'application/zip', + 'Content-Length' => strlen( $data ), + ) + ); + + // This filter will return our binary zip. + add_filter( 'rest_pre_serve_request', array( $this, 'serve_zip_action_handler' ), 0, 2 ); + + return $response; + } + + /** + * Action handler that is used by `get_zip_rest_response()` to serve a binary image + * instead of a JSON string. + * + * @param bool $served Whether the request has already been served. Default false. + * @param WP_HTTP_Response $result Result to send to the client. Usually a WP_REST_Response. + * + * @return bool Returns true, if the image was served; this will skip the + * default REST response logic. + * + * @see https://developer.wordpress.org/reference/hooks/rest_pre_serve_request/ + * */ + public function serve_zip_action_handler( bool $served, WP_HTTP_Response $result ): bool { + $is_zip = false; + $zip_data = null; + + // Check the "Content-Type" header to confirm that we really want to return + // binary zip data. + foreach ( $result->get_headers() as $header => $value ) { + if ( 'content-type' === strtolower( $header ) ) { + $is_zip = 0 === strpos( $value, 'application/zip' ); + $zip_data = $result->get_data(); + break; + } + } + + // Output the binary data and tell the REST server to not send any other + // details (via "return true"). + if ( $is_zip && is_string( $zip_data ) ) { + // phpcs:ignore + echo $zip_data; + + return true; + } + + return $served; + } +} diff --git a/BitPayLib/class-wcgatewaybitpay.php b/BitPayLib/class-wcgatewaybitpay.php index f9c5fd5c..2ce2fa28 100644 --- a/BitPayLib/class-wcgatewaybitpay.php +++ b/BitPayLib/class-wcgatewaybitpay.php @@ -344,4 +344,24 @@ private function get_bitpay_version_info(): string { return $plugin_name . ' ' . $plugin_data['Version']; } + + public function admin_options(): void { + parent::admin_options(); + $this->add_support_package_download_button(); + } + + private function add_support_package_download_button() { + ?> +
+
+

Support Package

+
+
+ +

Select to download a package of files that can be used for technical support. No personal + information will be captured.

+
+
+ ' ); } ) + + function downloadZipFile(blob, name) { + const a = document.createElement( 'a' ); + a.href = URL.createObjectURL( blob ); + a.download = name; + a.click(); + } + + document.getElementById( 'download_support_package' ).addEventListener( + 'click', + async function () { + const nonce = document.getElementById( '_wpnonce' ); + wp.apiFetch.use( wp.apiFetch.createNonceMiddleware( nonce ) ); + + const response = await wp.apiFetch( + { + path: '/bitpay/site/health-status', + parse: false + } + ); + const blob = await response.blob(); + + downloadZipFile( blob, 'bitpay-support-package.zip' ); + } + ); } ); diff --git a/phpcs.xml b/phpcs.xml index 09289484..ece088f0 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -10,4 +10,11 @@ - \ No newline at end of file + + + + + + + + diff --git a/scoper.inc.php b/scoper.inc.php index dfb153f9..f7974bf2 100644 --- a/scoper.inc.php +++ b/scoper.inc.php @@ -39,6 +39,8 @@ static function (string $filePath, string $prefix, string $contents): string { 'WP_User', 'WC_Order', 'WP_REST_Request', + 'WP_Rest_Response', + 'WP_Http_Response', 'WC_Admin_Settings', 'Automattic\WooCommerce\Blocks\Package', 'Automattic\WooCommerce\Blocks\Payments\PaymentMethodRegistry',