diff --git a/integration/class-litespeed-cache.php b/integration/class-litespeed-cache.php new file mode 100644 index 000000000..c154b1b8b --- /dev/null +++ b/integration/class-litespeed-cache.php @@ -0,0 +1,186 @@ + +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 + */ + public static $option_name = 'activitypub_litespeed_cache_setup'; + + /** + * The marker to identify the rules in the htaccess file. + * + * @var string + */ + public 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() { + $added_rules = self::append_with_markers( self::$marker, self::$rules ); + + \update_option( self::$option_name, $added_rules ); + } + + /** + * Remove the Litespeed Cache htaccess rules. + */ + public static function remove_htaccess_rules() { + self::append_with_markers( 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;
+ }
+
+ /**
+ * 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 ) {
+ $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 ( strpos( $htaccess, $marker ) !== false ) {
+ return \insert_with_markers( $htaccess_file, $marker, $rules );
+ }
+
+ $start_marker = "# BEGIN {$marker}";
+ $end_marker = "# END {$marker}";
+
+ $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 );
+ }
+
+ /**
+ * 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/integration/load.php b/integration/load.php
index 78b0dd6b9..03a408cb7 100644
--- a/integration/load.php
+++ b/integration/load.php
@@ -135,6 +135,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' );
@@ -142,6 +151,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.
diff --git a/tests/integration/class-test-litespeed-cache.php b/tests/integration/class-test-litespeed-cache.php
new file mode 100644
index 000000000..7c0ee4256
--- /dev/null
+++ b/tests/integration/class-test-litespeed-cache.php
@@ -0,0 +1,123 @@
+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' );
+ }
+}