Skip to content

Commit a94d7fd

Browse files
authored
Integration: Add support for the LightSpeed Cache plugin (#1683)
1 parent ef2cb8a commit a94d7fd

File tree

6 files changed

+571
-2
lines changed

6 files changed

+571
-2
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Significance: minor
2+
Type: added
3+
4+
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.

includes/functions.php

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1828,3 +1828,23 @@ function get_url_authority( $url ) {
18281828

18291829
return $parsed['scheme'] . '://' . $parsed['host'];
18301830
}
1831+
1832+
/**
1833+
* Check if a plugin is active, loading plugin.php if necessary.
1834+
*
1835+
* This is a wrapper around the core is_plugin_active() function that ensures
1836+
* the function is available by loading wp-admin/includes/plugin.php if needed.
1837+
* This is useful when checking plugin status outside of the admin context.
1838+
*
1839+
* @param string $plugin Plugin basename (e.g., 'plugin-folder/plugin-file.php').
1840+
*
1841+
* @return bool True if the plugin is active, false otherwise.
1842+
*/
1843+
function is_plugin_active( $plugin ) {
1844+
// Include plugin.php if not already loaded (needed for core is_plugin_active).
1845+
if ( ! \function_exists( 'is_plugin_active' ) ) {
1846+
require_once ABSPATH . 'wp-admin/includes/plugin.php';
1847+
}
1848+
1849+
return \is_plugin_active( $plugin );
1850+
}
Lines changed: 227 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,227 @@
1+
<?php
2+
/**
3+
* LiteSpeed Cache integration file.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
namespace Activitypub\Integration;
9+
10+
use function Activitypub\is_plugin_active;
11+
12+
/**
13+
* LiteSpeed Cache integration.
14+
*
15+
* @see https://wordpress.org/support/topic/avoiding-caching-activitypub-content/
16+
*/
17+
class Litespeed_Cache {
18+
19+
/**
20+
* The rules to add to the htaccess file.
21+
*
22+
* @var string
23+
*/
24+
public static $rules = '<IfModule LiteSpeed>
25+
RewriteEngine On
26+
RewriteCond %{HTTP:Accept} application
27+
RewriteRule ^ - [E=Cache-Control:vary=%{ENV:LSCACHE_VARY_VALUE}+isjson]
28+
</IfModule>';
29+
30+
/**
31+
* The option name to store the htaccess rules.
32+
*
33+
* @var string
34+
*/
35+
public static $option_name = 'activitypub_litespeed_cache_setup';
36+
37+
/**
38+
* The marker to identify the rules in the htaccess file.
39+
*
40+
* @var string
41+
*/
42+
public static $marker = 'ActivityPub LiteSpeed Cache';
43+
44+
/**
45+
* The LiteSpeed Cache plugin slug.
46+
*
47+
* @var string
48+
*/
49+
public static $plugin_slug = 'litespeed-cache/litespeed-cache.php';
50+
51+
/**
52+
* Initialize the integration.
53+
*/
54+
public static function init() {
55+
// Add rules if LiteSpeed Cache is active and rules aren't set.
56+
if ( is_plugin_active( self::$plugin_slug ) ) {
57+
if ( ! \get_option( self::$option_name ) ) {
58+
self::add_htaccess_rules();
59+
}
60+
61+
\add_filter( 'site_status_tests', array( self::class, 'add_site_health_test' ) );
62+
63+
// Remove rules if LiteSpeed Cache is not active but rules were previously set.
64+
} elseif ( \get_option( self::$option_name ) ) {
65+
self::remove_htaccess_rules();
66+
}
67+
68+
// Clean up when LiteSpeed Cache plugin is deleted.
69+
\add_action( 'deleted_plugin', array( self::class, 'on_plugin_deleted' ) );
70+
}
71+
72+
/**
73+
* Clean up htaccess rules when LiteSpeed Cache plugin is deleted.
74+
*
75+
* @param string $plugin_file Path to the plugin file relative to the plugins directory.
76+
*/
77+
public static function on_plugin_deleted( $plugin_file ) {
78+
if ( self::$plugin_slug === $plugin_file && \get_option( self::$option_name ) ) {
79+
self::remove_htaccess_rules();
80+
}
81+
}
82+
83+
/**
84+
* Add the LiteSpeed Cache htaccess rules.
85+
*/
86+
public static function add_htaccess_rules() {
87+
$added_rules = self::append_with_markers( self::$marker, self::$rules );
88+
89+
if ( $added_rules ) {
90+
\update_option( self::$option_name, '1' );
91+
} else {
92+
\update_option( self::$option_name, '0' );
93+
}
94+
}
95+
96+
/**
97+
* Remove the LiteSpeed Cache htaccess rules.
98+
*/
99+
public static function remove_htaccess_rules() {
100+
self::append_with_markers( self::$marker, '' );
101+
102+
\delete_option( self::$option_name );
103+
}
104+
105+
/**
106+
* Add the LiteSpeed Cache config test to site health.
107+
*
108+
* @param array $tests The site health tests.
109+
*
110+
* @return array The site health tests with the LiteSpeed Cache config test.
111+
*/
112+
public static function add_site_health_test( $tests ) {
113+
$tests['direct']['activitypub_test_litespeed_cache_integration'] = array(
114+
'label' => \__( 'LiteSpeed Cache Test', 'activitypub' ),
115+
'test' => array( self::class, 'test_litespeed_cache_integration' ),
116+
);
117+
118+
return $tests;
119+
}
120+
121+
/**
122+
* Test the LiteSpeed Cache integration.
123+
*
124+
* @return array The test results.
125+
*/
126+
public static function test_litespeed_cache_integration() {
127+
$result = array(
128+
'label' => \__( 'Compatibility with LiteSpeed Cache', 'activitypub' ),
129+
'status' => 'good',
130+
'badge' => array(
131+
'label' => \__( 'ActivityPub', 'activitypub' ),
132+
'color' => 'green',
133+
),
134+
'description' => \sprintf(
135+
'<p>%s</p>',
136+
\__( 'LiteSpeed Cache is well configured to work with ActivityPub.', 'activitypub' )
137+
),
138+
'actions' => '',
139+
'test' => 'test_litespeed_cache_integration',
140+
);
141+
142+
if ( ! \get_option( self::$option_name ) ) {
143+
$result['status'] = 'critical';
144+
$result['label'] = \__( 'LiteSpeed Cache might not be properly configured.', 'activitypub' );
145+
$result['badge']['color'] = 'red';
146+
$result['description'] = \sprintf(
147+
'<p>%s</p>',
148+
\__( 'LiteSpeed Cache isn&#8217;t currently set up to work with ActivityPub. While this isn&#8217;t a major problem, it&#8217;s a good idea to enable support. Without it, some technical files (like JSON) might accidentally show up in your website&#8217;s cache and be visible to visitors.', 'activitypub' )
149+
);
150+
$result['actions'] = \sprintf(
151+
'<p>%s</p><pre>%s</pre>',
152+
\__( 'To enable the ActivityPub integration with LiteSpeed Cache, add the following rules to your <code>.htaccess</code> file:', 'activitypub' ),
153+
\esc_html( self::$rules )
154+
);
155+
}
156+
157+
return $result;
158+
}
159+
160+
/**
161+
* Prepend rules to the top of a file with markers.
162+
*
163+
* @param string $marker The marker to identify the rules in the file.
164+
* @param string $rules The rules to prepend.
165+
*
166+
* @return bool True on success, false on failure.
167+
*/
168+
private static function append_with_markers( $marker, $rules ) {
169+
$htaccess_file = self::get_htaccess_file_path();
170+
171+
if ( ! \wp_is_writable( $htaccess_file ) ) {
172+
return false;
173+
}
174+
175+
// Ensure get_home_path() is declared.
176+
require_once ABSPATH . 'wp-admin/includes/file.php';
177+
178+
global $wp_filesystem;
179+
\WP_Filesystem();
180+
181+
$htaccess = $wp_filesystem->get_contents( $htaccess_file );
182+
183+
// If marker exists, remove the old block first.
184+
if ( strpos( $htaccess, $marker ) !== false ) {
185+
// Remove existing marker block.
186+
$pattern = '/# BEGIN ' . preg_quote( $marker, '/' ) . '.*?# END ' . preg_quote( $marker, '/' ) . '\r?\n?/s';
187+
$htaccess = preg_replace( $pattern, '', $htaccess );
188+
$htaccess = trim( $htaccess );
189+
}
190+
191+
// If rules are empty, just return (for removal case).
192+
if ( empty( $rules ) ) {
193+
return $wp_filesystem->put_contents( $htaccess_file, $htaccess, FS_CHMOD_FILE );
194+
}
195+
196+
// Prepend new rules to the top of the file.
197+
$start_marker = "# BEGIN {$marker}";
198+
$end_marker = "# END {$marker}";
199+
200+
$rules = $start_marker . PHP_EOL . $rules . PHP_EOL . $end_marker;
201+
$htaccess = $rules . PHP_EOL . PHP_EOL . $htaccess;
202+
203+
return $wp_filesystem->put_contents( $htaccess_file, $htaccess, FS_CHMOD_FILE );
204+
}
205+
206+
/**
207+
* Get the htaccess file.
208+
*
209+
* @return string|false The htaccess file or false.
210+
*/
211+
private static function get_htaccess_file_path() {
212+
$htaccess_file = false;
213+
214+
// phpcs:ignore WordPress.PHP.NoSilencedErrors
215+
if ( @file_exists( \get_home_path() . '.htaccess' ) ) {
216+
/** The htaccess file resides in ABSPATH */
217+
$htaccess_file = \get_home_path() . '.htaccess';
218+
}
219+
220+
/**
221+
* Filter the htaccess file path.
222+
*
223+
* @param string|false $htaccess_file The htaccess file path.
224+
*/
225+
return \apply_filters( 'activitypub_litespeed_cache_htaccess_file', $htaccess_file );
226+
}
227+
}

integration/class-surge.php

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@
77

88
namespace Activitypub\Integration;
99

10+
use function Activitypub\is_plugin_active;
11+
1012
/**
1113
* Surge Cache integration.
1214
*
@@ -38,7 +40,7 @@ public static function init() {
3840
*/
3941
public static function add_cache_config() {
4042
// Check if surge is installed and active.
41-
if ( ! \is_plugin_active( 'surge/surge.php' ) ) {
43+
if ( ! is_plugin_active( 'surge/surge.php' ) ) {
4244
return;
4345
}
4446

@@ -139,7 +141,7 @@ public static function get_config_file_path() {
139141
* @return array The site health tests with the Surge cache config test.
140142
*/
141143
public static function maybe_add_site_health( $tests ) {
142-
if ( ! \is_plugin_active( 'surge/surge.php' ) ) {
144+
if ( ! is_plugin_active( 'surge/surge.php' ) ) {
143145
return $tests;
144146
}
145147

integration/load.php

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -146,13 +146,27 @@ function ( $transformer, $data, $object_class ) {
146146
* @see https://wordpress.org/plugins/surge/
147147
*/
148148
Surge::init();
149+
150+
/**
151+
* Load the LiteSpeed Cache integration.
152+
*
153+
* The check for whether LiteSpeed Cache is loaded and initialized happens inside Litespeed_Cache::init().
154+
*
155+
* @see https://wordpress.org/plugins/litespeed-cache/
156+
*/
157+
Litespeed_Cache::init();
149158
}
150159
\add_action( 'plugins_loaded', __NAMESPACE__ . '\plugin_init' );
151160

152161
// Register activation and deactivation hooks for Surge integration.
153162
\register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'add_cache_config' ) );
154163
\register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\Surge', 'remove_cache_config' ) );
155164

165+
// Register activation and deactivation hooks for LiteSpeed Cache integration.
166+
\register_activation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\LiteSpeed_Cache', 'add_htaccess_rules' ) );
167+
\register_deactivation_hook( ACTIVITYPUB_PLUGIN_FILE, array( __NAMESPACE__ . '\LiteSpeed_Cache', 'remove_htaccess_rules' ) );
168+
169+
156170
/**
157171
* Register the Stream Connector for ActivityPub.
158172
*

0 commit comments

Comments
 (0)