From 9f8462ab0e035330ba23d9e639abe7011d6dab55 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 9 May 2025 12:57:58 +0200 Subject: [PATCH 01/18] init --- integration/class-litespeed-cache.php | 143 ++++++++++++++++++++++++++ integration/load.php | 13 +++ 2 files changed, 156 insertions(+) create mode 100644 integration/class-litespeed-cache.php diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php new file mode 100644 index 000000000..4cb78b08b --- /dev/null +++ b/integration/class-litespeed-cache.php @@ -0,0 +1,143 @@ + +RewriteEngine On +RewriteCond %{HTTP:Accept} application +RewriteRule ^ - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+isjson] +'; + + /** + * The option name to store the htaccess rules. + * + * @var string + */ + private static $option_name = 'activitypub_integration_litespeed_cache_htaccess_rules'; + + /** + * The marker to identify the rules in the htaccess file. + * + * @var string + */ + private static $marker = 'ActivityPub LiteSpeed Cache'; + + /** + * Initialize the integration. + */ + public static function init() { + \add_action( 'activate_litespeed-cache/litespeed-cache.php', array( self::class, 'add_htaccess_rules' ) ); + \add_action( 'deactivate_litespeed-cache/litespeed-cache.php', array( self::class, 'remove_htaccess_rules' ) ); + + \add_filter( 'site_status_tests', array( self::class, 'maybe_add_site_health' ) ); + } + + /** + * Add the Litespeed Cache htaccess rules. + */ + public static function add_htaccess_rules() { + // Ensure get_home_path() is declared. + require_once ABSPATH . 'wp-admin/includes/file.php'; + + $htaccess_file = get_home_path() . '.htaccess'; + $added_rules = false; + + if ( \wp_is_writable( $htaccess_file ) ) { + $added_rules = \insert_with_markers( $htaccess_file, self::$marker, self::$rules ); + } + + \update_option( self::$option_name, $added_rules ); + } + + /** + * Remove the Litespeed Cache htaccess rules. + */ + public static function remove_htaccess_rules() { + // Ensure get_home_path() is declared. + require_once ABSPATH . 'wp-admin/includes/file.php'; + + $htaccess_file = get_home_path() . '.htaccess'; + + if ( \wp_is_writable( $htaccess_file ) ) { + \insert_with_markers( $htaccess_file, self::$marker, '' ); + } + + \delete_option( self::$option_name ); + } + + /** + * Maybe add the Litespeed Cache config to the site health. + * + * @param array $tests The site health tests. + * + * @return array The site health tests with the Litespeed Cache config test. + */ + public static function maybe_add_site_health( $tests ) { + if ( ! \is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) ) { + return $tests; + } + + $tests['direct']['activitypub_test_litespeed_cache_integration'] = array( + 'label' => \__( 'Litespeed Cache Test', 'activitypub' ), + 'test' => array( self::class, 'test_litespeed_cache_integration' ), + ); + + return $tests; + } + + /** + * Test the Litespeed Cache integration. + * + * @return array The test results. + */ + public static function test_litespeed_cache_integration() { + $result = array( + 'label' => \__( 'Compatibility with Litespeed Cache', 'activitypub' ), + 'status' => 'good', + 'badge' => array( + 'label' => \__( 'ActivityPub', 'activitypub' ), + 'color' => 'green', + ), + 'description' => \sprintf( + '

%s

', + \__( 'Litespeed Cache is well configured to work with ActivityPub.', 'activitypub' ) + ), + 'actions' => '', + 'test' => 'test_litespeed_cache_integration', + ); + + if ( 0 === \get_option( self::$option_name, '0' ) ) { + $result['status'] = 'critical'; + $result['label'] = \__( 'Litespeed Cache might not be properly configured.', 'activitypub' ); + $result['badge']['color'] = 'red'; + $result['description'] = \sprintf( + '

%s

', + \__( 'Litespeed Cache isn’t currently set up to work with ActivityPub. While this isn’t a major problem, it’s a good idea to enable support. Without it, some technical files (like JSON) might accidentally show up in your website’s cache and be visible to visitors.', 'activitypub' ) + ); + $result['actions'] = \sprintf( + '

%s

