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,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();
|
||||
}
|
||||
}() );
|
||||
Reference in New Issue
Block a user