diff --git a/.changeset/giant-buttons-decide.md b/.changeset/giant-buttons-decide.md new file mode 100644 index 00000000..c9c7d771 --- /dev/null +++ b/.changeset/giant-buttons-decide.md @@ -0,0 +1,5 @@ +--- +"@wpengine/wp-graphql-content-blocks": minor +--- + +fix: cleanup constants and refactor autoload handling to improve Composer compatibility. diff --git a/bin/versionPlugin.js b/bin/versionPlugin.js index d4055811..451d568e 100644 --- a/bin/versionPlugin.js +++ b/bin/versionPlugin.js @@ -68,7 +68,7 @@ async function bumpStableTag(readmeTxt, version) { async function bumpVersionConstant(pluginFile, version) { return bumpVersion( pluginFile, - /^\s*\$this->define\(\s*'WPGRAPHQL_CONTENT_BLOCKS_VERSION', '([0-9.]+)/gm, + /^\s*define\(\s*'WPGRAPHQL_CONTENT_BLOCKS_VERSION', '([0-9.]+)/gm, version ); } diff --git a/includes/Autoloader.php b/includes/Autoloader.php new file mode 100644 index 00000000..0ba0df03 --- /dev/null +++ b/includes/Autoloader.php @@ -0,0 +1,125 @@ +GitHub Releases tab.', 'wp-graphql-content-blocks' ); + + if ( defined( 'WP_DEBUG' ) && WP_DEBUG ) { + error_log( // phpcs:ignore WordPress.PHP.DevelopmentFunctions.error_log_error_log -- This is a development notice. + sprintf( + wp_kses( + $error_message, + [ + 'a' => [ + 'href' => [], + 'target' => [], + ], + ] + ) + ) + ); + } + + $hooks = [ + 'admin_notices', + 'network_admin_notices', + ]; + + foreach ( $hooks as $hook ) { + add_action( + $hook, + static function () use ( $error_message ) { + + // Only show the notice to admins. + if ( ! current_user_can( 'manage_options' ) ) { + return; + } + + ?> +
+

+ [ + 'href' => [], + 'target' => [], + ], + ] + ) + ) + ?> +

+
+ slug = 'wp-graphql-content-blocks'; - $current_plugin_data = \get_plugin_data( WPGRAPHQL_CONTENT_BLOCKS_FILE ); + $current_plugin_data = \get_plugin_data( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ); $meets_wp_req = version_compare( get_bloginfo( 'version' ), $response->requires_at_least, '>=' ); // Only update the response if there's a newer version, otherwise WP shows an update notice for the same version. if ( $meets_wp_req && version_compare( $current_plugin_data['Version'], $response->version, '<' ) ) { - $response->plugin = plugin_basename( WPGRAPHQL_CONTENT_BLOCKS_FILE ); - $data->response[ WPGRAPHQL_CONTENT_BLOCKS_PATH ] = $response; + $response->plugin = plugin_basename( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ); + $data->response[ WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_PATH ] = $response; } return $data; @@ -60,10 +60,13 @@ function check_for_plugin_updates( $data ) { * @param string $action The type of information being requested from the Plugin Installation API. * @param object $args Plugin API arguments. * - * @return false|\WPGraphQL\ContentBlocks\PluginUpdater\stdClass $response Plugin API arguments. + * @return false|object|array $response Plugin API arguments. */ function custom_plugin_api_request( $api, $action, $args ) { - if ( empty( $args->slug ) || WPGRAPHQL_CONTENT_BLOCKS_SLUG !== $args->slug ) { + // Bail if it's not our plugin. + $plugin_slug = dirname( plugin_basename( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ) ); + + if ( empty( $args->slug ) || $plugin_slug !== $args->slug ) { return $api; } @@ -96,7 +99,7 @@ function delegate_plugin_row_notice() { return; } - $plugin_basename = plugin_basename( WPGRAPHQL_CONTENT_BLOCKS_FILE ); + $plugin_basename = plugin_basename( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ); remove_action( "after_plugin_row_{$plugin_basename}", 'wp_plugin_update_row' ); add_action( "after_plugin_row_{$plugin_basename}", __NAMESPACE__ . '\display_plugin_row_notice', 10 ); @@ -172,8 +175,9 @@ function display_update_page_notice() { * @return string */ function filter_semver_notice_text( $notice_text, $plugin_filename ) { - if ( WPGRAPHQL_CONTENT_BLOCKS_PATH !== $plugin_filename ) { + if ( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_PATH !== $plugin_filename ) { return $notice_text; } + return '

