From 79902b177240923efab52a037fdb281da7dc86b2 Mon Sep 17 00:00:00 2001 From: Igor at KAGG Design Date: Tue, 9 Jul 2024 22:53:07 +0300 Subject: [PATCH] V4.3.1 (#349) * Do not remove scoped packages before deploy. * Always remove scoped packages from vendor. * Fix warnings and deprecation messages in admin when CF7 is active. Add a live form in the Contact Form 7 admin form view. * Fix fatal error with Gravity Forms. * Fix CF7 tests. * Bump up version. * Update phpcs.xml. * Update readme.txt. * Bump up version. * Fix tag generator hCaptcha. * Fix CF7 live form with tag generator. * Fix CF7 live form with tag generator. * Fix CF7 live form with tag generator. --- .github/workflows/deploy-to-wp-org.yml | 3 - .../hcaptcha-wordpress-plugin-scoper.php | 2 +- .php-scoper/{ => src}/Scoper.php | 92 ++++++++++++++----- .tests/php/integration/CF7/AdminTest.php | 10 +- changelog.txt | 6 ++ composer.json | 5 +- hcaptcha.php | 4 +- phpcs.xml | 2 - readme.txt | 10 +- src/php/CF7/Admin.php | 56 ++++++++++- src/php/GravityForms/Field.php | 21 ++++- vendors/matthiasmullie/minify/src/CSS.php | 2 +- vendors/matthiasmullie/minify/src/JS.php | 2 +- vendors/matthiasmullie/minify/src/Minify.php | 2 +- .../path-converter/src/Converter.php | 8 +- 15 files changed, 175 insertions(+), 50 deletions(-) rename .php-scoper/{ => src}/Scoper.php (84%) diff --git a/.github/workflows/deploy-to-wp-org.yml b/.github/workflows/deploy-to-wp-org.yml index ae6f22f1..b8c779a9 100644 --- a/.github/workflows/deploy-to-wp-org.yml +++ b/.github/workflows/deploy-to-wp-org.yml @@ -15,9 +15,6 @@ jobs: - name: Checkout code uses: actions/checkout@v4 - - name: Remove scoped packages - run: rm -rf vendors/* - - name: Install dependencies with caching uses: kagg-design/composer-install@v2 with: diff --git a/.php-scoper/hcaptcha-wordpress-plugin-scoper.php b/.php-scoper/hcaptcha-wordpress-plugin-scoper.php index cb10e3ea..39ca0a6b 100644 --- a/.php-scoper/hcaptcha-wordpress-plugin-scoper.php +++ b/.php-scoper/hcaptcha-wordpress-plugin-scoper.php @@ -15,7 +15,7 @@ use HCaptcha\Scoper\Scoper; -require_once __DIR__ . '/Scoper.php'; +require_once __DIR__ . '/src/Scoper.php'; $finders = Scoper::get_finders(); diff --git a/.php-scoper/Scoper.php b/.php-scoper/src/Scoper.php similarity index 84% rename from .php-scoper/Scoper.php rename to .php-scoper/src/Scoper.php index 9b1b338f..4f7abd85 100644 --- a/.php-scoper/Scoper.php +++ b/.php-scoper/src/Scoper.php @@ -16,8 +16,8 @@ use Composer\DependencyResolver\Operation\UninstallOperation; use Composer\DependencyResolver\Operation\UpdateOperation; use Composer\EventDispatcher\Event as BaseEvent; -use Composer\Script\Event; use Composer\Installer\PackageEvent; +use Composer\Script\Event; use Isolated\Symfony\Component\Finder\Finder; use Seld\JsonLint\ParsingException; @@ -76,13 +76,16 @@ static function ( $package ) { ); $removed_packages = array_diff( $packages, $locked_packages ); - $vendor_prefixed = self::get_vendor_prefixed(); + $vendor_prefixed = self::get_vendor_prefixed_dir(); foreach ( $removed_packages as $removed_package ) { self::delete_package( $vendor_prefixed, $removed_package ); } } + // Always delete scoped packages from vendor. + self::cleanup_scope( $event ); + if ( self::$do_dump ) { self::dump( $event ); } @@ -111,10 +114,8 @@ public static function post_package_install( PackageEvent $package_event ): void return; } - $vendor_prefixed = self::get_vendor_prefixed(); - // Do not run scoper after installation if we already have package scoped. - self::$do_scope = self::$do_scope || ! self::is_not_empty_dir( $vendor_prefixed . '/' . $package ); + self::$do_scope = self::$do_scope || ! self::is_not_empty_dir( self::get_vendor_prefixed_dir( $package ) ); } /** @@ -166,7 +167,7 @@ public static function post_package_uninstall( PackageEvent $event ): void { return; } - self::delete_package( self::get_vendor_prefixed(), $package ); + self::delete_package( self::get_vendor_prefixed_dir(), $package ); self::$do_dump = true; } @@ -178,6 +179,7 @@ public static function post_package_uninstall( PackageEvent $event ): void { * @param Event $event Composer event. * * @return void + * @noinspection MkdirRaceConditionInspection */ private static function prepare_scope( Event $event ): void { $packages = $event->getComposer()->getPackage()->getExtra()['scope-packages'] ?? []; @@ -187,19 +189,18 @@ private static function prepare_scope( Event $event ): void { } // Bail if .php-scoper/vendor dir already exists and not empty. - if ( self::is_not_empty_dir( __DIR__ . self::VENDOR ) ) { + if ( self::is_not_empty_dir( self::get_scoper_dir( self::VENDOR ) ) ) { return; } - $vendor_prefixed = self::get_vendor_prefixed(); + $vendor_prefixed = self::get_vendor_prefixed_dir(); if ( ! is_dir( $vendor_prefixed ) ) { - // phpcs:disable WordPress.WP.AlternativeFunctions.file_system_operations_mkdir, Generic.Commenting.DocComment.MissingShort + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_mkdir mkdir( $vendor_prefixed ); - // phpcs:enable WordPress.WP.AlternativeFunctions.file_system_operations_mkdir, Generic.Commenting.DocComment.MissingShort } - $composer_cmd = 'composer --working-dir="' . __DIR__ . '" --no-plugins --no-scripts --no-dev install'; + $composer_cmd = 'composer --working-dir="' . self::get_scoper_dir() . '" --no-plugins --no-scripts --no-dev install'; // phpcs:disable WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec, WordPress.Security.EscapeOutput.OutputNotEscaped echo shell_exec( $composer_cmd ); @@ -221,8 +222,7 @@ private static function scope( Event $event ): void { } $slug = basename( getcwd() ); - $vendor = self::get_vendor(); - $output_dir = self::get_vendor_prefixed(); + $output_dir = self::get_vendor_prefixed_dir(); $vendors = array_unique( array_map( @@ -246,7 +246,7 @@ static function ( $scope_package ) { self::fix_logo_on_windows(); - $scoper_file = __DIR__ . self::VENDOR . '/humbug/php-scoper/bin/php-scoper'; + $scoper_file = self::get_scoper_dir( self::VENDOR . '/humbug/php-scoper/bin/php-scoper' ); $scoper_args = '" add-prefix' . ' --config=.php-scoper/' . $slug . '-scoper.php' . @@ -257,12 +257,29 @@ static function ( $scope_package ) { // phpcs:ignore WordPress.PHP.DiscouragedPHPFunctions.system_calls_shell_exec, WordPress.Security.EscapeOutput.OutputNotEscaped echo shell_exec( $scoper_cmd ); + self::$do_dump = true; + } + + /** + * Cleanup scoped libraries. + * + * @param Event $event Composer event. + * + * @return void + */ + private static function cleanup_scope( Event $event ): void { + $scope_packages = $event->getComposer()->getPackage()->getExtra()['scope-packages'] ?? []; + + if ( ! $scope_packages ) { + return; + } + + $vendor = self::get_vendor_dir(); + // Loop through the list of packages and delete relevant dirs in vendor. foreach ( $scope_packages as $package ) { self::delete_package( $vendor, $package ); } - - self::$do_dump = true; } /** @@ -341,7 +358,7 @@ public static function get_finders(): array { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $composer_json = json_decode( file_get_contents( getcwd() . '/composer.json' ), true ); $packages = $composer_json['extra']['scope-packages'] ?? []; - $vendor_dir = self::get_vendor(); + $vendor_dir = self::get_vendor_dir(); $filenames = [ '*.php', 'LICENSE', 'CHANGELOG.md', 'README.md' ]; $finders = []; @@ -384,7 +401,7 @@ private static function fix_logo_on_windows(): void { return; } - $file = __DIR__ . self::VENDOR . '/humbug/php-scoper/src/Console/Application.php'; + $file = self::get_scoper_dir( self::VENDOR . '/humbug/php-scoper/src/Console/Application.php' ); // phpcs:ignore WordPress.WP.AlternativeFunctions.file_get_contents_file_get_contents $contents = file_get_contents( $file ); @@ -473,18 +490,49 @@ private static function is_not_empty_dir( string $filename ): bool { /** * Get vendor dir. * + * @param string $path Path relative to the vendor prefixed dir. + * * @return string + * @noinspection PhpSameParameterValueInspection */ - private static function get_vendor(): string { - return getcwd() . self::VENDOR; + private static function get_vendor_dir( string $path = '' ): string { + return self::add_path_to_dir( getcwd() . self::VENDOR, $path ); } /** * Get vendor prefixed dir. * + * @param string $path Path relative to the vendor prefixed dir. + * * @return string */ - private static function get_vendor_prefixed(): string { - return getcwd() . self::VENDOR_PREFIXED; + private static function get_vendor_prefixed_dir( string $path = '' ): string { + return self::add_path_to_dir( getcwd() . self::VENDOR_PREFIXED, $path ); + } + + /** + * Get scoper dir. + * + * @param string $path Path relative to the scoper dir. + * + * @return string + */ + private static function get_scoper_dir( string $path = '' ): string { + return self::add_path_to_dir( dirname( __DIR__ ), $path ); + } + + /** + * Add a path to dir. + * + * @param string $dir Dir. + * @param string $path Path. + * + * @return string + */ + private static function add_path_to_dir( string $dir, string $path ): string { + $dir = rtrim( $dir, '/' ); + $path = ltrim( $path, '/' ); + + return rtrim( $dir . '/' . $path, '/' ); } } diff --git a/.tests/php/integration/CF7/AdminTest.php b/.tests/php/integration/CF7/AdminTest.php index c622bb12..2ba5cbe2 100644 --- a/.tests/php/integration/CF7/AdminTest.php +++ b/.tests/php/integration/CF7/AdminTest.php @@ -38,7 +38,7 @@ class AdminTest extends HCaptchaPluginWPTestCase { * Tear down the test. */ public function tearDown(): void { // phpcs:ignore PHPCompatibility.FunctionDeclarations.NewReturnTypeDeclarations.voidFound - unset( $GLOBALS['current_screen'] ); + unset( $GLOBALS['current_screen'], $_GET['post'], $_GET['page'] ); parent::tearDown(); } @@ -57,6 +57,9 @@ public function test_init_hooks( bool $mode_auto, bool $mode_embed, bool $is_adm $cf7_status = array_filter( [ $mode_auto ? 'form' : '', $mode_embed ? 'embed' : '' ] ); if ( $is_admin ) { + $_GET['page'] = 'wpcf7'; + $_GET['post'] = 177; + set_current_screen( 'some' ); } @@ -71,6 +74,10 @@ public function test_init_hooks( bool $mode_auto, bool $mode_embed, bool $is_adm $subject = new Admin(); + if ( $is_admin && $cf7_status ) { + set_current_screen( 'toplevel_page_wpcf7' ); + } + if ( $expected ) { self::assertSame( 54, @@ -185,7 +192,6 @@ public function test_toplevel_page_wpcf7() {
- HTML; diff --git a/changelog.txt b/changelog.txt index 9c06a033..a51215d0 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ += 4.3.1 = +* Added a live form in the Contact Form 7 admin form view. +* Fixed warnings and deprecation messages in admin when Contact Form 7 is active. +* Fixed tag generator with the live form for Contact Form 7. +* Fixed fatal error with Gravity Forms. + = 4.3.0 = * NOTE: the plugin has been renamed from "hCaptcha for WordPress" to "hCaptcha for WP" * Dropped support for PHP 7.0 and 7.1. The minimum required PHP version is now 7.2. diff --git a/composer.json b/composer.json index 709bca45..3f8ac607 100644 --- a/composer.json +++ b/composer.json @@ -49,11 +49,14 @@ "autoload": { "psr-4": { "HCaptcha\\": "src/php", - "HCaptcha\\Scoper\\": ".php-scoper", + "HCaptcha\\Scoper\\": ".php-scoper/src", "KAGG\\Settings\\Abstracts\\": "src/php/Settings/Abstracts" }, "classmap": [ "vendors" + ], + "exclude-from-classmap": [ + "src/php/Divi/WPTestCaseStub.php" ] }, "autoload-dev": { diff --git a/hcaptcha.php b/hcaptcha.php index 3715f0b8..68482a8e 100644 --- a/hcaptcha.php +++ b/hcaptcha.php @@ -10,7 +10,7 @@ * Plugin Name: hCaptcha for WP * Plugin URI: https://www.hcaptcha.com/ * Description: hCaptcha keeps out bots and spam while putting privacy first. It is a drop-in replacement for reCAPTCHA. - * Version: 4.3.0 + * Version: 4.3.1 * Requires at least: 5.3 * Requires PHP: 7.2 * Author: hCaptcha @@ -39,7 +39,7 @@ /** * Plugin version. */ -const HCAPTCHA_VERSION = '4.3.0'; +const HCAPTCHA_VERSION = '4.3.1'; /** * Path to the plugin dir. diff --git a/phpcs.xml b/phpcs.xml index 522d91b4..ccd4f875 100644 --- a/phpcs.xml +++ b/phpcs.xml @@ -10,7 +10,6 @@ */\.wordpress-org/* */\.yarn/* */assets/* - */bin/* */coverage/* */languages/* */node_modules/* @@ -22,7 +21,6 @@ - diff --git a/readme.txt b/readme.txt index 8a64d8d1..acc3fe35 100644 --- a/readme.txt +++ b/readme.txt @@ -4,9 +4,9 @@ Tags: captcha, hcaptcha, antispam, abuse, protect form Requires at least: 5.3 Tested up to: 6.6 Requires PHP: 7.2 -Stable tag: 4.3.0 +Stable tag: 4.3.1 License: GPLv2 or later -License URI: http://www.gnu.org/licenses/gpl-2.0.html +License URI: https://www.gnu.org/licenses/gpl-2.0.html Enables hCaptcha integration with WordPress and popular plugins. @@ -561,6 +561,12 @@ Instructions for popular native integrations are below: == Changelog == += 4.3.1 = +* Added a live form in the Contact Form 7 admin form view. +* Fixed warnings and deprecation messages in admin when Contact Form 7 is active. +* Fixed tag generator with the live form for Contact Form 7. +* Fixed fatal error with Gravity Forms. + = 4.3.0 = * NOTE: the plugin has been renamed from "hCaptcha for WordPress" to "hCaptcha for WP" * Dropped support for PHP 7.0 and 7.1. The minimum required PHP version is now 7.2. diff --git a/src/php/CF7/Admin.php b/src/php/CF7/Admin.php index 5e036665..79c95163 100644 --- a/src/php/CF7/Admin.php +++ b/src/php/CF7/Admin.php @@ -35,9 +35,31 @@ public function init_hooks(): void { return; } + if ( ! $this->is_cf7_form_admin_page() ) { + return; + } + add_action( 'wpcf7_admin_init', [ $this, 'add_tag_generator_hcaptcha' ], 54 ); - add_action( 'toplevel_page_wpcf7', [ $this, 'before_toplevel_page_wpcf7' ], 0 ); - add_action( 'toplevel_page_wpcf7', [ $this, 'after_toplevel_page_wpcf7' ], 20 ); + add_action( 'current_screen', [ $this, 'current_screen' ] ); + } + + /** + * Current screen. + * + * @param mixed $current_screen Current screen. + * + * @return void + */ + public function current_screen( $current_screen ): void { + $current_screen_id = $current_screen->id ?? ''; + + if ( ! $current_screen ) { + return; + } + + add_action( $current_screen_id, [ $this, 'before_toplevel_page_wpcf7' ], 0 ); + add_action( $current_screen_id, [ $this, 'after_toplevel_page_wpcf7' ], 20 ); + add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts_before_cf7' ], 0 ); add_action( 'admin_enqueue_scripts', [ $this, 'enqueue_admin_scripts_after_cf7' ], 20 ); } @@ -75,6 +97,12 @@ private function insert_live_form( string $output ): string { $form_start = $m[1]; + if ( ! preg_match( '~().*?~s', $output, $m ) ) { + return $output; + } + + [ $form_end, $stuff_end ] = $m; + if ( ! preg_match( '/' ], '', $output ); + $output = str_replace( [ $form_start, $form_end ], [ '', $stuff_end ], $output ); // Insert form start at the beginning of the id="post-body". $search = '
'; @@ -255,4 +283,26 @@ public function enqueue_admin_scripts_after_cf7(): void { $wp_scripts->registered['wpcf7-admin']->extra['data'] = 'var wpcf7 = ' . wp_json_encode( $wpcf7 ) . ';'; } } + + /** + * Check if the current page is a CF7 form create, edit or view page. + * + * @return bool + */ + private function is_cf7_form_admin_page(): bool { + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + $page = isset( $_GET['page'] ) ? sanitize_text_field( wp_unslash( $_GET['page'] ) ) : ''; + + if ( ! in_array( $page, [ 'wpcf7-new', 'wpcf7' ], true ) ) { + return false; + } + + // phpcs:ignore WordPress.Security.NonceVerification.Recommended + if ( ( 'wpcf7' === $page ) && ! isset( $_GET['post'] ) ) { + return false; + } + + return true; + } } diff --git a/src/php/GravityForms/Field.php b/src/php/GravityForms/Field.php index 0ad1087e..6cae07ee 100644 --- a/src/php/GravityForms/Field.php +++ b/src/php/GravityForms/Field.php @@ -114,8 +114,10 @@ public function add_to_field_groups( array $field_groups ): array { * Get form editor field title. * * @return string + * @noinspection PhpMissingReturnTypeInspection + * @noinspection ReturnTypeCanBeDeclaredInspection */ - public function get_form_editor_field_title(): string { + public function get_form_editor_field_title() { return esc_attr( 'hCaptcha' ); } @@ -123,8 +125,10 @@ public function get_form_editor_field_title(): string { * Returns the field's form editor description. * * @return string + * @noinspection PhpMissingReturnTypeInspection + * @noinspection ReturnTypeCanBeDeclaredInspection */ - public function get_form_editor_field_description(): string { + public function get_form_editor_field_description() { return ( esc_attr__( 'Adds a hCaptcha field to your form to help protect your website from spam and bot abuse.', @@ -144,8 +148,10 @@ public function get_form_editor_field_description(): string { * This could be an icon url or a gform-icon class. * * @return string + * @noinspection PhpMissingReturnTypeInspection + * @noinspection ReturnTypeCanBeDeclaredInspection */ - public function get_form_editor_field_icon(): string { + public function get_form_editor_field_icon() { return HCAPTCHA_URL . '/assets/images/hcaptcha-icon-black-and-white.svg'; } @@ -153,8 +159,10 @@ public function get_form_editor_field_icon(): string { * Get field settings. * * @return array + * @noinspection PhpMissingReturnTypeInspection + * @noinspection ReturnTypeCanBeDeclaredInspection */ - public function get_form_editor_field_settings(): array { + public function get_form_editor_field_settings() { return [ 'label_placement_setting', 'description_setting', @@ -172,8 +180,11 @@ public function get_form_editor_field_settings(): array { * @return string * @noinspection PhpCastIsUnnecessaryInspection * @noinspection PhpUnusedParameterInspection + * @noinspection PhpMissingParamTypeInspection + * @noinspection PhpMissingReturnTypeInspection + * @noinspection ReturnTypeCanBeDeclaredInspection */ - public function get_field_input( array $form, $value = '', $entry = null ): string { + public function get_field_input( $form, $value = '', $entry = null ) { $form_id = (int) $form['id']; $is_entry_detail = $this->is_entry_detail(); $is_form_editor = $this->is_form_editor(); diff --git a/vendors/matthiasmullie/minify/src/CSS.php b/vendors/matthiasmullie/minify/src/CSS.php index 968ad933..20dc25ed 100644 --- a/vendors/matthiasmullie/minify/src/CSS.php +++ b/vendors/matthiasmullie/minify/src/CSS.php @@ -380,7 +380,7 @@ protected function move(ConverterInterface $converter, $content) // loop all urls foreach ($matches as $match) { // determine if it's a url() or an @import match - $type = (strpos($match[0], '@import') === 0) ? 'import' : 'url'; + $type = strpos($match[0], '@import') === 0 ? 'import' : 'url'; $url = $match['path']; if ($this->canImportByPath($url)) { // attempting to interpret GET-params makes no sense, so let's discard them for awhile diff --git a/vendors/matthiasmullie/minify/src/JS.php b/vendors/matthiasmullie/minify/src/JS.php index 8156a437..fed1caf2 100644 --- a/vendors/matthiasmullie/minify/src/JS.php +++ b/vendors/matthiasmullie/minify/src/JS.php @@ -505,7 +505,7 @@ protected function shortenBools($content) if (trim($match[1]) === '.') { return $match[0]; } - return $match[1] . (($match[2] === 'true') ? '!0' : '!1'); + return $match[1] . ($match[2] === 'true' ? '!0' : '!1'); }; $content = preg_replace_callback('/(^|.\s*)\b(true|false)\b(?!:)/', $callback, $content); // for(;;) is exactly the same as while(true), but shorter :) diff --git a/vendors/matthiasmullie/minify/src/Minify.php b/vendors/matthiasmullie/minify/src/Minify.php index a01ea49f..b4fbb83c 100644 --- a/vendors/matthiasmullie/minify/src/Minify.php +++ b/vendors/matthiasmullie/minify/src/Minify.php @@ -80,7 +80,7 @@ public function add($data) $data = (string) $data; // load data $value = $this->load($data); - $key = ($data != $value) ? $data : count($this->data); + $key = $data != $value ? $data : count($this->data); // replace CR linefeeds etc. // @see https://github.com/matthiasmullie/minify/pull/139 $value = str_replace(array("\r\n", "\r"), "\n", $value); diff --git a/vendors/matthiasmullie/path-converter/src/Converter.php b/vendors/matthiasmullie/path-converter/src/Converter.php index b415ae13..fda361af 100644 --- a/vendors/matthiasmullie/path-converter/src/Converter.php +++ b/vendors/matthiasmullie/path-converter/src/Converter.php @@ -38,12 +38,12 @@ public function __construct($from, $to, $root = '') // when both paths have nothing in common, one of them is probably // absolute while the other is relative $root = $root ?: getcwd(); - $from = (strpos($from, $root) === 0) ? $from : preg_replace('/\/+/', '/', $root . '/' . $from); - $to = (strpos($to, $root) === 0) ? $to : preg_replace('/\/+/', '/', $root . '/' . $to); + $from = strpos($from, $root) === 0 ? $from : preg_replace('/\/+/', '/', $root . '/' . $from); + $to = strpos($to, $root) === 0 ? $to : preg_replace('/\/+/', '/', $root . '/' . $to); // or traveling the tree via `..` // attempt to resolve path, or assume it's fine if it doesn't exist - $from = (@realpath($from)) ?: $from; - $to = (@realpath($to)) ?: $to; + $from = @realpath($from) ?: $from; + $to = @realpath($to) ?: $to; } $from = $this->dirname($from); $to = $this->dirname($to);