Skip to content

Commit e93d232

Browse files
Add WordPress plugin for LibreCaptcha integration
- Create a new WordPress plugin at `plugins/wordpress/librecaptcha.php`. - Add settings page to configure server URL, configuration JSON, and checkboxes for Login, Registration, and Comments forms. - Implement shortcode and standard WordPress hooks to inject the CAPTCHA and fetch it dynamically via JavaScript from the LibreCaptcha `/v2/captcha` endpoint. - Add verification logic utilizing `wp_remote_post` to hit the `/v2/answer` endpoint and hook into standard authentication/submission filters to block invalid entries. - Add installation instructions to the project README.md. Co-authored-by: hrj <345879+hrj@users.noreply.github.com>
1 parent 19952dc commit e93d232

2 files changed

Lines changed: 252 additions & 0 deletions

File tree

README.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -216,3 +216,16 @@ Things to do in the future:
216216
* Sandboxed plugin architecture
217217
* Audio CAPTCHA samples
218218
* Interactive CAPTCHA samples
219+
220+
## WordPress Plugin
221+
222+
A WordPress plugin is included to protect forms on your WordPress site (such as Comments, Login, and Registration).
223+
224+
### Installation
225+
1. Copy the `plugins/wordpress/librecaptcha.php` file (or the `plugins/wordpress` directory) to your WordPress `wp-content/plugins/` directory.
226+
2. Log into your WordPress admin dashboard and go to **Plugins**.
227+
3. Activate the **LibreCaptcha** plugin.
228+
4. Navigate to **Settings > LibreCaptcha**.
229+
5. Enter the **LibreCaptcha Server URL** (e.g., `http://localhost:8888`).
230+
6. Adjust the JSON configuration and select the forms you wish to protect.
231+
7. You can also use the `[librecaptcha]` shortcode to embed the CAPTCHA in custom pages.

plugins/wordpress/librecaptcha.php

