From 17bbfe2622c9b57de9673e4255765b0b256b101d Mon Sep 17 00:00:00 2001 From: David Levine Date: Sat, 24 Jun 2023 22:31:46 +0000 Subject: [PATCH 1/5] dev: scaffold Updater PHP --- src/Admin/Updater.php | 411 ++++++++++++++++++++++++++++++++++++++++++ src/class-acf.php | 39 ++-- 2 files changed, 431 insertions(+), 19 deletions(-) create mode 100644 src/Admin/Updater.php diff --git a/src/Admin/Updater.php b/src/Admin/Updater.php new file mode 100644 index 0000000..05a9f1e --- /dev/null +++ b/src/Admin/Updater.php @@ -0,0 +1,411 @@ +plugin_config = [ + 'plugin_file' => WPGRAPHQL_ACF_PLUGIN_FILE, + 'slug' => self::PLUGIN_SLUG, + 'proper_folder_name' => self::PLUGIN_SLUG, + 'api_url' => 'https://api.wordpress.org/plugins/info/1.0/' . self::PLUGIN_SLUG . '.json', + 'repo_url' => 'https://wordpress.org/plugins/' . self::PLUGIN_SLUG, + ]; + } + + /** + * Sets up the hooks. + */ + public function init() : void { + add_filter( 'auto_update_plugin', [ $this, 'disable_autoupdate' ], 10, 2 ); + add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'api_check' ] ); + add_filter( 'plugins_api_result', [ $this, 'api_result' ], 10, 3 ); + add_filter( 'upgrader_source_selection', [ $this, 'upgrader_source_selection' ], 10, 2 ); + } + + /** + * Disable auto updates for major releases of this plugin. + * + * @param bool|null $update Whether to update. + * @param object $item The plugin object. + * + * @return bool|null + */ + public function disable_autoupdate( $update, $item ) { + // Return early if this is not our plugin. + if ( $item->slug !== $this->plugin_config['slug'] ) { + return $update; + } + + // Bail if there's no new version. + if ( empty( $item->new_version ) ) { + return $update; + } + + // Get the update type. + $update_type = self::get_semver_update_type( $item->new_version, WPGRAPHQL_ACF_VERSION ); + + // Non-'major' updates are allowed. + if ( 'major' !== $update_type ) { + return $update; + } + + // Major updates should never happen automatically. + return false; + } + + /** + * Hooks into the plugin upgrader to get the correct plugin version we want to install. + * + * @param object $transient The plugin upgrader transient. + * + * @return object + */ + public function api_check( $transient ) { + // Clear the transient. + delete_site_transient( self::VERSION_TRANSIENT ); + + // Get the latest version we allow to be installed from this version. + // In the new codebase, we'll just do this on semver-autoupdates. + $plugin_data = $this->get_plugin_data(); + $version = $plugin_data['Version']; + $new_version = $this->get_latest_version(); + + // Check if this is a version update. + $is_update = version_compare( $new_version, $version, '>' ); + + if ( ! $is_update ) { + return $transient; + } + + // Get the download URL for reuse. + $download_url = $this->get_download_url( $new_version ); + + // Populate the transient data. + if ( ! isset( $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ] ) ) { + $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ] = (object) $this->plugin_config; + } + + $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ]->new_version = $new_version; + $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ]->package = $download_url; + $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ]->zip_url = $download_url; + + return $transient; + } + + /** + * Filters the Installation API response result + * + * @param object|\WP_Error $response The API response object. + * @param string $action The type of information being requested from the Plugin Installation API. + * @param object $args Plugin API arguments. + * + * @return object|\WP_Error + */ + public function api_result( $response, $action, $args ) { + // Bail if this is not checking our plugin. + if ( ! isset( $args->slug ) || $args->slug !== $this->plugin_config['slug'] ) { + return $response; + } + + // Get the latest version. + $new_version = $this->get_latest_version(); + + // Bail if the version is not newer. + if ( version_compare( $new_version, $args->version, '<=' ) ) { + return $response; + } + + // If we're returning a different version than the latest from WP.org, override the response. + $response->version = $new_version; + $response->download_link = $this->get_download_url( $new_version ); + + // If this is a major update, add a warning. + $update_type = self::get_semver_update_type( $new_version, WPGRAPHQL_ACF_VERSION ); + $warning = ''; + + if ( 'major' === $update_type ) { + $warning = sprintf( + /* translators: %s: version number. */ + __( '

%s

', 'wp-graphql-acf' ), + self::get_breaking_change_message( $new_version ) + ); + } + + // If there is a warning, append it to each section. + if ( '' !== $warning ) { + foreach ( $response->sections as $key => $section ) { + $response->sections[ $key ] = $warning . $section; + } + } + + return $response; + } + + /** + * Rename the downloaded zip + * + * @param string $source File source location. + * @param string $remote_source Remote file source location. + * + * @return string|\WP_Error + */ + public function upgrader_source_selection( $source, $remote_source ) { + global $wp_filesystem; + + if ( strstr( $source, '/wp-graphql-acf' ) ) { + $corrected_source = trailingslashit( $remote_source ) . trailingslashit( $this->plugin_config['proper_folder_name'] ); + + if ( $wp_filesystem->move( $source, $corrected_source, true ) ) { + return $corrected_source; + } else { + return new \WP_Error(); + } + } + + return $source; + } + + /** + * Returns the notice to display inline on the plugins page. + * + * @param string $upgrade_type The type of upgrade. + * @param string $message The message to display. + */ + public function get_inline_notice( string $upgrade_type, string $message, ) : string { + ob_start(); + ?> +
+

+
+ +
+
+

+
+
+

+
+ + +
+ + +
+ +
+
+
+ + get_wporg_data(); + + /** @var string $latest_version */ + $latest_version = $data['version'] ?? ''; + + /** @var array $versions */ + $versions = $data['versions'] ?? []; + + + foreach ( $versions as $version => $download_url ) { + // Skip trunk. + if ( 'trunk' === $version ) { + continue; + } + + // If the current version is < 1.0.0, but this version is >= 2, skip it. + // @phpstan-ignore-next-line + if ( version_compare( WPGRAPHQL_ACF_VERSION, '1.0.0', '<' ) && version_compare( $version, '2.0.0', '>=' ) ) { + continue; + } + + // Return the first matching version. + $latest_version = $version; + } + + if ( ! empty( $latest_version ) ) { + set_site_transient( self::VERSION_TRANSIENT, $latest_version, 6 * HOUR_IN_SECONDS ); + } + } + + return $latest_version; + } + + /** + * Gets the data from the WordPress plugin directory. + * + * @return array + */ + protected function get_wporg_data() : array { + // Return cached data if available. + if ( ! empty( $this->wporg_data ) ) { + return $this->wporg_data; + } + + // Get data from transient. + $data = get_site_transient( self::WPORG_DATA_TRANSIENT ); + + if ( empty( $data ) || ! is_array( $data ) ) { + $data = wp_remote_get( $this->plugin_config['api_url'] ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get + + $body = wp_remote_retrieve_body( $data ); + + // Bail because we couldn't parse the body. + if ( empty( $body ) ) { + return []; + } + + $data = json_decode( $body, true ); + + // Bail because we couldn't decode the body. + if ( empty( $data ) || ! is_array( $data ) ) { + return []; + } + + // Refresh every 6 hours. + set_site_transient( self::WPORG_DATA_TRANSIENT, $data, 6 * HOUR_IN_SECONDS ); + } + + // Stash for reuse. + $this->wporg_data = $data; + + return $data; + } + + /** + * Gets the download URL for a specific version. + * + * @param string $version Version number. + * + * @return string|false + */ + protected function get_download_url( string $version ) { + $data = $this->get_wporg_data(); + + if ( empty( $data['versions'][ $version ] ) ) { + return false; + } + + return $data['versions'][ $version ]; + } + + /** + * Returns the SemVer type of update based on the new version. + * + * @param string $new_version The SemVer-compliant version number (x.y.z) + * @param string $current_version The SemVer-compliant version number (x.y.z) + * + * @return string{major|minor|patch} The type of update (major, minor, patch). + */ + protected static function get_semver_update_type( string $new_version, string $current_version ) : string { + $current = explode( '.', $current_version ); + $new = explode( '.', $new_version ); + + // If the first digit is 0, we need to compare the next digit. + if ( '0' === $new[0] && '0' !== $current[0] ) { + return self::get_semver_update_type( + implode( '.', array_slice( $new, 1 ) ), + implode( '.', array_slice( $current, 1 ) ) + ); + } + + // If the major version is different, this is a major update. + if ( $current[0] !== $new[0] ) { + return 'major'; + } + + // If the minor version is different, this is a minor update. + if ( $current[1] !== $new[1] ) { + return 'minor'; + } + + return 'patch'; + } + + /** + * Gets the message to display for a breaking change. + * + * @param string $version The version number. + */ + protected static function get_breaking_change_message( string $version ) : string { + return sprintf( + /* translators: %s: version number. */ + __( 'Version %s of WPGraphQL for ACF is a major update and may contain breaking changes. Please review the changelog and test before updating on a production site.', 'wp-graphql-acf' ), + $version + ); + } + +} diff --git a/src/class-acf.php b/src/class-acf.php index e7f8e21..e53d31c 100644 --- a/src/class-acf.php +++ b/src/class-acf.php @@ -8,6 +8,7 @@ namespace WPGraphQL\ACF; use GraphQL\Type\Definition\ResolveInfo; +use WPGraphQL\ACF\Admin\Updater; /** * Final class ACF @@ -17,7 +18,7 @@ final class ACF { /** * Stores the instance of the WPGraphQL\ACF class * - * @var ACF The one true WPGraphQL\Extensions\ACF + * @var \WPGraphQL\ACF\ACF The one true WPGraphQL\Extensions\ACF * @access private */ private static $instance; @@ -25,12 +26,11 @@ final class ACF { /** * Get the singleton. * - * @return ACF + * @return \WPGraphQL\ACF\ACF */ public static function instance() { - - if ( ! isset( self::$instance ) && ! ( self::$instance instanceof ACF ) ) { - self::$instance = new ACF(); + if ( ! isset( self::$instance ) && ! ( self::$instance instanceof self ) ) { + self::$instance = new self(); self::$instance->setup_constants(); self::$instance->includes(); self::$instance->actions(); @@ -41,7 +41,7 @@ public static function instance() { /** * Fire off init action * - * @param ACF $instance The instance of the WPGraphQL\ACF class + * @param \WPGraphQL\ACF\ACF $instance The instance of the WPGraphQL\ACF class */ do_action( 'graphql_acf_init', self::$instance ); @@ -63,7 +63,6 @@ public function __clone() { // Cloning instances of the class is forbidden. _doing_it_wrong( __FUNCTION__, esc_html__( 'The \WPGraphQL\ACF class should not be cloned.', 'wp-graphql-acf' ), '0.0.1' ); - } /** @@ -76,7 +75,6 @@ public function __wakeup() { // De-serializing instances of the class is forbidden. _doing_it_wrong( __FUNCTION__, esc_html__( 'De-serializing instances of the \WPGraphQL\ACF class is not allowed', 'wp-graphql-acf' ), '0.0.1' ); - } /** @@ -101,7 +99,6 @@ private function setup_constants() { if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_FILE' ) ) { define( 'WPGRAPHQL_ACF_PLUGIN_FILE', __FILE__ . '/..' ); } - } /** @@ -121,7 +118,6 @@ private function includes() { * cycle */ private function actions() { - } /** @@ -133,28 +129,33 @@ private function filters() { * This filters any field that returns the `ContentTemplate` type * to pass the source node down to the template for added context */ - add_filter( 'graphql_resolve_field', function( $result, $source, $args, $context, ResolveInfo $info, $type_name, $field_key, $field, $field_resolver ) { - if ( isset( $info->returnType ) && strtolower( 'ContentTemplate' ) === strtolower( $info->returnType ) ) { - if ( is_array( $result ) && ! isset( $result['node'] ) && ! empty( $source ) ) { - $result['node'] = $source; + add_filter( + 'graphql_resolve_field', + static function ( $result, $source, $args, $context, ResolveInfo $info, $type_name, $field_key, $field, $field_resolver ) { + if ( isset( $info->returnType ) && strtolower( 'ContentTemplate' ) === strtolower( $info->returnType ) ) { + if ( is_array( $result ) && ! isset( $result['node'] ) && ! empty( $source ) ) { + $result['node'] = $source; + } } - } - return $result; - }, 10, 9 ); - + return $result; + }, + 10, + 9 + ); } /** * Initialize */ private function init() { - $config = new Config(); add_action( 'graphql_register_types', [ $config, 'init' ], 10, 1 ); $acf_settings = new ACF_Settings(); $acf_settings->init(); + $updater = new Updater(); + $updater->init(); } } From dd3644d1c0324a472dce81f212a03fc7a876be27 Mon Sep 17 00:00:00 2001 From: David Levine Date: Sun, 25 Jun 2023 09:54:03 +0000 Subject: [PATCH 2/5] fix: incorrect plugin constants --- src/Admin/Updater.php | 3 ++- src/class-acf.php | 8 +++++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/src/Admin/Updater.php b/src/Admin/Updater.php index 05a9f1e..75cda79 100644 --- a/src/Admin/Updater.php +++ b/src/Admin/Updater.php @@ -72,7 +72,7 @@ public function init() : void { */ public function disable_autoupdate( $update, $item ) { // Return early if this is not our plugin. - if ( $item->slug !== $this->plugin_config['slug'] ) { + if ( $item->slug !== $this->plugin_config['slug'] && $item->plugin !== $this->plugin_config['slug'] .'/' .$this->plugin_config['slug'] .'.php') { return $update; } @@ -129,6 +129,7 @@ public function api_check( $transient ) { $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ]->package = $download_url; $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ]->zip_url = $download_url; + return $transient; } diff --git a/src/class-acf.php b/src/class-acf.php index e53d31c..1566641 100644 --- a/src/class-acf.php +++ b/src/class-acf.php @@ -84,20 +84,22 @@ public function __wakeup() { * @return void */ private function setup_constants() { + $main_file_path = dirname( __DIR__ ) . '/wp-graphql-acf.php'; + // Plugin Folder Path. if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_DIR' ) ) { - define( 'WPGRAPHQL_ACF_PLUGIN_DIR', plugin_dir_path( __FILE__ . '/..' ) ); + define( 'WPGRAPHQL_ACF_PLUGIN_DIR', plugin_dir_path( $main_file_path ) ); } // Plugin Folder URL. if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_URL' ) ) { - define( 'WPGRAPHQL_ACF_PLUGIN_URL', plugin_dir_url( __FILE__ . '/..' ) ); + define( 'WPGRAPHQL_ACF_PLUGIN_URL', plugin_dir_url( $main_file_path ) ); } // Plugin Root File. if ( ! defined( 'WPGRAPHQL_ACF_PLUGIN_FILE' ) ) { - define( 'WPGRAPHQL_ACF_PLUGIN_FILE', __FILE__ . '/..' ); + define( 'WPGRAPHQL_ACF_PLUGIN_FILE', $main_file_path ); } } From 1b5086cd6f19f8f576ef129de73205307fd681a2 Mon Sep 17 00:00:00 2001 From: David Levine Date: Sun, 25 Jun 2023 10:32:54 +0000 Subject: [PATCH 3/5] fix: internal logic --- src/Admin/Updater.php | 23 +++++++++++++---------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/src/Admin/Updater.php b/src/Admin/Updater.php index 75cda79..6ccd0a5 100644 --- a/src/Admin/Updater.php +++ b/src/Admin/Updater.php @@ -44,7 +44,7 @@ class Updater { public function __construct() { // Defining this in the constructor makes things easier to mock/test $this->plugin_config = [ - 'plugin_file' => WPGRAPHQL_ACF_PLUGIN_FILE, + 'plugin_file' => self::PLUGIN_SLUG . '/' . self::PLUGIN_SLUG . '.php', 'slug' => self::PLUGIN_SLUG, 'proper_folder_name' => self::PLUGIN_SLUG, 'api_url' => 'https://api.wordpress.org/plugins/info/1.0/' . self::PLUGIN_SLUG . '.json', @@ -72,7 +72,7 @@ public function init() : void { */ public function disable_autoupdate( $update, $item ) { // Return early if this is not our plugin. - if ( $item->slug !== $this->plugin_config['slug'] && $item->plugin !== $this->plugin_config['slug'] .'/' .$this->plugin_config['slug'] .'.php') { + if ( $item->slug !== $this->plugin_config['slug'] && $item->plugin !== $this->plugin_config['plugin_file'] ) { return $update; } @@ -121,14 +121,13 @@ public function api_check( $transient ) { $download_url = $this->get_download_url( $new_version ); // Populate the transient data. - if ( ! isset( $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ] ) ) { - $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ] = (object) $this->plugin_config; + if ( ! isset( $transient->response[ $this->plugin_config['plugin_file'] ] ) ) { + $transient->response[ $this->plugin_config['plugin_file'] ] = (object) $this->plugin_config; } - $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ]->new_version = $new_version; - $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ]->package = $download_url; - $transient->response[ WPGRAPHQL_ACF_PLUGIN_FILE ]->zip_url = $download_url; - + $transient->response[ $this->plugin_config['plugin_file'] ]->new_version = $new_version; + $transient->response[ $this->plugin_config['plugin_file'] ]->package = $download_url; + $transient->response[ $this->plugin_config['plugin_file'] ]->zip_url = $download_url; return $transient; } @@ -270,7 +269,6 @@ protected function get_plugin_data() : array { */ protected function get_latest_version() : string { $latest_version = get_site_transient( self::VERSION_TRANSIENT ); - if ( empty( $latest_version ) ) { $data = $this->get_wporg_data(); @@ -280,6 +278,10 @@ protected function get_latest_version() : string { /** @var array $versions */ $versions = $data['versions'] ?? []; + // Sort the versions by descending order. + uksort( $versions, function( $a, $b ) { + return version_compare( $b, $a ); + }); foreach ( $versions as $version => $download_url ) { // Skip trunk. @@ -295,6 +297,7 @@ protected function get_latest_version() : string { // Return the first matching version. $latest_version = $version; + break; } if ( ! empty( $latest_version ) ) { @@ -319,7 +322,7 @@ protected function get_wporg_data() : array { // Get data from transient. $data = get_site_transient( self::WPORG_DATA_TRANSIENT ); - if ( empty( $data ) || ! is_array( $data ) ) { + if ( empty( $data ) || ! is_array( $data ) || isset( $data['error'] ) ) { $data = wp_remote_get( $this->plugin_config['api_url'] ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get $body = wp_remote_retrieve_body( $data ); From c21819619e1893702b6a82678a84ede575b1ef2f Mon Sep 17 00:00:00 2001 From: David Levine Date: Sun, 25 Jun 2023 15:35:47 +0000 Subject: [PATCH 4/5] dev: add inline update messages --- src/Admin/Updater.php | 113 ++++++++++++++++++++++++++++++++---------- 1 file changed, 87 insertions(+), 26 deletions(-) diff --git a/src/Admin/Updater.php b/src/Admin/Updater.php index 6ccd0a5..5aca0c8 100644 --- a/src/Admin/Updater.php +++ b/src/Admin/Updater.php @@ -34,7 +34,7 @@ class Updater { /** * The Plugin data from wordpress.org * - * @var array + * @var ?object */ protected $wporg_data; @@ -60,6 +60,7 @@ public function init() : void { add_filter( 'pre_set_site_transient_update_plugins', [ $this, 'api_check' ] ); add_filter( 'plugins_api_result', [ $this, 'api_result' ], 10, 3 ); add_filter( 'upgrader_source_selection', [ $this, 'upgrader_source_selection' ], 10, 2 ); + add_action( 'in_plugin_update_message-' . $this->plugin_config['plugin_file'], [ $this, 'update_message' ] ); } /** @@ -129,6 +130,17 @@ public function api_check( $transient ) { $transient->response[ $this->plugin_config['plugin_file'] ]->package = $download_url; $transient->response[ $this->plugin_config['plugin_file'] ]->zip_url = $download_url; + $update_type = self::get_semver_update_type( $new_version, WPGRAPHQL_ACF_VERSION ); + if ( 'major' === $update_type ) { + $message = sprintf( + /* translators: %s: breaking change message. */ + __( '⚠ Warning: %s', 'wp-graphql-acf' ), + self::get_breaking_change_message( $new_version ) + ); + $transient->response[ $this->plugin_config['plugin_file'] ]->upgrade_notice = $message; + } + + return $transient; } @@ -142,6 +154,18 @@ public function api_check( $transient ) { * @return object|\WP_Error */ public function api_result( $response, $action, $args ) { + /** + * Filters the slug used to check the API response. + * This is useful for testing. + * + * @param ?string $custom_slug The custom slug to use. If null, the default slug is used. + * @param object $args The API request arguments. + */ + $custom_slug = apply_filters( 'wpgraphql_acf_wporg_api_result_slug', null, $args ); + if ( null !== $custom_slug ) { + $args->slug = $custom_slug; + } + // Bail if this is not checking our plugin. if ( ! isset( $args->slug ) || $args->slug !== $this->plugin_config['slug'] ) { return $response; @@ -151,7 +175,7 @@ public function api_result( $response, $action, $args ) { $new_version = $this->get_latest_version(); // Bail if the version is not newer. - if ( version_compare( $new_version, $args->version, '<=' ) ) { + if ( version_compare( $new_version, WPGRAPHQL_ACF_VERSION, '<=' ) ) { return $response; } @@ -164,11 +188,12 @@ public function api_result( $response, $action, $args ) { $warning = ''; if ( 'major' === $update_type ) { - $warning = sprintf( + $message = sprintf( /* translators: %s: version number. */ - __( '

%s

', 'wp-graphql-acf' ), + __( '⚠ Warning

%s

', 'wp-graphql-acf' ), self::get_breaking_change_message( $new_version ) ); + $warning = $this->get_inline_notice( 'major', $message ); } // If there is a warning, append it to each section. @@ -205,6 +230,35 @@ public function upgrader_source_selection( $source, $remote_source ) { return $source; } + /** + * Outputs a warning message about a major update. + * + * @param array $args The update message arguments. + */ + public function update_message( $args ) : void { + if ( ! isset( $args['slug'] ) || $args['slug'] !== $this->plugin_config['slug'] ) { + return; + } + + // Get the latest version. + $new_version = $args['new_version']; + + // If this is a major update, add a warning. + $update_type = self::get_semver_update_type( $new_version, WPGRAPHQL_ACF_VERSION ); + + if ( 'major' !== $update_type ) { + return; + } + + $message = sprintf( + /* translators: %s: version number. */ + __( '⚠ Warning

%s

', 'wp-graphql-acf' ), + self::get_breaking_change_message( $new_version ) + ); + + echo '

' . wp_kses_post( $this->get_inline_notice( 'major', $message ) ); + } + /** * Returns the notice to display inline on the plugins page. * @@ -214,8 +268,8 @@ public function upgrader_source_selection( $source, $remote_source ) { public function get_inline_notice( string $upgrade_type, string $message, ) : string { ob_start(); ?> -
-

+
+
get_wporg_data(); /** @var string $latest_version */ - $latest_version = $data['version'] ?? ''; + $latest_version = isset( $data->version ) ? $data->version : ''; /** @var array $versions */ - $versions = $data['versions'] ?? []; + $versions = isset( $data->versions ) ? (array) $data->versions : []; // Sort the versions by descending order. - uksort( $versions, function( $a, $b ) { - return version_compare( $b, $a ); - }); + uksort( + $versions, + static function ( $a, $b ) { + return version_compare( $b, $a ); + } + ); foreach ( $versions as $version => $download_url ) { // Skip trunk. @@ -311,9 +369,9 @@ protected function get_latest_version() : string { /** * Gets the data from the WordPress plugin directory. * - * @return array + * @return ?object */ - protected function get_wporg_data() : array { + protected function get_wporg_data() { // Return cached data if available. if ( ! empty( $this->wporg_data ) ) { return $this->wporg_data; @@ -322,21 +380,28 @@ protected function get_wporg_data() : array { // Get data from transient. $data = get_site_transient( self::WPORG_DATA_TRANSIENT ); - if ( empty( $data ) || ! is_array( $data ) || isset( $data['error'] ) ) { - $data = wp_remote_get( $this->plugin_config['api_url'] ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get + if ( empty( $data ) || ! is_object( $data ) ) { + /** + * Filters the API URL to use for fetching the plugin data. + * Useful for testing. + * + * @param string $api_url The API URL to use. + */ + $api_url = apply_filters( 'wpgraphql_acf_wporg_api_url', $this->plugin_config['api_url'] ); + + $req = wp_remote_get( $api_url ); // phpcs:ignore WordPressVIPMinimum.Functions.RestrictedFunctions.wp_remote_get_wp_remote_get - $body = wp_remote_retrieve_body( $data ); + $body = wp_remote_retrieve_body( $req ); - // Bail because we couldn't parse the body. if ( empty( $body ) ) { - return []; + return null; } - $data = json_decode( $body, true ); + $data = json_decode( $body ); // Bail because we couldn't decode the body. - if ( empty( $data ) || ! is_array( $data ) ) { - return []; + if ( empty( $data ) || ! is_object( $data ) ) { + return null; } // Refresh every 6 hours. @@ -359,11 +424,7 @@ protected function get_wporg_data() : array { protected function get_download_url( string $version ) { $data = $this->get_wporg_data(); - if ( empty( $data['versions'][ $version ] ) ) { - return false; - } - - return $data['versions'][ $version ]; + return ! empty( $data->versions->$version ) ? $data->versions->$version : false; } /** From 760c6c01f8eb037cf7f0b60c606af47d7ffb01e4 Mon Sep 17 00:00:00 2001 From: David Levine Date: Mon, 26 Jun 2023 15:27:21 +0000 Subject: [PATCH 5/5] dev: check for test slug in section notice --- src/Admin/Updater.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Admin/Updater.php b/src/Admin/Updater.php index 5aca0c8..c179b53 100644 --- a/src/Admin/Updater.php +++ b/src/Admin/Updater.php @@ -167,7 +167,7 @@ public function api_result( $response, $action, $args ) { } // Bail if this is not checking our plugin. - if ( ! isset( $args->slug ) || $args->slug !== $this->plugin_config['slug'] ) { + if ( ! isset( $args->slug ) || ( $args->slug !== $this->plugin_config['slug'] && $args->slug !== $custom_slug ) ) { return $response; }