@@ -30,6 +30,10 @@ public function __construct() {
3030 // Register shortcode
3131 add_shortcode ( 'librecaptcha ' , array ( $ this , 'render_captcha_shortcode ' ) );
3232
33+ // AJAX actions for testing settings
34+ add_action ( 'wp_ajax_lc_test_load ' , array ( $ this , 'ajax_test_load ' ) );
35+ add_action ( 'wp_ajax_lc_test_check ' , array ( $ this , 'ajax_test_check ' ) );
36+
3337 // Verification hooks
3438 if ( get_option ( 'lc_enable_login ' , 0 ) ) {
3539 add_filter ( 'authenticate ' , array ( $ this , 'verify_login_captcha ' ), 20 , 3 );
@@ -56,9 +60,15 @@ private function verify_captcha() {
5660 $ captcha_answer = sanitize_text_field ( $ _POST ['lc_captcha_answer ' ] );
5761
5862 $ server_url = rtrim ( $ server_url , '/ ' );
63+ $ auth_key = get_option ( 'lc_auth_key ' , '' );
64+
65+ $ headers = array ( 'Content-Type ' => 'application/json ' );
66+ if ( ! empty ( $ auth_key ) ) {
67+ $ headers ['Auth ' ] = $ auth_key ;
68+ }
5969
6070 $ response = wp_remote_post ( $ server_url . '/v2/answer ' , array (
61- 'headers ' => array ( ' Content-Type ' => ' application/json ' ) ,
71+ 'headers ' => $ headers ,
6272 'body ' => wp_json_encode ( array (
6373 'id ' => $ captcha_id ,
6474 'answer ' => $ captcha_answer ,
@@ -110,6 +120,86 @@ public function verify_comment_captcha( $comment_post_id ) {
110120 }
111121 }
112122
123+ public function ajax_test_load () {
124+ if ( ! current_user_can ( 'manage_options ' ) ) {
125+ wp_send_json_error ( 'Unauthorized ' );
126+ }
127+
128+ $ server_url = rtrim ( get_option ( 'lc_server_url ' , '' ), '/ ' );
129+ $ auth_key = get_option ( 'lc_auth_key ' , '' );
130+ $ config_json_string = get_option ( 'lc_config_json ' , '{"level":"easy","media":"image/png","input_type":"text","size":"350x100"} ' );
131+
132+ if ( empty ( $ server_url ) ) {
133+ wp_send_json_error ( 'Server URL is not configured. ' );
134+ }
135+
136+ $ headers = array ( 'Content-Type ' => 'application/json ' );
137+ if ( ! empty ( $ auth_key ) ) {
138+ $ headers ['Auth ' ] = $ auth_key ;
139+ }
140+
141+ $ response = wp_remote_post ( $ server_url . '/v2/captcha ' , array (
142+ 'headers ' => $ headers ,
143+ 'body ' => $ config_json_string ,
144+ 'method ' => 'POST ' ,
145+ 'data_format ' => 'body ' ,
146+ ) );
147+
148+ if ( is_wp_error ( $ response ) ) {
149+ wp_send_json_error ( 'Failed to connect to LibreCaptcha server. ' );
150+ }
151+
152+ $ body = wp_remote_retrieve_body ( $ response );
153+ $ data = json_decode ( $ body , true );
154+
155+ if ( isset ( $ data ['id ' ] ) ) {
156+ // Also return the full server URL so the client can load the image
157+ wp_send_json_success ( array ( 'id ' => $ data ['id ' ], 'server_url ' => $ server_url ) );
158+ } else {
159+ wp_send_json_error ( 'Invalid response from LibreCaptcha server. ' );
160+ }
161+ }
162+
163+ public function ajax_test_check () {
164+ if ( ! current_user_can ( 'manage_options ' ) ) {
165+ wp_send_json_error ( 'Unauthorized ' );
166+ }
167+
168+ $ captcha_id = sanitize_text_field ( $ _POST ['captcha_id ' ] ?? '' );
169+ $ captcha_answer = sanitize_text_field ( $ _POST ['captcha_answer ' ] ?? '' );
170+
171+ if ( empty ( $ captcha_id ) || empty ( $ captcha_answer ) ) {
172+ wp_send_json_error ( 'Missing ID or Answer. ' );
173+ }
174+
175+ $ server_url = rtrim ( get_option ( 'lc_server_url ' , '' ), '/ ' );
176+ $ auth_key = get_option ( 'lc_auth_key ' , '' );
177+
178+ $ headers = array ( 'Content-Type ' => 'application/json ' );
179+ if ( ! empty ( $ auth_key ) ) {
180+ $ headers ['Auth ' ] = $ auth_key ;
181+ }
182+
183+ $ response = wp_remote_post ( $ server_url . '/v2/answer ' , array (
184+ 'headers ' => $ headers ,
185+ 'body ' => wp_json_encode ( array (
186+ 'id ' => $ captcha_id ,
187+ 'answer ' => $ captcha_answer ,
188+ ) ),
189+ 'method ' => 'POST ' ,
190+ 'data_format ' => 'body ' ,
191+ ) );
192+
193+ if ( is_wp_error ( $ response ) ) {
194+ wp_send_json_error ( 'Failed to connect to LibreCaptcha server. ' );
195+ }
196+
197+ $ body = wp_remote_retrieve_body ( $ response );
198+ $ data = json_decode ( $ body , true );
199+
200+ wp_send_json_success ( $ data );
201+ }
202+
113203 public function render_captcha_shortcode () {
114204 ob_start ();
115205 $ this ->render_captcha ();
@@ -182,6 +272,7 @@ public function add_admin_menu() {
182272
183273 public function register_settings () {
184274 register_setting ( 'librecaptcha_options_group ' , 'lc_server_url ' );
275+ register_setting ( 'librecaptcha_options_group ' , 'lc_auth_key ' );
185276 register_setting ( 'librecaptcha_options_group ' , 'lc_config_json ' );
186277 register_setting ( 'librecaptcha_options_group ' , 'lc_enable_login ' );
187278 register_setting ( 'librecaptcha_options_group ' , 'lc_enable_registration ' );
@@ -203,6 +294,13 @@ public function settings_page() {
203294 <p class="description">The URL to your LibreCaptcha instance (e.g. http://localhost:8888). Leave empty to disable.</p>
204295 </td>
205296 </tr>
297+ <tr valign="top">
298+ <th scope="row">Auth Key (Optional)</th>
299+ <td>
300+ <input type="text" name="lc_auth_key" value="<?php echo esc_attr ( get_option ('lc_auth_key ' , '' ) ); ?> " class="regular-text" placeholder="Secret Key" />
301+ <p class="description">Optional auth key if your server requires it.</p>
302+ </td>
303+ </tr>
206304 <tr valign="top">
207305 <th scope="row">Config JSON</th>
208306 <td>
@@ -258,58 +356,29 @@ public function settings_page() {
258356 var idInput = document.getElementById('lc-test-captcha-id');
259357 var answerInput = document.getElementById('lc-test-captcha-answer');
260358
261- function getServerUrl() {
262- return document.querySelector('input[name="lc_server_url"]').value.replace(/\/$/, '');
263- }
264-
265- function getConfigJson() {
266- try {
267- return JSON.parse(document.querySelector('textarea[name="lc_config_json"]').value);
268- } catch (e) {
269- return null;
270- }
271- }
272-
273359 loadBtn.addEventListener('click', function() {
274- var serverUrl = getServerUrl();
275- var configJson = getConfigJson();
276-
277- if (!serverUrl) {
278- statusEl.innerText = 'Error: Server URL is empty.';
279- statusEl.style.color = 'red';
280- return;
281- }
282-
283- if (!configJson) {
284- statusEl.innerText = 'Error: Invalid Config JSON.';
285- statusEl.style.color = 'red';
286- return;
287- }
288-
289360 statusEl.innerText = 'Loading CAPTCHA...';
290361 statusEl.style.color = '#0073aa';
291362 captchaArea.style.display = 'none';
292363 imageContainer.innerHTML = '';
293364 answerInput.value = '';
294365
295- fetch(serverUrl + '/v2/captcha', {
366+ var formData = new FormData();
367+ formData.append('action', 'lc_test_load');
368+
369+ fetch(ajaxurl, {
296370 method: 'POST',
297- headers: {
298- 'Content-Type': 'application/json'
299- },
300- body: JSON.stringify(configJson)
371+ body: formData
301372 })
302373 .then(function(response) {
303- if (!response.ok) {
304- throw new Error('Server responded with ' + response.status);
305- }
306374 return response.json();
307375 })
308- .then(function(data) {
309- if (data && data.id) {
376+ .then(function(responseJson) {
377+ if (responseJson.success && responseJson.data.id) {
378+ var data = responseJson.data;
310379 idInput.value = data.id;
311380 var img = document.createElement('img');
312- img.src = serverUrl + '/v1/media?id=' + data.id;
381+ img.src = data.server_url + '/v1/media?id=' + data.id;
313382 img.alt = 'Test CAPTCHA';
314383 img.style.maxWidth = '100%';
315384 img.onload = function() {
@@ -323,7 +392,7 @@ function getConfigJson() {
323392 };
324393 imageContainer.appendChild(img);
325394 } else {
326- throw new Error('Invalid response format');
395+ throw new Error(responseJson.data || 'Invalid response format');
327396 }
328397 })
329398 .catch(function(error) {
@@ -334,7 +403,6 @@ function getConfigJson() {
334403 });
335404
336405 checkBtn.addEventListener('click', function() {
337- var serverUrl = getServerUrl();
338406 var captchaId = idInput.value;
339407 var answer = answerInput.value;
340408
@@ -346,26 +414,30 @@ function getConfigJson() {
346414 statusEl.innerText = 'Checking answer...';
347415 statusEl.style.color = '#0073aa';
348416
349- fetch(serverUrl + '/v2/answer', {
417+ var formData = new FormData();
418+ formData.append('action', 'lc_test_check');
419+ formData.append('captcha_id', captchaId);
420+ formData.append('captcha_answer', answer);
421+
422+ fetch(ajaxurl, {
350423 method: 'POST',
351- headers: {
352- 'Content-Type': 'application/json'
353- },
354- body: JSON.stringify({ id: captchaId, answer: answer })
424+ body: formData
355425 })
356426 .then(function(response) {
357- if (!response.ok) {
358- throw new Error('Server responded with ' + response.status);
359- }
360427 return response.json();
361428 })
362- .then(function(data) {
363- if (data && (data.result === 'True' || data.result === true)) {
364- statusEl.innerText = 'Success! Answer is correct.';
365- statusEl.style.color = 'green';
429+ .then(function(responseJson) {
430+ if (responseJson.success) {
431+ var data = responseJson.data;
432+ if (data && (data.result === 'True' || data.result === true)) {
433+ statusEl.innerText = 'Success! Answer is correct.';
434+ statusEl.style.color = 'green';
435+ } else {
436+ statusEl.innerText = 'Incorrect answer or expired.';
437+ statusEl.style.color = 'red';
438+ }
366439 } else {
367- statusEl.innerText = 'Incorrect answer or expired.';
368- statusEl.style.color = 'red';
440+ throw new Error(responseJson.data || 'Failed to verify');
369441 }
370442 })
371443 .catch(function(error) {
0 commit comments