Lines changed: 239 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,239 @@
1+
<?php
2+
/**
3+
* Plugin Name: LibreCaptcha
4+
* Description: Integrates LibreCaptcha for user comments, login, and registration.
5+
* Version: 1.0.0
6+
* Author: LibreCaptcha
7+
*/
8+
9+
if ( ! defined( 'ABSPATH' ) ) {
10+
exit; // Exit if accessed directly
11+
}
12+
13+
class LibreCaptchaPlugin {
14+
public function __construct() {
15+
add_action( 'admin_menu', array( $this, 'add_admin_menu' ) );
16+
add_action( 'admin_init', array( $this, 'register_settings' ) );
17+
18+
// Add CAPTCHA to forms if enabled
19+
if ( get_option( 'lc_enable_login', 0 ) ) {
20+
add_action( 'login_form', array( $this, 'render_captcha' ) );
21+
}
22+
if ( get_option( 'lc_enable_registration', 0 ) ) {
23+
add_action( 'register_form', array( $this, 'render_captcha' ) );
24+
}
25+
if ( get_option( 'lc_enable_comments', 0 ) ) {
26+
add_action( 'comment_form_after_fields', array( $this, 'render_captcha' ) );
27+
add_action( 'comment_form_logged_in_after', array( $this, 'render_captcha' ) );
28+
}
29+
30+
// Register shortcode
31+
add_shortcode( 'librecaptcha', array( $this, 'render_captcha_shortcode' ) );
32+
33+
// Verification hooks
34+
if ( get_option( 'lc_enable_login', 0 ) ) {
35+
add_filter( 'authenticate', array( $this, 'verify_login_captcha' ), 20, 3 );
36+
}
37+
if ( get_option( 'lc_enable_registration', 0 ) ) {
38+
add_filter( 'registration_errors', array( $this, 'verify_registration_captcha' ), 10, 3 );
39+
}
40+
if ( get_option( 'lc_enable_comments', 0 ) ) {
41+
add_filter( 'pre_comment_on_post', array( $this, 'verify_comment_captcha' ) );
42+
}
43+
}
44+
45+
private function verify_captcha() {
46+
$server_url = get_option( 'lc_server_url', '' );
47+
if ( empty( $server_url ) ) {
48+
return true; // Pass if not configured
49+
}
50+
51+
if ( ! isset( $_POST['lc_captcha_id'] ) || ! isset( $_POST['lc_captcha_answer'] ) ) {
52+
return false;
53+
}
54+
55+
$captcha_id = sanitize_text_field( $_POST['lc_captcha_id'] );
56+
$captcha_answer = sanitize_text_field( $_POST['lc_captcha_answer'] );
57+
58+
$server_url = rtrim( $server_url, '/' );
59+
60+
$response = wp_remote_post( $server_url . '/v2/answer', array(
61+
'headers' => array( 'Content-Type' => 'application/json' ),
62+
'body' => wp_json_encode( array(
63+
'id' => $captcha_id,
64+
'answer' => $captcha_answer,
65+
) ),
66+
'method' => 'POST',
67+
'data_format' => 'body',
68+
) );
69+
70+
if ( is_wp_error( $response ) ) {
71+
return false; // Fail safe or fail secure? typically fail secure for captcha
72+
}
73+
74+
$body = wp_remote_retrieve_body( $response );
75+
$data = json_decode( $body, true );
76+
77+
if ( isset( $data['result'] ) && ( $data['result'] === 'True' || $data['result'] === true ) ) {
78+
return true;
79+
}
80+
81+
return false;
82+
}
83+
84+
public function verify_login_captcha( $user, $username, $password ) {
85+
// Only check if it's a POST request (login attempt) and user is not already an error
86+
if ( $_SERVER['REQUEST_METHOD'] === 'POST' && ! is_wp_error( $user ) ) {
87+
if ( ! $this->verify_captcha() ) {
88+
return new WP_Error( 'authentication_failed', __( '<strong>ERROR</strong>: The CAPTCHA was incorrect.' ) );
89+
}
90+
}
91+
return $user;
92+
}
93+
94+
public function verify_registration_captcha( $errors, $sanitized_user_login, $user_email ) {
95+
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
96+
if ( ! $this->verify_captcha() ) {
97+
$errors->add( 'captcha_failed', __( '<strong>ERROR</strong>: The CAPTCHA was incorrect.' ) );
98+
}
99+
}
100+
return $errors;
101+
}
102+
103+
public function verify_comment_captcha( $comment_post_id ) {
104+
// If user is logged in, they might not see the captcha depending on form implementation,
105+
// but since we hooked `comment_form_logged_in_after`, they do see it.
106+
if ( $_SERVER['REQUEST_METHOD'] === 'POST' ) {
107+
if ( ! $this->verify_captcha() ) {
108+
wp_die( __( '<strong>ERROR</strong>: The CAPTCHA was incorrect. Please go back and try again.' ) );
109+
}
110+
}
111+
}
112+
113+
public function render_captcha_shortcode() {
114+
ob_start();
115+
$this->render_captcha();
116+
return ob_get_clean();
117+
}
118+
119+
public function render_captcha() {
120+
$server_url = get_option( 'lc_server_url', '' );
121+
if ( empty( $server_url ) ) {
122+
return;
123+
}
124+
125+
$server_url = rtrim( $server_url, '/' );
126+
$config_json = get_option( 'lc_config_json', '{"level":"easy","media":"image/png","input_type":"text","size":"350x100"}' );
127+
128+
?>
129+
<div class="librecaptcha-container" style="margin-bottom: 15px;">
130+
<div id="librecaptcha-image-container" style="margin-bottom: 10px;">
131+
<!-- Image will be injected here -->
132+
</div>
133+
<input type="hidden" name="lc_captcha_id" id="lc_captcha_id" value="" />
134+
<input type="text" name="lc_captcha_answer" id="lc_captcha_answer" placeholder="Enter CAPTCHA here" required style="width: 100%; max-width: 350px;" />
135+
</div>
136+
<script>
137+
document.addEventListener('DOMContentLoaded', function() {
138+
var serverUrl = <?php echo json_encode( $server_url ); ?>;
139+
var configJson = <?php echo $config_json; ?>;
140+
141+
fetch(serverUrl + '/v2/captcha', {
142+
method: 'POST',
143+
headers: {
144+
'Content-Type': 'application/json'
145+
},
146+
body: JSON.stringify(configJson)
147+
})
148+
.then(function(response) {
149+
if (!response.ok) {
150+
throw new Error('Network response was not ok');
151+
}
152+
return response.json();
153+
})
154+
.then(function(data) {
155+
if (data && data.id) {
156+
document.getElementById('lc_captcha_id').value = data.id;
157+
var img = document.createElement('img');
158+
img.src = serverUrl + '/v1/media?id=' + data.id;
159+
img.alt = 'CAPTCHA';
160+
img.style.maxWidth = '100%';
161+
document.getElementById('librecaptcha-image-container').appendChild(img);
162+
}
163+
})
164+
.catch(function(error) {
165+
console.error('Error fetching LibreCaptcha:', error);
166+
document.getElementById('librecaptcha-image-container').innerText = 'Error loading CAPTCHA. Please refresh the page.';
167+
});
168+
});
169+
</script>
170+
<?php
171+
}
172+
173+
public function add_admin_menu() {
174+
add_options_page(
175+
'LibreCaptcha Settings',
176+
'LibreCaptcha',
177+
'manage_options',
178+
'librecaptcha',
179+
array( $this, 'settings_page' )
180+
);
181+
}
182+
183+
public function register_settings() {
184+
register_setting( 'librecaptcha_options_group', 'lc_server_url' );
185+
register_setting( 'librecaptcha_options_group', 'lc_config_json' );
186+
register_setting( 'librecaptcha_options_group', 'lc_enable_login' );
187+
register_setting( 'librecaptcha_options_group', 'lc_enable_registration' );
188+
register_setting( 'librecaptcha_options_group', 'lc_enable_comments' );
189+
}
190+
191+
public function settings_page() {
192+
?>
193+
<div class="wrap">
194+
<h2>LibreCaptcha Settings</h2>
195+
<form method="post" action="options.php">
196+
<?php settings_fields( 'librecaptcha_options_group' ); ?>
197+
<?php do_settings_sections( 'librecaptcha_options_group' ); ?>
198+
<table class="form-table">
199+
<tr valign="top">
200+
<th scope="row">LibreCaptcha Server URL</th>
201+
<td>
202+
<input type="text" name="lc_server_url" value="<?php echo esc_attr( get_option('lc_server_url', '') ); ?>" class="regular-text" placeholder="http://localhost:8888" />
203+
<p class="description">The URL to your LibreCaptcha instance (e.g. http://localhost:8888). Leave empty to disable.</p>
204+
</td>
205+
</tr>
206+
<tr valign="top">
207+
<th scope="row">Config JSON</th>
208+
<td>
209+
<textarea name="lc_config_json" rows="5" cols="50" class="large-text code"><?php echo esc_textarea( get_option('lc_config_json', '{"level":"easy","media":"image/png","input_type":"text","size":"350x100"}') ); ?></textarea>
210+
<p class="description">JSON configuration for the CAPTCHA requests.</p>
211+
</td>
212+
</tr>
213+
<tr valign="top">
214+
<th scope="row">Enable on Login Form</th>
215+
<td>
216+
<input type="checkbox" name="lc_enable_login" value="1" <?php checked( 1, get_option( 'lc_enable_login', 0 ), true ); ?> />
217+
</td>
218+
</tr>
219+
<tr valign="top">
220+
<th scope="row">Enable on Registration Form</th>
221+
<td>
222+
<input type="checkbox" name="lc_enable_registration" value="1" <?php checked( 1, get_option( 'lc_enable_registration', 0 ), true ); ?> />
223+
</td>
224+
</tr>
225+
<tr valign="top">
226+
<th scope="row">Enable on Comment Form</th>
227+
<td>
228+
<input type="checkbox" name="lc_enable_comments" value="1" <?php checked( 1, get_option( 'lc_enable_comments', 0 ), true ); ?> />
229+
</td>
230+
</tr>
231+
</table>
232+
<?php submit_button(); ?>
233+
</form>
234+
</div>
235+
<?php
236+
}
237+
}
238+
239+
new LibreCaptchaPlugin();

0 commit comments

Comments
 (0)