Skip to content

Commit ca76268

Browse files
authored
Add health check for active Captcha plugins (#2231)
1 parent 905aaab commit ca76268

File tree

3 files changed

+303
-0
lines changed

3 files changed

+303
-0
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+
New site health check warns if active Captcha plugins may block ActivityPub comments.

includes/wp-admin/class-health-check.php

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -104,6 +104,11 @@ public static function add_tests( $tests ) {
104104
'test' => array( self::class, 'test_pretty_permalinks' ),
105105
);
106106

107+
$tests['direct']['activitypub_check_for_captcha_plugins'] = array(
108+
'label' => \__( 'Check for Captcha Plugins', 'activitypub' ),
109+
'test' => array( self::class, 'test_check_for_captcha_plugins' ),
110+
);
111+
107112
return $tests;
108113
}
109114

@@ -469,4 +474,75 @@ public static function test_pretty_permalinks() {
469474

470475
return $result;
471476
}
477+
478+
/**
479+
* Check for Captcha Plugins.
480+
*
481+
* @return array The test result.
482+
*/
483+
public static function test_check_for_captcha_plugins() {
484+
$result = array(
485+
'label' => \__( 'Check for Captcha Plugins', 'activitypub' ),
486+
'status' => 'good',
487+
'badge' => array(
488+
'label' => \__( 'ActivityPub', 'activitypub' ),
489+
'color' => 'green',
490+
),
491+
'description' => \sprintf(
492+
'<p>%s</p>',
493+
\__( 'No Captcha plugins were found that could interfere with ActivityPub functionality.', 'activitypub' )
494+
),
495+
'actions' => '',
496+
'test' => 'test_check_for_captcha_plugins',
497+
);
498+
499+
$active_plugins = (array) \get_option( 'active_plugins', array() );
500+
501+
// search for the word 'captcha' in the list of active plugins.
502+
$captcha_plugins = array_filter(
503+
$active_plugins,
504+
function ( $plugin ) {
505+
return \str_contains( strtolower( $plugin ), 'captcha' );
506+
}
507+
);
508+
509+
if ( ! $captcha_plugins ) {
510+
return $result;
511+
}
512+
513+
// Get nice plugin names instead of file paths using WordPress built-in functions.
514+
$all_plugins = \get_plugins();
515+
$captcha_plugin_names = array_map(
516+
function ( $plugin_file ) use ( $all_plugins ) {
517+
if ( isset( $all_plugins[ $plugin_file ]['Name'] ) ) {
518+
return $all_plugins[ $plugin_file ]['Name'];
519+
}
520+
return false;
521+
},
522+
$captcha_plugins
523+
);
524+
525+
$result['status'] = 'recommended';
526+
$result['label'] = \__( 'Captcha plugins detected', 'activitypub' );
527+
$result['badge']['color'] = 'orange';
528+
$result['description'] = \sprintf(
529+
'<p>%s</p><p>%s</p>',
530+
\sprintf(
531+
/* translators: %s: List of captcha plugins. */
532+
\esc_html__( 'The following Captcha plugins are active and may interfere with ActivityPub functionality: %s', 'activitypub' ),
533+
implode( ', ', array_map( 'esc_html', array_filter( $captcha_plugin_names ) ) )
534+
),
535+
\__( 'Captcha plugins require verification for comment submissions, but some may not distinguish between regular comments and those sent via an API (such as from ActivityPub). As a result, federated comments might be blocked because they cannot provide a Captcha response. If you experience missing comments, try disabling the Captcha plugin to determine if it resolves the issue.', 'activitypub' )
536+
);
537+
$result['actions'] = \sprintf(
538+
'<p>%s</p>',
539+
\sprintf(
540+
// translators: %s: Plugin page URL.
541+
\__( 'They can be disabled from the <a href="%s">Plugin Page</a>.', 'activitypub' ),
542+
esc_url( admin_url( 'plugins.php?s=captcha&plugin_status=all' ) )
543+
)
544+
);
545+
546+
return $result;
547+
}
472548
}
Lines changed: 223 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,223 @@
1+
<?php
2+
/**
3+
* Test Health_Check class.
4+
*
5+
* @package Activitypub
6+
*/
7+
8+
use Activitypub\WP_Admin\Health_Check;
9+
10+
/**
11+
* Test Health_Check class.
12+
*/
13+
class Test_Health_Check extends WP_UnitTestCase {
14+
15+
/**
16+
* Test that health check tests are properly registered.
17+
*/
18+
public function test_add_tests() {
19+
$tests = array();
20+
$result = Health_Check::add_tests( $tests );
21+
22+
// Check that the captcha test is registered.
23+
$this->assertArrayHasKey( 'direct', $result );
24+
$this->assertArrayHasKey( 'activitypub_check_for_captcha_plugins', $result['direct'] );
25+
26+
// Verify test structure.
27+
$captcha_test = $result['direct']['activitypub_check_for_captcha_plugins'];
28+
$this->assertArrayHasKey( 'label', $captcha_test );
29+
$this->assertArrayHasKey( 'test', $captcha_test );
30+
$this->assertEquals( array( Health_Check::class, 'test_check_for_captcha_plugins' ), $captcha_test['test'] );
31+
}
32+
33+
/**
34+
* Mock function to return active plugins without captcha.
35+
*
36+
* @return array List of active plugins.
37+
*/
38+
public function mock_active_plugins_no_captcha() {
39+
return array( 'some-other-plugin/plugin.php', 'another-plugin/main.php' );
40+
}
41+
42+
/**
43+
* Mock function to return active plugins with captcha.
44+
*
45+
* @return array List of active plugins.
46+
*/
47+
public function mock_active_plugins_with_captcha() {
48+
return array(
49+
'really-simple-captcha/really-simple-captcha.php',
50+
'some-other-plugin/plugin.php',
51+
'recaptcha-for-woocommerce/recaptcha.php',
52+
);
53+
}
54+
55+
/**
56+
* Mock function to return active plugins with mixed case captcha.
57+
*
58+
* @return array List of active plugins.
59+
*/
60+
public function mock_active_plugins_mixed_case() {
61+
return array(
62+
'CAPTCHA-plugin/captcha.php',
63+
'some-plugin-with-CaPtChA/main.php',
64+
'regular-plugin/plugin.php',
65+
);
66+
}
67+
68+
/**
69+
* Test captcha plugin detection when no captcha plugins are active.
70+
*/
71+
public function test_check_for_captcha_plugins_none_found() {
72+
// Mock empty active plugins.
73+
add_filter(
74+
'option_active_plugins',
75+
array( $this, 'mock_active_plugins_no_captcha' )
76+
);
77+
78+
$result = Health_Check::test_check_for_captcha_plugins();
79+
80+
$this->assertEquals( 'good', $result['status'] );
81+
$this->assertEquals( 'Check for Captcha Plugins', $result['label'] );
82+
$this->assertEquals( 'green', $result['badge']['color'] );
83+
$this->assertStringContainsString( 'No Captcha plugins were found', $result['description'] );
84+
85+
remove_all_filters( 'option_active_plugins' );
86+
}
87+
88+
/**
89+
* Test captcha plugin detection when captcha plugins are found.
90+
* This test focuses on the core detection logic rather than plugin name extraction.
91+
*/
92+
public function test_check_for_captcha_plugins_found() {
93+
// Mock active plugins with captcha plugins.
94+
add_filter(
95+
'option_active_plugins',
96+
array( $this, 'mock_active_plugins_with_captcha' )
97+
);
98+
99+
$result = Health_Check::test_check_for_captcha_plugins();
100+
101+
// Test the core functionality - captcha plugins should be detected.
102+
$this->assertEquals( 'recommended', $result['status'] );
103+
$this->assertEquals( 'Captcha plugins detected', $result['label'] );
104+
$this->assertEquals( 'orange', $result['badge']['color'] );
105+
$this->assertStringContainsString( 'The following Captcha plugins are active', $result['description'] );
106+
$this->assertStringContainsString( 'may interfere with ActivityPub functionality', $result['description'] );
107+
$this->assertStringContainsString( 'Plugin Page', $result['actions'] );
108+
109+
// Clean up.
110+
remove_all_filters( 'option_active_plugins' );
111+
}
112+
113+
/**
114+
* Test captcha plugin detection with case-insensitive matching.
115+
* This test focuses on the case-insensitive detection logic.
116+
*/
117+
public function test_check_for_captcha_plugins_case_insensitive() {
118+
// Mock active plugins with mixed case captcha plugins.
119+
add_filter(
120+
'option_active_plugins',
121+
array( $this, 'mock_active_plugins_mixed_case' )
122+
);
123+
124+
$result = Health_Check::test_check_for_captcha_plugins();
125+
126+
// Test that case-insensitive matching works.
127+
$this->assertEquals( 'recommended', $result['status'] );
128+
$this->assertEquals( 'Captcha plugins detected', $result['label'] );
129+
$this->assertEquals( 'orange', $result['badge']['color'] );
130+
131+
remove_all_filters( 'option_active_plugins' );
132+
}
133+
134+
/**
135+
* Test count_results method.
136+
*/
137+
public function test_count_results() {
138+
// Test counting all results.
139+
$all_results = Health_Check::count_results( 'all' );
140+
$this->assertIsArray( $all_results );
141+
$this->assertArrayHasKey( 'good', $all_results );
142+
$this->assertArrayHasKey( 'critical', $all_results );
143+
$this->assertArrayHasKey( 'recommended', $all_results );
144+
145+
// Test counting specific result types.
146+
$good_count = Health_Check::count_results( 'good' );
147+
$this->assertIsInt( $good_count );
148+
149+
$critical_count = Health_Check::count_results( 'critical' );
150+
$this->assertIsInt( $critical_count );
151+
152+
$recommended_count = Health_Check::count_results( 'recommended' );
153+
$this->assertIsInt( $recommended_count );
154+
}
155+
156+
/**
157+
* Test that the actions link points to the correct plugin page.
158+
* This test focuses on the action link generation.
159+
*/
160+
public function test_captcha_plugins_actions_link() {
161+
// Mock active plugins with captcha plugin.
162+
add_filter(
163+
'option_active_plugins',
164+
array( $this, 'mock_active_plugins_with_captcha' )
165+
);
166+
167+
$result = Health_Check::test_check_for_captcha_plugins();
168+
169+
// Test that the actions contain the correct plugin management link.
170+
// WordPress encodes & as &#038; for security, so we check for the encoded version.
171+
$this->assertStringContainsString( 'plugins.php?s=captcha&#038;plugin_status=all', $result['actions'] );
172+
$this->assertStringContainsString( 'Plugin Page', $result['actions'] );
173+
174+
remove_all_filters( 'option_active_plugins' );
175+
}
176+
177+
/**
178+
* Test debug_information method includes ActivityPub fields.
179+
*/
180+
public function test_debug_information() {
181+
$info = array();
182+
$result = Health_Check::debug_information( $info );
183+
184+
$this->assertArrayHasKey( 'activitypub', $result );
185+
$this->assertArrayHasKey( 'label', $result['activitypub'] );
186+
$this->assertArrayHasKey( 'fields', $result['activitypub'] );
187+
$this->assertEquals( 'ActivityPub', $result['activitypub']['label'] );
188+
}
189+
190+
/**
191+
* Test captcha plugin array filtering functionality.
192+
* This tests the array_filter behavior used in the health check.
193+
*/
194+
public function test_captcha_plugin_array_filtering() {
195+
// Test the array filtering used in the health check to remove empty plugin names.
196+
$captcha_plugins = array( 'really-simple-captcha/captcha.php', 'another-captcha/main.php' );
197+
198+
// Simulate the array_filter operation from the health check.
199+
$filtered_plugins = array_filter(
200+
$captcha_plugins,
201+
function ( $plugin ) {
202+
return str_contains( strtolower( $plugin ), 'captcha' );
203+
}
204+
);
205+
206+
$this->assertCount( 2, $filtered_plugins );
207+
$this->assertContains( 'really-simple-captcha/captcha.php', $filtered_plugins );
208+
$this->assertContains( 'another-captcha/main.php', $filtered_plugins );
209+
}
210+
211+
/**
212+
* Test that array_filter works correctly to remove false values.
213+
*/
214+
public function test_array_filter_removes_false_values() {
215+
$plugin_names = array( 'Really Simple CAPTCHA', false, 'Another Plugin', false );
216+
$filtered = array_filter( $plugin_names );
217+
218+
$this->assertCount( 2, $filtered );
219+
$this->assertContains( 'Really Simple CAPTCHA', $filtered );
220+
$this->assertContains( 'Another Plugin', $filtered );
221+
$this->assertNotContains( false, $filtered );
222+
}
223+
}

0 commit comments

Comments
 (0)