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:
+307
@@ -0,0 +1,307 @@
|
||||
/* global wpforms_ai_chat_element, wpFormsAIDock */
|
||||
|
||||
// noinspection ES6ConvertVarToLetConst
|
||||
/**
|
||||
* AI modal.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
// eslint-disable-next-line no-var
|
||||
var WPFormsAIModal = window.WPFormsAIModal || ( function( document, window, $ ) {
|
||||
/**
|
||||
* Public functions and properties.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @type {Object}
|
||||
*/
|
||||
const app = {
|
||||
/**
|
||||
* Default modal options.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
defaultOptions: {
|
||||
title: false,
|
||||
content: '',
|
||||
type: 'ai',
|
||||
smoothContent: true,
|
||||
bgOpacity: 1,
|
||||
boxWidth: 650,
|
||||
contentMaxHeight: 600,
|
||||
closeIcon: true,
|
||||
buttons: false,
|
||||
},
|
||||
|
||||
/**
|
||||
* Start the engine.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
init() {
|
||||
$( app.ready );
|
||||
},
|
||||
|
||||
/**
|
||||
* Initialized once the DOM is fully loaded.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
ready() {
|
||||
app.extendJqueryConfirm();
|
||||
app.bindChoicesActions();
|
||||
},
|
||||
|
||||
/**
|
||||
* Process various events for choices modal.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
bindChoicesActions() {
|
||||
$( document )
|
||||
.on( 'click', '.wpforms-ai-choices-button', app.initChoicesModal )
|
||||
.on( 'wpformsAIChatBeforeRefreshConfirm', app.beforeChoicesRefreshConfirm )
|
||||
.on( 'wpformsAIModalBeforeWarningMessageInsert', app.refreshModalHeight )
|
||||
.on( 'wpformsAIChatAfterRefresh', app.refreshModalHeight )
|
||||
.on( 'wpformsAIChatCancelRefresh', app.cancelChoicesRefresh )
|
||||
.on( 'wpformsAIChatBeforeSendMessage', function( e ) {
|
||||
app.resizeModalHeight( e.detail.fieldId );
|
||||
} )
|
||||
.on( 'wpformsAIChatAfterAddAnswer', function( e ) {
|
||||
app.resizeModalHeight( e.detail.fieldId );
|
||||
} )
|
||||
.on( 'wpformsAIModalAfterChoicesInsert', function( e ) {
|
||||
app.hideChoicesModal( e.detail.fieldId );
|
||||
} );
|
||||
|
||||
$( window ).on( 'resize', function() {
|
||||
$( '.jconfirm-wpforms-ai-modal wpforms-ai-chat' ).each( function() {
|
||||
app.resizeModalHeight( $( this ).attr( 'field-id' ) );
|
||||
} );
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Init modal window.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Object} args Modal window arguments.
|
||||
*/
|
||||
initModal( args ) {
|
||||
// Open the modal window.
|
||||
$.confirm( { ...app.defaultOptions, ...args } );
|
||||
},
|
||||
|
||||
/**
|
||||
* Init choices modal window.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
initChoicesModal() {
|
||||
const $button = $( this );
|
||||
|
||||
if ( $button.hasClass( 'wpforms-prevent-default' ) ) {
|
||||
$button.trigger( 'blur' );
|
||||
return;
|
||||
}
|
||||
|
||||
const fieldId = $button.data( 'field-id' ),
|
||||
$modal = $( `.jconfirm-wpforms-ai-modal-choices-${ fieldId }` );
|
||||
|
||||
// Close any other modals.
|
||||
$( `.jconfirm-wpforms-ai-modal:not(.jconfirm-wpforms-ai-modal-choices-${ fieldId })` )
|
||||
.addClass( 'wpforms-hidden' )
|
||||
.fadeOut();
|
||||
|
||||
if ( $modal.length ) {
|
||||
$modal.removeClass( 'wpforms-hidden' ).fadeIn();
|
||||
return;
|
||||
}
|
||||
|
||||
const args = {},
|
||||
hideChoices = function() {
|
||||
app.hideChoicesModal( fieldId );
|
||||
return false;
|
||||
};
|
||||
|
||||
args.content = `<wpforms-ai-chat mode="choices" field-id="${ fieldId }" />`;
|
||||
args.theme = `wpforms-ai-modal, wpforms-ai-purple, wpforms-ai-modal-choices-${ fieldId }`;
|
||||
args.backgroundDismiss = hideChoices;
|
||||
args.backgroundDismissAnimation = '';
|
||||
args.contentMaxHeight = Math.min( app.defaultOptions.contentMaxHeight, app.getMaxModalHeight() );
|
||||
args.onOpen = function() {
|
||||
// Unbind the click event from the close icon and use our own instead.
|
||||
this.$closeIcon.off( 'click' );
|
||||
this.$closeIcon.on( 'click', hideChoices );
|
||||
};
|
||||
args.onOpenBefore = function() {
|
||||
wpFormsAIDock.init( fieldId );
|
||||
};
|
||||
|
||||
app.initModal( args );
|
||||
},
|
||||
|
||||
/**
|
||||
* Hide the choices modal window.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {string} fieldId Choice field ID.
|
||||
*/
|
||||
hideChoicesModal( fieldId ) {
|
||||
$( `.jconfirm-wpforms-ai-modal-choices-${ fieldId }` ).addClass( 'wpforms-hidden' ).fadeOut();
|
||||
},
|
||||
|
||||
/**
|
||||
* Show the choices modal window.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {string} fieldId Choice field ID.
|
||||
*/
|
||||
showChoicesModal( fieldId ) {
|
||||
$( `.jconfirm-wpforms-ai-modal-choices-${ fieldId }` ).removeClass( 'wpforms-hidden' ).fadeIn();
|
||||
},
|
||||
|
||||
/**
|
||||
* Resize choices modal window height.
|
||||
*
|
||||
* @since 1.9.4
|
||||
*
|
||||
* @param {string} fieldId Field ID.
|
||||
*/
|
||||
resizeModalHeight( fieldId ) {
|
||||
const modalHeight = app.getMaxModalHeight();
|
||||
const $modal = $( '.jconfirm-wpforms-ai-modal' ).filter( function() {
|
||||
// find class starts with jconfirm-wpforms-ai-modal- and ends with -{fieldId}.
|
||||
return $( this ).attr( 'class' ).match( new RegExp( 'jconfirm-wpforms-ai-modal-.*-' + fieldId, 'i' ) );
|
||||
} );
|
||||
|
||||
$modal.find( '.jconfirm-content-pane' )
|
||||
.css( {
|
||||
height: modalHeight,
|
||||
'max-height': modalHeight,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Before choices refresh confirm is displayed.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Event} e Event object.
|
||||
*/
|
||||
beforeChoicesRefreshConfirm( e ) {
|
||||
const fieldId = e.detail?.fieldId || 0;
|
||||
|
||||
app.hideChoicesModal( fieldId );
|
||||
},
|
||||
|
||||
/**
|
||||
* Cancel choices' refresh.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Event} e Event object.
|
||||
*/
|
||||
cancelChoicesRefresh( e ) {
|
||||
const fieldId = e.detail?.fieldId || 0;
|
||||
|
||||
app.showChoicesModal( fieldId );
|
||||
},
|
||||
|
||||
/**
|
||||
* Refresh the main modal window height.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Event} e Event object.
|
||||
*/
|
||||
refreshModalHeight( e ) {
|
||||
const fieldId = e.detail?.fieldId || 0;
|
||||
const maxHeight = Math.min( app.getMaxModalHeight(), app.defaultOptions.contentMaxHeight );
|
||||
|
||||
app.showChoicesModal( fieldId );
|
||||
|
||||
// Reset choices modal window height.
|
||||
$( `.jconfirm-wpforms-ai-modal-choices-${ fieldId } .jconfirm-content-pane` )
|
||||
.css( {
|
||||
height: maxHeight,
|
||||
'max-height': maxHeight,
|
||||
} );
|
||||
},
|
||||
|
||||
/**
|
||||
* Get the max modal height.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @return {number} The max modal height.
|
||||
*/
|
||||
getMaxModalHeight() {
|
||||
// 80% of the window height, but not more than 800 px.
|
||||
return Math.min( $( window ).height() * 0.8, 800 );
|
||||
},
|
||||
|
||||
/**
|
||||
* Extend jquery-confirm plugin with support of max-height for the content area.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*/
|
||||
extendJqueryConfirm() {
|
||||
// Extend a method of global instance.
|
||||
window.Jconfirm.prototype._updateContentMaxHeight = function() {
|
||||
this.$contentPane.css( {
|
||||
'max-height': this.contentMaxHeight + 'px',
|
||||
} );
|
||||
};
|
||||
},
|
||||
|
||||
/**
|
||||
* Confirm a modal window.
|
||||
*
|
||||
* This is a wrapper for the `jquery.confirm` plugin.
|
||||
*
|
||||
* @since 1.9.1
|
||||
*
|
||||
* @param {Object} args Modal window arguments.
|
||||
*/
|
||||
confirmModal( args ) {
|
||||
const options = {
|
||||
title: false,
|
||||
content: '',
|
||||
icon: 'fa fa-exclamation-circle',
|
||||
type: 'orange',
|
||||
buttons: {
|
||||
confirm: {
|
||||
text: wpforms_ai_chat_element.btnYes,
|
||||
btnClass: 'btn-confirm',
|
||||
keys: [ 'enter' ],
|
||||
action() {
|
||||
if ( typeof args.onConfirm === 'function' ) {
|
||||
args.onConfirm();
|
||||
}
|
||||
},
|
||||
},
|
||||
cancel: {
|
||||
text: wpforms_ai_chat_element.btnCancel,
|
||||
action() {
|
||||
if ( typeof args.onCancel === 'function' ) {
|
||||
args.onCancel();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
$.confirm( { ...options, ...args } );
|
||||
},
|
||||
};
|
||||
|
||||
// Provide access to public functions/properties.
|
||||
return app;
|
||||
}( document, window, jQuery ) );
|
||||
|
||||
// Initialize.
|
||||
WPFormsAIModal.init();
|
||||
Vendored
Executable
+1
@@ -0,0 +1 @@
|
||||
var WPFormsAIModal=window.WPFormsAIModal||((e,o,t)=>{let n={defaultOptions:{title:!1,content:"",type:"ai",smoothContent:!0,bgOpacity:1,boxWidth:650,contentMaxHeight:600,closeIcon:!0,buttons:!1},init(){t(n.ready)},ready(){n.extendJqueryConfirm(),n.bindChoicesActions()},bindChoicesActions(){t(e).on("click",".wpforms-ai-choices-button",n.initChoicesModal).on("wpformsAIChatBeforeRefreshConfirm",n.beforeChoicesRefreshConfirm).on("wpformsAIModalBeforeWarningMessageInsert",n.refreshModalHeight).on("wpformsAIChatAfterRefresh",n.refreshModalHeight).on("wpformsAIChatCancelRefresh",n.cancelChoicesRefresh).on("wpformsAIChatBeforeSendMessage",function(e){n.resizeModalHeight(e.detail.fieldId)}).on("wpformsAIChatAfterAddAnswer",function(e){n.resizeModalHeight(e.detail.fieldId)}).on("wpformsAIModalAfterChoicesInsert",function(e){n.hideChoicesModal(e.detail.fieldId)}),t(o).on("resize",function(){t(".jconfirm-wpforms-ai-modal wpforms-ai-chat").each(function(){n.resizeModalHeight(t(this).attr("field-id"))})})},initModal(e){t.confirm({...n.defaultOptions,...e})},initChoicesModal(){var o=t(this);if(o.hasClass("wpforms-prevent-default"))o.trigger("blur");else{let i=o.data("field-id"),e=t(".jconfirm-wpforms-ai-modal-choices-"+i);if(t(`.jconfirm-wpforms-ai-modal:not(.jconfirm-wpforms-ai-modal-choices-${i})`).addClass("wpforms-hidden").fadeOut(),e.length)e.removeClass("wpforms-hidden").fadeIn();else{let e={},o=function(){return n.hideChoicesModal(i),!1};e.content=`<wpforms-ai-chat mode="choices" field-id="${i}" />`,e.theme="wpforms-ai-modal, wpforms-ai-purple, wpforms-ai-modal-choices-"+i,e.backgroundDismiss=o,e.backgroundDismissAnimation="",e.contentMaxHeight=Math.min(n.defaultOptions.contentMaxHeight,n.getMaxModalHeight()),e.onOpen=function(){this.$closeIcon.off("click"),this.$closeIcon.on("click",o)},e.onOpenBefore=function(){wpFormsAIDock.init(i)},n.initModal(e)}}},hideChoicesModal(e){t(".jconfirm-wpforms-ai-modal-choices-"+e).addClass("wpforms-hidden").fadeOut()},showChoicesModal(e){t(".jconfirm-wpforms-ai-modal-choices-"+e).removeClass("wpforms-hidden").fadeIn()},resizeModalHeight(e){var o=n.getMaxModalHeight();t(".jconfirm-wpforms-ai-modal").filter(function(){return t(this).attr("class").match(new RegExp("jconfirm-wpforms-ai-modal-.*-"+e,"i"))}).find(".jconfirm-content-pane").css({height:o,"max-height":o})},beforeChoicesRefreshConfirm(e){e=e.detail?.fieldId||0;n.hideChoicesModal(e)},cancelChoicesRefresh(e){e=e.detail?.fieldId||0;n.showChoicesModal(e)},refreshModalHeight(e){var e=e.detail?.fieldId||0,o=Math.min(n.getMaxModalHeight(),n.defaultOptions.contentMaxHeight);n.showChoicesModal(e),t(`.jconfirm-wpforms-ai-modal-choices-${e} .jconfirm-content-pane`).css({height:o,"max-height":o})},getMaxModalHeight(){return Math.min(.8*t(o).height(),800)},extendJqueryConfirm(){o.Jconfirm.prototype._updateContentMaxHeight=function(){this.$contentPane.css({"max-height":this.contentMaxHeight+"px"})}},confirmModal(e){var o={title:!1,content:"",icon:"fa fa-exclamation-circle",type:"orange",buttons:{confirm:{text:wpforms_ai_chat_element.btnYes,btnClass:"btn-confirm",keys:["enter"],action(){"function"==typeof e.onConfirm&&e.onConfirm()}},cancel:{text:wpforms_ai_chat_element.btnCancel,action(){"function"==typeof e.onCancel&&e.onCancel()}}}};t.confirm({...o,...e})}};return n})(document,window,jQuery);WPFormsAIModal.init();
|
||||
Reference in New Issue
Block a user