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:
+253
@@ -0,0 +1,253 @@
|
||||
|
||||
// noinspection ES6ConvertVarToLetConst
|
||||
// eslint-disable-next-line no-var, no-unused-vars
|
||||
var WPFormsUtils = window.WPFormsUtils || ( function( document, window, $ ) {
|
||||
/**
|
||||
* Public functions and properties.
|
||||
*
|
||||
* @since 1.7.6
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const app = {
|
||||
|
||||
/**
|
||||
* Wrapper to trigger a native or custom event and return the event object.
|
||||
*
|
||||
* @since 1.7.6
|
||||
*
|
||||
* @param {jQuery} $element Element to trigger event on.
|
||||
* @param {string} eventName Event name to trigger (custom or native).
|
||||
* @param {Array} args Trigger arguments.
|
||||
*
|
||||
* @return {Event} Event object.
|
||||
*/
|
||||
triggerEvent( $element, eventName, args = [] ) {
|
||||
const eventObject = new $.Event( eventName );
|
||||
|
||||
$element.trigger( eventObject, args );
|
||||
|
||||
return eventObject;
|
||||
},
|
||||
|
||||
/**
|
||||
* Debounce.
|
||||
*
|
||||
* This function comes directly from underscore.js:
|
||||
*
|
||||
* Returns a function, that, as long as it continues to be invoked, will not
|
||||
* be triggered. The function will be called after it stops being called for
|
||||
* N milliseconds. If `immediate` is passed, trigger the function on the
|
||||
* leading edge, instead of the trailing.
|
||||
*
|
||||
* Debouncing is removing unwanted input noise from buttons, switches or other user input.
|
||||
* Debouncing prevents extra activations or slow functions from triggering too often.
|
||||
*
|
||||
* @param {Function} func The function to be debounced.
|
||||
* @param {number} wait The amount of time to delay calling func.
|
||||
* @param {boolean} immediate Whether or not to trigger the function on the leading edge.
|
||||
*
|
||||
* @return {Function} Returns a function that, as long as it continues to be invoked, will not be triggered.
|
||||
*/
|
||||
debounce( func, wait, immediate ) {
|
||||
let timeout;
|
||||
|
||||
return function() {
|
||||
const context = this,
|
||||
args = arguments;
|
||||
const later = function() {
|
||||
timeout = null;
|
||||
|
||||
if ( ! immediate ) {
|
||||
func.apply( context, args );
|
||||
}
|
||||
};
|
||||
|
||||
const callNow = immediate && ! timeout;
|
||||
|
||||
clearTimeout( timeout );
|
||||
|
||||
timeout = setTimeout( later, wait );
|
||||
|
||||
if ( callNow ) {
|
||||
func.apply( context, args );
|
||||
}
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* CSS color operations.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
cssColorsUtils: {
|
||||
/**
|
||||
* Checks if the provided color has transparency.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param {string} color The color to check.
|
||||
* @param {number} opacityThreshold The max opacity value of the color that is considered as transparent.
|
||||
*
|
||||
* @return {boolean} Returns true if the color is transparent.
|
||||
*/
|
||||
isTransparentColor( color, opacityThreshold = 0.33 ) {
|
||||
const rgba = app.cssColorsUtils.getColorAsRGBArray( color );
|
||||
const opacity = Number( rgba?.[ 3 ] );
|
||||
|
||||
// Compare the opacity value with the threshold.
|
||||
return opacity <= opacityThreshold;
|
||||
},
|
||||
|
||||
/**
|
||||
* Get color as an array of RGB(A) values.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param {string} color Color.
|
||||
*
|
||||
* @return {Array|boolean} Color as an array of RGBA values. False on error.
|
||||
*/
|
||||
getColorAsRGBArray( color ) {
|
||||
// Check if the given color is a valid CSS color.
|
||||
if ( ! app.cssColorsUtils.isValidColor( color ) ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Remove # from the beginning of the string and remove whitespaces.
|
||||
color = color.replace( /^#/, '' ).replaceAll( ' ', '' );
|
||||
color = color === 'transparent' ? 'rgba(0,0,0,0)' : color;
|
||||
|
||||
const rgba = color;
|
||||
let rgbArray;
|
||||
|
||||
// Check if color is in HEX(A) format.
|
||||
const isHex = rgba.match( /[0-9a-f]{6,8}$/ig );
|
||||
|
||||
if ( isHex ) {
|
||||
// Search and split HEX(A) color into an array of couples of chars.
|
||||
rgbArray = rgba.match( /\w\w/g ).map( ( x ) => parseInt( x, 16 ) );
|
||||
rgbArray[ 3 ] = rgbArray[ 3 ] || rgbArray[ 3 ] === 0 ? ( rgbArray[ 3 ] / 255 ).toFixed( 2 ) : 1;
|
||||
} else {
|
||||
rgbArray = rgba.split( '(' )[ 1 ].split( ')' )[ 0 ].split( ',' );
|
||||
}
|
||||
|
||||
return rgbArray;
|
||||
},
|
||||
|
||||
/**
|
||||
* Check if the given color is a valid CSS color.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param {string} color Color.
|
||||
*
|
||||
* @return {boolean} True if the given color is a valid CSS color.
|
||||
*/
|
||||
isValidColor( color ) {
|
||||
// Create a temporary DOM element and use `style` property.
|
||||
const s = new Option().style;
|
||||
|
||||
s.color = color;
|
||||
|
||||
// Invalid color leads to the empty color property of DOM element style.
|
||||
return s.color !== '';
|
||||
},
|
||||
|
||||
/**
|
||||
* Get contrast color relative to given color.
|
||||
*
|
||||
* @since 1.8.8
|
||||
*
|
||||
* @param {string} color Color.
|
||||
*
|
||||
* @return {string} True if the given color is a valid CSS color.
|
||||
*/
|
||||
getContrastColor( color ) {
|
||||
const rgba = app.cssColorsUtils.getColorAsRGBArray( color );
|
||||
const sum = rgba.reduce( ( a, b ) => a + b, 0 );
|
||||
const avg = Math.round( ( sum / 3 ) * ( rgba[ 3 ] ?? 1 ) );
|
||||
|
||||
return avg < 128 ? '#ffffff' : '#000000';
|
||||
},
|
||||
|
||||
/**
|
||||
* 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.9
|
||||
*
|
||||
* @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 ) {
|
||||
color = color.trim();
|
||||
|
||||
const rgbArray = app.cssColorsUtils.getColorAsRGBArray( color );
|
||||
|
||||
if ( ! rgbArray ) {
|
||||
return color;
|
||||
}
|
||||
|
||||
// Default opacity is 1.
|
||||
opacity = ! opacity || opacity.length === 0 ? '1' : opacity.toString();
|
||||
|
||||
const alpha = rgbArray.length === 4 ? parseFloat( rgbArray[ 3 ] ) : 1;
|
||||
|
||||
// Calculate new alpha value.
|
||||
const newAlpha = parseFloat( opacity ) * alpha;
|
||||
|
||||
// Combine and return the RGBA color.
|
||||
return `rgba(${ rgbArray[ 0 ] },${ rgbArray[ 1 ] },${ rgbArray[ 2 ] },${ newAlpha })`.replace( /\s+/g, '' );
|
||||
},
|
||||
|
||||
/**
|
||||
* Convert an RGBA color string to HEX format.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param {string} color Color in "rgba(r, g, b, a)" or "rgb(r, g, b)" format.
|
||||
*
|
||||
* @return {false|string} HEX color.
|
||||
*/
|
||||
rgbaToHex( color ) {
|
||||
if ( ! /^rgb/.test( color ) ) {
|
||||
return color;
|
||||
}
|
||||
|
||||
const rgbArray = app.cssColorsUtils.getColorAsRGBArray( color );
|
||||
|
||||
if ( ! rgbArray ) {
|
||||
return false;
|
||||
}
|
||||
|
||||
const red = Number( rgbArray[ 0 ] );
|
||||
const green = Number( rgbArray[ 1 ] );
|
||||
const blue = Number( rgbArray[ 2 ] );
|
||||
const alpha = rgbArray[ 3 ] ? Math.round( Number( rgbArray[ 3 ] ) * 255 ) : 255;
|
||||
|
||||
// Ensure numbers are converted to valid two-character hexadecimal strings.
|
||||
const colorToHex = ( value ) => value.toString( 16 ).padStart( 2, '0' );
|
||||
|
||||
// Convert to hex and return as a single string.
|
||||
const hex = `#${ [
|
||||
colorToHex( red ),
|
||||
colorToHex( green ),
|
||||
colorToHex( blue ),
|
||||
alpha < 255 ? colorToHex( alpha ) : '',
|
||||
].join( '' ) }`;
|
||||
|
||||
return hex.toLowerCase();
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
// Provide access to public functions/properties.
|
||||
return app;
|
||||
}( document, window, jQuery ) );
|
||||
+1
@@ -0,0 +1 @@
|
||||
var WPFormsUtils=window.WPFormsUtils||(o=>{let a={triggerEvent(r,t,e=[]){t=new o.Event(t);return r.trigger(t,e),t},debounce(o,s,l){let a;return function(){let r=this,t=arguments;var e=l&&!a;clearTimeout(a),a=setTimeout(function(){a=null,l||o.apply(r,t)},s),e&&o.apply(r,t)}},cssColorsUtils:{isTransparentColor(r,t=.33){r=a.cssColorsUtils.getColorAsRGBArray(r);return Number(r?.[3])<=t},getColorAsRGBArray(r){if(!a.cssColorsUtils.isValidColor(r))return!1;r="transparent"===(r=r.replace(/^#/,"").replaceAll(" ",""))?"rgba(0,0,0,0)":r;let t;return r.match(/[0-9a-f]{6,8}$/gi)?(t=r.match(/\w\w/g).map(r=>parseInt(r,16)))[3]=t[3]||0===t[3]?(t[3]/255).toFixed(2):1:t=r.split("(")[1].split(")")[0].split(","),t},isValidColor(r){var t=(new Option).style;return t.color=r,""!==t.color},getContrastColor(r){var r=a.cssColorsUtils.getColorAsRGBArray(r),t=r.reduce((r,t)=>r+t,0);return Math.round(t/3*(r[3]??1))<128?"#ffffff":"#000000"},getColorWithOpacity(r,t){r=r.trim();var e=a.cssColorsUtils.getColorAsRGBArray(r);if(!e)return r;t=t&&0!==t.length?t.toString():"1";r=4===e.length?parseFloat(e[3]):1,t=parseFloat(t)*r;return`rgba(${e[0]},${e[1]},${e[2]},${t})`.replace(/\s+/g,"")},rgbaToHex(r){var t,e,o,s,l;return/^rgb/.test(r)?!!(s=a.cssColorsUtils.getColorAsRGBArray(r))&&(t=Number(s[0]),e=Number(s[1]),o=Number(s[2]),s=s[3]?Math.round(255*Number(s[3])):255,("#"+[(l=r=>r.toString(16).padStart(2,"0"))(t),l(e),l(o),s<255?l(s):""].join("")).toLowerCase()):r}}};return a})((document,window,jQuery));
|
||||
Reference in New Issue
Block a user