Snapshot: MLS sync fixes, image refresh, plugin/theme updates
MLS plugin fixes from this session: - Fix silent insert failures: location column NOT NULL was rejecting wpdb->insert calls, causing ~18k new properties since Dec 2025 to be lost. Inserts now build raw SQL with ST_PointFromText so the spatial column is populated atomically. - Auto-refresh expired media URLs in MLS_Media_Handler::fetch_and_cache(), guarded by a property-level GET_LOCK so concurrent fetches share one API refresh. - Normalize WP_Error to null in mls_get_property_image() so callers can rely on the documented string|null contract. - Support comma-separated property_type filters in MLS_Query and MLS_Cluster so the homepage "View All Commercial" link (?property_type=Commercial+Sale,Land,Farm) actually filters correctly. - Incremental sync now looks back 10 minutes past the latest modification timestamp as a safety margin against missed records. - Smart sync exits silently (info-level, not warning) when a full sync is in progress. Operational: - New cron: weekly full sync Sundays at 3 AM (/usr/local/bin/mls-full-sync). - New cron: hourly 2GB cap on mls-thumbnails/ and cache/transformed-images/ (/usr/local/bin/mls-image-cache-cap). - Logrotate config for wp-content/debug.log (2-day retention, daily rotation, delaycompress). Repo policy: - CLAUDE.md updated with explicit "commit everything except build artifacts" policy. - .gitignore: untrack runtime image caches and debug.log rotations. Other modifications in this snapshot are pre-existing in-flight theme/plugin/db_content_updates work. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,125 @@
|
||||
/* global wpforms_settings */
|
||||
|
||||
/**
|
||||
* @param wpforms_settings.address_field.list_countries_without_states
|
||||
*/
|
||||
|
||||
( function( window, $ ) {
|
||||
const app = {
|
||||
|
||||
/**
|
||||
* List of countries without states.
|
||||
*
|
||||
* @see WPForms\Forms\Fields\Address\Frontend::strings for PHP filter.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*/
|
||||
noStateCountries: [],
|
||||
|
||||
/**
|
||||
* Init Address field.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*/
|
||||
init() {
|
||||
$( window ).on( 'load', app.onLoad );
|
||||
$( document )
|
||||
.on( 'wpformsRepeaterFieldCloneCreated', app.setChangeHandlers );
|
||||
},
|
||||
|
||||
/**
|
||||
* On load event.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*/
|
||||
onLoad() {
|
||||
app.noStateCountries = wpforms_settings?.address_field?.list_countries_without_states || [];
|
||||
|
||||
if ( ! app.noStateCountries.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.setChangeHandlers();
|
||||
},
|
||||
|
||||
/**
|
||||
* Set change handlers.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*/
|
||||
setChangeHandlers() {
|
||||
$( '.wpforms-field-address' ).each( function() {
|
||||
const $countrySelect = $( this ).find( 'select.wpforms-field-address-country' );
|
||||
|
||||
if ( ! $countrySelect.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
app.handleCountryChange( $countrySelect );
|
||||
|
||||
$countrySelect
|
||||
.off( 'change' )
|
||||
.on( 'change', function() {
|
||||
app.handleCountryChange( this );
|
||||
} );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle country change.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {HTMLElement} field Country select field.
|
||||
*/
|
||||
handleCountryChange( field ) {
|
||||
const $this = $( field ),
|
||||
$stateInput = $this.closest( '.wpforms-field' ).find( '.wpforms-field-address-state' ),
|
||||
$rowWithState = $stateInput.closest( '.wpforms-field-row' );
|
||||
|
||||
if ( ! $rowWithState.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const value = $this.val();
|
||||
|
||||
app.handleStateInput( $stateInput, $rowWithState, value );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handle state input.
|
||||
*
|
||||
* @since 1.9.5
|
||||
*
|
||||
* @param {jQuery} $stateInput State input.
|
||||
* @param {jQuery} $rowWithState Row with state.
|
||||
* @param {string} countryValue Country value.
|
||||
*/
|
||||
handleStateInput( $stateInput, $rowWithState, countryValue ) {
|
||||
if ( app.noStateCountries.includes( countryValue ) ) {
|
||||
$stateInput
|
||||
.val( '' )
|
||||
.prop( 'disabled', true )
|
||||
.prop( 'required', false )
|
||||
.on( 'change', function() {
|
||||
$( this ).val( '' );
|
||||
} );
|
||||
|
||||
$rowWithState.addClass( 'wpforms-without-state' );
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
$stateInput
|
||||
.prop( 'disabled', false )
|
||||
.prop( 'required', $rowWithState.find( '.wpforms-first input' ).prop( 'required' ) ) // Set required same as first input.
|
||||
.off( 'change' );
|
||||
|
||||
$rowWithState.removeClass( 'wpforms-without-state' );
|
||||
},
|
||||
};
|
||||
|
||||
app.init();
|
||||
|
||||
return app;
|
||||
}( window, jQuery ) );
|
||||
+1
@@ -0,0 +1 @@
|
||||
((e,s)=>{let o={noStateCountries:[],init(){s(e).on("load",o.onLoad),s(document).on("wpformsRepeaterFieldCloneCreated",o.setChangeHandlers)},onLoad(){o.noStateCountries=wpforms_settings?.address_field?.list_countries_without_states||[],o.noStateCountries.length&&o.setChangeHandlers()},setChangeHandlers(){s(".wpforms-field-address").each(function(){var e=s(this).find("select.wpforms-field-address-country");e.length&&(o.handleCountryChange(e),e.off("change").on("change",function(){o.handleCountryChange(this)}))})},handleCountryChange(e){var e=s(e),t=e.closest(".wpforms-field").find(".wpforms-field-address-state"),n=t.closest(".wpforms-field-row");n.length&&(e=e.val(),o.handleStateInput(t,n,e))},handleStateInput(e,t,n){o.noStateCountries.includes(n)?(e.val("").prop("disabled",!0).prop("required",!1).on("change",function(){s(this).val("")}),t.addClass("wpforms-without-state")):(e.prop("disabled",!1).prop("required",t.find(".wpforms-first input").prop("required")).off("change"),t.removeClass("wpforms-without-state"))}};o.init(),o})(window,jQuery);
|
||||
+287
File diff suppressed because one or more lines are too long
+1
@@ -0,0 +1 @@
|
||||
!function i(r,a,o){function l(n,t){if(!a[n]){if(!r[n]){var e="function"==typeof require&&require;if(!t&&e)return e(n,!0);if(s)return s(n,!0);throw new Error("Cannot find module '"+n+"'")}t=a[n]={exports:{}};r[n][0].call(t.exports,function(t){var e=r[n][1][t];return l(e||t)},t,t.exports,i,r,a,o)}return a[n].exports}for(var s="function"==typeof require&&require,t=0;t<o.length;t++)l(o[t]);return l}({1:[function(t,e,n){function r(t){return(r="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t})(t)}function l(t,e,n){return t.replace("{count}",e).replace("{limit}",n).replace("{remaining}",n-e)}function s(t,e,n){var i=document.createElement("div");return t="object"===r(t)?"":t,e="object"===r(e)?"":e,i.classList.add("wpforms-field-limit-text"),i.id="wpforms-field-limit-text-"+t+"-"+e,i.setAttribute("aria-live","polite"),i.textContent=n,i}function u(e){return"string"==typeof e&&e.length?([/([A-Z]+),([A-Z]+)/gi,/([0-9]+),([A-Z]+)/gi,/([A-Z]+),([0-9]+)/gi].forEach(function(t){e=e.replace(t,"$1, $2")}),e.split(/\s+/).length):0}function c(t){return window.clipboardData&&window.clipboardData.getData?window.clipboardData.getData("Text"):t.clipboardData&&t.clipboardData.getData?t.clipboardData.getData("text/plain"):""}function d(t,e){var n="",i=/\s+/g,r=t.trim().match(i)||[],a=t.split(i);a.splice(e,a.length);for(var o=0;o<a.length;o++)n+=a[o]+(r[o]||"");return n.trim()}function i(t){return[].slice.call(t)}function f(t){(t=t.parentNode.querySelector(".wpforms-field-limit-text"))&&t.remove()}function a(){(window.WPFormsTextLimit=o).initHint("body")}var o;o={initHint:function(t){i(document.querySelectorAll(t+" .wpforms-limit-characters-enabled")).map(function(t){function e(t){n.textContent=l(window.wpforms_settings.val_limit_characters,this.value.length,i)}var n,i,r,a=parseInt(t.dataset.textLimit,10)||0,o=(t.value=t.value.slice(0,a),s(t.dataset.formId,t.dataset.fieldId,l(wpforms_settings.val_limit_characters,t.value.length,a)));n=o,i=a;f(t),t.parentNode.appendChild(o),t.addEventListener("keydown",e),t.addEventListener("keyup",e),t.addEventListener("paste",(r=a,function(t){t.preventDefault();var t=c(t),e=this.selectionStart+t.length,t=this.value.substring(0,this.selectionStart)+t+this.value.substring(this.selectionStart);this.value=t.substring(0,r),this.setSelectionRange(e,e)}))}),i(document.querySelectorAll(t+" .wpforms-limit-words-enabled")).map(function(t){function e(t){var e=u(this.value.trim());n.textContent=l(window.wpforms_settings.val_limit_words,e,i),-1<[13,32,188].indexOf(t.keyCode)&&i<=e&&t.preventDefault()}var n,i,r,a=parseInt(t.dataset.textLimit,10)||0,o=(t.value=d(t.value,a),s(t.dataset.formId,t.dataset.fieldId,l(wpforms_settings.val_limit_words,u(t.value.trim()),a)));n=o,i=a;f(t),t.parentNode.appendChild(o),t.addEventListener("keydown",e),t.addEventListener("keyup",e),t.addEventListener("paste",(r=a,function(t){t.preventDefault();var t=c(t),e=this.selectionStart+t.length,t=this.value.substring(0,this.selectionStart)+t+this.value.substring(this.selectionStart);this.value=d(t,r),this.setSelectionRange(e,e)}))})}},"loading"===document.readyState?document.addEventListener("DOMContentLoaded",a):a()},{}]},{},[1]);
|
||||
@@ -0,0 +1,338 @@
|
||||
/* global wpforms_settings */
|
||||
|
||||
( function() {
|
||||
/**
|
||||
* Predefine hint text to display.
|
||||
*
|
||||
* @since 1.5.6
|
||||
* @since 1.6.4 Added a new macros - {remaining}.
|
||||
*
|
||||
* @param {string} hintText Hint text.
|
||||
* @param {number} count Current count.
|
||||
* @param {number} limit Limit to.
|
||||
*
|
||||
* @return {string} Predefined hint text.
|
||||
*/
|
||||
function renderHint( hintText, count, limit ) {
|
||||
return hintText.replace( '{count}', count ).replace( '{limit}', limit ).replace( '{remaining}', limit - count );
|
||||
}
|
||||
|
||||
/**
|
||||
* Create HTMLElement hint element with text.
|
||||
*
|
||||
* @since 1.5.6
|
||||
*
|
||||
* @param {number|string} formId Form id.
|
||||
* @param {number|string} fieldId Form field id.
|
||||
* @param {string} text Hint text.
|
||||
*
|
||||
* @return {Object} HTMLElement hint element with text.
|
||||
*/
|
||||
function createHint( formId, fieldId, text ) {
|
||||
const hint = document.createElement( 'div' );
|
||||
|
||||
formId = typeof formId === 'object' ? '' : formId;
|
||||
fieldId = typeof fieldId === 'object' ? '' : fieldId;
|
||||
|
||||
hint.classList.add( 'wpforms-field-limit-text' );
|
||||
hint.id = 'wpforms-field-limit-text-' + formId + '-' + fieldId;
|
||||
hint.setAttribute( 'aria-live', 'polite' );
|
||||
hint.textContent = text;
|
||||
|
||||
return hint;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyup/Keydown event higher order function for characters limit.
|
||||
*
|
||||
* @since 1.5.6
|
||||
*
|
||||
* @param {Object} hint HTMLElement hint element.
|
||||
* @param {number} limit Max allowed number of characters.
|
||||
*
|
||||
* @return {Function} Handler function.
|
||||
*/
|
||||
function checkCharacters( hint, limit ) {
|
||||
// noinspection JSUnusedLocalSymbols
|
||||
return function( e ) { // eslint-disable-line no-unused-vars
|
||||
hint.textContent = renderHint(
|
||||
window.wpforms_settings.val_limit_characters,
|
||||
this.value.length,
|
||||
limit
|
||||
);
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Count words in the string.
|
||||
*
|
||||
* @since 1.6.2
|
||||
*
|
||||
* @param {string} string String value.
|
||||
*
|
||||
* @return {number} Words count.
|
||||
*/
|
||||
function countWords( string ) {
|
||||
if ( typeof string !== 'string' ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if ( ! string.length ) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
[
|
||||
/([A-Z]+),([A-Z]+)/gi,
|
||||
/([0-9]+),([A-Z]+)/gi,
|
||||
/([A-Z]+),([0-9]+)/gi,
|
||||
].forEach( function( pattern ) {
|
||||
string = string.replace( pattern, '$1, $2' );
|
||||
} );
|
||||
|
||||
return string.split( /\s+/ ).length;
|
||||
}
|
||||
|
||||
/**
|
||||
* Keyup/Keydown event higher order function for words limit.
|
||||
*
|
||||
* @since 1.5.6
|
||||
*
|
||||
* @param {Object} hint HTMLElement hint element.
|
||||
* @param {number} limit Max allowed number of characters.
|
||||
*
|
||||
* @return {Function} Handler function.
|
||||
*/
|
||||
function checkWords( hint, limit ) {
|
||||
return function( e ) {
|
||||
const value = this.value.trim(),
|
||||
words = countWords( value );
|
||||
|
||||
hint.textContent = renderHint(
|
||||
window.wpforms_settings.val_limit_words,
|
||||
words,
|
||||
limit
|
||||
);
|
||||
|
||||
// We should prevent the keys: Enter, Space, Comma.
|
||||
if ( [ 13, 32, 188 ].indexOf( e.keyCode ) > -1 && words >= limit ) {
|
||||
e.preventDefault();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Get passed text from the clipboard.
|
||||
*
|
||||
* @since 1.5.6
|
||||
*
|
||||
* @param {ClipboardEvent} e Clipboard event.
|
||||
*
|
||||
* @return {string} Text from clipboard.
|
||||
*/
|
||||
function getPastedText( e ) {
|
||||
if ( window.clipboardData && window.clipboardData.getData ) { // IE
|
||||
return window.clipboardData.getData( 'Text' );
|
||||
} else if ( e.clipboardData && e.clipboardData.getData ) {
|
||||
return e.clipboardData.getData( 'text/plain' );
|
||||
}
|
||||
|
||||
return '';
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste event higher order function for character limit.
|
||||
*
|
||||
* @since 1.6.7.1
|
||||
*
|
||||
* @param {number} limit Max allowed number of characters.
|
||||
*
|
||||
* @return {Function} Event handler.
|
||||
*/
|
||||
function pasteText( limit ) {
|
||||
return function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const pastedText = getPastedText( e ),
|
||||
newPosition = this.selectionStart + pastedText.length,
|
||||
newText = this.value.substring( 0, this.selectionStart ) + pastedText + this.value.substring( this.selectionStart );
|
||||
|
||||
this.value = newText.substring( 0, limit );
|
||||
this.setSelectionRange( newPosition, newPosition );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Limit string length to a certain number of words, preserving line breaks.
|
||||
*
|
||||
* @since 1.6.8
|
||||
*
|
||||
* @param {string} text Text.
|
||||
* @param {number} limit Max allowed number of words.
|
||||
*
|
||||
* @return {string} Text with the limited number of words.
|
||||
*/
|
||||
function limitWords( text, limit ) {
|
||||
let result = '';
|
||||
|
||||
// Regular expression pattern: match any space character.
|
||||
const regEx = /\s+/g;
|
||||
|
||||
// Store separators for further join.
|
||||
const separators = text.trim().match( regEx ) || [];
|
||||
|
||||
// Split the new text by regular expression.
|
||||
const newTextArray = text.split( regEx );
|
||||
|
||||
// Limit the number of words.
|
||||
newTextArray.splice( limit, newTextArray.length );
|
||||
|
||||
// Join the words together using stored separators.
|
||||
for ( let i = 0; i < newTextArray.length; i++ ) {
|
||||
result += newTextArray[ i ] + ( separators[ i ] || '' );
|
||||
}
|
||||
|
||||
return result.trim();
|
||||
}
|
||||
|
||||
/**
|
||||
* Paste event higher order function for words limit.
|
||||
*
|
||||
* @since 1.5.6
|
||||
*
|
||||
* @param {number} limit Max allowed number of words.
|
||||
*
|
||||
* @return {Function} Event handler.
|
||||
*/
|
||||
function pasteWords( limit ) {
|
||||
return function( e ) {
|
||||
e.preventDefault();
|
||||
|
||||
const pastedText = getPastedText( e ),
|
||||
newPosition = this.selectionStart + pastedText.length,
|
||||
newText = this.value.substring( 0, this.selectionStart ) + pastedText + this.value.substring( this.selectionStart );
|
||||
|
||||
this.value = limitWords( newText, limit );
|
||||
this.setSelectionRange( newPosition, newPosition );
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Array.from polyfill.
|
||||
*
|
||||
* @since 1.5.6
|
||||
*
|
||||
* @param {Object} el Iterator.
|
||||
*
|
||||
* @return {Object} Array.
|
||||
*/
|
||||
function arrFrom( el ) {
|
||||
return [].slice.call( el );
|
||||
}
|
||||
|
||||
/**
|
||||
* Remove existing hint.
|
||||
*
|
||||
* @since 1.9.5.1
|
||||
*
|
||||
* @param {Object} element Element.
|
||||
*/
|
||||
const removeExistingHint = ( element ) => {
|
||||
const existingHint = element.parentNode.querySelector( '.wpforms-field-limit-text' );
|
||||
if ( existingHint ) {
|
||||
existingHint.remove();
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Public functions and properties.
|
||||
*
|
||||
* @since 1.8.9
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const app = {
|
||||
/**
|
||||
* Init text limit hint.
|
||||
*
|
||||
* @since 1.8.9
|
||||
*
|
||||
* @param {string} context Context selector.
|
||||
*/
|
||||
initHint( context ) {
|
||||
arrFrom( document.querySelectorAll( context + ' .wpforms-limit-characters-enabled' ) )
|
||||
.map(
|
||||
function( e ) { // eslint-disable-line array-callback-return
|
||||
const limit = parseInt( e.dataset.textLimit, 10 ) || 0;
|
||||
|
||||
e.value = e.value.slice( 0, limit );
|
||||
|
||||
const hint = createHint(
|
||||
e.dataset.formId,
|
||||
e.dataset.fieldId,
|
||||
renderHint(
|
||||
wpforms_settings.val_limit_characters,
|
||||
e.value.length,
|
||||
limit
|
||||
)
|
||||
);
|
||||
|
||||
const fn = checkCharacters( hint, limit );
|
||||
|
||||
removeExistingHint( e );
|
||||
|
||||
e.parentNode.appendChild( hint );
|
||||
e.addEventListener( 'keydown', fn );
|
||||
e.addEventListener( 'keyup', fn );
|
||||
e.addEventListener( 'paste', pasteText( limit ) );
|
||||
}
|
||||
);
|
||||
|
||||
arrFrom( document.querySelectorAll( context + ' .wpforms-limit-words-enabled' ) )
|
||||
.map(
|
||||
function( e ) { // eslint-disable-line array-callback-return
|
||||
const limit = parseInt( e.dataset.textLimit, 10 ) || 0;
|
||||
|
||||
e.value = limitWords( e.value, limit );
|
||||
|
||||
const hint = createHint(
|
||||
e.dataset.formId,
|
||||
e.dataset.fieldId,
|
||||
renderHint(
|
||||
wpforms_settings.val_limit_words,
|
||||
countWords( e.value.trim() ),
|
||||
limit
|
||||
)
|
||||
);
|
||||
|
||||
const fn = checkWords( hint, limit );
|
||||
|
||||
removeExistingHint( e );
|
||||
|
||||
e.parentNode.appendChild( hint );
|
||||
|
||||
e.addEventListener( 'keydown', fn );
|
||||
e.addEventListener( 'keyup', fn );
|
||||
e.addEventListener( 'paste', pasteWords( limit ) );
|
||||
}
|
||||
);
|
||||
},
|
||||
};
|
||||
|
||||
/**
|
||||
* DOMContentLoaded handler.
|
||||
*
|
||||
* @since 1.5.6
|
||||
*/
|
||||
function ready() {
|
||||
// Expose to the world.
|
||||
window.WPFormsTextLimit = app;
|
||||
|
||||
app.initHint( 'body' );
|
||||
}
|
||||
|
||||
if ( document.readyState === 'loading' ) {
|
||||
document.addEventListener( 'DOMContentLoaded', ready );
|
||||
} else {
|
||||
ready();
|
||||
}
|
||||
}() );
|
||||
@@ -0,0 +1,21 @@
|
||||
// Clear URL - remove wpforms_form_id
|
||||
( function() {
|
||||
var loc = window.location,
|
||||
query = loc.search;
|
||||
|
||||
if ( query.indexOf( 'wpforms_form_id=' ) !== -1 ) {
|
||||
query = query.replace( /([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/, '' );
|
||||
history.replaceState( {}, null, loc.origin + loc.pathname + query );
|
||||
}
|
||||
}() );
|
||||
|
||||
( function( $ ) {
|
||||
$( function() {
|
||||
if ( $( 'div.wpforms-confirmation-scroll' ).length ) {
|
||||
$( 'html,body' ).animate(
|
||||
{ scrollTop: ( $( 'div.wpforms-confirmation-scroll' ).offset().top ) - 100 },
|
||||
1000
|
||||
);
|
||||
}
|
||||
} );
|
||||
}( jQuery ) );
|
||||
+1
@@ -0,0 +1 @@
|
||||
(()=>{var o=window.location,r=o.search;-1!==r.indexOf("wpforms_form_id=")&&(r=r.replace(/([&?]wpforms_form_id=[0-9]*$|wpforms_form_id=[0-9]*&|[?&]wpforms_form_id=[0-9]*(?=#))/,""),history.replaceState({},null,o.origin+o.pathname+r))})(),(o=>{o(function(){o("div.wpforms-confirmation-scroll").length&&o("html,body").animate({scrollTop:o("div.wpforms-confirmation-scroll").offset().top-100},1e3)})})(jQuery);
|
||||
@@ -0,0 +1,544 @@
|
||||
/* global wpforms_settings, WPFormsUtils */
|
||||
|
||||
/**
|
||||
* @param wpforms_settings.css_vars
|
||||
* @param wpforms_settings.formErrorMessagePrefix
|
||||
* @param wpforms_settings.indicatorStepsPattern
|
||||
* @param wpforms_settings.submitBtnDisabled
|
||||
*/
|
||||
|
||||
// noinspection ES6ConvertVarToLetConst
|
||||
/**
|
||||
* Modern Frontend.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
// eslint-disable-next-line no-var
|
||||
var WPForms = window.WPForms || {};
|
||||
|
||||
WPForms.FrontendModern = WPForms.FrontendModern || ( function( document, window, $ ) {
|
||||
// noinspection JSUnusedLocalSymbols,JSUnusedGlobalSymbols
|
||||
const app = {
|
||||
|
||||
/**
|
||||
* Start the engine.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
init() {
|
||||
// Document ready.
|
||||
$( app.ready );
|
||||
app.bindOptinMonster();
|
||||
},
|
||||
|
||||
/**
|
||||
* Document ready.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
ready() {
|
||||
app.updateGBBlockAccentColors();
|
||||
app.initPageBreakButtons();
|
||||
app.initButtonStyle();
|
||||
app.events();
|
||||
},
|
||||
|
||||
/**
|
||||
* Events.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
events() {
|
||||
$( document )
|
||||
.on( 'wpforms_elementor_form_fields_initialized', app.initPageBreakButtons );
|
||||
|
||||
$( 'form.wpforms-form' )
|
||||
.on( 'wpformsCombinedUploadsSizeError', app.combinedUploadsSizeError )
|
||||
.on( 'wpformsFormSubmitButtonDisable', app.formSubmitButtonDisable )
|
||||
.on( 'wpformsFormSubmitButtonRestore', app.formSubmitButtonRestore )
|
||||
.on( 'wpformsPageChange', app.pageChange );
|
||||
|
||||
$( 'form.wpforms-form .wpforms-submit' )
|
||||
.on( 'keydown click', app.disabledButtonPress );
|
||||
|
||||
// Add styling to timepicker dropdown.
|
||||
$( document )
|
||||
.on( 'focus', '.wpforms-render-modern .wpforms-timepicker', app.updateTimepickerDropdown );
|
||||
|
||||
// Reset timepicker dropdown styles.
|
||||
$( document )
|
||||
.on( 'focusout', '.wpforms-render-modern .wpforms-timepicker', app.resetTimepickerDropdown );
|
||||
},
|
||||
|
||||
/**
|
||||
* OptinMonster compatibility.
|
||||
*
|
||||
* Re-initialize after OptinMonster loads to accommodate changes that
|
||||
* have occurred to the DOM.
|
||||
*
|
||||
* @since 1.9.0
|
||||
*/
|
||||
bindOptinMonster() {
|
||||
// OM v5.
|
||||
document.addEventListener( 'om.Campaign.load', function() {
|
||||
app.ready();
|
||||
} );
|
||||
|
||||
// OM Legacy.
|
||||
$( document ).on( 'OptinMonsterOnShow', function() {
|
||||
app.ready();
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Add styling to timepicker dropdown.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
updateTimepickerDropdown() {
|
||||
const cssVars = app.getCssVars( $( this ) );
|
||||
|
||||
setTimeout(
|
||||
function() {
|
||||
const $list = $( '.ui-timepicker-wrapper .ui-timepicker-list' );
|
||||
|
||||
$list.css( 'background', cssVars[ 'field-menu-color' ] );
|
||||
$list.find( 'li' ).css( 'color', cssVars[ 'field-text-color' ] );
|
||||
$list.find( '.ui-timepicker-selected' )
|
||||
.css( 'background', cssVars[ 'button-background-color' ] )
|
||||
.css( 'color', cssVars[ 'button-text-color' ] );
|
||||
},
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Reset timepicker dropdown styles.
|
||||
*
|
||||
* @since 1.8.9.5
|
||||
*/
|
||||
resetTimepickerDropdown() {
|
||||
setTimeout(
|
||||
function() {
|
||||
const $list = $( '.ui-timepicker-wrapper .ui-timepicker-list' );
|
||||
|
||||
$list.find( ':not(.ui-timepicker-selected)' ).attr( 'style', '' );
|
||||
},
|
||||
0
|
||||
);
|
||||
},
|
||||
|
||||
/**
|
||||
* Update accent colors of some fields in GB block in Modern Markup mode.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*/
|
||||
initButtonStyle() {
|
||||
// Loop through all the GB blocks on the page.
|
||||
$( '.wpforms-block.wpforms-container-full, .elementor-widget-wpforms .wpforms-container-full' ).each( function() {
|
||||
const $form = $( this );
|
||||
const contStyle = getComputedStyle( $form.get( 0 ) );
|
||||
const btnBgColor = app.getCssVar( contStyle, '--wpforms-button-background-color-alt' );
|
||||
|
||||
if ( app.isTransparentColor( btnBgColor ) ) {
|
||||
$form.find( 'button.wpforms-submit' ).addClass( 'wpforms-opacity-hover' );
|
||||
}
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks if the provided color has transparency.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param {string} color The color to check.
|
||||
*
|
||||
* @return {boolean} Returns true if the color is transparent.
|
||||
*/
|
||||
isTransparentColor( color ) {
|
||||
const rgba = app.getColorAsRGBArray( color );
|
||||
|
||||
// The max opacity value of the color that is considered as transparent.
|
||||
const opacityThreshold = 0.33;
|
||||
const opacity = Number( rgba?.[ 3 ] );
|
||||
|
||||
// Compare the opacity value with the threshold.
|
||||
return opacity <= opacityThreshold;
|
||||
},
|
||||
|
||||
/**
|
||||
* Update accent colors of some fields in GB block in Modern Markup mode.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
updateGBBlockAccentColors() {
|
||||
// Loop through all the GB blocks on the page.
|
||||
$( '.wpforms-block.wpforms-container-full, .elementor-widget-wpforms .wpforms-container-full' ).each( function() {
|
||||
const $form = $( this );
|
||||
|
||||
app.updateGBBlockPageIndicatorColor( $form );
|
||||
app.updateGBBlockIconChoicesColor( $form );
|
||||
app.updateGBBlockRatingColor( $form );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Update accent color of Page Indicator.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {jQuery} $form Form container.
|
||||
*/
|
||||
updateGBBlockPageIndicatorColor( $form ) {
|
||||
const $indicator = $form.find( '.wpforms-page-indicator' ),
|
||||
$indicatorPage = $indicator.find( '.wpforms-page-indicator-page-progress, .wpforms-page-indicator-page.active .wpforms-page-indicator-page-number' ),
|
||||
$indicatorTriangle = $indicatorPage.find( '.wpforms-page-indicator-page-triangle' );
|
||||
|
||||
$indicator.data( 'indicator-color', 'var( --wpforms-page-break-color )' );
|
||||
$indicatorPage.css( 'background-color', 'var( --wpforms-page-break-color )' );
|
||||
$indicatorTriangle.css( 'border-top-color', 'var( --wpforms-page-break-color )' );
|
||||
},
|
||||
/**
|
||||
* Update accent color of Icon Choices.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {jQuery} $form Form container.
|
||||
*/
|
||||
updateGBBlockIconChoicesColor( $form ) {
|
||||
$form
|
||||
.find( '.wpforms-icon-choices' )
|
||||
.css( '--wpforms-icon-choices-color', 'var( --wpforms-button-background-color )' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Update accent color of Rating field.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {jQuery} $form Form container.
|
||||
*/
|
||||
updateGBBlockRatingColor( $form ) {
|
||||
$form
|
||||
.find( '.wpforms-field-rating-item svg' )
|
||||
.css( 'color', 'var( --wpforms-page-break-color, var( --wpforms-button-background-color ) )' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Init Page Break fields.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*/
|
||||
initPageBreakButtons() {
|
||||
$( '.wpforms-page-button' )
|
||||
.removeClass( 'wpforms-disabled' )
|
||||
.attr( 'aria-disabled', 'false' )
|
||||
.attr( 'aria-describedby', '' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for `wpformsCombinedUploadsSizeError` event.
|
||||
* Accessibility enhancements to error container and submit button.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
* @param {jQuery} $form Form object.
|
||||
* @param {jQuery} $errorCnt Error container object.
|
||||
*/
|
||||
combinedUploadsSizeError( e, $form, $errorCnt ) {
|
||||
const formId = $form.data( 'formid' ),
|
||||
errormessage = $form.attr( 'aria-errormessage' ) || '',
|
||||
errorCntId = `wpforms-${ formId }-footer-error`,
|
||||
$submitBtn = $form.find( '.wpforms-submit' );
|
||||
|
||||
$form.attr( {
|
||||
'aria-invalid': 'true',
|
||||
'aria-errormessage': `${ errormessage } ${ errorCntId }`,
|
||||
} );
|
||||
|
||||
$errorCnt.attr( {
|
||||
role: 'alert',
|
||||
id: errorCntId,
|
||||
} );
|
||||
|
||||
// Add error message prefix.
|
||||
$errorCnt.find( '> .wpforms-hidden:first-child' ).remove();
|
||||
$errorCnt.prepend( `<span class="wpforms-hidden">${ wpforms_settings.formErrorMessagePrefix }</span>` );
|
||||
|
||||
// Instead of set the `disabled` property,
|
||||
// we must use `aria-disabled` and `aria-describedby` attributes in conduction with `wpforms-disabled` class.
|
||||
$submitBtn.attr( 'aria-describedby', errorCntId );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for `wpformsCombinedUploadsSizeOk` event.
|
||||
*
|
||||
* @since 1.8.1
|
||||
* @deprecated 1.8.3
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
* @param {jQuery} $form Form object.
|
||||
* @param {jQuery} $errorCnt Error container object.
|
||||
*/
|
||||
// eslint-disable-next-line no-unused-vars
|
||||
combinedUploadsSizeOk( e, $form, $errorCnt ) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.warn( 'WARNING! Function "WPForms.FrontendModern( e, $form, $errorCnt )" has been deprecated, please use the new "formSubmitButtonDisable: function( e, $form, $submitBtn )" function instead!' );
|
||||
|
||||
const $submitBtn = $form.find( '.wpforms-submit' );
|
||||
|
||||
// Revert aria-* attributes to the normal state.
|
||||
$submitBtn
|
||||
.removeClass( 'wpforms-disabled' )
|
||||
.attr( 'aria-disabled', 'false' )
|
||||
.attr( 'aria-describedby', '' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for `wpformsFormSubmitButtonDisable` event.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
* @param {jQuery} $form Form object.
|
||||
* @param {jQuery} $submitBtn Submit a button object.
|
||||
*/
|
||||
formSubmitButtonDisable( e, $form, $submitBtn ) {
|
||||
const disabledBtnDescId = $form.attr( 'id' ) + '-submit-btn-disabled';
|
||||
|
||||
$submitBtn.before( `<div class="wpforms-hidden" id="${ disabledBtnDescId }">${ wpforms_settings.submitBtnDisabled }</div>` );
|
||||
|
||||
$submitBtn
|
||||
.prop( 'disabled', false )
|
||||
.addClass( 'wpforms-disabled' )
|
||||
.attr( 'aria-disabled', 'true' )
|
||||
.attr( 'aria-describedby', disabledBtnDescId );
|
||||
},
|
||||
|
||||
/**
|
||||
* Handler for `wpformsFormSubmitButtonRestore` event.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
* @param {jQuery} $form Form object.
|
||||
* @param {jQuery} $submitBtn Submit a button object.
|
||||
*/
|
||||
formSubmitButtonRestore( e, $form, $submitBtn ) {
|
||||
const disabledBtnDescId = $form.attr( 'id' ) + '-submit-btn-disabled';
|
||||
|
||||
$form.find( '#' + disabledBtnDescId ).remove();
|
||||
|
||||
$submitBtn
|
||||
.removeClass( 'wpforms-disabled' )
|
||||
.attr( 'aria-disabled', 'false' )
|
||||
.attr( 'aria-describedby', '' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Disabled button click/keydown event handler.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
*/
|
||||
disabledButtonPress( e ) {
|
||||
const $submitBtn = $( this );
|
||||
|
||||
if ( ! $submitBtn.hasClass( 'wpforms-disabled' ) ) {
|
||||
return;
|
||||
}
|
||||
|
||||
if ( e.key === 'Enter' || e.type === 'click' ) {
|
||||
e.preventDefault();
|
||||
e.stopImmediatePropagation();
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Page change event handler.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {Object} e Event object.
|
||||
* @param {number} nextPage The next page number.
|
||||
* @param {jQuery} $form Current form.
|
||||
*/
|
||||
pageChange( e, nextPage, $form ) {
|
||||
const $pageIndicator = $form.find( '.wpforms-page-indicator' );
|
||||
|
||||
if ( ! wpforms_settings.indicatorStepsPattern || ! $pageIndicator.length ) {
|
||||
return;
|
||||
}
|
||||
|
||||
const totalPages = $form.find( '.wpforms-page' ).length;
|
||||
let msg = wpforms_settings.indicatorStepsPattern;
|
||||
let pageTitle;
|
||||
|
||||
msg = msg.replace( '{current}', nextPage ).replace( '{total}', totalPages );
|
||||
|
||||
if ( $pageIndicator.hasClass( 'progress' ) ) {
|
||||
pageTitle = $pageIndicator.find( '.wpforms-page-indicator-page-title' ).data( `page-${ nextPage }-title` );
|
||||
} else {
|
||||
pageTitle = $pageIndicator.find( `.wpforms-page-indicator-page-${ nextPage } .wpforms-page-indicator-page-title` ).text();
|
||||
}
|
||||
|
||||
msg = pageTitle ? pageTitle + '. ' + msg : msg;
|
||||
|
||||
$pageIndicator.attr( 'aria-valuenow', nextPage );
|
||||
app.screenReaderAnnounce( msg, 'polite' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Allows the screen reader to talk directly through the use of JS.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {string} text The message to be vocalised
|
||||
* @param {string} priority Aria-live priority. "polite" (by default) or "assertive".
|
||||
*/
|
||||
screenReaderAnnounce( text, priority ) {
|
||||
const el = document.createElement( 'div' );
|
||||
const id = 'wpforms-screen-reader-announce-' + Date.now();
|
||||
|
||||
el.setAttribute( 'id', id );
|
||||
el.setAttribute( 'aria-live', priority || 'polite' );
|
||||
el.classList.add( 'wpforms-screen-reader-announce' );
|
||||
|
||||
const node = document.body.appendChild( el );
|
||||
|
||||
setTimeout( function() {
|
||||
node.innerHTML = text;
|
||||
}, 100 );
|
||||
|
||||
setTimeout( function() {
|
||||
document.body.removeChild( node );
|
||||
}, 1000 );
|
||||
},
|
||||
|
||||
/**
|
||||
* Add opacity to color string.
|
||||
* Supports formats: RGB, RGBA, HEX, HEXA.
|
||||
*
|
||||
* If the given color has an alpha channel, the new alpha channel will be calculated according to the given opacity.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {string} color Color.
|
||||
* @param {string} opacity Opacity.
|
||||
*
|
||||
* @return {string} Color in RGBA format with an added alpha channel according to given opacity.
|
||||
*/
|
||||
getColorWithOpacity( color, opacity ) {
|
||||
// Moved to ../share/utils.js
|
||||
return WPFormsUtils.cssColorsUtils.getColorWithOpacity( color, opacity );
|
||||
},
|
||||
|
||||
/**
|
||||
* Remove opacity from the color value.
|
||||
* Supports formats: RGB, RGBA, HEX, HEXA.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {string} color Color.
|
||||
*
|
||||
* @return {string} Color in RGB format.
|
||||
*/
|
||||
getSolidColor( color ) {
|
||||
color = color.trim();
|
||||
|
||||
const rgbArray = app.getColorAsRGBArray( color );
|
||||
|
||||
if ( ! rgbArray ) {
|
||||
return color;
|
||||
}
|
||||
|
||||
// Combine and return the RGB color.
|
||||
return `rgb(${ rgbArray[ 0 ] },${ rgbArray[ 1 ] },${ rgbArray[ 2 ] })`;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the given color is a valid CSS color.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {string} color Color.
|
||||
*
|
||||
* @return {boolean} True if the given color is a valid CSS color.
|
||||
*/
|
||||
isValidColor( color ) {
|
||||
// Moved to ../share/utils.js
|
||||
return WPFormsUtils.cssColorsUtils.isValidColor( color );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get color as an array of RGB(A) values.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {string} color Color.
|
||||
*
|
||||
* @return {Array|boolean} Color as an array of RGBA values. False on error.
|
||||
*/
|
||||
getColorAsRGBArray( color ) {
|
||||
// Moved to ../share/utils.js
|
||||
return WPFormsUtils.cssColorsUtils.getColorAsRGBArray( color );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get CSS variable value.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {Object} style Computed style object.
|
||||
* @param {string} varName Style custom property name.
|
||||
*
|
||||
* @return {string|null} CSS variable value;
|
||||
*/
|
||||
getCssVar( style, varName ) {
|
||||
if ( ! style || typeof style.getPropertyValue !== 'function' ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
let value = style.getPropertyValue( varName ).trim();
|
||||
|
||||
if ( varName.includes( 'color' ) ) {
|
||||
value = value.replace( /\s/g, '' );
|
||||
}
|
||||
|
||||
return value;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get all CSS variables.
|
||||
*
|
||||
* @since 1.8.1
|
||||
*
|
||||
* @param {jQuery} $form Form OR any element inside the form.
|
||||
*
|
||||
* @return {Object} CSS variables;
|
||||
*/
|
||||
getCssVars( $form ) {
|
||||
if ( ! $form || ! $form.length ) {
|
||||
return null;
|
||||
}
|
||||
|
||||
const $cont = $form.hasClass( 'wpforms-container' ) ? $form : $form.closest( '.wpforms-container' );
|
||||
const contStyle = getComputedStyle( $cont.get( 0 ) );
|
||||
const cssVars = wpforms_settings.css_vars;
|
||||
const vars = {};
|
||||
|
||||
for ( let i = 0; i < cssVars.length; i++ ) {
|
||||
vars[ cssVars[ i ] ] = app.getCssVar( contStyle, '--wpforms-' + cssVars[ i ] );
|
||||
}
|
||||
|
||||
return vars;
|
||||
},
|
||||
};
|
||||
|
||||
return app;
|
||||
}( document, window, jQuery ) );
|
||||
|
||||
// Initialize.
|
||||
WPForms.FrontendModern.init();
|
||||
+1
File diff suppressed because one or more lines are too long
+4313
File diff suppressed because it is too large
Load Diff
+5
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user