' . __( 'THIS UPDATE MAY CONTAIN BREAKING CHANGES: This plugin uses Semantic Versioning, and this new version is a major release. Please review the changelog before updating.', 'wp-graphql-content-blocks' ); } diff --git a/includes/PluginUpdater/UpdateFunctions.php b/includes/PluginUpdater/UpdateFunctions.php index 7b736349..3f554182 100644 --- a/includes/PluginUpdater/UpdateFunctions.php +++ b/includes/PluginUpdater/UpdateFunctions.php @@ -30,7 +30,7 @@ function get_plugin_data_from_wpe( $args ) { return $args; } - $current_plugin_data = \get_plugin_data( WPGRAPHQL_CONTENT_BLOCKS_FILE ); + $current_plugin_data = \get_plugin_data( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ); $meets_wp_req = version_compare( get_bloginfo( 'version' ), $product_info->requires_at_least, '>=' ); $api = new stdClass(); @@ -65,7 +65,7 @@ function get_plugin_api_error() { * @return \stdClass */ function get_remote_plugin_info() { - $current_plugin_data = \get_plugin_data( WPGRAPHQL_CONTENT_BLOCKS_FILE ); + $current_plugin_data = \get_plugin_data( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ); $response = get_transient( 'wpgraphql_content_blocks_product_info' ); if ( false === $response ) { @@ -101,7 +101,9 @@ function get_remote_plugin_info() { ); if ( ! property_exists( $response, 'icons' ) || empty( $response->icons['default'] ) ) { - $response->icons['default'] = WPGRAPHQL_CONTENT_BLOCKS_URL . 'includes/updates/images/wpe-logo-stacked-inverse.svg'; + $plugin_url = plugin_dir_url( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ); + + $response->icons['default'] = $plugin_url . 'includes/updates/images/wpe-logo-stacked-inverse.svg'; } set_transient( 'wpgraphql_content_blocks_product_info', $response, HOUR_IN_SECONDS * 12 ); diff --git a/includes/WPGraphQLContentBlocks.php b/includes/WPGraphQLContentBlocks.php index 25bf5873..febdade1 100644 --- a/includes/WPGraphQLContentBlocks.php +++ b/includes/WPGraphQLContentBlocks.php @@ -28,7 +28,9 @@ final class WPGraphQLContentBlocks { public static function instance() { if ( ! isset( self::$instance ) || ! ( self::$instance instanceof self ) ) { self::$instance = new self(); - self::$instance->setup_constants(); + // @todo Remove this in a major version bump. + self::$instance->deprecated_constants(); + if ( self::$instance->includes() ) { self::$instance->actions(); } @@ -64,28 +66,6 @@ public function __wakeup() { _doing_it_wrong( __FUNCTION__, esc_html__( 'De-serializing instances of the WPGraphQLContentBlocks class is not allowed', 'wp-graphql-content-blocks' ), '0.0.1' ); } - /** - * Setup plugin constants. - * - * @since 0.0.1 - */ - private function setup_constants(): void { - - // Set main file path. - $main_file_path = dirname( __DIR__ ) . '/wp-graphql.php'; - - // Plugin version. - $this->define( 'WPGRAPHQL_CONTENT_BLOCKS_VERSION', '4.1.0' ); - // Plugin Folder Path. - $this->define( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR', plugin_dir_path( $main_file_path ) ); - // Plugin Root File. - $this->define( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE', $main_file_path ); - // Whether to autoload the files or not. - $this->define( 'WPGRAPHQL_CONTENT_BLOCKS_AUTOLOAD', true ); - // The minimum version of PHP this plugin requires to work properly - $this->define( 'WPGRAPHQL_CONTENT_BLOCKS_MIN_PHP_VERSION', '7.4' ); - } - /** * Include required files. * Uses composer's autoload @@ -93,67 +73,42 @@ private function setup_constants(): void { * @since 0.0.1 */ private function includes(): bool { - /** - * WPGRAPHQL_CONTENT_BLOCKS_AUTOLOAD can be set to "false" to prevent the autoloader from running. - * In most cases, this is not something that should be disabled, but some environments - * may bootstrap their dependencies in a global autoloader that will autoload files - * before we get to this point, and requiring the autoloader again can trigger fatal errors. - */ - if ( defined( 'WPGRAPHQL_CONTENT_BLOCKS_AUTOLOAD' ) && true === WPGRAPHQL_CONTENT_BLOCKS_AUTOLOAD ) { - if ( file_exists( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR . 'vendor/autoload.php' ) ) { - // Autoload Required Classes. - require_once WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR . 'vendor/autoload.php'; - } else { - add_action( - 'admin_notices', - static function () { - if ( ! current_user_can( 'manage_options' ) ) { - return; - } - - printf( - '
' . - '

