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/.phpcs.xml.dist b/.phpcs.xml.dist index 192f47fe..4cdec649 100644 --- a/.phpcs.xml.dist +++ b/.phpcs.xml.dist @@ -40,7 +40,7 @@ Tests for WordPress version compatibility. https://github.com/WordPress-Coding-Standards/WordPress-Coding-Standards/wiki/Customizable-sniff-properties --> - + 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' => [], + ], + ] + ) + ) + ?> +

+
+ $args GraphQL query args to pass to the connection resolver. + * @param string[] $allowed_block_names The list of allowed block names to filter. */ public static function resolve_content_blocks( $node, $args, $allowed_block_names = [] ): array { /** @@ -64,57 +63,18 @@ public static function resolve_content_blocks( $node, $args, $allowed_block_name } // Parse the blocks from HTML comments to an array of blocks - $parsed_blocks = parse_blocks( $content ); + $parsed_blocks = self::parse_blocks( $content ); if ( empty( $parsed_blocks ) ) { return []; } - // 1st Level filtering of blocks that are empty - $parsed_blocks = array_filter( - $parsed_blocks, - static function ( $parsed_block ) { - if ( ! empty( $parsed_block['blockName'] ) ) { - return true; - } - - // Strip empty comments and spaces - $stripped = preg_replace( '//Uis', '', render_block( $parsed_block ) ); - return ! empty( trim( $stripped ?? '' ) ); - }, - ARRAY_FILTER_USE_BOTH - ); - - // 2nd Level assigning of unique id's and missing blockNames - $parsed_blocks = array_map( - static function ( $parsed_block ) { - $parsed_block['clientId'] = uniqid(); - // Since Gutenberg assigns an empty blockName for Classic block - // we define the name here - if ( empty( $parsed_block['blockName'] ) ) { - $parsed_block['blockName'] = 'core/freeform'; - } - return $parsed_block; - }, - $parsed_blocks - ); - - // Resolve reusable blocks - replaces "core/block" with the corresponding block(s) from the reusable ref ID - TraverseHelpers::traverse_blocks( $parsed_blocks, [ TraverseHelpers::class, 'replace_reusable_blocks' ], 0, PHP_INT_MAX ); // Flatten block list here if requested or if 'flat' value is not selected (default) if ( ! isset( $args['flat'] ) || 'true' == $args['flat'] ) { // phpcs:ignore Universal.Operators.StrictComparisons.LooseEqual $parsed_blocks = self::flatten_block_list( $parsed_blocks ); } // Final level of filtering out blocks not in the allowed list - if ( ! empty( $allowed_block_names ) ) { - $parsed_blocks = array_filter( - $parsed_blocks, - static function ( $parsed_block ) use ( $allowed_block_names ) { - return in_array( $parsed_block['blockName'], $allowed_block_names, true ); - }, - ARRAY_FILTER_USE_BOTH - ); - } + $parsed_blocks = self::filter_allowed_blocks( $parsed_blocks, $allowed_block_names ); /** * Filters the content blocks after they have been resolved. @@ -129,6 +89,120 @@ static function ( $parsed_block ) use ( $allowed_block_names ) { return is_array( $parsed_blocks ) ? $parsed_blocks : []; } + /** + * Get blocks from html string. + * + * @param string $content Content to parse. + * + * @return array List of blocks. + */ + private static function parse_blocks( $content ): array { + $blocks = parse_blocks( $content ); + + return self::handle_do_blocks( $blocks ); + } + + /** + * Recursively process blocks. + * + * This mirrors the `do_blocks` function in WordPress which is responsible for hydrating certain block attributes and supports, but without the forced rendering. + * + * @param array[] $blocks Blocks data. + * + * @return array[] The processed blocks. + */ + private static function handle_do_blocks( array $blocks ): array { + $parsed = []; + foreach ( $blocks as $block ) { + $block_data = self::handle_do_block( $block ); + + if ( $block_data ) { + $parsed[] = $block_data; + } + } + + // Remove empty blocks. + return array_filter( $parsed ); + } + + /** + * Process a block, getting all extra fields. + * + * @param array $block Block data. + * + * @return ?array The processed block. + */ + private static function handle_do_block( array $block ): ?array { + if ( self::is_block_empty( $block ) ) { + return null; + } + + // Since Gutenberg assigns an empty blockName for Classic block, we define it here. + if ( empty( $block['blockName'] ) ) { + $block['blockName'] = 'core/freeform'; + } + + // Assign a unique clientId to the block. + $block['clientId'] = uniqid(); + + // @todo apply more hydrations. + + $block = self::populate_reusable_blocks( $block ); + + // Prepare innerBlocks. + if ( ! empty( $block['innerBlocks'] ) ) { + $block['innerBlocks'] = self::handle_do_blocks( $block['innerBlocks'] ); + } + + return $block; + } + + /** + * Checks whether a block is really empty, and not just a `core/freeform`. + * + * @param array $block The block to check. + */ + private static function is_block_empty( array $block ): bool { + // If we have a blockName, no need to check further. + if ( ! empty( $block['blockName'] ) ) { + return false; + } + + // @todo add more checks and avoid using render_block(). + + // Strip empty comments and spaces + $stripped = preg_replace( '//Uis', '', render_block( $block ) ); + + return empty( trim( $stripped ?? '' ) ); + } + + /** + * Populates reusable blocks with the blocks from the reusable ref ID. + * + * @param array $block The block to populate. + * + * @return array The populated block. + */ + private static function populate_reusable_blocks( array $block ): array { + if ( 'core/block' !== $block['blockName'] || ! isset( $block['attrs']['ref'] ) ) { + return $block; + } + + $reusable_block = get_post( $block['attrs']['ref'] ); + + if ( ! $reusable_block ) { + return $block; + } + + $parsed_blocks = ! empty( $reusable_block->post_content ) ? self::parse_blocks( $reusable_block->post_content ) : null; + + if ( empty( $parsed_blocks ) ) { + return $block; + } + + return array_merge( ...$parsed_blocks ); + } + /** * Flattens a list blocks into a single array * @@ -145,16 +219,41 @@ private static function flatten_block_list( $blocks ): array { /** * Flattens a block and its inner blocks into a single while attaching unique clientId's * - * @param mixed $block A block. + * @param array $block A parsed block. */ private static function flatten_inner_blocks( $block ): array { - $result = []; + $result = []; + + // Assign a unique clientId to the block if it doesn't already have one. $block['clientId'] = isset( $block['clientId'] ) ? $block['clientId'] : uniqid(); array_push( $result, $block ); + foreach ( $block['innerBlocks'] as $child ) { $child['parentClientId'] = $block['clientId']; - $result = array_merge( $result, self::flatten_inner_blocks( $child ) ); + + // Flatten the child, and merge with the result. + $result = array_merge( $result, self::flatten_inner_blocks( $child ) ); } + return $result; } + + /** + * Filters out disallowed blocks from the list of blocks + * + * @param array $blocks A list of blocks to filter. + * @param string[] $allowed_block_names The list of allowed block names to filter. + */ + private static function filter_allowed_blocks( array $blocks, array $allowed_block_names ): array { + if ( empty( $allowed_block_names ) ) { + return $blocks; + } + + return array_filter( + $blocks, + static function ( $block ) use ( $allowed_block_names ) { + return in_array( $block['blockName'], $allowed_block_names, true ); + } + ); + } } diff --git a/includes/PluginUpdater/UpdateCallbacks.php b/includes/PluginUpdater/UpdateCallbacks.php index 92cfc4ac..1aed0449 100644 --- a/includes/PluginUpdater/UpdateCallbacks.php +++ b/includes/PluginUpdater/UpdateCallbacks.php @@ -36,13 +36,13 @@ function check_for_plugin_updates( $data ) { } $response->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/Utilities/TraverseHelpers.php b/includes/Utilities/TraverseHelpers.php index 2a4064cc..bc2ed91d 100644 --- a/includes/Utilities/TraverseHelpers.php +++ b/includes/Utilities/TraverseHelpers.php @@ -7,21 +7,37 @@ namespace WPGraphQL\ContentBlocks\Utilities; +use WPGraphQL\ContentBlocks\Data\ContentBlocksResolver; + /** * Class TraverseHelpers * * Provides utility functions to traverse and manipulate blocks. + * + * @deprecated @todo Blocks should be manipulated directly inside ContentBlocksResolver::handle_do_block() */ final class TraverseHelpers { /** * Traverse blocks and apply a callback with optional depth limit. * + * @deprecated @todo Blocks should be manipulated directly inside ContentBlocksResolver::handle_do_block() + * * @param array &$blocks The blocks to traverse. * @param callable $callback The callback function to apply to each block. * @param int $depth The current depth of traversal. * @param int $max_depth The maximum depth to traverse. */ public static function traverse_blocks( &$blocks, $callback, $depth = 0, $max_depth = PHP_INT_MAX ): void { + _deprecated_function( + __METHOD__, + '@todo', + sprintf( + // translators: %s: class name + esc_html__( 'Manipulate blocks directly inside %s::handle_do_block', 'wp-graphql-content-blocks' ), + ContentBlocksResolver::class + ) + ); + foreach ( $blocks as &$block ) { $callback( $block ); if ( ! empty( $block['innerBlocks'] ) && $depth < $max_depth ) { @@ -33,9 +49,13 @@ public static function traverse_blocks( &$blocks, $callback, $depth = 0, $max_de /** * Example callback function to replace reusable blocks. * + * @deprecated @todo Blocks should be manipulated directly inside ContentBlocksResolver::handle_do_block() + * * @param array $block The block to potentially replace. */ public static function replace_reusable_blocks( &$block ): void { + _deprecated_function( __METHOD__, '@todo', ContentBlocksResolver::class . '::populate_reusable_blocks' ); + if ( 'core/block' === $block['blockName'] && isset( $block['attrs']['ref'] ) ) { $post = get_post( $block['attrs']['ref'] ); $reusable_blocks = ! empty( $post->post_content ) ? parse_blocks( $post->post_content ) : null; 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/TraverseHelpersTest.php b/tests/unit/TraverseHelpersTest.php deleted file mode 100644 index 7b9589fc..00000000 --- a/tests/unit/TraverseHelpersTest.php +++ /dev/null @@ -1,89 +0,0 @@ -post_id = wp_insert_post( - [ - 'post_title' => 'Post Title', - 'post_content' => preg_replace( - '/\s+/', - ' ', - trim( - ' - -

Test

- - ' - ) - ), - 'post_status' => 'publish', - ] - ); - } - - public function tearDown(): void { - // your tear down methods here - parent::tearDown(); - - wp_delete_post( $this->post_id, true ); - } - - public function testTraverseBlocks() { - // Sample blocks data - $blocks = [ - [ - 'blockName' => 'core/group', - 'attrs' => [], - 'innerBlocks' => [ - [ - 'blockName' => 'core/block', - 'attrs' => [ 'ref' => $this->post_id ], - 'innerBlocks' => [], - ], - ], - ], - [ - 'blockName' => 'core/block', - 'attrs' => [ 'ref' => $this->post_id ], - 'innerBlocks' => [], - ], - ]; - - // Expected result after replacing reusable blocks - $expected = [ - [ - 'blockName' => 'core/group', - 'attrs' => [], - 'innerBlocks' => [ - [ - 'blockName' => 'core/paragraph', - 'attrs' => [], - 'innerBlocks' => [], - 'innerHTML' => '

Test

', - 'innerContent' => [ 0 => '

Test

' ], - ], - ], - ], - [ - 'blockName' => 'core/paragraph', - 'attrs' => [], - 'innerBlocks' => [], - 'innerHTML' => '

Test

', - 'innerContent' => [ 0 => '

Test

' ], - ], - ]; - - TraverseHelpers::traverse_blocks( $blocks, [ TraverseHelpers::class, 'replace_reusable_blocks' ], 0, PHP_INT_MAX ); - $this->assertEquals( $expected, $blocks ); - } -} 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(); }