diff --git a/cyr-to-lat.php b/cyr-to-lat.php index 7a10818..2bcba31 100644 --- a/cyr-to-lat.php +++ b/cyr-to-lat.php @@ -10,7 +10,7 @@ * Plugin Name: Cyr-To-Lat * Plugin URI: https://wordpress.org/plugins/cyr2lat/ * Description: Convert Non-Latin characters in post and term slugs to Latin characters. Useful for creating human-readable URLs. Based on the original plugin by Anton Skorobogatov. - * Version: 6.0.7 + * Version: 6.0.8 * Requires at least: 5.1 * Requires PHP: 7.0.0 * Author: Sergey Biryukov, Mikhail Kobzarev, Igor Gergel @@ -43,7 +43,7 @@ /** * Plugin version. */ -define( 'CYR_TO_LAT_VERSION', '6.0.7' ); +define( 'CYR_TO_LAT_VERSION', '6.0.8' ); /** * Path to the plugin dir. diff --git a/readme.txt b/readme.txt index 5848414..fd790d8 100644 --- a/readme.txt +++ b/readme.txt @@ -3,7 +3,7 @@ Contributors: SergeyBiryukov, mihdan, karevn, webvitaly, kaggdesign Tags: cyrillic, belorussian, ukrainian, bulgarian, macedonian, georgian, kazakh, latin, l10n, russian, cyr-to-lat, cyr2lat, rustolat, slugs, translations, transliteration Requires at least: 5.1 Tested up to: 6.4 -Stable tag: 6.0.7 +Stable tag: 6.0.8 Requires PHP: 7.0.0 License: GPLv2 or later License URI: http://www.gnu.org/licenses/gpl-2.0.html @@ -221,6 +221,10 @@ Yes, you can! == Changelog == += 6.0.8 (14.02.2024) = +* Improved detection of the Gutenberg editor. +* Fixed processing of product attributes. + = 6.0.7 (11.02.2024) = * Tested with WooCommerce 8.5. * Added redirect from the cyrillic post title when creating a new post. diff --git a/src/php/Main.php b/src/php/Main.php index 9a06e84..5e8959f 100644 --- a/src/php/Main.php +++ b/src/php/Main.php @@ -384,7 +384,7 @@ public function woocommerce_after_template_part_filter() { * @noinspection PhpUndefinedFunctionInspection */ protected function is_wc_attribute_taxonomy( string $title ): bool { - $title = str_replace( 'pa_', '', $title ); + $title = preg_replace( '/^pa_/', '', $title ); foreach ( wc_get_attribute_taxonomies() as $attribute_taxonomy ) { if ( $title === $attribute_taxonomy->attribute_name ) { @@ -396,22 +396,28 @@ protected function is_wc_attribute_taxonomy( string $title ): bool { } /** - * Check if title is a product attribute. + * Check if title is a product not converted attribute. * * @param string $title Title. * * @return bool * @noinspection PhpUndefinedFunctionInspection */ - protected function is_wc_product_attribute( string $title ): bool { + protected function is_wc_product_not_converted_attribute( string $title ): bool { + global $product; - if ( null === $product ) { + if ( ! is_a( $product, 'WC_Product' ) ) { return false; } - foreach ( $product->get_attributes() as $attribute ) { - if ( $title === $attribute->get_name() ) { + // We have to get attributes from postmeta here to see the converted slug. + $attributes = (array) get_post_meta( $product->get_id(), '_product_attributes', true ); + + foreach ( $attributes as $slug => $attribute ) { + $name = $attribute['name'] ?? ''; + + if ( $name === $title && sanitize_title_with_dashes( $title ) === $slug ) { return true; } } @@ -432,7 +438,7 @@ protected function is_wc_attribute( string $title ): bool { return false; } - return $this->is_wc_attribute_taxonomy( $title ) || $this->is_wc_product_attribute( $title ); + return $this->is_wc_attribute_taxonomy( $title ) || $this->is_wc_product_not_converted_attribute( $title ); } /** @@ -536,48 +542,31 @@ public function transliterate( string $str ): string { } /** - * Check if Classic Editor plugin is active. - * - * @link https://kagg.eu/how-to-catch-gutenberg/ + * Check if the Block Editor is active. + * Must only be used after plugins_loaded action is fired. * * @return bool + * @noinspection PhpUndefinedFunctionInspection */ - private function is_classic_editor_plugin_active(): bool { + private function is_gutenberg_editor_active(): bool { + // Gutenberg plugin is installed and activated. + // This filter was removed in WP 5.5. + if ( has_filter( 'replace_editor', 'gutenberg_init' ) ) { + return true; + } + // @codeCoverageIgnoreStart if ( ! function_exists( 'is_plugin_active' ) ) { include_once ABSPATH . 'wp-admin/includes/plugin.php'; } - // @codeCoverageIgnoreEnd - return is_plugin_active( 'classic-editor/classic-editor.php' ); - } - - /** - * Check if Block Editor is active. - * Must only be used after plugins_loaded action is fired. - * - * @link https://kagg.eu/how-to-catch-gutenberg/ - * - * @return bool - */ - private function is_gutenberg_editor_active(): bool { - - // Gutenberg plugin is installed and activated. - $gutenberg = ! ( false === has_filter( 'replace_editor', 'gutenberg_init' ) ); - - // Block editor since 5.0. - $block_editor = version_compare( $GLOBALS['wp_version'], '5.0-beta', '>' ); - - if ( ! $gutenberg && ! $block_editor ) { - return false; + if ( is_plugin_active( 'classic-editor/classic-editor.php' ) ) { + return in_array( get_option( 'classic-editor-replace' ), [ 'no-replace', 'block' ], true ); } - if ( $this->is_classic_editor_plugin_active() ) { - $editor_option = get_option( 'classic-editor-replace' ); - $block_editor_active = [ 'no-replace', 'block' ]; - - return in_array( $editor_option, $block_editor_active, true ); + if ( is_plugin_active( 'disable-gutenberg/disable-gutenberg.php' ) ) { + return ! disable_gutenberg(); } return true; diff --git a/tests/unit/MainTest.php b/tests/unit/MainTest.php index 21d17bd..262d829 100644 --- a/tests/unit/MainTest.php +++ b/tests/unit/MainTest.php @@ -53,7 +53,7 @@ class MainTest extends CyrToLatTestCase { public function tearDown(): void { // phpcs:disable WordPress.Security.NonceVerification.Missing // phpcs:disable WordPress.Security.NonceVerification.Recommended - unset( $GLOBALS['wp_version'], $GLOBALS['wpdb'], $GLOBALS['current_screen'], $GLOBALS['product'], $_POST, $_GET ); + unset( $GLOBALS['wpdb'], $GLOBALS['current_screen'], $GLOBALS['product'], $_POST, $_GET ); // phpcs:enable WordPress.Security.NonceVerification.Recommended // phpcs:enable WordPress.Security.NonceVerification.Missing } @@ -779,36 +779,30 @@ static function ( $function_name ) use ( $is_wc ) { } /** - * Test is_wc_product_attribute(). + * Test is_wc_product_not_converted_attribute(). * * @param string $title Title. * @param bool $is_product Whether it is a product page. - * @param array $names Attribute names. + * @param array $attributes Attribute names. * @param bool $expected Expected result. * * @dataProvider dp_test_is_wc_product_attribute * @throws ReflectionException ReflectionException. */ - public function test_is_wc_product_attribute( string $title, bool $is_product, array $names, bool $expected ) { - $method = 'is_wc_product_attribute'; - $subject = $this->get_subject(); + public function test_is_wc_product_not_converted_attribute( string $title, bool $is_product, array $attributes, bool $expected ) { + $product_id = 5; + $method = 'is_wc_product_not_converted_attribute'; + $subject = $this->get_subject(); $this->set_method_accessibility( $subject, $method ); - $attributes = []; - - foreach ( $names as $name ) { - $attribute = Mockery::mock( 'WC_Product_Attribute' ); - - $attribute->shouldReceive( 'get_name' )->andReturn( $name ); - - $attributes[] = $attribute; - } - $product = Mockery::mock( 'WC_Product' ); - $product->shouldReceive( 'get_attributes' )->andReturn( $attributes ); + $product->shouldReceive( 'get_id' )->andReturn( $product_id ); $GLOBALS['product'] = $is_product ? $product : null; + WP_Mock::userFunction( 'get_post_meta' )->with( $product_id, '_product_attributes', true )->andReturn( $attributes ); + WP_Mock::passthruFunction( 'sanitize_title_with_dashes' ); + self::assertSame( $expected, $subject->$method( $title ) ); } @@ -821,8 +815,16 @@ public function dp_test_is_wc_product_attribute(): array { return [ 'not a product page' => [ 'атрибут 1', false, [], false ], 'no attributes' => [ 'атрибут 1', true, [], false ], - 'no matching' => [ 'атрибут 1', true, [ 'some' ], false ], - 'matching' => [ 'атрибут 1', true, [ 'some', 'атрибут 1' ], true ], + 'no matching' => [ 'атрибут 1', true, [ 'some' => [ 'name' => 'some' ] ], false ], + 'matching' => [ + 'атрибут 1', + true, + [ + 'some' => [ 'name' => 'some' ], + 'атрибут 1' => [ 'name' => 'атрибут 1' ], + ], + true, + ], ]; } @@ -1135,8 +1137,6 @@ public static function dp_test_min_suffix(): array { * Test that sanitize_post_name() does nothing if no Block/Gutenberg editor is active */ public function test_sanitize_post_name_without_gutenberg() { - $subject = Mockery::mock( Main::class )->makePartial()->shouldAllowMockingProtectedMethods(); - $data = [ 'something' ]; WP_Mock::userFunction( @@ -1146,13 +1146,6 @@ public function test_sanitize_post_name_without_gutenberg() { 'return' => false, ] ); - - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - $GLOBALS['wp_version'] = '4.9'; - self::assertSame( $data, $subject->sanitize_post_name( $data ) ); - - FunctionMocker::replace( 'function_exists', true ); - WP_Mock::userFunction( 'is_plugin_active', [ @@ -1161,7 +1154,6 @@ public function test_sanitize_post_name_without_gutenberg() { 'return' => true, ] ); - WP_Mock::userFunction( 'get_option', [ @@ -1171,8 +1163,45 @@ public function test_sanitize_post_name_without_gutenberg() { ] ); - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - $GLOBALS['wp_version'] = '5.0'; + $subject = Mockery::mock( Main::class )->makePartial()->shouldAllowMockingProtectedMethods(); + + self::assertSame( $data, $subject->sanitize_post_name( $data ) ); + } + + /** + * Test that sanitize_post_name() does nothing if Disable Gutenberg plugin is active + */ + public function test_sanitize_post_name_with_disable_gutenberg_plugin() { + $data = [ 'something' ]; + + WP_Mock::userFunction( + 'has_filter', + [ + 'args' => [ 'replace_editor', 'gutenberg_init' ], + 'return' => false, + ] + ); + WP_Mock::userFunction( + 'is_plugin_active', + [ + 'times' => 1, + 'args' => [ 'classic-editor/classic-editor.php' ], + 'return' => false, + ] + ); + WP_Mock::userFunction( + 'is_plugin_active', + [ + 'times' => 1, + 'args' => [ 'disable-gutenberg/disable-gutenberg.php' ], + 'return' => true, + ] + ); + + $subject = Mockery::mock( Main::class )->makePartial()->shouldAllowMockingProtectedMethods(); + + FunctionMocker::replace( 'disable_gutenberg', true ); + self::assertSame( $data, $subject->sanitize_post_name( $data ) ); } @@ -1190,9 +1219,6 @@ public function test_sanitize_post_name_not_post_edit_screen() { ] ); - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - $GLOBALS['wp_version'] = '5.0'; - $subject = Mockery::mock( Main::class )->makePartial()->shouldAllowMockingProtectedMethods(); FunctionMocker::replace( 'function_exists', true ); @@ -1203,6 +1229,13 @@ public function test_sanitize_post_name_not_post_edit_screen() { 'return' => false, ] ); + WP_Mock::userFunction( + 'is_plugin_active', + [ + 'args' => [ 'disable-gutenberg/disable-gutenberg.php' ], + 'return' => false, + ] + ); $current_screen = Mockery::mock( WP_Screen::class ); $current_screen->base = 'not post'; @@ -1226,9 +1259,6 @@ public function test_sanitize_post_name_not_post_edit_screen() { */ public function test_sanitize_post_name( array $data, array $expected ) { - // phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited - $GLOBALS['wp_version'] = '5.0'; - $subject = Mockery::mock( Main::class )->makePartial()->shouldAllowMockingProtectedMethods(); FunctionMocker::replace( 'function_exists', true ); @@ -1239,6 +1269,13 @@ public function test_sanitize_post_name( array $data, array $expected ) { 'return' => false, ] ); + WP_Mock::userFunction( + 'is_plugin_active', + [ + 'args' => [ 'disable-gutenberg/disable-gutenberg.php' ], + 'return' => false, + ] + ); $current_screen = Mockery::mock( WP_Screen::class ); $current_screen->base = 'post'; diff --git a/tests/unit/bootstrap.php b/tests/unit/bootstrap.php index 697b6ff..6dc83bd 100644 --- a/tests/unit/bootstrap.php +++ b/tests/unit/bootstrap.php @@ -39,7 +39,7 @@ /** * Plugin version. */ -const CYR_TO_LAT_TEST_VERSION = '6.0.7'; +const CYR_TO_LAT_TEST_VERSION = '6.0.8'; /** * Path to the plugin dir.