%s

' . - '
', - wp_kses_post( - __( 'WPGraphQL Content Blocks appears to have been installed without its dependencies. If you meant to download the source code, you can run `composer install` to install dependencies. If you are looking for the production version of the plugin, you can download it from the GitHub Releases tab.', 'wp-graphql-content-blocks' ) - ) - ); - } - ); - }//end if - - // If GraphQL class doesn't exist, then dependencies cannot be - // detected. This likely means the user cloned the repo from GitHub - // but did not run `composer install` - if ( ! class_exists( 'WPGraphQL' ) ) { - add_action( - 'admin_notices', - static function () { - if ( ! current_user_can( 'manage_options' ) ) { - return; - } - - printf( - '
' . - '

%s

' . - '
', - esc_html__( 'WPGraphQL Content Blocks will not work without WPGraphQL installed and active.', 'wp-graphql-content-blocks' ) - ); + // Holds the status of whether the plugin is active or not so we can load the updater functions regardless. + $success = true; + + // If GraphQL class doesn't exist, then that plugin is not active. + if ( ! class_exists( 'WPGraphQL' ) ) { + add_action( + 'admin_notices', + static function () { + if ( ! current_user_can( 'manage_options' ) ) { + return; } - ); - return false; - }//end if - }//end if + printf( + '
' . + '

%s

' . + '
', + esc_html__( 'WPGraphQL Content Blocks will not work without WPGraphQL installed and active.', 'wp-graphql-content-blocks' ) + ); + } + ); - require_once WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR . 'includes/PluginUpdater/UpdateFunctions.php'; - require_once WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR . 'includes/PluginUpdater/UpdateCallbacks.php'; + $success = false; + } + + // Include the updater functions. + require_once WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR . '/includes/PluginUpdater/UpdateFunctions.php'; + require_once WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR . '/includes/PluginUpdater/UpdateCallbacks.php'; - // phpcs:ignore SlevomatCodingStandard.Variables.UnusedVariable.UnusedVariable -- Library bootstraps itself, hence variable is unused. - $semver = new \EnforceSemVer\EnforceSemVer( WPGRAPHQL_CONTENT_BLOCKS_PATH ); + // Bail if the Enforce SemVer class doesn't exist. + if ( ! class_exists( 'EnforceSemVer\EnforceSemVer' ) ) { + return false; + } - return true; + new \EnforceSemVer\EnforceSemVer( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_PATH ); + + return $success; } /** @@ -176,16 +131,27 @@ public function init_block_editor_registry( \WPGraphQL\Registry\TypeRegistry $ty } /** - * Define constant if not already set. - * - * @param string $name Constant name. - * @param string|bool $value Constant value. + * Sets up deprecated constants. * - * @since 1.4.0 + * @deprecated @todo This can be removed in a major version bump. */ - private function define( string $name, $value ): void { - if ( ! defined( $name ) ) { - define( $name, $value ); // phpcs:ignore WordPress.NamingConventions.PrefixAllGlobals.VariableConstantNameFound + private function deprecated_constants(): void { + if ( defined( 'WPGRAPHQL_CONTENT_BLOCKS_FILE' ) ) { + _doing_it_wrong( 'WPGRAPHQL_CONTENT_BLOCKS_FILE', 'The WPGRAPHQL_CONTENT_BLOCKS_VERSION constant has been deprecated. Use the WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE constant instead.', '@todo' ); + } else { + define( 'WPGRAPHQL_CONTENT_BLOCKS_FILE', WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ); + } + + if ( defined( 'WPGRAPHQL_CONTENT_BLOCKS_PATH' ) ) { + _doing_it_wrong( 'WPGRAPHQL_CONTENT_BLOCKS_PATH', 'The WPGRAPHQL_CONTENT_BLOCKS_PATH constant has been deprecated. Use the WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_PATH constant instead.', '@todo' ); + } else { + define( 'WPGRAPHQL_CONTENT_BLOCKS_PATH', WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_PATH ); + } + + if ( defined( 'WPGRAPHQL_CONTENT_BLOCKS_MIN_PHP_VERSION' ) ) { + _doing_it_wrong( 'WPGRAPHQL_CONTENT_BLOCKS_MIN_PHP_VERSION', 'The WPGRAPHQL_CONTENT_BLOCKS_VERSION constant has been deprecated, with no replacement. It will be removed in a future release.', '@todo' ); + } else { + define( 'WPGRAPHQL_CONTENT_BLOCKS_MIN_PHP_VERSION', WPGRAPHQL_CONTENT_BLOCKS_VERSION ); } } } diff --git a/phpstan-baseline.neon b/phpstan-baseline.neon index 62cc5e54..cfa731d0 100644 --- a/phpstan-baseline.neon +++ b/phpstan-baseline.neon @@ -10,17 +10,4 @@ parameters: count: 1 path: includes/PluginUpdater/UpdateCallbacks.php - - - message: "#^Function WPGraphQL\\\\ContentBlocks\\\\PluginUpdater\\\\custom_plugin_api_request\\(\\) has invalid return type WPGraphQL\\\\ContentBlocks\\\\PluginUpdater\\\\stdClass\\.$#" - count: 1 - path: includes/PluginUpdater/UpdateCallbacks.php - - - message: "#^Function WPGraphQL\\\\ContentBlocks\\\\PluginUpdater\\\\custom_plugin_api_request\\(\\) should return WPGraphQL\\\\ContentBlocks\\\\PluginUpdater\\\\stdClass\\|false but returns array\\|object\\|false\\.$#" - count: 2 - path: includes/PluginUpdater/UpdateCallbacks.php - - - - message: "#^Function WPGraphQL\\\\ContentBlocks\\\\PluginUpdater\\\\custom_plugin_api_request\\(\\) should return WPGraphQL\\\\ContentBlocks\\\\PluginUpdater\\\\stdClass\\|false but returns object\\.$#" - count: 1 - path: includes/PluginUpdater/UpdateCallbacks.php diff --git a/phpstan/constants.php b/phpstan/constants.php index 24df0f25..ab25000d 100644 --- a/phpstan/constants.php +++ b/phpstan/constants.php @@ -8,5 +8,4 @@ define( 'WPGRAPHQL_CONTENT_BLOCKS_VERSION', '0.2.1' ); define( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE', 'wp-graphql-content-blocks.php' ); define( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR', '' ); -define( 'WPGRAPHQL_CONTENT_BLOCKS_MIN_PHP_VERSION', '7.4' ); -define( 'WPGRAPHQL_CONTENT_BLOCKS_PATH', 'wp-graphql-content-blocks.php' ); +define( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_PATH', 'wp-graphql-content-blocks.php' ); diff --git a/tests/unit/AutoloaderTest.php b/tests/unit/AutoloaderTest.php new file mode 100644 index 00000000..47515c3b --- /dev/null +++ b/tests/unit/AutoloaderTest.php @@ -0,0 +1,68 @@ +autoloader = new MockAutoloader(); + MockAutoloader::reset(); + + parent::setUp(); + } + + public function tearDown(): void { + unset( $this->autoloader ); + + parent::tearDown(); + } + + public function testAutoload() { + $this->assertTrue( $this->autoloader->autoload() ); + } + + public function testRequireAutoloader() { + $reflection = new \ReflectionClass( $this->autoloader ); + $is_loaded_property = $reflection->getProperty( 'is_loaded' ); + $is_loaded_property->setAccessible( true ); + $is_loaded_property->setValue( $this->autoloader, false ); + + $method = $reflection->getMethod( 'require_autoloader' ); + $method->setAccessible( true ); + + + $this->assertTrue( $method->invokeArgs( $this->autoloader, [ WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR . '/vendor/autoload.php' ] ) ); + + $is_loaded_property->setValue( $this->autoloader, false ); + $this->assertFalse( $method->invokeArgs( $this->autoloader, [ '/path/to/invalid/autoload.php' ] ) ); + + // Capture the admin notice output + + $admin_id = $this->factory->user->create( [ 'role' => 'administrator' ] ); + + wp_set_current_user( $admin_id ); + + set_current_screen( 'dashboard' ); + ob_start(); + do_action( 'admin_notices' ); + + $output = ob_get_clean(); + $this->assertStringContainsString( 'WPGraphQL Content Blocks appears to have been installed without its dependencies.', $output ); + + // Cleanup + wp_delete_user( $admin_id ); + remove_all_actions( 'admin_notices' ); + } +} diff --git a/tests/unit/WPGraphQLContentBlocksTest.php b/tests/unit/WPGraphQLContentBlocksTest.php index fad91b7a..7421305e 100644 --- a/tests/unit/WPGraphQLContentBlocksTest.php +++ b/tests/unit/WPGraphQLContentBlocksTest.php @@ -36,15 +36,4 @@ public function testCloneWPGraphQL() { $this->assertTrue( $rc->hasMethod( '__clone' ) ); $this->assertTrue( $rc->hasMethod( '__wakeup' ) ); } - - /** - * @covers WPGraphQLContentBlocks::setup_constants() - */ - public function testSetupConstants() { - $this->assertTrue( defined( 'WPGRAPHQL_CONTENT_BLOCKS_VERSION' ) ); - $this->assertTrue( defined( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR' ) ); - $this->assertTrue( defined( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE' ) ); - $this->assertTrue( defined( 'WPGRAPHQL_CONTENT_BLOCKS_AUTOLOAD' ) ); - $this->assertTrue( defined( 'WPGRAPHQL_CONTENT_BLOCKS_MIN_PHP_VERSION' ) ); - } } diff --git a/wp-graphql-content-blocks.php b/wp-graphql-content-blocks.php index c8fa64fd..00089f6d 100644 --- a/wp-graphql-content-blocks.php +++ b/wp-graphql-content-blocks.php @@ -19,14 +19,33 @@ exit; } -define( 'WPGRAPHQL_CONTENT_BLOCKS_DIR', __DIR__ ); -define( 'WPGRAPHQL_CONTENT_BLOCKS_FILE', __FILE__ ); -define( 'WPGRAPHQL_CONTENT_BLOCKS_URL', plugin_dir_url( __FILE__ ) ); -define( 'WPGRAPHQL_CONTENT_BLOCKS_PATH', plugin_basename( WPGRAPHQL_CONTENT_BLOCKS_FILE ) ); -define( 'WPGRAPHQL_CONTENT_BLOCKS_SLUG', dirname( plugin_basename( WPGRAPHQL_CONTENT_BLOCKS_FILE ) ) ); - -if ( ! class_exists( 'WPGraphQLContentBlocks' ) ) { - require_once __DIR__ . '/includes/WPGraphQLContentBlocks.php'; +if ( ! function_exists( 'wpgraphql_content_blocks_constants' ) ) { + /** + * Defines plugin constants. + * + * @since @todo + */ + function wpgraphql_content_blocks_constants(): void { + // Whether to autoload the files or not. + if ( ! defined( 'WPGRAPHQL_CONTENT_BLOCKS_AUTOLOAD' ) ) { + define( 'WPGRAPHQL_CONTENT_BLOCKS_AUTOLOAD', true ); + } + + if ( ! defined( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR' ) ) { + define( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_DIR', __DIR__ ); + } + + if ( ! defined( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE' ) ) { + define( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE', __FILE__ ); + } + + if ( ! defined( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_PATH' ) ) { + define( 'WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_PATH', plugin_basename( WPGRAPHQL_CONTENT_BLOCKS_PLUGIN_FILE ) ); + } + if ( ! defined( 'WPGRAPHQL_CONTENT_BLOCKS_VERSION' ) ) { + define( 'WPGRAPHQL_CONTENT_BLOCKS_VERSION', '4.1.0' ); + } + } } @@ -37,6 +56,15 @@ * @since 0.0.1 */ function wpgraphql_content_blocks_init(): void { + // Define plugin constants. + wpgraphql_content_blocks_constants(); + + // Load the autoloader. + require_once __DIR__ . '/includes/Autoloader.php'; + if ( ! \WPGraphQL\ContentBlocks\Autoloader::autoload() ) { + return; + } + // Instantiate the plugin class. WPGraphQLContentBlocks::instance(); }