From 9f8462ab0e035330ba23d9e639abe7011d6dab55 Mon Sep 17 00:00:00 2001 From: Matthias Pfefferle Date: Fri, 9 May 2025 12:57:58 +0200 Subject: [PATCH 1/8] 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 2/8] 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 3/8] 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 4/8] 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 5/8] 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 6/8] 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 7/8] 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 8/8] 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';