diff --git a/.tests/php/unit/Settings/ListPageBaseTest.php b/.tests/php/unit/Settings/ListPageBaseTest.php
index c1245d24..ba9270ea 100644
--- a/.tests/php/unit/Settings/ListPageBaseTest.php
+++ b/.tests/php/unit/Settings/ListPageBaseTest.php
@@ -55,12 +55,12 @@ public function test_date_picker_display(): void {
diff --git a/.tests/php/unit/includes/FunctionsTest.php b/.tests/php/unit/includes/FunctionsTest.php
index 5e488328..7064b8a6 100644
--- a/.tests/php/unit/includes/FunctionsTest.php
+++ b/.tests/php/unit/includes/FunctionsTest.php
@@ -53,6 +53,7 @@ public function test_hcap_shortcode( array $atts, array $expected ): void {
'action' => HCAPTCHA_ACTION,
'name' => HCAPTCHA_NONCE,
'auto' => false,
+ 'ajax' => false,
'force' => false,
'theme' => 'light',
'size' => 'normal',
@@ -104,6 +105,7 @@ public function dp_test_hcap_shortcode(): array {
'action' => HCAPTCHA_ACTION,
'name' => HCAPTCHA_NONCE,
'auto' => false,
+ 'ajax' => false,
'force' => false,
'theme' => 'light',
'size' => 'normal',
@@ -119,6 +121,7 @@ public function dp_test_hcap_shortcode(): array {
'action' => HCAPTCHA_ACTION,
'name' => HCAPTCHA_NONCE,
'auto' => '1',
+ 'ajax' => false,
'force' => false,
'theme' => 'light',
'size' => 'normal',
@@ -134,6 +137,7 @@ public function dp_test_hcap_shortcode(): array {
'action' => HCAPTCHA_ACTION,
'name' => HCAPTCHA_NONCE,
'auto' => false,
+ 'ajax' => false,
'force' => true,
'theme' => 'light',
'size' => 'normal',
@@ -149,6 +153,7 @@ public function dp_test_hcap_shortcode(): array {
'action' => HCAPTCHA_ACTION,
'name' => HCAPTCHA_NONCE,
'auto' => false,
+ 'ajax' => false,
'force' => false,
'theme' => 'light',
'size' => 'normal',
diff --git a/assets/css/events.css b/assets/css/events.css
index 3a37d073..b5beb174 100644
--- a/assets/css/events.css
+++ b/assets/css/events.css
@@ -48,12 +48,13 @@
width: 220px;
}
-@media (max-width: 600px) {
+@media (max-width: 782px) {
#hcaptcha-options #hcaptcha-events-wrap table tbody tr td {
padding-block-start: 3px;
padding-inline-end: 8px;
padding-block-end: 3px;
padding-inline-start: 35%;
+ min-height: 19.5px;
}
#hcaptcha-options #hcaptcha-events-wrap table tbody tr td.column-primary {
diff --git a/assets/css/forms.css b/assets/css/forms.css
index 493d0cd5..a361e699 100644
--- a/assets/css/forms.css
+++ b/assets/css/forms.css
@@ -44,12 +44,13 @@
aspect-ratio: 3/1;
}
-@media (max-width: 600px) {
+@media (max-width: 782px) {
#hcaptcha-options #hcaptcha-forms-wrap table tbody tr td {
padding-block-start: 3px;
padding-inline-end: 8px;
padding-block-end: 3px;
padding-inline-start: 35%;
+ min-height: 19.5px;
}
#hcaptcha-options #hcaptcha-forms-wrap table tbody tr td.column-primary {
diff --git a/assets/css/settings-base.css b/assets/css/settings-base.css
index 2ac75daf..91446094 100644
--- a/assets/css/settings-base.css
+++ b/assets/css/settings-base.css
@@ -53,7 +53,7 @@ body.settings_page_hcaptcha {
.hcaptcha-header-bar {
position: sticky;
top: 60px;
- z-index: 1;
+ z-index: 2;
background: #f0f2f5;
display: flex;
justify-content: space-between;
@@ -86,7 +86,7 @@ body.settings_page_hcaptcha {
position: relative;
}
-#hcaptcha-options table tr td input[type="checkbox"] {
+#hcaptcha-options table tr td fieldset input[type="checkbox"] {
display: inline;
border: none;
box-shadow: none;
@@ -98,7 +98,7 @@ body.settings_page_hcaptcha {
margin-inline-start: 0;
}
-#hcaptcha-options table tr td input[type="checkbox"]::before {
+#hcaptcha-options table tr td fieldset input[type="checkbox"]::before {
background: url('../images/checkbox-off.svg');
background-size: cover;
margin: 0;
@@ -108,7 +108,7 @@ body.settings_page_hcaptcha {
display: inline-block;
}
-#hcaptcha-options table tr td input[type="checkbox"]:checked::before {
+#hcaptcha-options table tr td fieldset input[type="checkbox"]:checked::before {
background: no-repeat url('../images/checkbox-on.svg');
background-size: cover;
}
@@ -250,11 +250,18 @@ body.settings_page_hcaptcha {
.hcaptcha-excerpt {
display: block;
+ position: relative;
+ width: max-content;
+ max-width: 100%;
overflow: hidden;
white-space: nowrap;
text-overflow: ellipsis;
}
+.hcaptcha-excerpt:hover {
+ overflow: visible;
+}
+
.hcaptcha-excerpt:hover .hcaptcha-hide {
position: absolute;
color: #f0f2f5;
@@ -264,8 +271,9 @@ body.settings_page_hcaptcha {
max-width: 300px;
width: max-content;
padding: 8px 10px;
- top: 0;
- inset-inline-start: 0;
+ top: 50%;
+ inset-inline-start: 50%;
+ transform: translate(-50%, -50%);
box-shadow: inset 0 -1px 0 rgba(0, 0, 0, 0.1);
border-radius: 6px;
border: 1px solid #c3c4c7;
@@ -274,6 +282,31 @@ body.settings_page_hcaptcha {
}
@media (max-width: 782px) {
+ #hcaptcha-options .wp-list-table .toggle-row {
+ top: 15px;
+ }
+
+ #hcaptcha-options .wp-list-table tr.is-expanded .toggle-row {
+ top: 6.5px;
+ }
+
+ #hcaptcha-options .widefat tbody th.check-column {
+ padding: 0;
+ vertical-align: middle;
+ }
+
+ #hcaptcha-options .widefat thead td.check-column, .widefat tfoot td.check-column {
+ padding-left: 0;
+ }
+
+ #hcaptcha-options .widefat th input[type="checkbox"] {
+ margin-bottom: 0;
+ }
+
+ #hcaptcha-options .wp-list-table .is-expanded td:not(.hidden) {
+ overflow: visible;
+ }
+
.hcaptcha-settings-tabs {
margin: unset;
margin-block-start: 10px;
@@ -285,6 +318,16 @@ body.settings_page_hcaptcha {
padding-block-end: 0;
padding-inline-start: 10px;
}
+
+ .hcaptcha-excerpt {
+ display: table-cell;
+ vertical-align: middle;
+ height: 44px;
+ }
+
+ tr.is-expanded .hcaptcha-excerpt {
+ height: 19.5px;
+ }
}
@media (max-width: 600px) {
diff --git a/assets/css/settings-list-page-base.css b/assets/css/settings-list-page-base.css
index 044495a2..7f6f5a36 100644
--- a/assets/css/settings-list-page-base.css
+++ b/assets/css/settings-list-page-base.css
@@ -2,7 +2,6 @@
position: relative;
display: flex;
flex-direction: row-reverse;
- margin-bottom: 15px;
}
#hcaptcha-datepicker-popover-button {
diff --git a/assets/js/events.js b/assets/js/events.js
index 233f9d1c..48946b5f 100644
--- a/assets/js/events.js
+++ b/assets/js/events.js
@@ -1,62 +1,130 @@
-/* global Chart, HCaptchaEventsObject */
+/* global jQuery, Chart, hCaptchaSettingsBase, HCaptchaListPageBaseObject, HCaptchaEventsObject */
/**
+ * @param HCaptchaEventsObject.ajaxUrl
+ * @param HCaptchaEventsObject.bulkAction
+ * @param HCaptchaEventsObject.bulkNonce
* @param HCaptchaEventsObject.failed
* @param HCaptchaEventsObject.failedLabel
* @param HCaptchaEventsObject.succeed
* @param HCaptchaEventsObject.succeedLabel
* @param HCaptchaEventsObject.unit
*/
-document.addEventListener( 'DOMContentLoaded', function() {
- const ctx = document.getElementById( 'eventsChart' );
- const aspectRatio = window.innerWidth > 600 ? 3 : 2;
-
- new Chart( ctx, {
- type: 'bar',
- data: {
- datasets: [
- {
- label: HCaptchaEventsObject.succeedLabel,
- data: HCaptchaEventsObject.succeed,
- borderWidth: 1,
- },
- {
- label: HCaptchaEventsObject.failedLabel,
- data: HCaptchaEventsObject.failed,
- borderWidth: 1,
- },
- ],
- },
- options: {
- responsive: true,
- maintainAspectRatio: true,
- aspectRatio,
- scales: {
- x: {
- type: 'time',
- time: {
- displayFormats: {
- millisecond: 'HH:mm:ss',
- second: 'HH:mm:ss',
- minute: 'HH:mm',
- hour: 'HH:mm',
- day: 'dd.MM.yyyy',
- week: 'dd.MM.yyyy',
- month: 'dd.MM.yyyy',
- quarter: 'dd.MM.yyyy',
- year: 'dd.MM.yyyy',
+
+/**
+ * Events page logic.
+ *
+ * @param {Object} $ jQuery instance.
+ */
+const events = function( $ ) {
+ function initChart() {
+ const ctx = document.getElementById( 'eventsChart' );
+ const aspectRatio = window.innerWidth > 600 ? 3 : 2;
+
+ new Chart( ctx, {
+ type: 'bar',
+ data: {
+ datasets: [
+ {
+ label: HCaptchaEventsObject.succeedLabel,
+ data: HCaptchaEventsObject.succeed,
+ borderWidth: 1,
+ },
+ {
+ label: HCaptchaEventsObject.failedLabel,
+ data: HCaptchaEventsObject.failed,
+ borderWidth: 1,
+ },
+ ],
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ aspectRatio,
+ scales: {
+ x: {
+ type: 'time',
+ time: {
+ displayFormats: {
+ millisecond: 'HH:mm:ss',
+ second: 'HH:mm:ss',
+ minute: 'HH:mm',
+ hour: 'HH:mm',
+ day: 'dd.MM.yyyy',
+ week: 'dd.MM.yyyy',
+ month: 'dd.MM.yyyy',
+ quarter: 'dd.MM.yyyy',
+ year: 'dd.MM.yyyy',
+ },
+ tooltipFormat: 'dd.MM.yyyy HH:mm',
+ unit: HCaptchaEventsObject.unit,
},
- tooltipFormat: 'dd.MM.yyyy HH:mm',
- unit: HCaptchaEventsObject.unit,
},
- },
- y: {
- beginAtZero: true,
- ticks: {
- precision: 0,
+ y: {
+ beginAtZero: true,
+ ticks: {
+ precision: 0,
+ },
},
},
},
- },
- } );
-} );
+ } );
+ }
+
+ function handleBulkAction( event ) {
+ event.preventDefault();
+
+ const form = event.target.closest( 'form' );
+ const formData = new FormData( form );
+
+ const bulk = formData.get( 'action' );
+
+ if ( bulk === '-1' ) {
+ hCaptchaSettingsBase.showErrorMessage( HCaptchaListPageBaseObject.noAction );
+
+ return;
+ }
+
+ const ids = formData.getAll( 'bulk-checkbox[]' );
+
+ if ( ! ids.length ) {
+ hCaptchaSettingsBase.showErrorMessage( HCaptchaListPageBaseObject.noItems );
+
+ return;
+ }
+
+ const data = {
+ action: HCaptchaEventsObject.bulkAction,
+ nonce: HCaptchaEventsObject.bulkNonce,
+ bulk,
+ ids: JSON.stringify( ids ),
+ };
+
+ $.post( {
+ url: HCaptchaEventsObject.ajaxUrl,
+ data,
+ beforeSend: () => hCaptchaSettingsBase.showSuccessMessage( HCaptchaListPageBaseObject.DoingBulk ),
+ } )
+ .done( function( response ) {
+ if ( ! response.success ) {
+ hCaptchaSettingsBase.showErrorMessage( response.data );
+
+ return;
+ }
+
+ window.location.reload();
+ } )
+ .fail(
+ function( response ) {
+ hCaptchaSettingsBase.showErrorMessage( response.statusText );
+ },
+ );
+ }
+
+ initChart();
+ document.getElementById( 'doaction' ).addEventListener( 'click', handleBulkAction );
+};
+
+window.hCaptchaForms = events;
+
+jQuery( document ).ready( events );
diff --git a/assets/js/forms.js b/assets/js/forms.js
index 18fbb12d..ae0c0252 100644
--- a/assets/js/forms.js
+++ b/assets/js/forms.js
@@ -1,56 +1,135 @@
-/* global Chart, HCaptchaFormsObject */
+/* global jQuery, Chart, hCaptchaSettingsBase, HCaptchaListPageBaseObject, HCaptchaFormsObject */
/**
+ * @param HCaptchaFormsObject.ajaxUrl
+ * @param HCaptchaFormsObject.bulkAction
+ * @param HCaptchaFormsObject.bulkNonce
* @param HCaptchaFormsObject.served
* @param HCaptchaFormsObject.servedLabel
* @param HCaptchaFormsObject.unit
+ * @param HCaptchaListPageBaseObject.noAction
+ * @param HCaptchaListPageBaseObject.noItems
+ * @param HCaptchaListPageBaseObject.DoingBulk
*/
-document.addEventListener( 'DOMContentLoaded', function() {
- const ctx = document.getElementById( 'formsChart' );
- const aspectRatio = window.innerWidth > 600 ? 3 : 2;
-
- new Chart( ctx, {
- type: 'bar',
- data: {
- datasets: [
- {
- label: HCaptchaFormsObject.servedLabel,
- backgroundColor: 'rgba(2,101,147,0.5)',
- data: HCaptchaFormsObject.served,
- borderWidth: 1,
- },
- ],
- },
- options: {
- responsive: true,
- maintainAspectRatio: true,
- aspectRatio,
- scales: {
- x: {
- type: 'time',
- time: {
- displayFormats: {
- millisecond: 'HH:mm:ss',
- second: 'HH:mm:ss',
- minute: 'HH:mm',
- hour: 'HH:mm',
- day: 'dd.MM.yyyy',
- week: 'dd.MM.yyyy',
- month: 'dd.MM.yyyy',
- quarter: 'dd.MM.yyyy',
- year: 'dd.MM.yyyy',
+
+/**
+ * Forms page logic.
+ *
+ * @param {Object} $ jQuery instance.
+ */
+const forms = function( $ ) {
+ function initChart() {
+ const ctx = document.getElementById( 'formsChart' );
+ const aspectRatio = window.innerWidth > 600 ? 3 : 2;
+
+ new Chart( ctx, {
+ type: 'bar',
+ data: {
+ datasets: [
+ {
+ label: HCaptchaFormsObject.servedLabel,
+ backgroundColor: 'rgba(2,101,147,0.5)',
+ data: HCaptchaFormsObject.served,
+ borderWidth: 1,
+ },
+ ],
+ },
+ options: {
+ responsive: true,
+ maintainAspectRatio: true,
+ aspectRatio,
+ scales: {
+ x: {
+ type: 'time',
+ time: {
+ displayFormats: {
+ millisecond: 'HH:mm:ss',
+ second: 'HH:mm:ss',
+ minute: 'HH:mm',
+ hour: 'HH:mm',
+ day: 'dd.MM.yyyy',
+ week: 'dd.MM.yyyy',
+ month: 'dd.MM.yyyy',
+ quarter: 'dd.MM.yyyy',
+ year: 'dd.MM.yyyy',
+ },
+ tooltipFormat: 'dd.MM.yyyy HH:mm',
+ unit: HCaptchaFormsObject.unit,
},
- tooltipFormat: 'dd.MM.yyyy HH:mm',
- unit: HCaptchaFormsObject.unit,
},
- },
- y: {
- beginAtZero: true,
- ticks: {
- precision: 0,
+ y: {
+ beginAtZero: true,
+ ticks: {
+ precision: 0,
+ },
},
},
},
- },
- } );
-} );
+ } );
+ }
+
+ function handleBulkAction( event ) {
+ event.preventDefault();
+
+ const form = event.target.closest( 'form' );
+ const formData = new FormData( form );
+
+ const bulk = formData.get( 'action' );
+
+ if ( bulk === '-1' ) {
+ hCaptchaSettingsBase.showErrorMessage( HCaptchaListPageBaseObject.noAction );
+
+ return;
+ }
+
+ const ids = formData.getAll( 'bulk-checkbox[]' ).map(
+ ( id ) => {
+ const row = form.querySelector( `input[name="bulk-checkbox[]"][value="${ id }"]` ).closest( 'tr' );
+ const source = row.querySelector( 'td.name .hcaptcha-excerpt' ).dataset.source;
+ const formId = row.querySelector( 'td.form_id' ).textContent;
+
+ return { source, formId };
+ },
+ );
+
+ if ( ! ids.length ) {
+ hCaptchaSettingsBase.showErrorMessage( HCaptchaListPageBaseObject.noItems );
+
+ return;
+ }
+
+ const data = {
+ action: HCaptchaFormsObject.bulkAction,
+ nonce: HCaptchaFormsObject.bulkNonce,
+ bulk,
+ ids: JSON.stringify( ids ),
+ };
+
+ $.post( {
+ url: HCaptchaFormsObject.ajaxUrl,
+ data,
+ beforeSend: () => hCaptchaSettingsBase.showSuccessMessage( HCaptchaListPageBaseObject.DoingBulk ),
+ } )
+ .done( function( response ) {
+ if ( ! response.success ) {
+ hCaptchaSettingsBase.showErrorMessage( response.data );
+
+ return;
+ }
+
+ window.location.reload();
+ } )
+ .fail(
+ function( response ) {
+ hCaptchaSettingsBase.showErrorMessage( response.statusText );
+ },
+ );
+ }
+
+ initChart();
+ document.getElementById( 'doaction' ).addEventListener( 'click', handleBulkAction );
+};
+
+window.hCaptchaForms = forms;
+
+jQuery( document ).ready( forms );
diff --git a/assets/js/general.js b/assets/js/general.js
index de03480e..eef4bd6f 100644
--- a/assets/js/general.js
+++ b/assets/js/general.js
@@ -1,4 +1,4 @@
-/* global jQuery, hCaptcha, HCaptchaGeneralObject, kaggDialog */
+/* global jQuery, hCaptcha, hCaptchaSettingsBase, HCaptchaGeneralObject, kaggDialog */
/**
* @param HCaptchaGeneralObject.ajaxUrl
@@ -153,7 +153,7 @@ const general = function( $ ) {
function clearMessage() {
$message.remove();
- // Concat below to avoid inspection message.
+ // Concat below to avoid an inspection message.
$( '
' + '
' ).insertAfter( headerBarSelector );
$message = $( msgSelector );
}
@@ -180,12 +180,9 @@ const general = function( $ ) {
$( document ).trigger( 'wp-updates-notice-added' );
- const $wpwrap = $( '#wpwrap' );
- const top = $wpwrap.position().top;
-
$( 'html, body' ).animate(
{
- scrollTop: $message.offset().top - top - parseInt( $message.css( 'margin-bottom' ) ),
+ scrollTop: $message.offset().top - hCaptchaSettingsBase.getStickyHeight(),
},
1000
);
diff --git a/assets/js/hcaptcha-auto-verify.js b/assets/js/hcaptcha-auto-verify.js
new file mode 100644
index 00000000..8084c68a
--- /dev/null
+++ b/assets/js/hcaptcha-auto-verify.js
@@ -0,0 +1,65 @@
+/* globals HCaptchaAutoVerifyObject */
+
+document.addEventListener( 'DOMContentLoaded', () => {
+ const formSelector = 'form';
+
+ [ ...document.querySelectorAll( formSelector ) ].map( ( formElement ) => {
+ const hCaptchaAjaxSelector = 'h-captcha[data-ajax="true"]';
+ const hCaptcha = formElement.querySelector( hCaptchaAjaxSelector );
+
+ if ( ! hCaptcha ) {
+ return formElement;
+ }
+
+ formElement.addEventListener( 'submit', async ( event ) => {
+ event.preventDefault();
+ event.stopPropagation();
+
+ /**
+ * @type {HTMLElement}
+ */
+ const currentTarget = event.currentTarget;
+
+ /**
+ * @type {HTMLFormElement} formElement
+ */
+ const currentFormElement = currentTarget.closest( formSelector );
+ let resultContainer = currentFormElement.previousElementSibling;
+ const autoverifyResultClass = 'autoverify-result';
+ const autoverifyResultTag = 'p';
+
+ if ( resultContainer && resultContainer.matches( autoverifyResultTag + '.' + autoverifyResultClass ) ) {
+ resultContainer.innerHTML = '';
+ } else {
+ resultContainer = document.createElement( autoverifyResultTag );
+ resultContainer.classList.add( autoverifyResultClass );
+ currentFormElement.parentNode.insertBefore( resultContainer, currentFormElement );
+ }
+
+ const formData = new FormData( currentFormElement );
+
+ try {
+ const response = await fetch( currentFormElement.action, {
+ method: 'POST',
+ body: formData,
+ } );
+
+ if ( ! response.ok ) {
+ resultContainer.innerHTML = await response.text();
+
+ return;
+ }
+
+ resultContainer.innerHTML = HCaptchaAutoVerifyObject.successMsg;
+ } catch ( error ) {
+ resultContainer.innerHTML = error;
+ }
+
+ const currentHCaptcha = currentFormElement.querySelector( hCaptchaAjaxSelector );
+
+ window.hCaptchaReset( currentHCaptcha );
+ } );
+
+ return formElement;
+ } );
+} );
diff --git a/assets/js/integrations.js b/assets/js/integrations.js
index 047f87a4..96fd9da8 100644
--- a/assets/js/integrations.js
+++ b/assets/js/integrations.js
@@ -1,4 +1,4 @@
-/* global jQuery, HCaptchaIntegrationsObject, kaggDialog */
+/* global jQuery, hCaptchaSettingsBase, HCaptchaIntegrationsObject, kaggDialog */
/**
* @param HCaptchaIntegrationsObject.CancelBtnText
@@ -25,38 +25,15 @@
* @param {jQuery} $ The jQuery instance.
*/
const integrations = function( $ ) {
- /**
- * @type {HTMLElement}
- */
- const adminBar = document.querySelector( '#wpadminbar' );
-
- /**
- * @type {HTMLElement}
- */
- const tabs = document.querySelector( '.hcaptcha-settings-tabs' );
-
- /**
- * @type {HTMLElement}
- */
- const headerBar = document.querySelector( '.hcaptcha-header-bar' );
const msgSelector = '#hcaptcha-message';
let $message = $( msgSelector );
const $wpWrap = $( '#wpwrap' );
const $adminmenuwrap = $( '#adminmenuwrap' );
const $search = $( '#hcaptcha-integrations-search' );
- function getStickyHeight() {
- const isAbsolute = adminBar ? window.getComputedStyle( adminBar ).position === 'absolute' : true;
- const adminBarHeight = ( adminBar && ! isAbsolute ) ? adminBar.offsetHeight : 0;
- const tabsHeight = tabs ? tabs.offsetHeight : 0;
- const headerBarHeight = headerBar ? headerBar.offsetHeight : 0;
-
- return adminBarHeight + tabsHeight + headerBarHeight;
- }
-
function clearMessage() {
$message.remove();
- // Concat below to avoid inspection message.
+ // Concat to avoid an inspection message.
$( '
' + '
' ).insertAfter( '.hcaptcha-header-bar' );
$message = $( msgSelector );
}
@@ -277,7 +254,7 @@ const integrations = function( $ ) {
$( 'html, body' ).animate(
{
- scrollTop: $tr.offset().top - getStickyHeight(),
+ scrollTop: $tr.offset().top - hCaptchaSettingsBase.getStickyHeight(),
},
1000
);
diff --git a/assets/js/settings-base.js b/assets/js/settings-base.js
index 91e095e8..f706c84f 100644
--- a/assets/js/settings-base.js
+++ b/assets/js/settings-base.js
@@ -5,26 +5,28 @@
*
* @param {Object} $ jQuery instance.
*/
-const settingsBase = function( $ ) {
- const h2Selector = '.hcaptcha-header h2';
- const msgSelector = '#hcaptcha-message';
+const settingsBase = ( function( $ ) {
+ /**
+ * @type {HTMLElement}
+ */
+ const adminBar = document.querySelector( '#wpadminbar' );
- function setHeaderBarTop() {
- /**
- * @type {HTMLElement}
- */
- const adminBar = document.querySelector( '#wpadminbar' );
+ /**
+ * @type {HTMLElement}
+ */
+ const tabs = document.querySelector( '.hcaptcha-settings-tabs' );
- /**
- * @type {HTMLElement}
- */
- const tabs = document.querySelector( '.hcaptcha-settings-tabs' );
+ /**
+ * @type {HTMLElement}
+ */
+ const headerBar = document.querySelector( '.hcaptcha-header-bar' );
- /**
- * @type {HTMLElement}
- */
- const headerBar = document.querySelector( '.hcaptcha-header-bar' );
+ const h2Selector = '.hcaptcha-header h2';
+ const headerBarSelector = '.hcaptcha-header-bar';
+ const msgSelector = '#hcaptcha-message';
+ let $message = $( msgSelector );
+ function setHeaderBarTop() {
const isAbsolute = adminBar ? window.getComputedStyle( adminBar ).position === 'absolute' : true;
const adminBarHeight = ( adminBar && ! isAbsolute ) ? adminBar.offsetHeight : 0;
const tabsHeight = tabs ? tabs.offsetHeight : 0;
@@ -70,6 +72,61 @@ const settingsBase = function( $ ) {
}
}
+ /**
+ * Public properties and functions.
+ */
+ const app = {
+ getStickyHeight() {
+ const isAbsolute = adminBar ? window.getComputedStyle( adminBar ).position === 'absolute' : true;
+ const adminBarHeight = ( adminBar && ! isAbsolute ) ? adminBar.offsetHeight : 0;
+ const tabsHeight = tabs ? tabs.offsetHeight : 0;
+ const headerBarHeight = headerBar ? headerBar.offsetHeight : 0;
+
+ return adminBarHeight + tabsHeight + headerBarHeight;
+ },
+
+ clearMessage() {
+ $message.remove();
+ // Concat below to avoid an inspection message.
+ $( '
' + '
' ).insertAfter( headerBarSelector );
+ $message = $( msgSelector );
+ },
+
+ showMessage( message = '', msgClass = '' ) {
+ message = message === undefined ? '' : String( message );
+
+ if ( ! message ) {
+ return;
+ }
+
+ app.clearMessage();
+ $message.addClass( msgClass + ' notice is-dismissible' );
+
+ const messageLines = message.split( '\n' ).map( function( line ) {
+ return `
${ line }
`;
+ } );
+
+ $message.html( messageLines.join( '' ) );
+
+ $( document ).trigger( 'wp-updates-notice-added' );
+
+ $( 'html, body' ).animate(
+ {
+ scrollTop: $message.offset().top - app.getStickyHeight(),
+ },
+ 1000,
+ );
+ },
+
+ showSuccessMessage( message = '' ) {
+ app.showMessage( message, 'notice-success' );
+ },
+
+ showErrorMessage( message = '' ) {
+ app.showMessage( message, 'notice-error' );
+ },
+ };
+
// Move WP notices to the message area.
$( h2Selector ).siblings().appendTo( msgSelector );
@@ -80,7 +137,9 @@ const settingsBase = function( $ ) {
setHeaderBarTop();
highLight();
-};
+
+ return app;
+}( jQuery ) );
window.hCaptchaSettingsBase = settingsBase;
diff --git a/assets/js/settings-list-page-base.js b/assets/js/settings-list-page-base.js
index 6bc4f7fd..03e1619e 100644
--- a/assets/js/settings-list-page-base.js
+++ b/assets/js/settings-list-page-base.js
@@ -1,4 +1,4 @@
-/* global HCaptchaFlatPickerObject, flatpickr */
+/* global HCaptchaListPageBaseObject, flatpickr */
/**
* @param flatpickr.l10ns
@@ -17,8 +17,8 @@ document.addEventListener( 'DOMContentLoaded', function() {
hide: 'hcaptcha-hide',
selected: 'hcaptcha-is-selected',
};
- const delimiter = HCaptchaFlatPickerObject.delimiter;
- const locale = HCaptchaFlatPickerObject.locale;
+ const delimiter = HCaptchaListPageBaseObject.delimiter;
+ const locale = HCaptchaListPageBaseObject.locale;
let flatPickerObj;
const wrapper = document.getElementById( 'hcaptcha-options' );
diff --git a/changelog.txt b/changelog.txt
index 9e7b1c74..ff56dc40 100644
--- a/changelog.txt
+++ b/changelog.txt
@@ -1,3 +1,22 @@
+= 4.10.0 =
+* Added support for wp_login_form() function and LoginOut block.
+* Added support for hCaptcha in HTML Gravity Forms fields.
+* Added support for custom nonce action and name in the [hcaptcha] shortcode.
+* Added compatibility with Cookies and Content Security Policy plugin.
+* Added auto-verification of arbitrary forms in ajax.
+* Added deletion of events on the Forms page.
+* Added deletion of events on the Events page.
+* Improved error messaging for hCaptcha verification.
+* Fixed IP detection in the WordPress core via filter. Now syncs with hCaptcha event information when IP collection is activated.
+* Fixed fatal error with the WPForms plugin in rare cases.
+* Fixed error message at the first entry to the login page when Hide Login Errors in on.
+* Fixed scrolling to the message on the General page.
+* Fixed fatal error during integration installation in some cases.
+* Fixed the Integrations page when active plugin was deleted.
+* Fixed error when hCaptcha is disabled for standard login but enabled for LearnPress login.
+* Fixed error when hCaptcha is disabled for standard login but enabled for Tutor login.
+* Fixed layout for Forms and Events pages on small screens.
+
= 4.9.0 =
* Added LearnPress integration.
* Added Tutor LMS integration.
diff --git a/composer.json b/composer.json
index bbd4f2b1..31504317 100644
--- a/composer.json
+++ b/composer.json
@@ -44,7 +44,7 @@
"lucatume/wp-browser": "3.7.11 - 4.4.1",
"squizlabs/php_codesniffer": "^3.11.2",
"phpcompatibility/php-compatibility": "^9.3.5",
- "phpcompatibility/phpcompatibility-wp": "^2.1.5",
+ "phpcompatibility/phpcompatibility-wp": "^2.1.6",
"wp-coding-standards/wpcs": "^3.1.0"
},
"autoload": {
diff --git a/hcaptcha.php b/hcaptcha.php
index 095244e8..c110af16 100644
--- a/hcaptcha.php
+++ b/hcaptcha.php
@@ -10,7 +10,7 @@
* Plugin Name: hCaptcha for WP
* Plugin URI: https://www.hcaptcha.com/
* Description: hCaptcha keeps out bots and spam while putting privacy first. It is a drop-in replacement for reCAPTCHA.
- * Version: 4.9.0
+ * Version: 4.10.0
* Requires at least: 5.3
* Requires PHP: 7.2
* Author: hCaptcha
@@ -21,7 +21,7 @@
* Domain Path: /languages/
*
* WC requires at least: 3.0
- * WC tested up to: 9.3
+ * WC tested up to: 9.6
*/
// phpcs:ignore Generic.Commenting.DocComment.MissingShort
@@ -39,7 +39,7 @@
/**
* Plugin version.
*/
-const HCAPTCHA_VERSION = '4.9.0';
+const HCAPTCHA_VERSION = '4.10.0';
/**
* Path to the plugin dir.
diff --git a/package.json b/package.json
index 5f1db954..7f696393 100644
--- a/package.json
+++ b/package.json
@@ -46,7 +46,7 @@
},
"packageManager": "yarn@4.5.3",
"dependencies": {
- "@wordpress/hooks": "^4.14.0"
+ "@wordpress/hooks": "^4.16.0"
},
"devDependencies": {
"@babel/core": "^7.26.0",
@@ -58,7 +58,7 @@
"css-loader": "^7.1.2",
"css-minimizer-webpack-plugin": "^7.0.0",
"eslint": "^8.57.1",
- "glob": "^11.0.0",
+ "glob": "^11.0.1",
"jest": "^29.7.0",
"jest-environment-jsdom": "^29.7.0",
"jest-fetch-mock": "^3.0.3",
@@ -66,13 +66,14 @@
"mini-css-extract-plugin": "^2.9.2",
"prettier": "^3.4.2",
"terser-webpack-plugin": "^5.3.11",
- "typescript": "^5.7.2",
+ "typescript": "^5.7.3",
"webpack": "^5.97.1",
"webpack-cli": "^5.1.4",
"webpack-remove-empty-scripts": "^1.0.4"
},
"overrides": {
"cross-spawn": "^7.0.6",
- "micromatch": "^4.0.8"
+ "micromatch": "^4.0.8",
+ "undici": "^5.28.5"
}
}
diff --git a/readme.txt b/readme.txt
index cf6f5faa..1b5ba31a 100644
--- a/readme.txt
+++ b/readme.txt
@@ -4,7 +4,7 @@ Tags: captcha, hcaptcha, antispam, abuse, protect
Requires at least: 5.3
Tested up to: 6.7
Requires PHP: 7.2
-Stable tag: 4.9.0
+Stable tag: 4.10.0
License: GPLv2 or later
License URI: https://www.gnu.org/licenses/gpl-2.0.html
@@ -111,7 +111,7 @@ To make hCaptcha work, the shortcode must be inside the
t
Full list of arguments:
`
-[hcaptcha action="my_hcap_action" name="my_hcap_name" auto="true|false" force="true|false" theme="light|dark|auto" size="normal|compact|invisible"]
+[hcaptcha action="my_hcap_action" name="my_hcap_name" auto="true|false" ajax="true|false" force="true|false" theme="light|dark|auto" size="normal|compact|invisible"]
`
The shortcode adds not only the hCaptcha div to the form, but also a nonce field. You can set your own nonce action and name. For this, use arguments in the shortcode:
@@ -187,6 +187,12 @@ You can add also `force="true"` or `force="1"` argument to prevent sending a for
[hcaptcha auto="true" force="true"]
`
+Arbitrary forms can also be verified in ajax via the `ajax` argument. There is no need to specify `auto="true"` in this case, as `ajax` implies `auto="true"`.
+
+`
+[hcaptcha ajax="true"]
+`
+
= How to block hCaptcha on a specific page? =
hCaptcha starts early, so you cannot use standard WP functions to determine the page. For instance, to block it on `my-account` page, add the following code to your plugin's (or mu-plugin's) main file. This code won't work being added to a theme's functions.php file.
@@ -551,6 +557,7 @@ If this feature is enabled, anonymized statistics on your plugin configuration,
* CoBlocks Form
* Colorlib Customizer Login, Lost Password, and Customizer Register Forms
* Contact Form 7
+* Cookies and Content Security Policy
* Divi Comment, Contact, Email Optin and Login Forms
* Divi Builder Comment, Contact, Email Optin and Login Forms
* Download Manager Form
@@ -620,6 +627,25 @@ Instructions for popular native integrations are below:
== Changelog ==
+= 4.10.0 =
+* Added support for wp_login_form() function and LoginOut block.
+* Added support for hCaptcha in HTML Gravity Forms fields.
+* Added support for custom nonce action and name in the [hcaptcha] shortcode.
+* Added compatibility with Cookies and Content Security Policy plugin.
+* Added auto-verification of arbitrary forms in ajax.
+* Added deletion of events on the Forms page.
+* Added deletion of events on the Events page.
+* Improved error messaging for hCaptcha verification.
+* Fixed IP detection in the WordPress core via filter. Now syncs with hCaptcha event information when IP collection is activated.
+* Fixed fatal error with the WPForms plugin in rare cases.
+* Fixed error message at the first entry to the login page when Hide Login Errors in on.
+* Fixed scrolling to the message on the General page.
+* Fixed fatal error during integration installation in some cases.
+* Fixed the Integrations page when active plugin was deleted.
+* Fixed error when hCaptcha is disabled for standard login but enabled for LearnPress login.
+* Fixed error when hCaptcha is disabled for standard login but enabled for Tutor login.
+* Fixed layout for Forms and Events pages on small screens.
+
= 4.9.0 =
* Added LearnPress integration.
* Added Tutor LMS integration.
diff --git a/src/js/hcaptcha/hcaptcha.js b/src/js/hcaptcha/hcaptcha.js
index b9747ef7..a94ee658 100644
--- a/src/js/hcaptcha/hcaptcha.js
+++ b/src/js/hcaptcha/hcaptcha.js
@@ -110,11 +110,12 @@ class HCaptcha {
}
/**
- * Validate hCaptcha widget.
+ * Set current form.
*
* @param {CustomEvent} event Event.
+ * @return {Object|undefined} Currently processing form.
*/
- validate( event ) {
+ getCurrentForm( event ) {
/**
* @type {HTMLElement}
*/
@@ -128,24 +129,38 @@ class HCaptcha {
/**
* @type {{submitButtonElement: HTMLElement, widgetId: string}|null}
*/
- const form = this.getFoundFormById( formElement.dataset.hCaptchaId );
+ const form = this.getFoundFormById( formElement?.dataset?.hCaptchaId );
- const submitButtonElement = form.submitButtonElement;
- const widgetId = form.widgetId;
+ const submitButtonElement = form?.submitButtonElement;
+ const widgetId = form?.widgetId;
- if ( ! this.isSameOrDescendant( submitButtonElement, event.target ) ) {
- return;
+ if (
+ ! widgetId ||
+ ! this.isSameOrDescendant( submitButtonElement, event.target )
+ ) {
+ return undefined;
}
event.preventDefault();
event.stopPropagation();
- this.currentForm = { formElement, submitButtonElement };
+ return { formElement, submitButtonElement, widgetId };
+ }
- if ( ! widgetId ) {
+ /**
+ * Validate hCaptcha widget.
+ *
+ * @param {CustomEvent} event Event.
+ */
+ validate( event ) {
+ this.currentForm = this.getCurrentForm( event );
+
+ if ( ! this.currentForm ) {
return;
}
+ const { formElement, widgetId } = this.currentForm;
+
/**
* @type {HTMLTextAreaElement}
*/
@@ -457,18 +472,15 @@ class HCaptcha {
this.foundForms.push( { hCaptchaId, submitButtonElement, widgetId } );
- if (
- ( 'invisible' !== hcaptchaElement.dataset.size ) &&
- ( 'true' !== hcaptchaElement.dataset.force )
- ) {
- return formElement;
- }
-
if ( ! submitButtonElement ) {
return formElement;
}
- submitButtonElement.addEventListener( 'click', this.validate, true );
+ const dataset = hcaptchaElement.dataset;
+
+ if ( dataset.size === 'invisible' || dataset.force === 'true' ) {
+ submitButtonElement.addEventListener( 'click', this.validate, true );
+ }
return formElement;
}, this );
@@ -493,14 +505,18 @@ class HCaptcha {
* Submit a form containing hCaptcha.
*/
submit() {
- const formElement = this.currentForm.formElement;
- const submitButtonElement = this.currentForm.submitButtonElement;
+ if ( ! this.currentForm ) {
+ return;
+ }
+
+ const { formElement, submitButtonElement } = this.currentForm;
if (
'form' !== formElement.tagName.toLowerCase() ||
this.isAjaxSubmitButton( submitButtonElement )
) {
submitButtonElement.removeEventListener( 'click', this.validate, true );
+
submitButtonElement.click();
return;
diff --git a/src/php/Abstracts/LoginBase.php b/src/php/Abstracts/LoginBase.php
index b3451491..aeeac438 100644
--- a/src/php/Abstracts/LoginBase.php
+++ b/src/php/Abstracts/LoginBase.php
@@ -161,6 +161,12 @@ public function hide_login_error( $user, $username, $password ) {
return $user;
}
+ $ignore_codes = [ 'empty_username', 'empty_password' ];
+
+ if ( in_array( $user->get_error_code(), $ignore_codes, true ) ) {
+ return $user;
+ }
+
$codes = $user->get_error_codes();
$messages = $user->get_error_messages();
$hcap_messages = hcap_get_error_messages();
diff --git a/src/php/Admin/Events/Events.php b/src/php/Admin/Events/Events.php
index 07324300..5cda6d86 100644
--- a/src/php/Admin/Events/Events.php
+++ b/src/php/Admin/Events/Events.php
@@ -233,7 +233,7 @@ public static function get_forms( array $args = [] ): array {
// phpcs:disable WordPress.DB.PreparedSQL.InterpolatedNotPrepared
"SELECT
SQL_CALC_FOUND_ROWS
- source, form_id, COUNT(*) as served
+ id, source, form_id, COUNT(*) as served
FROM $table_name
WHERE $where_date
GROUP BY source, form_id
diff --git a/src/php/Admin/Events/EventsTable.php b/src/php/Admin/Events/EventsTable.php
index be25a9c5..2a83a236 100644
--- a/src/php/Admin/Events/EventsTable.php
+++ b/src/php/Admin/Events/EventsTable.php
@@ -8,45 +8,26 @@
namespace HCaptcha\Admin\Events;
use HCaptcha\Settings\ListPageBase;
-use WP_List_Table;
-
-// If this file is called directly, abort.
-if ( ! defined( 'ABSPATH' ) ) {
- // @codeCoverageIgnoreStart
- exit;
- // @codeCoverageIgnoreEnd
-}
-
-if ( ! class_exists( 'WP_List_Table', false ) ) {
- // IMPORTANT NOTICE:
- // This line is needed to prevent fatal errors in the third-party plugins.
- // We know that Jetpack (probably others also) can load WP classes during cron jobs.
- require_once ABSPATH . 'wp-admin/includes/class-wp-list-table.php';
-}
/**
* List events in the table.
*/
-class EventsTable extends WP_List_Table {
+class EventsTable extends TableBase {
/**
- * Events per page option.
+ * Singular table name.
*/
- private const EVENTS_PER_PAGE = 'hcaptcha_events_per_page';
+ protected const SINGULAR = 'event';
/**
- * Plugin page hook.
- *
- * @var string
+ * Plural table name.
*/
- private $plugin_page_hook;
+ protected const PLURAL = 'events';
/**
- * Default number of events to show per page.
- *
- * @var int
+ * Items per page option.
*/
- public $per_page_default = 20;
+ protected const ITEMS_PER_PAGE = 'hcaptcha_events_per_page';
/**
* Date and time formats.
@@ -55,39 +36,6 @@ class EventsTable extends WP_List_Table {
*/
private $datetime_format = [];
- /**
- * Columns.
- *
- * @var array
- */
- private $columns;
-
- /**
- * Plugins installed.
- *
- * @var array[]
- */
- private $plugins;
-
- /**
- * Class constructor.
- *
- * @param string $plugin_page_hook Plugin page hook.
- */
- public function __construct( string $plugin_page_hook ) {
- parent::__construct(
- [
- 'singular' => 'event',
- 'plural' => 'events',
- 'screen' => $plugin_page_hook,
- ]
- );
-
- $this->plugin_page_hook = $plugin_page_hook;
-
- $this->init();
- }
-
/**
* Init class.
*
@@ -100,6 +48,7 @@ public function init(): void {
];
$this->columns = [
+ 'cb' => '
',
'name' => __( 'Source', 'hcaptcha-for-forms-and-more' ),
'form_id' => __( 'Form Id', 'hcaptcha-for-forms-and-more' ),
'ip' => __( 'IP', 'hcaptcha-for-forms-and-more' ),
@@ -108,51 +57,7 @@ public function init(): void {
'date_gmt' => __( 'Date', 'hcaptcha-for-forms-and-more' ),
];
- $this->plugins = get_plugins();
-
- add_action( 'load-' . $this->plugin_page_hook, [ $this, 'add_screen_option' ] );
- add_filter( 'set_screen_option_' . self::EVENTS_PER_PAGE, [ $this, 'set_screen_option' ], 10, 3 );
-
- set_screen_options();
- }
-
- /**
- * Add screen options.
- *
- * @return void
- */
- public function add_screen_option(): void {
- $args = [
- 'label' => __( 'Number of items per page:', 'hcaptcha-for-forms-and-more' ),
- 'default' => $this->per_page_default,
- 'option' => self::EVENTS_PER_PAGE,
- ];
-
- add_screen_option( 'per_page', $args );
- }
-
- /**
- * Set screen option.
- *
- * @param mixed $screen_option The value to save instead of the option value.
- * Default false (to skip saving the current option).
- * @param string $option The option name.
- * @param mixed $value The option value.
- *
- * @return mixed
- * @noinspection PhpUnusedParameterInspection
- */
- public function set_screen_option( $screen_option, string $option, $value ) {
- return $value;
- }
-
- /**
- * Retrieve the table columns.
- *
- * @return array Array of all the list table columns.
- */
- public function get_columns(): array {
- return $this->columns;
+ parent::init();
}
/**
@@ -186,6 +91,8 @@ public function get_sortable_columns(): array {
/**
* Fetch and set up the final data for the table.
+ *
+ * @return void
*/
public function prepare_items(): void {
$hidden = get_hidden_columns( $this->screen );
@@ -203,8 +110,8 @@ public function prepare_items(): void {
$dates = explode( ListPageBase::TIMESPAN_DELIMITER, $date );
$dates = array_filter( array_map( 'trim', $dates ) );
- $column_slugs = str_replace( 'name', 'source', array_keys( $this->columns ) );
- $per_page = $this->get_items_per_page( self::EVENTS_PER_PAGE, $this->per_page_default );
+ $column_slugs = str_replace( [ 'cb', 'name' ], [ 'id', 'source' ], array_keys( $this->columns ) );
+ $per_page = $this->get_items_per_page( self::ITEMS_PER_PAGE, $this->per_page_default );
$offset = ( $paged - 1 ) * $per_page;
$args = [
'columns' => $column_slugs,
@@ -228,37 +135,6 @@ public function prepare_items(): void {
);
}
- /**
- * Column Source.
- * Has 'name' slug not to be hidden.
- * WP has no filter for special columns.
- *
- * @see \WP_Screen::render_list_table_columns_preferences.
- *
- * @param object $item Item.
- *
- * @noinspection PhpUnused PhpUnused.
- */
- protected function column_name( object $item ): string {
- $source = (array) json_decode( $item->source, true );
-
- foreach ( $source as &$slug ) {
- if ( 'WordPress' === $slug ) {
- continue;
- }
-
- if ( false === strpos( $slug, '/' ) ) {
- continue;
- }
-
- $slug = isset( $this->plugins[ $slug ] ) ? $this->plugins[ $slug ]['Name'] : $slug;
- }
-
- unset( $slug );
-
- return $this->excerpt( implode( ', ', $source ), 15 );
- }
-
/**
* Column IP.
*
@@ -323,36 +199,4 @@ protected function column_date_gmt( object $item ): string {
esc_html( $wp_date )
);
}
-
- /**
- * Column default.
- *
- * @param object $item Item.
- * @param string $column_name Column name.
- */
- protected function column_default( $item, $column_name ): string {
- return (string) $item->$column_name;
- }
-
- /**
- * Excerpt text.
- *
- * @param string $text Text.
- * @param int $length Excerpt length.
- *
- * @return string
- */
- private function excerpt( string $text, int $length = 35 ): string {
- $excerpt = mb_substr( $text, 0, $length );
-
- ob_start();
-
- ?>
-
-
-
- 'form',
- 'plural' => 'forms',
- 'screen' => $plugin_page_hook,
- ]
- );
-
- $this->plugin_page_hook = $plugin_page_hook;
-
- $this->init();
- }
-
/**
* Init class.
*
@@ -95,56 +43,13 @@ public function __construct( string $plugin_page_hook ) {
*/
public function init(): void {
$this->columns = [
+ 'cb' => '
',
'name' => __( 'Source', 'hcaptcha-for-forms-and-more' ),
'form_id' => __( 'Form Id', 'hcaptcha-for-forms-and-more' ),
'served' => __( 'Served', 'hcaptcha-for-forms-and-more' ),
];
- $this->plugins = get_plugins();
-
- add_action( 'load-' . $this->plugin_page_hook, [ $this, 'add_screen_option' ] );
- add_filter( 'set_screen_option_' . self::FORMS_PER_PAGE, [ $this, 'set_screen_option' ], 10, 3 );
-
- set_screen_options();
- }
-
- /**
- * Add screen options.
- *
- * @return void
- */
- public function add_screen_option(): void {
- $args = [
- 'label' => __( 'Number of items per page:', 'hcaptcha-for-forms-and-more' ),
- 'default' => $this->per_page_default,
- 'option' => self::FORMS_PER_PAGE,
- ];
-
- add_screen_option( 'per_page', $args );
- }
-
- /**
- * Set screen option.
- *
- * @param mixed $screen_option The value to save instead of the option value.
- * Default false (to skip saving the current option).
- * @param string $option The option name.
- * @param mixed $value The option value.
- *
- * @return mixed
- * @noinspection PhpUnusedParameterInspection
- */
- public function set_screen_option( $screen_option, string $option, $value ) {
- return $value;
- }
-
- /**
- * Retrieve the table columns.
- *
- * @return array Array of all the list table columns.
- */
- public function get_columns(): array {
- return $this->columns;
+ parent::init();
}
/**
@@ -196,7 +101,7 @@ public function prepare_items(): void {
$dates = explode( ListPageBase::TIMESPAN_DELIMITER, $date );
$dates = array_filter( array_map( 'trim', $dates ) );
- $per_page = $this->get_items_per_page( self::FORMS_PER_PAGE, $this->per_page_default );
+ $per_page = $this->get_items_per_page( self::ITEMS_PER_PAGE, $this->per_page_default );
$offset = ( $paged - 1 ) * $per_page;
$args = [
'offset' => $offset,
@@ -219,67 +124,4 @@ public function prepare_items(): void {
]
);
}
-
- /**
- * Column Source.
- * Has 'name' slug not to be hidden.
- * WP has no filter for special columns.
- *
- * @see \WP_Screen::render_list_table_columns_preferences.
- *
- * @param object $item Item.
- *
- * @noinspection PhpUnused PhpUnused.
- */
- protected function column_name( object $item ): string {
- $source = (array) json_decode( $item->source, true );
-
- foreach ( $source as &$slug ) {
- if ( 'WordPress' === $slug ) {
- continue;
- }
-
- if ( false === strpos( $slug, '/' ) ) {
- continue;
- }
-
- $slug = isset( $this->plugins[ $slug ] ) ? $this->plugins[ $slug ]['Name'] : $slug;
- }
-
- unset( $slug );
-
- return $this->excerpt( implode( ', ', $source ), 15 );
- }
-
- /**
- * Column default.
- *
- * @param object $item Item.
- * @param string $column_name Column name.
- */
- protected function column_default( $item, $column_name ): string {
- return (string) $item->$column_name;
- }
-
- /**
- * Excerpt text.
- *
- * @param string $text Text.
- * @param int $length Excerpt length.
- *
- * @return string
- */
- private function excerpt( string $text, int $length = 35 ): string {
- $excerpt = mb_substr( $text, 0, $length );
-
- ob_start();
-
- ?>
-
-
-
- static::SINGULAR,
+ 'plural' => static::PLURAL,
+ 'screen' => $plugin_page_hook,
+ ]
+ );
+
+ $this->plugin_page_hook = $plugin_page_hook;
+
+ $this->init();
+ }
+
+ /**
+ * Init class.
+ *
+ * @return void
+ */
+ public function init(): void {
+ $this->plugins = get_plugins();
+
+ add_action( 'load-' . $this->plugin_page_hook, [ $this, 'add_screen_option' ] );
+ add_filter( 'set_screen_option_' . static::ITEMS_PER_PAGE, [ $this, 'set_screen_option' ], 10, 3 );
+
+ set_screen_options();
+ }
+
+ /**
+ * Add screen options.
+ *
+ * @return void
+ */
+ public function add_screen_option(): void {
+ $args = [
+ 'label' => __( 'Number of items per page:', 'hcaptcha-for-forms-and-more' ),
+ 'default' => $this->per_page_default,
+ 'option' => static::ITEMS_PER_PAGE,
+ ];
+
+ add_screen_option( 'per_page', $args );
+ }
+
+ /**
+ * Set screen option.
+ *
+ * @param mixed $screen_option The value to save instead of the option value.
+ * Default false (to skip saving the current option).
+ * @param string $option The option name.
+ * @param mixed $value The option value.
+ *
+ * @return mixed
+ * @noinspection PhpUnusedParameterInspection
+ */
+ public function set_screen_option( $screen_option, string $option, $value ) {
+ return $value;
+ }
+
+ /**
+ * Retrieve the table columns.
+ *
+ * @return array Array of all the list table columns.
+ */
+ public function get_columns(): array {
+ return $this->columns;
+ }
+
+ /**
+ * Get bulk actions.
+ *
+ * @global string $comment_status
+ *
+ * @return array
+ * @noinspection PhpMissingReturnTypeInspection
+ * @noinspection ReturnTypeCanBeDeclaredInspection
+ */
+ protected function get_bulk_actions() {
+ $actions = [];
+
+ $actions['trash'] = __( 'Delete', 'hcaptcha-for-forms-and-more' );
+
+ return $actions;
+ }
+
+ /**
+ * Generate content for the checkbox column.
+ *
+ * @param object $item The current item.
+ * @return string The checkbox HTML.
+ */
+ protected function column_cb( $item ): string {
+ $id = isset( $item->id ) ? (int) $item->id : 0;
+
+ return sprintf(
+ '
',
+ $id
+ );
+ }
+
+ /**
+ * Column Source.
+ * Has 'name' slug not to be hidden.
+ * WP has no filter for special columns.
+ *
+ * @see \WP_Screen::render_list_table_columns_preferences.
+ *
+ * @param object $item Item.
+ *
+ * @noinspection PhpUnused PhpUnused.
+ */
+ protected function column_name( object $item ): string {
+ $source = (array) json_decode( $item->source, true );
+
+ foreach ( $source as &$slug ) {
+ if ( 'WordPress' === $slug ) {
+ continue;
+ }
+
+ if ( false === strpos( $slug, '/' ) ) {
+ continue;
+ }
+
+ $slug = isset( $this->plugins[ $slug ] ) ? $this->plugins[ $slug ]['Name'] : $slug;
+ }
+
+ unset( $slug );
+
+ return $this->excerpt( implode( ', ', $source ), 15, $item->source );
+ }
+
+ /**
+ * Column default.
+ *
+ * @param object $item Item.
+ * @param string $column_name Column name.
+ */
+ protected function column_default( $item, $column_name ): string {
+ return (string) $item->$column_name;
+ }
+
+ /**
+ * Excerpt text.
+ *
+ * @param string $text Text.
+ * @param int $length Excerpt length.
+ * @param string $source Source.
+ *
+ * @return string
+ */
+ protected function excerpt( string $text, int $length = 35, string $source = '' ): string {
+ $excerpt = mb_substr( $text, 0, $length );
+
+ ob_start();
+
+ ?>
+
+
+
+
+ process_content( $content );
}
+ /**
+ * Register hCaptcha form.
+ *
+ * @param array|mixed $args Arguments.
+ *
+ * @return void
+ */
+ public function register_hcaptcha( $args ): void {
+ if ( ! is_array( $args ) ) {
+ return;
+ }
+
+ $widget_id = HCaptcha::widget_id_value( $args['id'] ?? [] );
+
+ $this->registry[ $widget_id ] = $args;
+ }
+
+ /**
+ * Enqueue scripts.
+ *
+ * @return void
+ */
+ public function enqueue_scripts(): void {
+ if ( ! array_filter( array_column( $this->registry ?? [], 'ajax' ) ) ) {
+ return;
+ }
+
+ $min = hcap_min_suffix();
+
+ wp_enqueue_script(
+ self::HANDLE,
+ constant( 'HCAPTCHA_URL' ) . "/assets/js/hcaptcha-auto-verify$min.js",
+ [ 'jquery' ],
+ constant( 'HCAPTCHA_VERSION' ),
+ true
+ );
+
+ wp_localize_script(
+ self::HANDLE,
+ self::OBJECT,
+ [
+ 'successMsg' => __( 'The form was submitted successfully.', 'hcaptcha-for-forms-and-more' ),
+ ]
+ );
+
+ wp_enqueue_script( 'hcaptcha' );
+ }
+
/**
* Verify a form automatically.
*
* @return void
* @noinspection ForgottenDebugOutputInspection
*/
- public function verify_form(): void {
+ public function verify(): void {
if ( ! Request::is_post() || ! Request::is_frontend() ) {
return;
}
@@ -83,14 +151,25 @@ public function verify_form(): void {
return;
}
- if ( ! $this->is_form_registered( $path ) ) {
+ $registered_form = $this->get_registered_form( $path );
+
+ if ( null === $registered_form ) {
return;
}
- $result = hcaptcha_verify_post();
+ $args = $registered_form['args'] ?? [];
+ $action = $args['action'] ?? '';
+ $name = $args['name'] ?? '';
+ $ajax = $args['ajax'] ?? '';
+ $result = hcaptcha_verify_post( $name, $action );
+
+ if ( $ajax ) {
+ add_filter( 'wp_doing_ajax', '__return_true' );
+ }
if ( null !== $result ) {
$_POST = [];
+
wp_die(
esc_html( $result ),
'hCaptcha',
@@ -118,10 +197,13 @@ protected function register_forms( array $forms ): void {
continue;
}
+ $widget_id_value = $this->get_widget_id_value( $form );
+ $args = $this->registry[ $widget_id_value ] ?? [];
+
$forms_data[] = [
'action' => $action,
'inputs' => $this->get_visible_input_names( $form ),
- 'auto' => $this->is_form_auto( $form ),
+ 'args' => $args,
];
}
@@ -138,7 +220,7 @@ protected function register_forms( array $forms ): void {
private function get_form_action( string $form ): string {
$form_action = '';
- if ( preg_match( '#