From b8551cc0475289cc64619190256637a53d5e9841 Mon Sep 17 00:00:00 2001 From: Anichur Rahaman <38912435+anisAronno@users.noreply.github.com> Date: Wed, 10 Jul 2024 17:37:11 +0600 Subject: [PATCH 1/2] Implement release wise WP management. --- composer.json | 4 +- composer.lock | 61 ++++- src/Updater.php | 677 +++++++++++++++++++++++++++++------------------- 3 files changed, 470 insertions(+), 272 deletions(-) diff --git a/composer.json b/composer.json index 9477997..3988221 100644 --- a/composer.json +++ b/composer.json @@ -14,5 +14,7 @@ "Appsero\\": "src/" } }, - "require": {} + "require": { + "appsero/client": "^2.0" + } } diff --git a/composer.lock b/composer.lock index 7cfe760..da15e55 100644 --- a/composer.lock +++ b/composer.lock @@ -4,8 +4,63 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "544fff9097a4a74ed06dabd6548f01ab", - "packages": [], + "content-hash": "c01d37d47aae63ca76c84bcc896f65d9", + "packages": [ + { + "name": "appsero/client", + "version": "v2.0.2", + "source": { + "type": "git", + "url": "https://github.com/Appsero/client.git", + "reference": "b61c3ab21df4d44f805ee9476f9d880f8370a36b" + }, + "dist": { + "type": "zip", + "url": "https://api.github.com/repos/Appsero/client/zipball/b61c3ab21df4d44f805ee9476f9d880f8370a36b", + "reference": "b61c3ab21df4d44f805ee9476f9d880f8370a36b", + "shasum": "" + }, + "require": { + "php": ">=5.6" + }, + "require-dev": { + "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", + "phpcompatibility/phpcompatibility-wp": "dev-master", + "phpunit/phpunit": "^8.5.31", + "squizlabs/php_codesniffer": "^3.7", + "tareq1988/wp-php-cs-fixer": "dev-master", + "wp-coding-standards/wpcs": "dev-develop" + }, + "type": "library", + "autoload": { + "psr-4": { + "Appsero\\": "src/" + } + }, + "notification-url": "https://packagist.org/downloads/", + "license": [ + "MIT" + ], + "authors": [ + { + "name": "Tareq Hasan", + "email": "tareq@appsero.com" + } + ], + "description": "Appsero Client", + "keywords": [ + "analytics", + "plugin", + "theme", + "wordpress" + ], + "support": { + "issues": "https://github.com/Appsero/client/issues", + "source": "https://github.com/Appsero/client/tree/v2.0.2" + }, + "time": "2024-01-30T08:15:01+00:00" + } + ], "packages-dev": [], "aliases": [], "minimum-stability": "stable", @@ -14,5 +69,5 @@ "prefer-lowest": false, "platform": [], "platform-dev": [], - "plugin-api-version": "2.3.0" + "plugin-api-version": "2.6.0" } diff --git a/src/Updater.php b/src/Updater.php index 5c1abe1..e77f80d 100644 --- a/src/Updater.php +++ b/src/Updater.php @@ -7,272 +7,413 @@ /** * Appsero Updater * - * This class will show new updates project + * This class will show new updates for the project */ -class Updater { - - /** - * Appsero\Client - * - * @var object - */ - protected $client; - - /** - * Object of Updater - * - * @var object - */ - protected static $instance; - - /** - * Cache key - * - * @var string - */ - protected $cache_key; - - /** - * Initialize the class - * - * @param $client - */ - public function __construct( $client ) { - $this->client = $client; - $this->cache_key = 'appsero_' . md5( $this->client->slug ) . '_version_info'; - - // Run hooks. - if ( $this->client->type === 'plugin' ) { - $this->run_plugin_hooks(); - } elseif ( $this->client->type === 'theme' ) { - $this->run_theme_hooks(); - } - } - - public static function init($client) - { - if (!self::$instance) { - self::$instance = new self($client); - } - - return self::$instance; - } - - /** - * Set up WordPress filter to hooks to get update. - * - * @return void - */ - public function run_plugin_hooks() { - add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'check_plugin_update' ] ); - add_filter( 'plugins_api', [ $this, 'plugins_api_filter' ], 10, 3 ); - } - - /** - * Set up WordPress filter to hooks to get update. - * - * @return void - */ - public function run_theme_hooks() { - add_filter( 'pre_set_site_transient_update_themes', [ $this, 'check_theme_update' ] ); - } - - /** - * Check for Update for this specific project - */ - public function check_plugin_update( $transient_data ) { - global $pagenow; - - if ( ! is_object( $transient_data ) ) { - $transient_data = new stdClass(); - } - - if ( 'plugins.php' === $pagenow && is_multisite() ) { - return $transient_data; - } - - if ( ! empty( $transient_data->response ) && ! empty( $transient_data->response[ $this->client->basename ] ) ) { - return $transient_data; - } - - $version_info = $this->get_version_info(); - - if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) { - unset( $version_info->sections ); - - // If new version available then set to `response` - if ( version_compare( $this->client->project_version, $version_info->new_version, '<' ) ) { - $transient_data->response[ $this->client->basename ] = $version_info; - } else { - // If new version is not available then set to `no_update` - $transient_data->no_update[ $this->client->basename ] = $version_info; - } - - $transient_data->last_checked = time(); - $transient_data->checked[ $this->client->basename ] = $this->client->project_version; - } - - return $transient_data; - } - - /** - * Get version info from database - * - * @return object or Boolean - */ - private function get_cached_version_info() { - global $pagenow; - - // If updater page then fetch from API now - if ( 'update-core.php' === $pagenow ) { - return false; // Force to fetch data - } - - $value = get_transient( $this->cache_key ); - - if ( ! $value && ! isset( $value->name ) ) { - return false; // Cache is expired - } - - // We need to turn the icons into an array - if ( isset( $value->icons ) ) { - $value->icons = (array) $value->icons; - } - - // We need to turn the banners into an array - if ( isset( $value->banners ) ) { - $value->banners = (array) $value->banners; - } - - if ( isset( $value->sections ) ) { - $value->sections = (array) $value->sections; - } - - return $value; - } - - /** - * Set version info to database - */ - private function set_cached_version_info( $value ) { - if ( ! $value ) { - return; - } - - set_transient( $this->cache_key, $value, 3 * HOUR_IN_SECONDS ); - } - - /** - * Get plugin info from Appsero - */ - private function get_project_latest_version() { - $license = $this->client->license()->get_license(); - - $params = [ - 'version' => $this->client->project_version, - 'name' => $this->client->name, - 'slug' => $this->client->slug, - 'basename' => $this->client->basename, - 'license_key' => ! empty( $license ) && isset( $license['key'] ) ? $license['key'] : '', - ]; - - $route = 'update/' . $this->client->hash . '/check'; - - $response = $this->client->send_request( $params, $route, true ); - - if ( is_wp_error( $response ) ) { - return false; - } - - $response = json_decode( wp_remote_retrieve_body( $response ) ); - - if ( ! isset( $response->slug ) ) { - return false; - } - - if ( isset( $response->icons ) ) { - $response->icons = (array) $response->icons; - } - - if ( isset( $response->banners ) ) { - $response->banners = (array) $response->banners; - } - - if ( isset( $response->sections ) ) { - $response->sections = (array) $response->sections; - } - - return $response; - } - - /** - * Updates information on the "View version x.x details" page with custom data. - * - * @param mixed $data - * @param string $action - * @param object $args - * - * @return object $data - */ - public function plugins_api_filter( $data, $action = '', $args = null ) { - if ( $action !== 'plugin_information' ) { - return $data; - } - - if ( ! isset( $args->slug ) || ( $args->slug !== $this->client->slug ) ) { - return $data; - } - - return $this->get_version_info(); - } - - /** - * Check theme upate - */ - public function check_theme_update( $transient_data ) { - global $pagenow; - - if ( ! is_object( $transient_data ) ) { - $transient_data = new stdClass(); - } - - if ( 'themes.php' === $pagenow && is_multisite() ) { - return $transient_data; - } - - if ( ! empty( $transient_data->response ) && ! empty( $transient_data->response[ $this->client->slug ] ) ) { - return $transient_data; - } - - $version_info = $this->get_version_info(); - - if ( false !== $version_info && is_object( $version_info ) && isset( $version_info->new_version ) ) { - // If new version available then set to `response` - if ( version_compare( $this->client->project_version, $version_info->new_version, '<' ) ) { - $transient_data->response[ $this->client->slug ] = (array) $version_info; - } else { - // If new version is not available then set to `no_update` - $transient_data->no_update[ $this->client->slug ] = (array) $version_info; - } - - $transient_data->last_checked = time(); - $transient_data->checked[ $this->client->slug ] = $this->client->project_version; - } - - return $transient_data; - } - - /** - * Get version information - */ - private function get_version_info() { - $version_info = $this->get_cached_version_info(); - - if ( false === $version_info ) { - $version_info = $this->get_project_latest_version(); - $this->set_cached_version_info( $version_info ); - } - - return $version_info; - } -} +class Updater +{ + /** + * Appsero\Client + * + * @var object + */ + protected $client; + + /** + * Object of Updater + * + * @var object + */ + protected static $instance; + + /** + * Cache key + * + * @var string + */ + protected $cache_key; + + /** + * Initialize the class + * + * @param object $client + */ + public function __construct($client) + { + $this->client = $client; + $this->cache_key = 'appsero_' . md5(sanitize_key($this->client->slug)) . '_version_info'; + + // Run hooks based on the client type + if ($this->client->type === 'plugin') { + $this->run_plugin_hooks(); + } elseif ($this->client->type === 'theme') { + $this->run_theme_hooks(); + } + } + + /** + * Initialize the Updater + * + * @param object $client + * @return object + */ + public static function init($client) + { + if (!self::$instance) { + self::$instance = new self($client); + } + + return self::$instance; + } + + /** + * Set up WordPress filter hooks to get plugin updates + * + * @return void + */ + public function run_plugin_hooks() + { + add_action('admin_init', [$this, 'check_plugin_update']); + } + + /** + * Set up WordPress filter hooks to get theme updates + * + * @return void + */ + public function run_theme_hooks() + { + add_filter('pre_set_site_transient_update_themes', [$this, 'check_theme_update']); + } + + /** + * Check for plugin updates + * + * @param object $transient_data + * @return object + */ + public function check_plugin_update($transient_data) + { + global $pagenow; + + if (!is_object($transient_data)) { + $transient_data = new stdClass(); + } + + if ('plugins.php' === $pagenow && is_multisite()) { + return $transient_data; + } + + if (!empty($transient_data->response) && !empty($transient_data->response[$this->client->basename])) { + return $transient_data; + } + + $version_info = $this->get_version_info(); + + if (false !== $version_info && is_object($version_info) && isset($version_info->new_version)) { + unset($version_info->sections); + + // If new version available, set to response + if (version_compare($this->client->project_version, $version_info->new_version, '<')) { + $warnings = $this->check_required_plugins($version_info->required_plugins); + + if (!empty($warnings)) { + $this->show_warning_notice($warnings); + } else { + $transient_data->response[$this->client->basename] = $version_info; + add_filter('pre_set_site_transient_update_plugins', function () use ($transient_data) { + return $transient_data; + }); + add_filter('plugins_api', [$this, 'plugins_api_filter'], 10, 3); + } + } else { + $transient_data->no_update[$this->client->basename] = $version_info; + } + + $transient_data->last_checked = time(); + $transient_data->checked[$this->client->basename] = $this->client->project_version; + } + + return $transient_data; + } + + /** + * Get cached version info from the database + * + * @return object|bool + */ + private function get_cached_version_info() + { + global $pagenow; + + // If updater page, fetch from API now + if ('update-core.php' === $pagenow) { + return false; // Force to fetch data + } + + $value = get_transient($this->cache_key); + + if (!$value || !isset($value->name)) { + return false; // Cache is expired + } + + // Turn the icons into an array + if (isset($value->icons)) { + $value->icons = (array)$value->icons; + } + + // Turn the banners into an array + if (isset($value->banners)) { + $value->banners = (array)$value->banners; + } + + if (isset($value->sections)) { + $value->sections = (array)$value->sections; + } + + return $value; + } + + /** + * Set version info to the database + * + * @param object $value + * @return void + */ + private function set_cached_version_info($value) + { + if (!$value) { + return; + } + + set_transient($this->cache_key, $value, 3 * HOUR_IN_SECONDS); + } + + /** + * Get project latest version info from Appsero + * + * @return object|bool + */ + private function get_project_latest_version() + { + $license = $this->client->license()->get_license(); + + $params = [ + 'version' => $this->client->project_version, + 'name' => sanitize_text_field($this->client->name), + 'slug' => sanitize_key($this->client->slug), + 'basename' => sanitize_text_field($this->client->basename), + 'license_key' => !empty($license) && isset($license['key']) ? sanitize_text_field($license['key']) : '', + ]; + + $route = 'v2/update/' . $this->client->hash . '/check'; + + $response = $this->client->send_request($params, $route, true); + + if (is_wp_error($response)) { + return false; + } + + $response = json_decode(wp_remote_retrieve_body($response)); + + if (!isset($response->slug)) { + return false; + } + + if (isset($response->icons)) { + $response->icons = (array)$response->icons; + } + + if (isset($response->banners)) { + $response->banners = (array)$response->banners; + } + + if (isset($response->sections)) { + $response->sections = (array)$response->sections; + } + + return $response; + } + + /** + * Update information on the "View version x.x details" page with custom data + * + * @param mixed $data + * @param string $action + * @param object $args + * @return object + */ + public function plugins_api_filter($data, $action = '', $args = null) + { + if ($action !== 'plugin_information') { + return $data; + } + + if (!isset($args->slug) || ($args->slug !== $this->client->slug)) { + return $data; + } + + return $this->get_version_info(); + } + + /** + * Check for theme updates + * + * @param object $transient_data + * @return object + */ + public function check_theme_update($transient_data) + { + global $pagenow; + + if (!is_object($transient_data)) { + $transient_data = new stdClass(); + } + + if ('themes.php' === $pagenow && is_multisite()) { + return $transient_data; + } + + if (!empty($transient_data->response) && !empty($transient_data->response[$this->client->slug])) { + return $transient_data; + } + + $version_info = $this->get_version_info(); + + if (false !== $version_info && is_object($version_info) && isset($version_info->new_version)) { + // If new version available, set to response + if (version_compare($this->client->project_version, $version_info->new_version, '<')) { + $transient_data->response[$this->client->slug] = (array)$version_info; + } else { + // If new version is not available, set to no_update + $transient_data->no_update[$this->client->slug] = (array)$version_info; + } + + $transient_data->last_checked = time(); + $transient_data->checked[$this->client->slug] = $this->client->project_version; + } + + return $transient_data; + } + + /** + * Get version information + * + * @return object|bool + */ + private function get_version_info() + { + $version_info = $this->get_cached_version_info(); + + if (false === $version_info) { + $version_info = $this->get_project_latest_version(); + $this->set_cached_version_info($version_info); + } + + return $version_info; + } + + /** + * Check required plugins + * + * @param array $required_plugins + * @return array + */ + private function check_required_plugins($required_plugins = []) + { + $warnings = []; + + if (!function_exists('get_plugins')) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + $installed_plugins = get_plugins(); + + foreach ($required_plugins as $plugin_slug => $required_version) { + $plugin_file = $this->get_plugin_file($plugin_slug, $installed_plugins); + $plugin_name = isset($installed_plugins[$plugin_file]) ? $installed_plugins[$plugin_file]['Name'] : $plugin_slug; + + if (!$plugin_file) { + $warnings[] = $this->client->__trans( sprintf('%s (version %s) is required but not installed.', $plugin_name, $required_version) ); + } elseif (!is_plugin_active($plugin_file)) { + $warnings[] = $this->client->__trans( sprintf('%s (version %s) is required but not active.', $plugin_name, $required_version) ); + } else { + $installed_version = $installed_plugins[$plugin_file]['Version']; + if (version_compare($installed_version, $required_version, '<')) { + $warnings[] = $this->client->__trans( sprintf('%s requires version %s, but %s is installed. Please update %s.', $plugin_name, $required_version, $installed_version, $plugin_name) ); + } + } + } + + return $warnings; + } + + /** + * Get plugin file from slug + * + * @param string $plugin_slug + * @param array $installed_plugins + * @return string|null + */ + private function get_plugin_file($plugin_slug, $installed_plugins) + { + foreach ($installed_plugins as $plugin_file => $plugin_info) { + if (strpos($plugin_file, $plugin_slug . '/') === 0) { + return $plugin_file; + } + } + + return null; + } + + /** + * Show warning notice for required plugins + * + * @param array $warnings + * @return void + */ + public function show_warning_notice($warnings) + { + add_action("after_plugin_row_{$this->client->basename}", function ($plugin_file, $plugin_data, $status) use ($warnings) { + $this->add_custom_plugin_row($plugin_file, $plugin_data, $status, $warnings); + }, 10, 3); + } + + /** + * Add custom plugin row with warnings + * + * @param string $plugin_file + * @param array $plugin_data + * @param string $status + * @param array $warnings + * @return void + */ + public function add_custom_plugin_row($plugin_file, $plugin_data, $status, $warnings) + { + $plugin_slug = dirname($plugin_file); + $wp_list_table = _get_list_table('WP_Plugins_List_Table', ['screen' => get_current_screen()]); + $column_count = esc_attr($wp_list_table->get_column_count()); + + $version_info = $this->get_version_info(); + $new_version = $version_info->new_version; + + printf( + '' . + '' . + '
' . + '

%s

' . + '

%s

' . + '
', + esc_attr($plugin_slug), + esc_attr($plugin_slug), + esc_attr($plugin_file), + $column_count, + esc_html(sprintf( + __('%s %s is available. %s', 'appsero'), + $plugin_data['Name'], + $new_version, + __('Please resolve the following issues to update the plugin:', 'appsero') + )), + esc_html(implode(' ', $warnings)) + ); + } +} \ No newline at end of file From 21c4a2706ef0a7e9bc000273c384622d3f82c387 Mon Sep 17 00:00:00 2001 From: Anichur Rahaman <38912435+anisAronno@users.noreply.github.com> Date: Tue, 30 Jul 2024 11:29:01 +0600 Subject: [PATCH 2/2] Remove Client Package from composer required --- composer.json | 3 --- composer.lock | 59 ++------------------------------------------------- 2 files changed, 2 insertions(+), 60 deletions(-) diff --git a/composer.json b/composer.json index 3988221..7fcbb5e 100644 --- a/composer.json +++ b/composer.json @@ -13,8 +13,5 @@ "psr-4": { "Appsero\\": "src/" } - }, - "require": { - "appsero/client": "^2.0" } } diff --git a/composer.lock b/composer.lock index da15e55..6a977c1 100644 --- a/composer.lock +++ b/composer.lock @@ -4,63 +4,8 @@ "Read more about it at https://getcomposer.org/doc/01-basic-usage.md#installing-dependencies", "This file is @generated automatically" ], - "content-hash": "c01d37d47aae63ca76c84bcc896f65d9", - "packages": [ - { - "name": "appsero/client", - "version": "v2.0.2", - "source": { - "type": "git", - "url": "https://github.com/Appsero/client.git", - "reference": "b61c3ab21df4d44f805ee9476f9d880f8370a36b" - }, - "dist": { - "type": "zip", - "url": "https://api.github.com/repos/Appsero/client/zipball/b61c3ab21df4d44f805ee9476f9d880f8370a36b", - "reference": "b61c3ab21df4d44f805ee9476f9d880f8370a36b", - "shasum": "" - }, - "require": { - "php": ">=5.6" - }, - "require-dev": { - "dealerdirect/phpcodesniffer-composer-installer": "^0.7.2", - "phpcompatibility/phpcompatibility-wp": "dev-master", - "phpunit/phpunit": "^8.5.31", - "squizlabs/php_codesniffer": "^3.7", - "tareq1988/wp-php-cs-fixer": "dev-master", - "wp-coding-standards/wpcs": "dev-develop" - }, - "type": "library", - "autoload": { - "psr-4": { - "Appsero\\": "src/" - } - }, - "notification-url": "https://packagist.org/downloads/", - "license": [ - "MIT" - ], - "authors": [ - { - "name": "Tareq Hasan", - "email": "tareq@appsero.com" - } - ], - "description": "Appsero Client", - "keywords": [ - "analytics", - "plugin", - "theme", - "wordpress" - ], - "support": { - "issues": "https://github.com/Appsero/client/issues", - "source": "https://github.com/Appsero/client/tree/v2.0.2" - }, - "time": "2024-01-30T08:15:01+00:00" - } - ], + "content-hash": "e5f94ee0b2ada09e53c33a569b9cf4d5", + "packages": [], "packages-dev": [], "aliases": [], "minimum-stability": "stable",