%s
', + \__( 'To enable the ActivityPub integration with Litespeed Cache, add the following rules to your .htaccess file:', 'activitypub' ), + \esc_html( self::$rules ) + ); + } + + return $result; + } +} diff --git a/integration/load.php b/integration/load.php index 16a7b5ad7..d37b1e6b7 100644 --- a/integration/load.php +++ b/integration/load.php @@ -131,6 +131,15 @@ function ( $transformer, $data, $object_class ) { * @see https://wordpress.org/plugins/surge/ */ Surge::init(); + + /** + * Load the LiteSpeed Cache integration. + * + * Only load code that needs LiteSpeed Cache to run once LiteSpeed Cache is loaded and initialized. + * + * @see https://wordpress.org/plugins/litespeed-cache/ + */ + Litespeed_Cache::init(); } \add_action( 'plugins_loaded', __NAMESPACE__ . '\plugin_init' ); @@ -138,6 +147,10 @@ function ( $transformer, $data, $object_class ) { \register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'add_cache_config' ) ); \register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'remove_cache_config' ) ); +// Register activation and deactivation hooks for Litespeed Cache integration. +\register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Litespeed_Cache', 'add_htaccess_rules' ) ); +\register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Litespeed_Cache', 'remove_htaccess_rules' ) ); + /** * Register the Stream Connector for ActivityPub. From d32ae580825a8ed37623da4cb462e2bd0830e844 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 15 May 2025 14:03:22 +0200 Subject: [PATCH 02/18] simplify option key --- integration/class-litespeed-cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index 4cb78b08b..5732cb0d3 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -30,7 +30,7 @@ class Litespeed_Cache { * * @var string */ - private static $option_name = 'activitypub_integration_litespeed_cache_htaccess_rules'; + private static $option_name = 'activitypub_litespeed_cache_setup'; /** * The marker to identify the rules in the htaccess file. From 110b92c0e340dcf70b9f10300eeb97fb4b8d22d1 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 15 May 2025 14:15:45 +0200 Subject: [PATCH 03/18] ensure to add to the beginning of the file --- integration/class-litespeed-cache.php | 49 ++++++++++++++++++++++----- 1 file changed, 40 insertions(+), 9 deletions(-) diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index 5732cb0d3..ac4b62f4e 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -53,15 +53,7 @@ public static function init() { * Add the Litespeed Cache htaccess rules. */ public static function add_htaccess_rules() { - // Ensure get_home_path() is declared. - require_once ABSPATH . 'wp-admin/includes/file.php'; - - $htaccess_file = get_home_path() . '.htaccess'; - $added_rules = false; - - if ( \wp_is_writable( $htaccess_file ) ) { - $added_rules = \insert_with_markers( $htaccess_file, self::$marker, self::$rules ); - } + $added_rules = self::append_with_markers( self::$marker, self::$rules ); \update_option( self::$option_name, $added_rules ); } @@ -140,4 +132,43 @@ public static function test_litespeed_cache_integration() { return $result; } + + /** + * Append rules to a file with markers. + * + * @param string $marker The marker to identify the rules in the file. + * @param string $rules The rules to append. + * + * @return bool True on success, false on failure. + */ + public static function append_with_markers( $marker, $rules ) { + // Ensure get_home_path() is declared. + require_once ABSPATH . 'wp-admin/includes/file.php'; + + $htaccess_file = get_home_path() . '.htaccess'; + + if ( ! \wp_is_writable( $htaccess_file ) ) { + return false; + } + + global $wp_filesystem; + \WP_Filesystem(); + + $htaccess = $wp_filesystem->get_contents( $htaccess_file ); + + if ( \preg_match( $marker, $htaccess ) ) { + return \insert_with_markers( $htaccess_file, $marker, $rules ); + } + + $start_marker = "# BEGIN {$marker}"; + $end_marker = "# END {$marker}"; + + // Add marker to the rules. + $rules = $start_marker . PHP_EOL . $rules . PHP_EOL . $end_marker; + + // Add rules to the top of the file. + $htaccess = $rules . PHP_EOL . PHP_EOL . $htaccess; + + return \wp_filesystem->put_contents( $htaccess_file, $htaccess, FS_CHMOD_FILE ); + } } From 4f28033b857068f195041fbc09700d3e1412d2ed Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 15 May 2025 14:21:26 +0200 Subject: [PATCH 04/18] styling --- integration/class-litespeed-cache.php | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index ac4b62f4e..eb932e82f 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -65,7 +65,7 @@ public static function remove_htaccess_rules() { // Ensure get_home_path() is declared. require_once ABSPATH . 'wp-admin/includes/file.php'; - $htaccess_file = get_home_path() . '.htaccess'; + $htaccess_file = \get_home_path() . '.htaccess'; if ( \wp_is_writable( $htaccess_file ) ) { \insert_with_markers( $htaccess_file, self::$marker, '' ); @@ -145,7 +145,7 @@ public static function append_with_markers( $marker, $rules ) { // Ensure get_home_path() is declared. require_once ABSPATH . 'wp-admin/includes/file.php'; - $htaccess_file = get_home_path() . '.htaccess'; + $htaccess_file = \get_home_path() . '.htaccess'; if ( ! \wp_is_writable( $htaccess_file ) ) { return false; @@ -163,10 +163,7 @@ public static function append_with_markers( $marker, $rules ) { $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; - // Add marker to the rules. - $rules = $start_marker . PHP_EOL . $rules . PHP_EOL . $end_marker; - - // Add rules to the top of the file. + $rules = $start_marker . PHP_EOL . $rules . PHP_EOL . $end_marker; $htaccess = $rules . PHP_EOL . PHP_EOL . $htaccess; return \wp_filesystem->put_contents( $htaccess_file, $htaccess, FS_CHMOD_FILE ); From 3db8099aa083fda4ad6d2c2f493bf79bd4d29e36 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 15 May 2025 14:26:21 +0200 Subject: [PATCH 05/18] fix typo --- integration/class-litespeed-cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index eb932e82f..fbc76ebd0 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -166,6 +166,6 @@ public static function append_with_markers( $marker, $rules ) { $rules = $start_marker . PHP_EOL . $rules . PHP_EOL . $end_marker; $htaccess = $rules . PHP_EOL . PHP_EOL . $htaccess; - return \wp_filesystem->put_contents( $htaccess_file, $htaccess, FS_CHMOD_FILE ); + return $wp_filesystem->put_contents( $htaccess_file, $htaccess, FS_CHMOD_FILE ); } } From a928082880939a1fe5899487af7d283ac5b4dc53 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 15 May 2025 14:42:22 +0200 Subject: [PATCH 06/18] add tests --- integration/class-litespeed-cache.php | 47 ++++--- .../class-test-litespeed-cache.php | 119 ++++++++++++++++++ 2 files changed, 150 insertions(+), 16 deletions(-) create mode 100644 tests/integration/class-test-litespeed-cache.php diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index fbc76ebd0..7e4a33ffb 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -19,7 +19,7 @@ class Litespeed_Cache { * * @var string */ - private static $rules = ' + public static $rules = ' RewriteEngine On RewriteCond %{HTTP:Accept} application RewriteRule ^ - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+isjson] @@ -30,14 +30,14 @@ class Litespeed_Cache { * * @var string */ - private static $option_name = 'activitypub_litespeed_cache_setup'; + public static $option_name = 'activitypub_litespeed_cache_setup'; /** * The marker to identify the rules in the htaccess file. * * @var string */ - private static $marker = 'ActivityPub LiteSpeed Cache'; + public static $marker = 'ActivityPub LiteSpeed Cache'; /** * Initialize the integration. @@ -62,14 +62,7 @@ public static function add_htaccess_rules() { * Remove the Litespeed Cache htaccess rules. */ public static function remove_htaccess_rules() { - // Ensure get_home_path() is declared. - require_once ABSPATH . 'wp-admin/includes/file.php'; - - $htaccess_file = \get_home_path() . '.htaccess'; - - if ( \wp_is_writable( $htaccess_file ) ) { - \insert_with_markers( $htaccess_file, self::$marker, '' ); - } + self::append_with_markers( self::$marker, '' ); \delete_option( self::$option_name ); } @@ -142,21 +135,21 @@ public static function test_litespeed_cache_integration() { * @return bool True on success, false on failure. */ public static function append_with_markers( $marker, $rules ) { - // Ensure get_home_path() is declared. - require_once ABSPATH . 'wp-admin/includes/file.php'; - - $htaccess_file = \get_home_path() . '.htaccess'; + $htaccess_file = self::get_htaccess_file_path(); if ( ! \wp_is_writable( $htaccess_file ) ) { return false; } + // Ensure get_home_path() is declared. + require_once ABSPATH . 'wp-admin/includes/file.php'; + global $wp_filesystem; \WP_Filesystem(); $htaccess = $wp_filesystem->get_contents( $htaccess_file ); - if ( \preg_match( $marker, $htaccess ) ) { + if ( strpos( $htaccess, $marker ) !== false ) { return \insert_with_markers( $htaccess_file, $marker, $rules ); } @@ -168,4 +161,26 @@ public static function append_with_markers( $marker, $rules ) { return $wp_filesystem->put_contents( $htaccess_file, $htaccess, FS_CHMOD_FILE ); } + + /** + * Get the htaccess file. + * + * @return string|false The htaccess file or false. + */ + public static function get_htaccess_file_path() { + $htaccess_file = false; + + // phpcs:ignore WordPress.PHP.NoSilencedErrors + if ( @file_exists( \get_home_path() . '.htaccess' ) ) { + /** The htaccess file resides in ABSPATH */ + $htaccess_file = \get_home_path() . '.htaccess'; + } + + /** + * Filter the htaccess file path. + * + * @param string|false $htaccess_file The htaccess file path. + */ + return \apply_filters( 'activitypub_litespeed_cache_htaccess_file', $htaccess_file ); + } } diff --git a/tests/integration/class-test-litespeed-cache.php b/tests/integration/class-test-litespeed-cache.php new file mode 100644 index 000000000..9f57a00fa --- /dev/null +++ b/tests/integration/class-test-litespeed-cache.php @@ -0,0 +1,119 @@ +htaccess_file = \sys_get_temp_dir() . '/.htaccess-test'; + $this->original_htaccess = "# BEGIN WordPress\n# END WordPress"; + // phpcs:ignore + \file_put_contents( $this->htaccess_file, $this->original_htaccess ); + // Patch get_home_path to use our temp dir. + \add_filter( 'activitypub_litespeed_cache_home_path', array( $this, 'get_home_path' ) ); + } + + /** + * Tear down the test environment. + */ + public function tear_down() { + parent::tear_down(); + if ( \file_exists( $this->htaccess_file ) ) { + \wp_delete_file( $this->htaccess_file ); + } + \remove_all_filters( 'activitypub_litespeed_cache_home_path' ); + } + + /** + * Get the home path for the test environment. + * + * @return string The home path. + */ + public function get_home_path() { + return \dirname( $this->htaccess_file ) . '/'; + } + + /** + * Test adding htaccess rules. + */ + public function test_add_htaccess_rules() { + // Ensure filter is set for correct htaccess file path + \add_filter( + 'activitypub_litespeed_cache_htaccess_file', + function( $file ) { return $this->htaccess_file; } + ); + Litespeed_Cache::add_htaccess_rules(); + // phpcs:ignore + $contents = \file_get_contents( $this->htaccess_file ); + $this->assertStringContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be present in htaccess' ); + } + + /** + * Test removing htaccess rules. + */ + public function test_remove_htaccess_rules() { + // First add, then remove. + Litespeed_Cache::add_htaccess_rules(); + Litespeed_Cache::remove_htaccess_rules(); + // phpcs:ignore + $contents = \file_get_contents( $this->htaccess_file ); + $this->assertStringNotContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be removed from htaccess' ); + } + + /** + * Test no duplicate rules. + */ + public function test_no_duplicate_rules() { + // Ensure filter is set for correct htaccess file path + \add_filter( + 'activitypub_litespeed_cache_htaccess_file', + function( $file ) { return $this->htaccess_file; } + ); + Litespeed_Cache::add_htaccess_rules(); + Litespeed_Cache::add_htaccess_rules(); + // phpcs:ignore + $contents = \file_get_contents( $this->htaccess_file ); + // Count number of rule blocks. + $rule_count = substr_count( $contents, Litespeed_Cache::$rules ); + $this->assertEquals( 1, $rule_count, 'Litespeed rules should appear only once' ); + } + + /** + * Test that the option is updated when rules are added. + * + * @return void + */ + public function test_option_updated_on_add() { + Litespeed_Cache::add_htaccess_rules(); + $option = \get_option( Litespeed_Cache::$option_name ); + $this->assertTrue( $option, 'Option should be updated to true after adding rules' ); + } +} From fa30482905a0f7a8e401a3dd76df4f715fa5d809 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 15 May 2025 14:45:38 +0200 Subject: [PATCH 07/18] fix phpcs --- .../class-test-litespeed-cache.php | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/tests/integration/class-test-litespeed-cache.php b/tests/integration/class-test-litespeed-cache.php index 9f57a00fa..7c0ee4256 100644 --- a/tests/integration/class-test-litespeed-cache.php +++ b/tests/integration/class-test-litespeed-cache.php @@ -65,15 +65,17 @@ public function get_home_path() { * Test adding htaccess rules. */ public function test_add_htaccess_rules() { - // Ensure filter is set for correct htaccess file path - \add_filter( - 'activitypub_litespeed_cache_htaccess_file', - function( $file ) { return $this->htaccess_file; } - ); + $function = function () { + return $this->htaccess_file; + }; + \add_filter( 'activitypub_litespeed_cache_htaccess_file', $function ); + Litespeed_Cache::add_htaccess_rules(); // phpcs:ignore $contents = \file_get_contents( $this->htaccess_file ); $this->assertStringContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be present in htaccess' ); + + \remove_filter( 'activitypub_litespeed_cache_htaccess_file', $function ); } /** @@ -92,11 +94,11 @@ public function test_remove_htaccess_rules() { * Test no duplicate rules. */ public function test_no_duplicate_rules() { - // Ensure filter is set for correct htaccess file path - \add_filter( - 'activitypub_litespeed_cache_htaccess_file', - function( $file ) { return $this->htaccess_file; } - ); + $function = function () { + return $this->htaccess_file; + }; + \add_filter( 'activitypub_litespeed_cache_htaccess_file', $function ); + Litespeed_Cache::add_htaccess_rules(); Litespeed_Cache::add_htaccess_rules(); // phpcs:ignore @@ -104,6 +106,8 @@ function( $file ) { return $this->htaccess_file; } // Count number of rule blocks. $rule_count = substr_count( $contents, Litespeed_Cache::$rules ); $this->assertEquals( 1, $rule_count, 'Litespeed rules should appear only once' ); + + \remove_filter( 'activitypub_litespeed_cache_htaccess_file', $function ); } /** From 3df4796eeedff98a8ea08d464b748ab3c7bb79bd Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 15 May 2025 14:46:23 +0200 Subject: [PATCH 08/18] fix check --- integration/class-litespeed-cache.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index 7e4a33ffb..c154b1b8b 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -108,7 +108,7 @@ public static function test_litespeed_cache_integration() { 'test' => 'test_litespeed_cache_integration', ); - if ( 0 === \get_option( self::$option_name, '0' ) ) { + if ( '0' === \get_option( self::$option_name, '0' ) ) { $result['status'] = 'critical'; $result['label'] = \__( 'Litespeed Cache might not be properly configured.', 'activitypub' ); $result['badge']['color'] = 'red'; From 96f78812512b5558a792413d4242503737a8a3d4 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 23 Oct 2025 12:13:49 +0200 Subject: [PATCH 09/18] Refactor Litespeed Cache integration and tests Improves Litespeed Cache integration by updating htaccess rule management, removing activation/deactivation hooks, and refining option handling. Replaces and enhances integration tests, moving them to phpunit and adding coverage for option state and write failures. --- integration/class-litespeed-cache.php | 30 ++- integration/load.php | 4 - .../class-test-litespeed-cache.php | 123 ----------- .../class-test-litespeed-cache.php | 193 ++++++++++++++++++ 4 files changed, 216 insertions(+), 134 deletions(-) delete mode 100644 tests/integration/class-test-litespeed-cache.php create mode 100644 tests/phpunit/tests/integration/class-test-litespeed-cache.php diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index c154b1b8b..e5fc11305 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -43,8 +43,10 @@ class Litespeed_Cache { * Initialize the integration. */ public static function init() { - \add_action( 'activate_litespeed-cache/litespeed-cache.php', array( self::class, 'add_htaccess_rules' ) ); - \add_action( 'deactivate_litespeed-cache/litespeed-cache.php', array( self::class, 'remove_htaccess_rules' ) ); + // Add rules if LiteSpeed Cache is active and rules aren't set. + if ( \is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) && ! \get_option( self::$option_name ) ) { + self::add_htaccess_rules(); + } \add_filter( 'site_status_tests', array( self::class, 'maybe_add_site_health' ) ); } @@ -55,7 +57,11 @@ public static function init() { public static function add_htaccess_rules() { $added_rules = self::append_with_markers( self::$marker, self::$rules ); - \update_option( self::$option_name, $added_rules ); + if ( $added_rules ) { + \update_option( self::$option_name, '1' ); + } else { + \update_option( self::$option_name, '0' ); + } } /** @@ -108,7 +114,7 @@ public static function test_litespeed_cache_integration() { 'test' => 'test_litespeed_cache_integration', ); - if ( '0' === \get_option( self::$option_name, '0' ) ) { + if ( ! \get_option( self::$option_name ) ) { $result['status'] = 'critical'; $result['label'] = \__( 'Litespeed Cache might not be properly configured.', 'activitypub' ); $result['badge']['color'] = 'red'; @@ -127,10 +133,10 @@ public static function test_litespeed_cache_integration() { } /** - * Append rules to a file with markers. + * Prepend rules to the top of a file with markers. * * @param string $marker The marker to identify the rules in the file. - * @param string $rules The rules to append. + * @param string $rules The rules to prepend. * * @return bool True on success, false on failure. */ @@ -149,10 +155,20 @@ public static function append_with_markers( $marker, $rules ) { $htaccess = $wp_filesystem->get_contents( $htaccess_file ); + // If marker exists, remove the old block first. if ( strpos( $htaccess, $marker ) !== false ) { - return \insert_with_markers( $htaccess_file, $marker, $rules ); + // Remove existing marker block. + $pattern = '/# BEGIN ' . preg_quote( $marker, '/' ) . '.*?# END ' . preg_quote( $marker, '/' ) . '\r?\n?/s'; + $htaccess = preg_replace( $pattern, '', $htaccess ); + $htaccess = trim( $htaccess ); + } + + // If rules are empty, just return (for removal case). + if ( empty( $rules ) ) { + return $wp_filesystem->put_contents( $htaccess_file, $htaccess, FS_CHMOD_FILE ); } + // Prepend new rules to the top of the file. $start_marker = "# BEGIN {$marker}"; $end_marker = "# END {$marker}"; diff --git a/integration/load.php b/integration/load.php index 25732e473..e5e87032e 100644 --- a/integration/load.php +++ b/integration/load.php @@ -162,10 +162,6 @@ function ( $transformer, $data, $object_class ) { \register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'add_cache_config' ) ); \register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'remove_cache_config' ) ); -// Register activation and deactivation hooks for Litespeed Cache integration. -\register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Litespeed_Cache', 'add_htaccess_rules' ) ); -\register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Litespeed_Cache', 'remove_htaccess_rules' ) ); - /** * Register the Stream Connector for ActivityPub. * diff --git a/tests/integration/class-test-litespeed-cache.php b/tests/integration/class-test-litespeed-cache.php deleted file mode 100644 index 7c0ee4256..000000000 --- a/tests/integration/class-test-litespeed-cache.php +++ /dev/null @@ -1,123 +0,0 @@ -htaccess_file = \sys_get_temp_dir() . '/.htaccess-test'; - $this->original_htaccess = "# BEGIN WordPress\n# END WordPress"; - // phpcs:ignore - \file_put_contents( $this->htaccess_file, $this->original_htaccess ); - // Patch get_home_path to use our temp dir. - \add_filter( 'activitypub_litespeed_cache_home_path', array( $this, 'get_home_path' ) ); - } - - /** - * Tear down the test environment. - */ - public function tear_down() { - parent::tear_down(); - if ( \file_exists( $this->htaccess_file ) ) { - \wp_delete_file( $this->htaccess_file ); - } - \remove_all_filters( 'activitypub_litespeed_cache_home_path' ); - } - - /** - * Get the home path for the test environment. - * - * @return string The home path. - */ - public function get_home_path() { - return \dirname( $this->htaccess_file ) . '/'; - } - - /** - * Test adding htaccess rules. - */ - public function test_add_htaccess_rules() { - $function = function () { - return $this->htaccess_file; - }; - \add_filter( 'activitypub_litespeed_cache_htaccess_file', $function ); - - Litespeed_Cache::add_htaccess_rules(); - // phpcs:ignore - $contents = \file_get_contents( $this->htaccess_file ); - $this->assertStringContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be present in htaccess' ); - - \remove_filter( 'activitypub_litespeed_cache_htaccess_file', $function ); - } - - /** - * Test removing htaccess rules. - */ - public function test_remove_htaccess_rules() { - // First add, then remove. - Litespeed_Cache::add_htaccess_rules(); - Litespeed_Cache::remove_htaccess_rules(); - // phpcs:ignore - $contents = \file_get_contents( $this->htaccess_file ); - $this->assertStringNotContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be removed from htaccess' ); - } - - /** - * Test no duplicate rules. - */ - public function test_no_duplicate_rules() { - $function = function () { - return $this->htaccess_file; - }; - \add_filter( 'activitypub_litespeed_cache_htaccess_file', $function ); - - Litespeed_Cache::add_htaccess_rules(); - Litespeed_Cache::add_htaccess_rules(); - // phpcs:ignore - $contents = \file_get_contents( $this->htaccess_file ); - // Count number of rule blocks. - $rule_count = substr_count( $contents, Litespeed_Cache::$rules ); - $this->assertEquals( 1, $rule_count, 'Litespeed rules should appear only once' ); - - \remove_filter( 'activitypub_litespeed_cache_htaccess_file', $function ); - } - - /** - * Test that the option is updated when rules are added. - * - * @return void - */ - public function test_option_updated_on_add() { - Litespeed_Cache::add_htaccess_rules(); - $option = \get_option( Litespeed_Cache::$option_name ); - $this->assertTrue( $option, 'Option should be updated to true after adding rules' ); - } -} diff --git a/tests/phpunit/tests/integration/class-test-litespeed-cache.php b/tests/phpunit/tests/integration/class-test-litespeed-cache.php new file mode 100644 index 000000000..eda4683a7 --- /dev/null +++ b/tests/phpunit/tests/integration/class-test-litespeed-cache.php @@ -0,0 +1,193 @@ +htaccess_file = \sys_get_temp_dir() . '/.htaccess-test'; + $this->original_htaccess = "# BEGIN WordPress\n# END WordPress"; + // phpcs:ignore + \file_put_contents( $this->htaccess_file, $this->original_htaccess ); + // Patch htaccess file path to use our temp file. + \add_filter( 'activitypub_litespeed_cache_htaccess_file', array( $this, 'get_htaccess_file_path' ) ); + } + + /** + * Tear down the test environment. + */ + public function tear_down() { + parent::tear_down(); + if ( \file_exists( $this->htaccess_file ) ) { + \wp_delete_file( $this->htaccess_file ); + } + \remove_all_filters( 'activitypub_litespeed_cache_htaccess_file' ); + } + + /** + * Get the htaccess file path for the test environment. + * + * @return string The htaccess file path. + */ + public function get_htaccess_file_path() { + return $this->htaccess_file; + } + + /** + * Test adding htaccess rules. + * + * @covers ::add_htaccess_rules + * @covers ::append_with_markers + * @covers ::get_htaccess_file_path + */ + public function test_add_htaccess_rules() { + Litespeed_Cache::add_htaccess_rules(); + // phpcs:ignore + $contents = \file_get_contents( $this->htaccess_file ); + $this->assertStringContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be present in htaccess' ); + } + + /** + * Test removing htaccess rules. + * + * @covers ::remove_htaccess_rules + * @covers ::append_with_markers + */ + public function test_remove_htaccess_rules() { + // First add, then remove. + Litespeed_Cache::add_htaccess_rules(); + Litespeed_Cache::remove_htaccess_rules(); + // phpcs:ignore + $contents = \file_get_contents( $this->htaccess_file ); + $this->assertStringNotContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be removed from htaccess' ); + } + + /** + * Test no duplicate rules. + * + * @covers ::add_htaccess_rules + * @covers ::append_with_markers + */ + public function test_no_duplicate_rules() { + Litespeed_Cache::add_htaccess_rules(); + Litespeed_Cache::add_htaccess_rules(); + // phpcs:ignore + $contents = \file_get_contents( $this->htaccess_file ); + // Count number of rule blocks. + $rule_count = substr_count( $contents, Litespeed_Cache::$rules ); + $this->assertEquals( 1, $rule_count, 'Litespeed rules should appear only once' ); + } + + /** + * Test that the option is updated when rules are added. + * + * @covers ::add_htaccess_rules + */ + public function test_option_updated_on_add() { + Litespeed_Cache::add_htaccess_rules(); + $option = \get_option( Litespeed_Cache::$option_name ); + $this->assertEquals( '1', $option, 'Option should be set to 1 after adding rules' ); + } + + /** + * Test that the option is deleted when rules are removed. + * + * @covers ::remove_htaccess_rules + */ + public function test_option_deleted_on_remove() { + // First add rules to set the option. + Litespeed_Cache::add_htaccess_rules(); + $this->assertNotFalse( \get_option( Litespeed_Cache::$option_name ), 'Option should exist after adding rules' ); + + // Then remove rules. + Litespeed_Cache::remove_htaccess_rules(); + $this->assertFalse( \get_option( Litespeed_Cache::$option_name ), 'Option should be deleted after removing rules' ); + } + + /** + * Test Site Health status when properly configured. + * + * @covers ::test_litespeed_cache_integration + */ + public function test_site_health_when_configured() { + // Set up as if rules were added successfully. + \update_option( Litespeed_Cache::$option_name, '1' ); + + $result = Litespeed_Cache::test_litespeed_cache_integration(); + + $this->assertEquals( 'good', $result['status'], 'Status should be good when configured' ); + $this->assertEquals( 'green', $result['badge']['color'], 'Badge should be green' ); + $this->assertStringContainsString( 'well configured', $result['description'] ); + } + + /** + * Test Site Health status when not configured. + * + * @covers ::test_litespeed_cache_integration + */ + public function test_site_health_when_not_configured() { + // Ensure option is false (not configured). + \delete_option( Litespeed_Cache::$option_name ); + + $result = Litespeed_Cache::test_litespeed_cache_integration(); + + $this->assertEquals( 'critical', $result['status'], 'Status should be critical when not configured' ); + $this->assertEquals( 'red', $result['badge']['color'], 'Badge should be red' ); + $this->assertStringContainsString( 'not be properly configured', $result['label'] ); + $this->assertStringContainsString( 'add the following rules', $result['actions'] ); + $this->assertStringContainsString( \esc_html( Litespeed_Cache::$rules ), $result['actions'], 'Actions should contain HTML-escaped rules' ); + } + + /** + * Test write failure handling when htaccess is not writable. + * + * @covers ::add_htaccess_rules + * @covers ::append_with_markers + */ + public function test_write_failure_handling() { + // Create a read-only file. + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod + \chmod( $this->htaccess_file, 0444 ); + + Litespeed_Cache::add_htaccess_rules(); + + // Option should be set to '0' on failure. + $option = \get_option( Litespeed_Cache::$option_name ); + $this->assertEquals( '0', $option, 'Option should be 0 when write fails' ); + + // Restore permissions for cleanup. + // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod + \chmod( $this->htaccess_file, 0644 ); + } +} From cc600df621cb88ba156c56aefdaa9b4b8031b4b5 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 23 Oct 2025 12:28:17 +0200 Subject: [PATCH 10/18] Add plugin active check fallback utility Introduces is_plugin_active_with_fallback() to safely check plugin activation status outside admin context. Updates LiteSpeed Cache and Surge integrations to use this utility for improved reliability when checking if dependent plugins are active. --- includes/functions.php | 20 ++++++++++++++++++++ integration/class-litespeed-cache.php | 6 ++++-- integration/class-surge.php | 6 ++++-- 3 files changed, 28 insertions(+), 4 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index a853cccae..ad1c12dee 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1828,3 +1828,23 @@ function get_url_authority( $url ) { return $parsed['scheme'] . '://' . $parsed['host']; } + +/** + * Check if a plugin is active, loading plugin.php if necessary. + * + * This is a wrapper around is_plugin_active() that ensures the function + * is available by loading wp-admin/includes/plugin.php if needed. This is + * useful when checking plugin status outside of the admin context. + * + * @param string $plugin Plugin basename (e.g., 'plugin-folder/plugin-file.php'). + * + * @return bool True if the plugin is active, false otherwise. + */ +function is_plugin_active_with_fallback( $plugin ) { + // Include plugin.php if not already loaded (needed for is_plugin_active). + if ( ! \function_exists( 'is_plugin_active' ) ) { + require_once ABSPATH . 'wp-admin/includes/plugin.php'; + } + + return \is_plugin_active( $plugin ); +} diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index e5fc11305..32a6e4d6f 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -7,6 +7,8 @@ namespace Activitypub\Integration; +use function Activitypub\is_plugin_active_with_fallback; + /** * LiteSpeed Cache integration. * @@ -44,7 +46,7 @@ class Litespeed_Cache { */ public static function init() { // Add rules if LiteSpeed Cache is active and rules aren't set. - if ( \is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) && ! \get_option( self::$option_name ) ) { + if ( is_plugin_active_with_fallback( 'litespeed-cache/litespeed-cache.php' ) && ! \get_option( self::$option_name ) ) { self::add_htaccess_rules(); } @@ -81,7 +83,7 @@ public static function remove_htaccess_rules() { * @return array The site health tests with the Litespeed Cache config test. */ public static function maybe_add_site_health( $tests ) { - if ( ! \is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) ) { + if ( ! is_plugin_active_with_fallback( 'litespeed-cache/litespeed-cache.php' ) ) { return $tests; } diff --git a/integration/class-surge.php b/integration/class-surge.php index 49002073e..45dc83f32 100644 --- a/integration/class-surge.php +++ b/integration/class-surge.php @@ -7,6 +7,8 @@ namespace Activitypub\Integration; +use function Activitypub\is_plugin_active_with_fallback; + /** * Surge Cache integration. * @@ -38,7 +40,7 @@ public static function init() { */ public static function add_cache_config() { // Check if surge is installed and active. - if ( ! \is_plugin_active( 'surge/surge.php' ) ) { + if ( ! is_plugin_active_with_fallback( 'surge/surge.php' ) ) { return; } @@ -139,7 +141,7 @@ public static function get_config_file_path() { * @return array The site health tests with the Surge cache config test. */ public static function maybe_add_site_health( $tests ) { - if ( ! \is_plugin_active( 'surge/surge.php' ) ) { + if ( ! is_plugin_active_with_fallback( 'surge/surge.php' ) ) { return $tests; } From 0b16e7dd7c49325f625ce2c73d1acd18dbcdd0e6 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 23 Oct 2025 12:30:02 +0200 Subject: [PATCH 11/18] Standardize LiteSpeed Cache naming Replaces all instances of 'Litespeed Cache' with 'LiteSpeed Cache' in integration and test files for consistency and correct branding. --- integration/class-litespeed-cache.php | 22 +++++++++---------- .../class-test-litespeed-cache.php | 10 ++++----- 2 files changed, 16 insertions(+), 16 deletions(-) diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index 32a6e4d6f..5c21d7b64 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -54,7 +54,7 @@ public static function init() { } /** - * Add the Litespeed Cache htaccess rules. + * Add the LiteSpeed Cache htaccess rules. */ public static function add_htaccess_rules() { $added_rules = self::append_with_markers( self::$marker, self::$rules ); @@ -67,7 +67,7 @@ public static function add_htaccess_rules() { } /** - * Remove the Litespeed Cache htaccess rules. + * Remove the LiteSpeed Cache htaccess rules. */ public static function remove_htaccess_rules() { self::append_with_markers( self::$marker, '' ); @@ -76,11 +76,11 @@ public static function remove_htaccess_rules() { } /** - * Maybe add the Litespeed Cache config to the site health. + * Maybe add the LiteSpeed Cache config to the site health. * * @param array $tests The site health tests. * - * @return array The site health tests with the Litespeed Cache config test. + * @return array The site health tests with the LiteSpeed Cache config test. */ public static function maybe_add_site_health( $tests ) { if ( ! is_plugin_active_with_fallback( 'litespeed-cache/litespeed-cache.php' ) ) { @@ -88,7 +88,7 @@ public static function maybe_add_site_health( $tests ) { } $tests['direct']['activitypub_test_litespeed_cache_integration'] = array( - 'label' => \__( 'Litespeed Cache Test', 'activitypub' ), + 'label' => \__( 'LiteSpeed Cache Test', 'activitypub' ), 'test' => array( self::class, 'test_litespeed_cache_integration' ), ); @@ -96,13 +96,13 @@ public static function maybe_add_site_health( $tests ) { } /** - * Test the Litespeed Cache integration. + * Test the LiteSpeed Cache integration. * * @return array The test results. */ public static function test_litespeed_cache_integration() { $result = array( - 'label' => \__( 'Compatibility with Litespeed Cache', 'activitypub' ), + 'label' => \__( 'Compatibility with LiteSpeed Cache', 'activitypub' ), 'status' => 'good', 'badge' => array( 'label' => \__( 'ActivityPub', 'activitypub' ), @@ -110,7 +110,7 @@ public static function test_litespeed_cache_integration() { ), 'description' => \sprintf( '

%s

', - \__( 'Litespeed Cache is well configured to work with ActivityPub.', 'activitypub' ) + \__( 'LiteSpeed Cache is well configured to work with ActivityPub.', 'activitypub' ) ), 'actions' => '', 'test' => 'test_litespeed_cache_integration', @@ -118,15 +118,15 @@ public static function test_litespeed_cache_integration() { if ( ! \get_option( self::$option_name ) ) { $result['status'] = 'critical'; - $result['label'] = \__( 'Litespeed Cache might not be properly configured.', 'activitypub' ); + $result['label'] = \__( 'LiteSpeed Cache might not be properly configured.', 'activitypub' ); $result['badge']['color'] = 'red'; $result['description'] = \sprintf( '

%s

', - \__( 'Litespeed Cache isn’t currently set up to work with ActivityPub. While this isn’t a major problem, it’s a good idea to enable support. Without it, some technical files (like JSON) might accidentally show up in your website’s cache and be visible to visitors.', 'activitypub' ) + \__( 'LiteSpeed Cache isn’t currently set up to work with ActivityPub. While this isn’t a major problem, it’s a good idea to enable support. Without it, some technical files (like JSON) might accidentally show up in your website’s cache and be visible to visitors.', 'activitypub' ) ); $result['actions'] = \sprintf( '

%s

%s
', - \__( 'To enable the ActivityPub integration with Litespeed Cache, add the following rules to your .htaccess file:', 'activitypub' ), + \__( 'To enable the ActivityPub integration with LiteSpeed Cache, add the following rules to your .htaccess file:', 'activitypub' ), \esc_html( self::$rules ) ); } diff --git a/tests/phpunit/tests/integration/class-test-litespeed-cache.php b/tests/phpunit/tests/integration/class-test-litespeed-cache.php index eda4683a7..995dd70dc 100644 --- a/tests/phpunit/tests/integration/class-test-litespeed-cache.php +++ b/tests/phpunit/tests/integration/class-test-litespeed-cache.php @@ -1,6 +1,6 @@ htaccess_file ); - $this->assertStringContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be present in htaccess' ); + $this->assertStringContainsString( Litespeed_Cache::$rules, $contents, 'LiteSpeed rules should be present in htaccess' ); } /** @@ -90,7 +90,7 @@ public function test_remove_htaccess_rules() { Litespeed_Cache::remove_htaccess_rules(); // phpcs:ignore $contents = \file_get_contents( $this->htaccess_file ); - $this->assertStringNotContainsString( Litespeed_Cache::$rules, $contents, 'Litespeed rules should be removed from htaccess' ); + $this->assertStringNotContainsString( Litespeed_Cache::$rules, $contents, 'LiteSpeed rules should be removed from htaccess' ); } /** @@ -106,7 +106,7 @@ public function test_no_duplicate_rules() { $contents = \file_get_contents( $this->htaccess_file ); // Count number of rule blocks. $rule_count = substr_count( $contents, Litespeed_Cache::$rules ); - $this->assertEquals( 1, $rule_count, 'Litespeed rules should appear only once' ); + $this->assertEquals( 1, $rule_count, 'LiteSpeed rules should appear only once' ); } /** From 08eb4d90c1561b97485b632c957f590a305f2fcd Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 23 Oct 2025 12:30:57 +0200 Subject: [PATCH 12/18] Update integration/load.php Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- integration/load.php | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/integration/load.php b/integration/load.php index e5e87032e..2b9d95576 100644 --- a/integration/load.php +++ b/integration/load.php @@ -150,7 +150,7 @@ function ( $transformer, $data, $object_class ) { /** * Load the LiteSpeed Cache integration. * - * Only load code that needs LiteSpeed Cache to run once LiteSpeed Cache is loaded and initialized. + * The check for whether LiteSpeed Cache is loaded and initialized happens inside Litespeed_Cache::init(). * * @see https://wordpress.org/plugins/litespeed-cache/ */ From 232721ea8dee6eff729026b300fdc625c69d6830 Mon Sep 17 00:00:00 2001 From: Automattic Bot Date: Thu, 23 Oct 2025 12:32:00 +0200 Subject: [PATCH 13/18] Add changelog --- .github/changelog/1683-from-description | 4 ++++ 1 file changed, 4 insertions(+) create mode 100644 .github/changelog/1683-from-description diff --git a/.github/changelog/1683-from-description b/.github/changelog/1683-from-description new file mode 100644 index 000000000..1c632eb65 --- /dev/null +++ b/.github/changelog/1683-from-description @@ -0,0 +1,4 @@ +Significance: minor +Type: added + +Add LiteSpeed Cache integration to prevent ActivityPub JSON responses from being cached incorrectly. Includes automatic .htaccess rules and Site Health check to ensure proper configuration. From 83d5eb4cd512861d9f9965eb97ba94d13d0d8aaa Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 23 Oct 2025 12:40:59 +0200 Subject: [PATCH 14/18] Rename is_plugin_active_with_fallback to is_plugin_active Refactored the plugin activation check by renaming is_plugin_active_with_fallback to is_plugin_active in functions.php. Updated all references in LiteSpeed Cache and Surge integration classes to use the new function name for consistency and clarity. --- includes/functions.php | 10 +++++----- integration/class-litespeed-cache.php | 6 +++--- integration/class-surge.php | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/includes/functions.php b/includes/functions.php index ad1c12dee..c597f0ae0 100644 --- a/includes/functions.php +++ b/includes/functions.php @@ -1832,16 +1832,16 @@ function get_url_authority( $url ) { /** * Check if a plugin is active, loading plugin.php if necessary. * - * This is a wrapper around is_plugin_active() that ensures the function - * is available by loading wp-admin/includes/plugin.php if needed. This is - * useful when checking plugin status outside of the admin context. + * This is a wrapper around the core is_plugin_active() function that ensures + * the function is available by loading wp-admin/includes/plugin.php if needed. + * This is useful when checking plugin status outside of the admin context. * * @param string $plugin Plugin basename (e.g., 'plugin-folder/plugin-file.php'). * * @return bool True if the plugin is active, false otherwise. */ -function is_plugin_active_with_fallback( $plugin ) { - // Include plugin.php if not already loaded (needed for is_plugin_active). +function is_plugin_active( $plugin ) { + // Include plugin.php if not already loaded (needed for core is_plugin_active). if ( ! \function_exists( 'is_plugin_active' ) ) { require_once ABSPATH . 'wp-admin/includes/plugin.php'; } diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index 5c21d7b64..efa708a31 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -7,7 +7,7 @@ namespace Activitypub\Integration; -use function Activitypub\is_plugin_active_with_fallback; +use function Activitypub\is_plugin_active; /** * LiteSpeed Cache integration. @@ -46,7 +46,7 @@ class Litespeed_Cache { */ public static function init() { // Add rules if LiteSpeed Cache is active and rules aren't set. - if ( is_plugin_active_with_fallback( 'litespeed-cache/litespeed-cache.php' ) && ! \get_option( self::$option_name ) ) { + if ( is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) && ! \get_option( self::$option_name ) ) { self::add_htaccess_rules(); } @@ -83,7 +83,7 @@ public static function remove_htaccess_rules() { * @return array The site health tests with the LiteSpeed Cache config test. */ public static function maybe_add_site_health( $tests ) { - if ( ! is_plugin_active_with_fallback( 'litespeed-cache/litespeed-cache.php' ) ) { + if ( ! is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) ) { return $tests; } diff --git a/integration/class-surge.php b/integration/class-surge.php index 45dc83f32..dbc4f72e1 100644 --- a/integration/class-surge.php +++ b/integration/class-surge.php @@ -7,7 +7,7 @@ namespace Activitypub\Integration; -use function Activitypub\is_plugin_active_with_fallback; +use function Activitypub\is_plugin_active; /** * Surge Cache integration. @@ -40,7 +40,7 @@ public static function init() { */ public static function add_cache_config() { // Check if surge is installed and active. - if ( ! is_plugin_active_with_fallback( 'surge/surge.php' ) ) { + if ( ! is_plugin_active( 'surge/surge.php' ) ) { return; } @@ -141,7 +141,7 @@ public static function get_config_file_path() { * @return array The site health tests with the Surge cache config test. */ public static function maybe_add_site_health( $tests ) { - if ( ! is_plugin_active_with_fallback( 'surge/surge.php' ) ) { + if ( ! is_plugin_active( 'surge/surge.php' ) ) { return $tests; } From 0144a4c42c026b1e3389e63764fc56dda6dc804b Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 23 Oct 2025 17:05:44 +0200 Subject: [PATCH 15/18] Add cleanup for LiteSpeed Cache htaccess rules Implements automatic removal of LiteSpeed Cache htaccess rules when the plugin is deactivated, deleted, or when ActivityPub is deactivated. Adds activation/deactivation hooks and tests to ensure proper cleanup in various scenarios. --- includes/class-activitypub.php | 5 + integration/class-litespeed-cache.php | 20 ++++ integration/load.php | 5 + .../class-test-litespeed-cache.php | 108 ++++++++++++++++++ 4 files changed, 138 insertions(+) diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index b95b69ec0..610f55a55 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -68,6 +68,11 @@ public static function deactivate( $network_wide ) { \remove_filter( 'pre_wp_update_comment_count_now', array( Comment::class, 'pre_wp_update_comment_count_now' ) ); Migration::update_comment_counts( 2000 ); + // Clean up LiteSpeed Cache htaccess rules. + if ( \class_exists( '\Activitypub\Integration\Litespeed_Cache' ) ) { + \Activitypub\Integration\Litespeed_Cache::remove_htaccess_rules(); + } + if ( \is_multisite() && $network_wide && ! \wp_is_large_network() ) { $sites = \get_sites( array( 'fields' => 'ids' ) ); foreach ( $sites as $site ) { diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index efa708a31..726713903 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -50,9 +50,29 @@ public static function init() { self::add_htaccess_rules(); } + // Remove rules if LiteSpeed Cache is not active but rules were previously set. + if ( ! is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) && \get_option( self::$option_name ) ) { + self::remove_htaccess_rules(); + \delete_option( self::$option_name ); + } + + // Clean up when LiteSpeed Cache plugin is deleted. + \add_action( 'deleted_plugin', array( self::class, 'on_plugin_deleted' ) ); + \add_filter( 'site_status_tests', array( self::class, 'maybe_add_site_health' ) ); } + /** + * Clean up htaccess rules when LiteSpeed Cache plugin is deleted. + * + * @param string $plugin_file Path to the plugin file relative to the plugins directory. + */ + public static function on_plugin_deleted( $plugin_file ) { + if ( 'litespeed-cache/litespeed-cache.php' === $plugin_file && \get_option( self::$option_name ) ) { + self::remove_htaccess_rules(); + } + } + /** * Add the LiteSpeed Cache htaccess rules. */ diff --git a/integration/load.php b/integration/load.php index 2b9d95576..13d1d6954 100644 --- a/integration/load.php +++ b/integration/load.php @@ -162,6 +162,11 @@ function ( $transformer, $data, $object_class ) { \register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'add_cache_config' ) ); \register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'remove_cache_config' ) ); +// Register activation and deactivation hooks for LiteSpeed Cache integration. +\register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\LiteSpeed_Cache', 'add_htaccess_rules' ) ); +\register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\LiteSpeed_Cache', 'remove_htaccess_rules' ) ); + + /** * Register the Stream Connector for ActivityPub. * diff --git a/tests/phpunit/tests/integration/class-test-litespeed-cache.php b/tests/phpunit/tests/integration/class-test-litespeed-cache.php index 995dd70dc..d49fd4d61 100644 --- a/tests/phpunit/tests/integration/class-test-litespeed-cache.php +++ b/tests/phpunit/tests/integration/class-test-litespeed-cache.php @@ -190,4 +190,112 @@ public function test_write_failure_handling() { // phpcs:ignore WordPress.WP.AlternativeFunctions.file_system_operations_chmod \chmod( $this->htaccess_file, 0644 ); } + + /** + * Test that rules are removed when LiteSpeed Cache is deactivated. + * + * @covers ::init + * @covers ::remove_htaccess_rules + */ + public function test_cleanup_when_litespeed_deactivated() { + // Simulate rules being previously added. + Litespeed_Cache::add_htaccess_rules(); + $this->assertEquals( '1', \get_option( Litespeed_Cache::$option_name ) ); + + // Mock that LiteSpeed is NOT active. + \add_filter( + 'activitypub_is_plugin_active', + function ( $is_active, $plugin ) { + if ( 'litespeed-cache/litespeed-cache.php' === $plugin ) { + return false; + } + return $is_active; + }, + 10, + 2 + ); + + // Run init (should detect LiteSpeed is deactivated and clean up). + Litespeed_Cache::init(); + + // Verify cleanup occurred. + $this->assertFalse( \get_option( Litespeed_Cache::$option_name ), 'Option should be deleted when LiteSpeed is deactivated' ); + + // phpcs:ignore + $contents = \file_get_contents( $this->htaccess_file ); + $this->assertStringNotContainsString( Litespeed_Cache::$rules, $contents, 'Rules should be removed when LiteSpeed is deactivated' ); + + \remove_all_filters( 'activitypub_is_plugin_active' ); + } + + /** + * Test that rules are cleaned up when ActivityPub is deactivated. + * + * @covers \Activitypub\Activitypub::deactivate + */ + public function test_cleanup_on_activitypub_deactivation() { + // Add rules first. + Litespeed_Cache::add_htaccess_rules(); + $this->assertEquals( '1', \get_option( Litespeed_Cache::$option_name ) ); + + // phpcs:ignore + $contents_before = \file_get_contents( $this->htaccess_file ); + $this->assertStringContainsString( Litespeed_Cache::$rules, $contents_before ); + + // Simulate deactivation. + \Activitypub\Activitypub::deactivate( false ); + + // Verify cleanup. + $this->assertFalse( \get_option( Litespeed_Cache::$option_name ), 'Option should be deleted on deactivation' ); + + // phpcs:ignore + $contents_after = \file_get_contents( $this->htaccess_file ); + $this->assertStringNotContainsString( Litespeed_Cache::$rules, $contents_after, 'Rules should be removed on deactivation' ); + } + + /** + * Test that rules are cleaned up when LiteSpeed Cache plugin is deleted. + * + * @covers ::on_plugin_deleted + */ + public function test_cleanup_when_litespeed_deleted() { + // Add rules first. + Litespeed_Cache::add_htaccess_rules(); + $this->assertEquals( '1', \get_option( Litespeed_Cache::$option_name ) ); + + // phpcs:ignore + $contents_before = \file_get_contents( $this->htaccess_file ); + $this->assertStringContainsString( Litespeed_Cache::$rules, $contents_before ); + + // Simulate LiteSpeed Cache plugin deletion. + \do_action( 'deleted_plugin', 'litespeed-cache/litespeed-cache.php', false ); + + // Verify cleanup. + $this->assertFalse( \get_option( Litespeed_Cache::$option_name ), 'Option should be deleted when plugin is deleted' ); + + // phpcs:ignore + $contents_after = \file_get_contents( $this->htaccess_file ); + $this->assertStringNotContainsString( Litespeed_Cache::$rules, $contents_after, 'Rules should be removed when plugin is deleted' ); + } + + /** + * Test that rules are NOT cleaned up when a different plugin is deleted. + * + * @covers ::on_plugin_deleted + */ + public function test_no_cleanup_when_other_plugin_deleted() { + // Add rules first. + Litespeed_Cache::add_htaccess_rules(); + $this->assertEquals( '1', \get_option( Litespeed_Cache::$option_name ) ); + + // Simulate a different plugin deletion. + \do_action( 'deleted_plugin', 'some-other-plugin/plugin.php', false ); + + // Verify rules still exist. + $this->assertEquals( '1', \get_option( Litespeed_Cache::$option_name ), 'Option should remain when other plugin is deleted' ); + + // phpcs:ignore + $contents = \file_get_contents( $this->htaccess_file ); + $this->assertStringContainsString( Litespeed_Cache::$rules, $contents, 'Rules should remain when other plugin is deleted' ); + } } From 2dd10037e72900a0a32ad909b9125fa3de867b7b Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Thu, 23 Oct 2025 21:47:29 +0200 Subject: [PATCH 16/18] Remove LiteSpeed Cache htaccess cleanup on uninstall Deleted code that removed LiteSpeed Cache htaccess rules during the uninstall process. This streamlines the uninstall routine and removes dependency on the Litespeed_Cache integration. --- includes/class-activitypub.php | 5 ----- 1 file changed, 5 deletions(-) diff --git a/includes/class-activitypub.php b/includes/class-activitypub.php index 610f55a55..b95b69ec0 100644 --- a/includes/class-activitypub.php +++ b/includes/class-activitypub.php @@ -68,11 +68,6 @@ public static function deactivate( $network_wide ) { \remove_filter( 'pre_wp_update_comment_count_now', array( Comment::class, 'pre_wp_update_comment_count_now' ) ); Migration::update_comment_counts( 2000 ); - // Clean up LiteSpeed Cache htaccess rules. - if ( \class_exists( '\Activitypub\Integration\Litespeed_Cache' ) ) { - \Activitypub\Integration\Litespeed_Cache::remove_htaccess_rules(); - } - if ( \is_multisite() && $network_wide && ! \wp_is_large_network() ) { $sites = \get_sites( array( 'fields' => 'ids' ) ); foreach ( $sites as $site ) { From 87c885287e9021cbd2ea9d65ccfee918ff13331b Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 24 Oct 2025 13:01:41 +0200 Subject: [PATCH 17/18] Trigger deactivate action in cache test Adds a call to the 'deactivate_{plugin}' action in the Litespeed Cache integration test to better simulate plugin deactivation and ensure proper cleanup. --- tests/phpunit/tests/integration/class-test-litespeed-cache.php | 1 + 1 file changed, 1 insertion(+) diff --git a/tests/phpunit/tests/integration/class-test-litespeed-cache.php b/tests/phpunit/tests/integration/class-test-litespeed-cache.php index d49fd4d61..3602d7129 100644 --- a/tests/phpunit/tests/integration/class-test-litespeed-cache.php +++ b/tests/phpunit/tests/integration/class-test-litespeed-cache.php @@ -244,6 +244,7 @@ public function test_cleanup_on_activitypub_deactivation() { // Simulate deactivation. \Activitypub\Activitypub::deactivate( false ); + \do_action( 'deactivate_' . ACTIVITYPUB_PLUGIN_BASENAME ); // Verify cleanup. $this->assertFalse( \get_option( Litespeed_Cache::$option_name ), 'Option should be deleted on deactivation' ); From 97a046b154f7adbbbb030b9e8c5504cad0c0c102 Mon Sep 17 00:00:00 2001 From: Konstantin Obenland Date: Fri, 24 Oct 2025 15:46:41 -0500 Subject: [PATCH 18/18] LiteSpeed Cache integration suggestions (#2364) --- integration/class-litespeed-cache.php | 37 ++++++++++--------- .../class-test-litespeed-cache.php | 4 +- 2 files changed, 22 insertions(+), 19 deletions(-) diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php index 726713903..fb5d52847 100644 --- a/integration/class-litespeed-cache.php +++ b/integration/class-litespeed-cache.php @@ -41,25 +41,32 @@ class Litespeed_Cache { */ public static $marker = 'ActivityPub LiteSpeed Cache'; + /** + * The LiteSpeed Cache plugin slug. + * + * @var string + */ + public static $plugin_slug = 'litespeed-cache/litespeed-cache.php'; + /** * Initialize the integration. */ public static function init() { // Add rules if LiteSpeed Cache is active and rules aren't set. - if ( is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) && ! \get_option( self::$option_name ) ) { - self::add_htaccess_rules(); - } + if ( is_plugin_active( self::$plugin_slug ) ) { + if ( ! \get_option( self::$option_name ) ) { + self::add_htaccess_rules(); + } + + \add_filter( 'site_status_tests', array( self::class, 'add_site_health_test' ) ); - // Remove rules if LiteSpeed Cache is not active but rules were previously set. - if ( ! is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) && \get_option( self::$option_name ) ) { + // Remove rules if LiteSpeed Cache is not active but rules were previously set. + } elseif ( \get_option( self::$option_name ) ) { self::remove_htaccess_rules(); - \delete_option( self::$option_name ); } // Clean up when LiteSpeed Cache plugin is deleted. \add_action( 'deleted_plugin', array( self::class, 'on_plugin_deleted' ) ); - - \add_filter( 'site_status_tests', array( self::class, 'maybe_add_site_health' ) ); } /** @@ -68,7 +75,7 @@ public static function init() { * @param string $plugin_file Path to the plugin file relative to the plugins directory. */ public static function on_plugin_deleted( $plugin_file ) { - if ( 'litespeed-cache/litespeed-cache.php' === $plugin_file && \get_option( self::$option_name ) ) { + if ( self::$plugin_slug === $plugin_file && \get_option( self::$option_name ) ) { self::remove_htaccess_rules(); } } @@ -96,17 +103,13 @@ public static function remove_htaccess_rules() { } /** - * Maybe add the LiteSpeed Cache config to the site health. + * Add the LiteSpeed Cache config test to site health. * * @param array $tests The site health tests. * * @return array The site health tests with the LiteSpeed Cache config test. */ - public static function maybe_add_site_health( $tests ) { - if ( ! is_plugin_active( 'litespeed-cache/litespeed-cache.php' ) ) { - return $tests; - } - + public static function add_site_health_test( $tests ) { $tests['direct']['activitypub_test_litespeed_cache_integration'] = array( 'label' => \__( 'LiteSpeed Cache Test', 'activitypub' ), 'test' => array( self::class, 'test_litespeed_cache_integration' ), @@ -162,7 +165,7 @@ public static function test_litespeed_cache_integration() { * * @return bool True on success, false on failure. */ - public static function append_with_markers( $marker, $rules ) { + private static function append_with_markers( $marker, $rules ) { $htaccess_file = self::get_htaccess_file_path(); if ( ! \wp_is_writable( $htaccess_file ) ) { @@ -205,7 +208,7 @@ public static function append_with_markers( $marker, $rules ) { * * @return string|false The htaccess file or false. */ - public static function get_htaccess_file_path() { + private static function get_htaccess_file_path() { $htaccess_file = false; // phpcs:ignore WordPress.PHP.NoSilencedErrors diff --git a/tests/phpunit/tests/integration/class-test-litespeed-cache.php b/tests/phpunit/tests/integration/class-test-litespeed-cache.php index 3602d7129..b315dea14 100644 --- a/tests/phpunit/tests/integration/class-test-litespeed-cache.php +++ b/tests/phpunit/tests/integration/class-test-litespeed-cache.php @@ -206,7 +206,7 @@ public function test_cleanup_when_litespeed_deactivated() { \add_filter( 'activitypub_is_plugin_active', function ( $is_active, $plugin ) { - if ( 'litespeed-cache/litespeed-cache.php' === $plugin ) { + if ( Litespeed_Cache::$plugin_slug === $plugin ) { return false; } return $is_active; @@ -269,7 +269,7 @@ public function test_cleanup_when_litespeed_deleted() { $this->assertStringContainsString( Litespeed_Cache::$rules, $contents_before ); // Simulate LiteSpeed Cache plugin deletion. - \do_action( 'deleted_plugin', 'litespeed-cache/litespeed-cache.php', false ); + \do_action( 'deleted_plugin', Litespeed_Cache::$plugin_slug, false ); // Verify cleanup. $this->assertFalse( \get_option( Litespeed_Cache::$option_name ), 'Option should be deleted when plugin is deleted' );