From 335876ddbd897bcd9870ee330234839461103eb3 Mon Sep 17 00:00:00 2001 From: Paul Kilmurray Date: Tue, 12 Dec 2023 16:26:02 +0100 Subject: [PATCH] refactor pro updater --- includes/AJAX.php | 23 +- .../Admin/Updaters/Pro_Plugin_Updater.php | 228 ++++++++---------- 2 files changed, 115 insertions(+), 136 deletions(-) diff --git a/includes/AJAX.php b/includes/AJAX.php index dc3ec65..2aac00c 100644 --- a/includes/AJAX.php +++ b/includes/AJAX.php @@ -3,6 +3,7 @@ namespace WCPOS\WooCommercePOS; use WCPOS\WooCommercePOS\Admin\Products\Single_Product; +use WCPOS\WooCommercePOS\Admin\Updaters\Pro_Plugin_Updater; class AJAX { /** @@ -44,7 +45,9 @@ class AJAX { 'woocommerce_get_order_details', ); - + /** + * Constructor. + */ public function __construct() { if ( ! isset( $_REQUEST['action'] ) ) { return; @@ -65,7 +68,8 @@ public function __construct() { } // we need to hook into these actions to save our custom fields via AJAX - add_action( 'woocommerce_product_quick_edit_save', + add_action( + 'woocommerce_product_quick_edit_save', array( '\WCPOS\WooCommercePOS\Admin\Products\List_Products', 'quick_edit_save', @@ -76,8 +80,10 @@ public function __construct() { /** * The Admin\Products\Single_Product class is not loaded for AJAX requests. * We need to load it manually here. + * + * @return void */ - public function load_single_product_class(): void { + public function load_single_product_class() { $single_product_class = apply_filters( 'woocommerce_pos_single_product_admin_ajax_class', Single_Product::class ); new $single_product_class(); } @@ -85,14 +91,9 @@ public function load_single_product_class(): void { /** * The Admin\Products\List_Products class is not loaded for AJAX requests. * We need to load it manually here. + * + * @return void */ - public function load_list_products_class(): void { - } - - /** - * The Admin\Orders class is not loaded for AJAX requests. - * We need to load it manually here. - */ - public function load_orders_class(): void { + public function load_list_products_class() { } } diff --git a/includes/Admin/Updaters/Pro_Plugin_Updater.php b/includes/Admin/Updaters/Pro_Plugin_Updater.php index c85b34e..45e5a50 100644 --- a/includes/Admin/Updaters/Pro_Plugin_Updater.php +++ b/includes/Admin/Updaters/Pro_Plugin_Updater.php @@ -74,20 +74,6 @@ class Pro_Plugin_Updater { */ private $current_version; - /** - * The license key - * - * @var string $license_key - */ - private $license_key; - - /** - * The instance - * - * @var string $instance - */ - private $instance; - /** * Constructor. */ @@ -101,20 +87,12 @@ public function __construct() { $this->current_version = $status['version']; if ( $this->installed ) { - $this->check_pro_plugin_updates(); - add_filter( 'site_transient_update_plugins', array( $this, 'modify_plugin_update_transient' ) ); + add_filter( 'update_plugins_updates.wcpos.com', array( $this, 'update_plugins' ), 10, 4 ); + // add_filter( 'site_transient_update_plugins', array( $this, 'modify_plugin_update_transient' ) ); add_action( 'upgrader_process_complete', array( $this, 'after_plugin_update' ), 10, 2 ); add_filter( 'plugin_row_meta', array( $this, 'plugin_row_meta' ), 10, 4 ); add_action( 'in_plugin_update_message-' . $this->pro_plugin_path, array( $this, 'plugin_update_message' ), 10, 2 ); add_action( 'install_plugins_pre_plugin-information', array( $this, 'plugin_information' ), 5 ); - // add_filter( 'upgrader_package_options', array( $this, 'upgrade_package' ) ); - - // get license key from settings. - $license_settings = woocommerce_pos_get_settings( 'license' ); - if ( isset( $license_settings['key'] ) && isset( $license_settings['instance'] ) ) { - $this->license_key = $license_settings['key']; - $this->instance = $license_settings['instance']; - } } } @@ -149,6 +127,79 @@ public function check_pro_plugin_status() { return $status; } + /** + * Filters the update response for a given plugin hostname. + * + * @param array|false $update { + * The plugin update data with the latest details. Default false. + * + * @type string $id Optional. ID of the plugin for update purposes, should be a URI + * specified in the `Update URI` header field. + * @type string $slug Slug of the plugin. + * @type string $version The version of the plugin. + * @type string $url The URL for details of the plugin. + * @type string $package Optional. The update ZIP for the plugin. + * @type string $tested Optional. The version of WordPress the plugin is tested against. + * @type string $requires_php Optional. The version of PHP which the plugin requires. + * @type bool $autoupdate Optional. Whether the plugin should automatically update. + * @type array $icons Optional. Array of plugin icons. + * @type array $banners Optional. Array of plugin banners. + * @type array $banners_rtl Optional. Array of plugin RTL banners. + * @type array $translations { + * Optional. List of translation updates for the plugin. + * + * @type string $language The language the translation update is for. + * @type string $version The version of the plugin this translation is for. + * This is not the version of the language file. + * @type string $updated The update timestamp of the translation file. + * Should be a date in the `YYYY-MM-DD HH:MM:SS` format. + * @type string $package The ZIP location containing the translation update. + * @type string $autoupdate Whether the translation should be automatically installed. + * } + * } + * @param array $plugin_data Plugin headers. + * @param string $plugin_file Plugin filename. + * @param string[] $locales Installed locales to look up translations for. + */ + public function update_plugins( $update, $plugin_data, $plugin_file, $locales ) { + $update_data = $this->check_pro_plugin_updates(); + $is_development = isset( $_ENV['DEVELOPMENT'] ) && $_ENV['DEVELOPMENT']; + + if ( ! is_wp_error( $update_data ) && is_object( $update_data ) && isset( $update_data->version ) ) { + $latest_version = $update_data->version; + + if ( version_compare( $this->current_version, $latest_version, '>' ) ) { + return $update; + } + + $license_settings = $this->get_license_settings(); + + $update = array( + 'id' => 'https://updates.wcpos.com', + 'slug' => 'woocommerce-pos-pro', + 'plugin' => $this->pro_plugin_path, + 'version' => $latest_version, + 'url' => 'https://wcpos.com/pro', + 'package' => add_query_arg( + array( + 'key' => isset( $license_settings['key'] ) ? $license_settings['key'] : '', + 'instance' => isset( $license_settings['instance'] ) ? $license_settings['instance'] : '', + ), + $is_development ? 'http://localhost:8080/pro/download/1.4.0' : $update_data->download_url + ), + 'requires' => '5.6', + 'tested' => '6.5', + 'requires_php' => '7.4', + 'icons' => array( + '1x' => 'https://wcpos.com/wp-content/uploads/2014/06/woopos-pro.png', + ), + 'upgrade_notice' => $this->maybe_add_upgrade_notice(), + ); + } + + return $update; + } + /** * Check for updates to the Pro plugin * @@ -157,10 +208,10 @@ public function check_pro_plugin_status() { public function check_pro_plugin_updates( $force = false ) { $update_data = get_transient( $this->update_data_transient_key ); $is_development = isset( $_ENV['DEVELOPMENT'] ) && $_ENV['DEVELOPMENT']; - $expiration = 60 * 60 * 12; // 12 hours. - $endpoint = $is_development ? 'http://localhost:8080/pro' : $this->update_server; if ( empty( $update_data ) || $force ) { + $expiration = 60 * 60 * 12; // 12 hours. + $endpoint = $is_development ? 'http://localhost:8080/pro' : $this->update_server; $url = $endpoint . '/update/' . $this->current_version; // make the api call. @@ -176,17 +227,20 @@ public function check_pro_plugin_updates( $force = false ) { $data = $this->validate_api_response( $response ); - // Ensure $data has the expected structure. - $expected_properties = array( 'version', 'download_url', 'notes' ); - foreach ( $expected_properties as $property ) { - if ( ! property_exists( $data, $property ) ) { - $data = new WP_Error( 'invalid_response_structure', "Missing expected property: $property" ); + // Ensure $data has version, download_url and notes. + if ( ! is_wp_error( $data ) ) { + $expected_properties = array( 'version', 'download_url', 'notes' ); + foreach ( $expected_properties as $property ) { + if ( ! property_exists( $data, $property ) ) { + $data = new WP_Error( 'invalid_response_structure', "Missing expected property: $property" ); + break; + } } } if ( is_wp_error( $data ) ) { Logger::log( $data ); - $expiration = $is_development ? 1 : 60 * 60 * 1; // try again in an hour if error. + $expiration = 60 * 60 * 1; // try again in an hour if error. } set_transient( $this->update_data_transient_key, $data, $expiration ); @@ -218,7 +272,8 @@ private function check_license_status( $force = false ) { /** * If the Pro plugin is not activated, add a notice */ - if ( ! $this->license_key || ! $this->instance ) { + $license_settings = $this->get_license_settings(); + if ( empty( $license_settings['key'] ) || empty( $license_settings['instance'] ) ) { // set the transient to an error. $error = new WP_Error( 'missing_license_key', 'License key is not activated.' ); set_transient( $this->license_status_transient_key, $error, $expiration ); @@ -229,8 +284,8 @@ private function check_license_status( $force = false ) { // build the request. $url = add_query_arg( array( - 'key' => $this->license_key, - 'instance' => $this->instance, + 'key' => $license_settings['key'], + 'instance' => $license_settings['instance'], ), $endpoint . '/license/status' ); @@ -276,13 +331,9 @@ public function validate_api_response( $response ) { return new WP_Error( 'invalid_json', 'Invalid JSON in response', $response['body'] ); } - if ( $response['response']['code'] === 400 ) { + if ( $response['response']['code'] !== 200 ) { $error = isset( $decoded_response->error ) ? $decoded_response->error : 'No error message returned from server'; - return new WP_Error( 'server_error', $error ); - } - - if ( $response['response']['code'] != 200 ) { - return new WP_Error( 'invalid_response_code', 'Unexpected response code: ' . $response['response']['code'] ); + return new WP_Error( 'invalid_response_code', $error ); } // Ensure $decoded_response has the expected structure. @@ -293,55 +344,6 @@ public function validate_api_response( $response ) { return $decoded_response->data; } - /** - * Modify the plugin update transient to include the Pro plugin - * - * @param object $transient The plugin update transient. - * - * @return object $transient The modified plugin update transient. - */ - public function modify_plugin_update_transient( $transient ) { - $update_data = get_transient( $this->update_data_transient_key ); - $is_development = isset( $_ENV['DEVELOPMENT'] ) && $_ENV['DEVELOPMENT']; - - /** - * NOTE: sometimes the $transient = false, ie: after updates. - */ - if ( empty( $update_data ) || false === $transient ) { - return $transient; - } - - if ( ! is_wp_error( $update_data ) && is_object( $update_data ) && isset( $update_data->version ) ) { - $latest_version = $update_data->version; - - if ( version_compare( $this->current_version, $latest_version, '>' ) ) { - return $transient; - } - - // check and make sure the update is not already in the transient. - if ( isset( $transient->response ) && ! isset( $transient->response[ $this->pro_plugin_path ] ) ) { - $transient->response[ $this->pro_plugin_path ] = $this->create_update_response_object( - array( - 'new_version' => $latest_version, - 'package' => add_query_arg( - array( - 'key' => $this->license_key, - 'instance' => $this->instance, - ), - $is_development ? 'http://localhost:8080/pro/download/1.4.0' : $update_data->download_url - ), - 'release_notes' => $update_data->notes, - /** - * NOTE: Upgrade Notice only seems to appear on the Dashboard > Updates page - */ - 'upgrade_notice' => $this->maybe_add_upgrade_notice(), - ) - ); - } - } - - return $transient; - } /** * Create an update response object for the WordPress transient @@ -590,43 +592,19 @@ private function maybe_add_upgrade_notice() { } /** - * Filters the package options before running an update. - * - * @param array $options { - * Options used by the upgrader. + * Get the license settings * - * @type string $package Package for update. - * @type string $destination Update location. - * @type bool $clear_destination Clear the destination resource. - * @type bool $clear_working Clear the working resource. - * @type bool $abort_if_destination_exists Abort if the Destination directory exists. - * @type bool $is_multi Whether the upgrader is running multiple times. - * @type array $hook_extra { - * Extra hook arguments. - * - * @type string $action Type of action. Default 'update'. - * @type string $type Type of update process. Accepts 'plugin', 'theme', or 'core'. - * @type bool $bulk Whether the update process is a bulk update. Default true. - * @type string $plugin Path to the plugin file relative to the plugins directory. - * @type string $theme The stylesheet or template name of the theme. - * @type string $language_update_type The language pack update type. Accepts 'plugin', 'theme', - * or 'core'. - * @type object $language_update The language pack update offer. - * } - * } - * @return array Modified options array. + * NOTE: it's possible the Pro plugin is not activated, so we need to add a bit of a hack to get the settings. */ - public function upgrade_package( $options ) { - if ( isset( $options['hook_extra'], $options['hook_extra']['plugin'] ) && $options['hook_extra']['plugin'] === $this->pro_plugin_path ) { - $options['package'] = add_query_arg( - array( - 'key' => $this->license_key, - 'instance' => $this->instance, - ), - $options['package'] - ); + private function get_license_settings() { + if ( ! $this->active ) { + if ( file_exists( WP_PLUGIN_DIR . '/woocommerce-pos-pro/includes/Services/Settings.php' ) ) { + include_once WP_PLUGIN_DIR . '/woocommerce-pos-pro/includes/Services/Settings.php'; + if ( method_exists( '\WCPOS\WooCommercePOSPro\Services\Settings', '_get_inactive_license_settings' ) ) { + return \WCPOS\WooCommercePOSPro\Services\Settings::_get_inactive_license_settings(); + } + } } - - return $options; + return woocommerce_pos_get_settings( 'license' ); } }