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:
root
2026-04-29 15:32:23 +00:00
parent 57b752f54e
commit b6df4dbb92
5385 changed files with 838580 additions and 2416 deletions
File diff suppressed because it is too large Load Diff
File diff suppressed because one or more lines are too long
@@ -0,0 +1,230 @@
/* global define */
/* eslint-disable */
/**
* This file contains a reusable, and dependency-free JavaScript class,
* providing a contrast checker. This class allows you to assess the readability
* of the given background and text colors against the WCAG 2.0 AA standard.
*
* Example Usage:
*
* // Create an instance of the plugin with custom settings.
* const contrastChecker = new WPFormsColorContrastChecker({
* textColor: '#de8e8e', // Replace with your actual text color.
* bgColor: '#ffffff', // Replace with your actual background color.
* message: {
* contrastPass: '',
* contrastFail: 'Insufficient contrast. Please choose a darker text color or a lighter background color.',
* },
* });
*
* // Perform the contrast check.
* const contrastFailed = contrastChecker.checkContrast();
*
* // Display the result or handle the error, if any.
* if (contrastFailed) {
* console.error(contrastFailed);
* }
*/
/* eslint-enable */
( function( root, factory ) {
const pluginName = 'WPFormsColorContrastChecker';
if ( typeof define === 'function' && define.amd ) {
define( [], factory( pluginName ) );
} else if ( typeof exports === 'object' ) {
module.exports = factory( pluginName );
} else {
root[ pluginName ] = factory( pluginName );
}
// eslint-disable-next-line max-lines-per-function
}( this, function( pluginName ) {
// eslint-disable-next-line strict
'use strict';
/**
* Plugin Error Object.
*
* @since 1.8.6
*
* @class PluginError
*
* @augments Error
*/
class PluginError extends Error {
/**
* Constructor.
*
* @since 1.8.6
*
* @param {string} message The error message.
*/
constructor( message ) {
super( message );
this.name = pluginName;
}
}
/**
* Log the error message.
* This function can be replaced with a custom error logging logic.
*
* @since 1.8.6
*
* @param {string} error The error message.
*/
function logError( error ) {
// Custom error logging logic.
// Display the error message in a specific format or send it to a logging service
// eslint-disable-next-line no-console
console.error( error );
}
/**
* Plugin Object.
*
* @since 1.8.6
*
* @class Plugin
*/
class Plugin {
// Default settings.
static defaults = {
textColor: '',
bgColor: '',
contrastThreshold: 4.5, // W3C recommended minimum contrast ratio for normal text
message: {
contrastPass: 'The contrast ratio between the text and background color is sufficient.',
contrastFail: 'The contrast ratio between the text and background color is insufficient. Please choose a darker text color or a lighter background color.',
},
};
/**
* Constructor.
*
* @since 1.8.6
*
* @param {Object} args The argument object.
*/
constructor( args ) {
// Merge the default settings with the provided settings.
this.args = Object.assign( {}, Plugin.defaults, args );
}
/**
* Convert hex color code to an RGB array.
*
* @since 1.8.6
*
* @param {string} hexColor The hex color code.
*
* @return {number[]|null} The RGB array or null if the conversion failed.
*/
hexToRgb( hexColor ) {
const shorthandRegex = /^#?([a-f\d])([a-f\d])([a-f\d])$/i;
const result = /^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec( hexColor );
if ( shorthandRegex.test( hexColor ) ) {
return result ? [
parseInt( result[ 1 ], 16 ) * 17,
parseInt( result[ 2 ], 16 ) * 17,
parseInt( result[ 3 ], 16 ) * 17,
] : null;
}
return result ? [
parseInt( result[ 1 ], 16 ),
parseInt( result[ 2 ], 16 ),
parseInt( result[ 3 ], 16 ),
] : null;
}
/**
* Calculate relative luminance for a color.
*
* The calculated relative luminance is a value between 0 and 1,
* where 0 represents black and 1 represents white.
*
* @since 1.8.6
*
* @param {string} rgb The RGB color code.
*
* @return {number} The relative luminance.
*/
calculateRelativeLuminance( rgb ) {
for ( let i = 0; i < rgb.length; i++ ) {
rgb[ i ] /= 255;
rgb[ i ] = rgb[ i ] <= 0.03928 ? rgb[ i ] / 12.92 : Math.pow( ( rgb[ i ] + 0.055 ) / 1.055, 2.4 );
}
// As Stated in WCAG the relative luminance of a color is defined as:
// L = 0.2126 * R + 0.7152 * G + 0.0722 * B
// where R, G and B are the color values normalized to the range [0, 1].
// @see https://www.w3.org/WAI/GL/wiki/Relative_luminance
// eslint-disable-next-line no-mixed-operators
return 0.2126 * rgb[ 0 ] + 0.7152 * rgb[ 1 ] + 0.0722 * rgb[ 2 ];
}
/**
* Get the contrast ratio between two colors.
*
* @since 1.8.6
*
* @return {number|null} The contrast ratio or an error if the calculation failed.
*/
getContrastRatio() {
try {
const rgbText = this.hexToRgb( this.args.textColor );
const rgbBg = this.hexToRgb( this.args.bgColor );
// Check for invalid color format
if ( ! rgbText || ! rgbBg ) {
throw new PluginError( 'Invalid color format. Provide valid hex color codes.' );
}
const [ l1, l2 ] = [ this.calculateRelativeLuminance( rgbText ), this.calculateRelativeLuminance( rgbBg ) ];
// The purpose of adding 0.05 to both the maximum and minimum relative luminance
// is to ensure that even if one of the luminance values is zero (which would cause division by zero),
// the result won't be infinite. This kind of adjustment is common in contrast ratio calculations
// to handle extreme cases and avoid mathematical errors.
return ( Math.max( l1, l2 ) + 0.05 ) / ( Math.min( l1, l2 ) + 0.05 );
} catch ( error ) {
logError( error.message );
return null;
}
}
/**
* Check the contrast and provide a warning if it's below the threshold.
*
* @since 1.8.6
*
* @return {string|null} The contrast check result or boolean false if the check failed.
*/
checkContrast() {
try {
const contrastRatio = this.getContrastRatio();
// Return early if invalid color format
if ( contrastRatio === null ) {
throw new PluginError( 'Invalid contrast ratio. Provide valid contrast ratio between two colors.' );
}
// Warn if the contrast is below the threshold.
if ( contrastRatio < this.args.contrastThreshold ) {
return this.args.message.contrastFail;
}
return this.args.message.contrastPass;
} catch ( error ) {
logError( error.message );
return null;
}
}
}
return Plugin;
} ) );
@@ -0,0 +1 @@
((t,e)=>{var r="WPFormsColorContrastChecker";"function"==typeof define&&define.amd?define([],e(r)):"object"==typeof exports?module.exports=e(r):t[r]=e(r)})(this,function(e){class o extends Error{constructor(t){super(t),this.name=e}}function s(t){console.error(t)}return class r{static defaults={textColor:"",bgColor:"",contrastThreshold:4.5,message:{contrastPass:"The contrast ratio between the text and background color is sufficient.",contrastFail:"The contrast ratio between the text and background color is insufficient. Please choose a darker text color or a lighter background color."}};constructor(t){this.args=Object.assign({},r.defaults,t)}hexToRgb(t){var e=/^#?([a-f\d]{2})([a-f\d]{2})([a-f\d]{2})$/i.exec(t);return/^#?([a-f\d])([a-f\d])([a-f\d])$/i.test(t)?e?[17*parseInt(e[1],16),17*parseInt(e[2],16),17*parseInt(e[3],16)]:null:e?[parseInt(e[1],16),parseInt(e[2],16),parseInt(e[3],16)]:null}calculateRelativeLuminance(e){for(let t=0;t<e.length;t++)e[t]/=255,e[t]=e[t]<=.03928?e[t]/12.92:Math.pow((e[t]+.055)/1.055,2.4);return.2126*e[0]+.7152*e[1]+.0722*e[2]}getContrastRatio(){try{var t,e,r=this.hexToRgb(this.args.textColor),a=this.hexToRgb(this.args.bgColor);if(r&&a)return[t,e]=[this.calculateRelativeLuminance(r),this.calculateRelativeLuminance(a)],(Math.max(t,e)+.05)/(Math.min(t,e)+.05);throw new o("Invalid color format. Provide valid hex color codes.")}catch(t){return s(t.message),null}}checkContrast(){try{var t=this.getContrastRatio();if(null===t)throw new o("Invalid contrast ratio. Provide valid contrast ratio between two colors.");return t<this.args.contrastThreshold?this.args.message.contrastFail:this.args.message.contrastPass}catch(t){return s(t.message),null}}}});
@@ -0,0 +1,847 @@
/* global wpforms_admin, htmx */
/**
* WPForms admin. Extend list tables functionality.
*
* @param wpforms_admin.column_selector_title
* @param wpforms_admin.save_changes
* @param wpforms_admin.uh_oh
* @param wpforms_admin.unknown_error
* @param wpforms_admin.column_selector_no_fields
* @param wpforms_admin.column_selector_no_meta
*
* @since 1.8.6
*/
var WPFormsAdminListTableExt = window.WPFormsAdminListTableExt || ( function( document, window, $ ) { // eslint-disable-line no-var
/**
* Supported pages' CSS selectors.
* It is the ids of the `.wpforms-admin-wrap` container, which reflects `page` + `view` URL attributes.
*
* @since 1.8.6
*
* @type {Array}
*/
const supportedPages = [
'#wpforms-overview',
'#wpforms-entries-list',
];
/**
* Element selectors shared between functions.
*
* @since 1.8.6
*
* @type {Object}
*/
const selectors = {
cogIcon: '#wpforms-list-table-ext-edit-columns-cog',
submitButton: '#wpforms-list-table-ext-edit-columns-select-submit',
};
/**
* Elements.
*
* @since 1.8.6
*
* @type {Object}
*/
const el = {};
/**
* Public functions and properties.
*
* @since 1.8.6
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.8.6
*/
init() {
app.initElements();
el.$doc.on( 'wpformsReady', app.initMultiSelect );
$( app.ready );
},
/**
* Document ready.
*
* @since 1.8.6
*/
ready() {
app.initPagination();
app.prepareTableFootColumns();
app.initTableScrollColumns();
app.initTableSortableColumns();
app.events();
app.windowResize();
},
/**
* Events.
*
* @since 1.8.6
*/
events() {
el.$doc
.on( 'click', selectors.cogIcon, app.onClickCog )
.on( 'wpforms_multiselect_checkbox_list_toggle', app.onMenuToggle )
.on( 'click', selectors.submitButton, app.onSaveChanges )
.on( 'click', '.tablenav-pages a.button', app.clickPaginationButton )
.on( 'keydown', '#wpforms-overview-search-term', app.searchTermKeydown )
.on( 'htmx:beforeSwap', app.htmxBeforeSwap )
.on( 'htmx:afterSettle', app.htmxAfterSettle );
el.$tableScroll?.on( 'scroll', app.tableScroll );
// noinspection TypeScriptUMDGlobal
$( window ).on( 'resize', _.debounce( app.windowResize, 100 ) );
el.$searchInput?.on( 'input', _.debounce( app.maybeShowNoResults, 310 ) ); // On 300 ms the multiselect lib is updating the list of items so we need to wait a bit more.
},
/**
* Init elements.
*
* @since 1.8.6
*/
initElements() {
el.$doc = $( document );
el.$body = $( 'body' );
el.$header = $( '#wpforms-header' );
el.$page = $( supportedPages.join( ',' ) );
el.$table = el.$page.find( '.wp-list-table' );
el.$tableContainer = el.$table.parent();
el.$menu = $( '#wpforms-list-table-ext-edit-columns-select-container' );
el.$cog = app.initCogIcon();
el.$wpcontent = $( '#wpcontent' );
el.$tablenavPages = $( '.tablenav-pages' );
el.$tablenavPagesLinks = $( '.tablenav-pages .pagination-links a' );
// The Forms Overview page has no table container, wrap the table.
if ( ! el.$tableContainer.hasClass( 'wpforms-table-container' ) ) {
el.$table.wrap( '<div class="wpforms-table-container"></div>' );
el.$tableContainer = el.$table.parent();
}
// Add specific classes to the page container.
el.$page.addClass( 'wpforms-list-table-ext-page' );
},
/**
* Init pagination.
*
* @since 1.9.3
*/
initPagination() {
// Prevent the error messages in console.
htmx.config.historyCacheSize = 2;
const perPage = $( '#pagination_per_page, #wpforms_entries_per_page' ).val();
// Do not proceed if the perPage value is too high.
// The HTMX pagination will be disabled in this case to avoid console errors coused by the large size of the page HTML.
if ( perPage > 200 ) {
return;
}
$( '.tablenav-pages .pagination-links a' ).each( function() {
const $link = $( this );
const url = $link.attr( 'href' );
$link
.attr( {
'hx-get': url,
'hx-target': '.wpforms-admin-content',
'hx-swap': 'outerHTML',
'hx-select': '.wpforms-admin-content',
'hx-replace-url': 'true',
} );
htmx.process( $link[ 0 ] );
} );
},
/**
* Click pagination button event handler.
*
* @since 1.9.3
*/
clickPaginationButton() {
el.$body.addClass( 'wpforms-loading' );
},
/**
* The search term keydown event handler.
*
* @since 1.9.3
*
* @param {Event} e Event.
*/
searchTermKeydown( e ) {
if ( e.keyCode === 13 ) {
$( '#current-page-selector' ).val( 1 );
}
},
/**
* The `htmx:beforeSwap` event handler.
*
* @since 1.9.3
*/
htmxBeforeSwap() {
el.$cog.detach();
},
/**
* The `htmx:afterSettle` event handler.
*
* @since 1.9.3
*/
htmxAfterSettle() {
app.initElements();
app.initMultiSelect();
app.prepareTableFootColumns();
app.initTableSortableColumns();
app.initTableScrollColumns();
el.$tableScroll?.on( 'scroll', app.tableScroll );
app.windowResize();
app.initPagination();
app.initMobileRowExpander();
window.WPFormsForms?.Overview.htmxAfterSettle();
window.WPFormsPagesEntries?.htmxAfterSettle();
el.$body.removeClass( 'wpforms-loading' );
},
/**
* Init mobile view row expander.
*
* @since 1.9.3
*/
initMobileRowExpander() {
$( 'tbody' ).on( 'click', '.toggle-row', function() {
$( this ).closest( 'tr' ).toggleClass( 'is-expanded' );
} );
},
/**
* Prepare table footer columns. Their IDs should match the IDs of the header columns.
*
* @since 1.8.6
*/
prepareTableFootColumns() {
el.$table.find( 'thead tr .manage-column' ).each( function() {
const columnId = $( this ).attr( 'id' );
el.$table.find( 'tfoot tr .column-' + columnId ).attr( 'id', columnId + '-foot' );
} );
// Disable sorting of the cog column.
el.$table.find( '.manage-column.column-cog' )
.addClass( 'wpforms-table-cell-sticky' );
},
/**
* Initialize table columns sortable container.
*
* @since 1.8.6
*/
initTableSortableColumns() { // eslint-disable-line max-lines-per-function
let $columnCells,
columnId;
el.$table.find( 'thead tr, tfoot tr' ).each( function() { // eslint-disable-line max-lines-per-function
const $sortable = $( this );
$sortable.sortable( {
items: '> th:not(:first-child):not(.wpforms-table-cell-sticky)',
connectWith: '',
delay: 100,
opacity: 0.75,
cursor: 'move',
cancel: '.wpforms-table-column-not-draggable',
placeholder: 'wpforms-table-column-drag-placeholder',
appendTo: el.$page,
zindex: 10000,
tolerance: 'intersect',
distance: 1,
helper( e, origin ) {
const $el = $( origin ),
$helper = $el.clone(),
width = $el.outerWidth();
return $helper.css( 'width', width + 'px' );
},
start( e, ui ) {
ui.helper.addClass( 'wpforms-table-column-drag-helper' ); // Add a specific class to the helper container.
ui.item.addClass( 'wpforms-table-column-dragged-out' ).css( 'display', '' );
// Disable global scrolling.
el.$wpcontent.addClass( 'wpforms-no-scroll' );
columnId = ui.item.attr( 'id' ).replace( '-foot', '' );
},
stop( e, ui ) {
// Remove specific classes from the helper.
ui.item
.removeClass( 'wpforms-table-column-drag-helper' )
.removeClass( 'wpforms-table-column-dragged-out' );
// Remove previously added vertical placeholder class from all columns.
el.$table.find( 'thead tr > *, tfoot tr > *' ).removeClass( 'wpforms-table-column-drag-placeholder-prev' );
// Enable global scrolling.
el.$wpcontent.removeClass( 'wpforms-no-scroll' );
const prevColumnId = ui.item.prev().attr( 'id' ).replace( '-foot', '' ),
$rows = el.$table.find( 'tbody tr:not(.wpforms-hidden)' ),
prevSelector = prevColumnId !== 'cb' ? '.column-' + prevColumnId : '.check-column';
// Move column cells.
$columnCells = $rows.find( 'td.column-' + columnId ).detach();
for ( let i = 0; i < $columnCells.length; i++ ) {
$rows.eq( i ).find( prevSelector ).after( $columnCells.eq( i ) );
}
// Move opposite column header.
const oppositeColumnsSelector = ui.item.closest( 'thead' ).length > 0 ? 'tfoot' : 'thead',
$oppositeColumn = el.$table.find( oppositeColumnsSelector + ' tr .column-' + columnId ).detach();
el.$table.find( oppositeColumnsSelector + ' tr ' + prevSelector ).after( $oppositeColumn );
app.updateMenuColumnsOrder();
},
change( e, ui ) {
// Remove previously added vertical placeholder class from all columns.
el.$table.find( 'thead tr > *, tfoot tr > *' ).removeClass( 'wpforms-table-column-drag-placeholder-prev' );
// Add the vertical placeholder class to the previous column.
ui.placeholder.prev().addClass( 'wpforms-table-column-drag-placeholder-prev' );
},
update() {
app.saveColumnsOrder();
},
} );
} );
},
/**
* Initialize table scroll sticky columns.
*
* @since 1.8.6
*/
initTableScrollColumns() {
// Init table horizontal scrolling only on the Entries page.
if ( ! el.$page.is( '#wpforms-entries-list' ) ) {
return;
}
el.$tableScroll = el.$tableContainer;
// The Entries page has own table container, add the class.
el.$tableScroll.addClass( 'wpforms-table-scroll' );
// Detect the Windows OS platform.
el.$tableScroll.toggleClass( 'wpforms-scrollbar', app.isCustomScrollbarNeeded() );
// Add specific class to the sticky columns.
el.$table.find( '.check-column, .column-indicators' )
.addClass( 'wpforms-table-cell-sticky' )
.addClass( 'left' );
el.$table.find( '.column-actions' )
.addClass( 'wpforms-table-cell-sticky' )
.addClass( 'right' );
},
/**
* Table scroll event.
*
* @since 1.8.6
*/
tableScroll() {
if ( ! el.$tableScroll?.length ) {
return;
}
const width = el.$tableScroll.outerWidth(),
scrollLeft = Math.abs( el.$tableScroll.get( 0 ).scrollLeft ),
scrollWidth = el.$tableScroll.get( 0 ).scrollWidth;
// Conditionally Add shadow to the sticky columns.
el.$tableScroll
.find( '.wpforms-table-cell-sticky.left' )
.toggleClass( 'shadow', scrollLeft > 1 ); // 1px is fix for the RTL mode.
el.$tableScroll
.find( '.wpforms-table-cell-sticky.right' )
.toggleClass( 'shadow', scrollWidth - width >= scrollLeft );
},
/**
* Window resize event.
*
* @since 1.8.6
*/
windowResize() {
// Disable dragging on mobiles.
el.$table.find( 'thead th, tfoot th' ).toggleClass( 'wpforms-table-column-not-draggable', window.innerWidth <= 782 );
app.closeMenu();
app.windowResizeToggleColumns();
app.tableScroll();
},
/**
* Toggle columns visibility for certain window sizes.
*
* @since 1.8.6
*/
windowResizeToggleColumns() {
// Proceed only on the Forms Overview page.
if ( ! el.$page.is( '#wpforms-overview' ) ) {
return;
}
const $visibleColumns = el.$table.find( 'thead tr th:visible' );
const $columnTags = el.$table.find( '.column-tags' );
// For browser window with the width between 960px and 1280px.
if ( window.innerWidth > 960 && window.innerWidth <= 1280 ) {
$columnTags.toggleClass( 'wpforms-hidden', $visibleColumns.length > 4 );
} else {
$columnTags.removeClass( 'wpforms-hidden' );
}
// Synchronize menu items visibility.
el.$menu.find( 'label' ).removeClass( 'wpforms-hidden' );
el.$table.find( 'thead tr th:not(:visible)' ).each( function() {
const $column = $( this );
el.$menu
.find( `input[value="${ $column.attr( 'id' ) }"]` )
.closest( 'label' )
.addClass( 'wpforms-hidden' );
} );
},
/**
* Show or hide no results text.
*
* @since 1.8.6
*/
maybeShowNoResults() {
[ 'fields', 'meta' ].forEach( ( section ) => {
const labels = el.$menu.find( '.wpforms-multiselect-checkbox-optgroup-' + section )
.nextUntil( '.wpforms-multiselect-checkbox-optgroup' )
.filter( 'label' );
const hiddenLabels = labels.filter( function() {
return $( this ).is( ':hidden' );
} );
el.$menu.find( '.wpforms-multiselect-checkbox-no-results-' + section )
.toggleClass( 'wpforms-hidden', labels.length !== hiddenLabels.length );
} );
},
/**
* Close the columns' selector menu.
*
* @since 1.8.6
*/
closeMenu() {
if ( ! el.$cog.hasClass( 'active' ) ) {
return;
}
el.$cog.removeClass( 'active' );
el.$menu.find( '.wpforms-multiselect-checkbox-list' ).removeClass( 'open' );
// Flush the search input.
el.$searchInput.val( '' );
el.$searchInput[ 0 ]?.dispatchEvent( new Event( 'input' ) );
},
/**
* Get columns order.
*
* @since 1.8.6
*
* @return {Array} Columns order.
*/
getColumnsOrder() {
const $row = el.$table.find( 'thead tr' );
const columns = [];
$row.find( 'th' ).each( function() {
columns.push( $( this ).attr( 'id' ) );
} );
return columns;
},
/**
* Get menu columns order.
*
* @since 1.8.6
*
* @return {Array} Columns order.
*/
getMenuColumnsOrder() {
let columnsOrder = app.getColumnsOrder();
const columnsChecked = [];
const columns = [];
el.$menu.find( `input:checked` ).each( function() {
columnsChecked.push( $( this ).val() );
} );
// Convert DOM element IDs to column IDs.
columnsOrder = columnsOrder.map( function( column ) {
return app.convertColumnId( column );
} );
// Add checked columns in the same order as in the table.
for ( let i = 0; i < columnsOrder.length; i++ ) {
const column = columnsOrder[ i ];
if ( columnsChecked.includes( column ) ) {
columns.push( column );
columnsChecked.splice( columnsChecked.indexOf( column ), 1 );
}
}
// Add the rest of the checked columns.
return columns.concat( columnsChecked );
},
/**
* Save columns order.
*
* @since 1.8.6
*/
saveColumnsOrder() {
const data = {
nonce: wpforms_admin.nonce,
action: el.$menu.find( '[name="action"]' ).val(),
form_id: el.$menu.find( '[name="form_id"]' ).val(), // eslint-disable-line camelcase
columns: app.getColumnsOrder(),
};
// AJAX request to save the columns order.
$.post( wpforms_admin.ajax_url, data )
.done( function( response ) {
if ( ! response.success ) {
app.displayErrorModal( response.data || wpforms_admin.unknown_error );
}
} )
.fail( function() {
app.displayErrorModal( wpforms_admin.server_error );
} );
},
/**
* Display modal window with an error message.
*
* @since 1.8.6
*
* @param {string} content Modal content.
*/
displayErrorModal( content ) {
$.alert( {
title : wpforms_admin.uh_oh,
content,
icon : 'fa fa-exclamation-circle',
type : 'red',
buttons: {
cancel: {
text : wpforms_admin.close,
btnClass: 'btn-confirm',
keys : [ 'enter' ],
},
},
} );
},
/**
* Update menu columns order.
*
* @since 1.8.6
*/
updateMenuColumnsOrder() { // eslint-disable-line complexity
let columnsOrder = app.getColumnsOrder();
const $groups = el.$menu.find( '.wpforms-multiselect-checkbox-optgroup' );
const $itemsCont = el.$menu.find( '.wpforms-multiselect-checkbox-items' );
const $items = $itemsCont.find( 'label' );
const itemsByGroup = [ 0 ];
// If there are no groups, add the items to the first group.
itemsByGroup[ 0 ] = $items;
// If there are groups, split the items by groups.
if ( $groups.length ) {
$groups.each( function( i ) {
itemsByGroup[ i ] = $( this ).nextUntil( '.wpforms-multiselect-checkbox-optgroup' );
} );
}
// Convert DOM element IDs to column IDs.
columnsOrder = columnsOrder.map( function( column ) {
return app.convertColumnId( column );
} );
// Rebuild the menu items order.
for ( let g = 0; g < itemsByGroup.length; g++ ) {
itemsByGroup[ g ] = itemsByGroup[ g ].filter( function() {
return $( this ).find( 'input:checked' ).length > 0;
} );
itemsByGroup[ g ].detach();
const $group = $groups.eq( g );
// Add the items in the same order as in the table.
// It is necessary to process it in reverse mode to reproduce the columns order.
for ( let i = columnsOrder.length - 1; i >= 0; i-- ) {
const column = columnsOrder[ i ];
const $item = itemsByGroup[ g ].filter( function() {
return $( this ).find( `[value="${ column }"]` ).length > 0;
} );
if ( ! $item.length ) {
continue;
}
if ( $group.length ) {
$group.after( $item );
} else {
$itemsCont.prepend( $item );
}
}
}
},
/**
* Convert column Id.
*
* @since 1.8.6
*
* @param {string|number} columnId Column ID.
*
* @return {string} Converted column ID.
*/
convertColumnId( columnId ) {
let id = columnId.replace( 'wpforms_field_', '' );
id = id.replace( '-foot', '' );
id = id === 'entry_id' ? '-1' : id;
id = id === 'notes_count' ? '-2' : id;
return id;
},
/**
* Initialize wpforms-multiselect-js on select elements.
*
* @since 1.8.6
*/
initMultiSelect() {
if ( ! el.$cog.length ) {
return;
}
el.$menu.find( '.wpforms-list-table-ext-edit-columns-select' ).each( function() {
const $select = $( this );
const isLongList = $select.find( 'option' ).length > 10;
const isEntriesPage = el.$page.is( '#wpforms-entries-list' );
const showSearch = isEntriesPage && isLongList;
const $selectWrapper = $select.parent( '.wpforms-multiselect-checkbox-dropdown' );
// If the multiselect is already initialized, skip.
if ( $selectWrapper.length ) {
return;
}
const multiSelectColumns = new window.WPFormsMultiSelectCheckbox(
this,
{
showMask: true,
showSearch,
customOpener: el.$cog.get( 0 ),
}
);
// Initialize the multiselect.
multiSelectColumns.init();
const $wrapper = $select.next( '.wpforms-multiselect-checkbox-wrapper' );
const $list = $wrapper.find( '.wpforms-multiselect-checkbox-list' );
app.appendNoResultsText( $list );
if ( ! showSearch ) {
$wrapper.find( '.wpforms-multiselect-checkbox-items' ).addClass( 'wpforms-multiselect-checkbox-items-no-search' );
}
$list.append( '<button type="button" class="button button-secondary" id="wpforms-list-table-ext-edit-columns-select-submit" data-value="save-table-columns">' + wpforms_admin.save_changes + '</button>' );
app.updateMenuColumnsOrder();
} );
el.$searchInput = $( '#wpforms-list-table-ext-edit-columns-select-container .wpforms-multiselect-checkbox-search' );
el.$menu.removeClass( 'wpforms-hidden' );
},
/**
* Append no results text to the multiselect list.
*
* @since 1.8.6
*
* @param {jQuery} $list Multiselect list.
*/
appendNoResultsText( $list ) {
$list.find( '.wpforms-multiselect-checkbox-optgroup' ).each( function( i ) {
const appendix = i === 0 ? 'fields' : 'meta';
const noResultsText = i === 0 ? wpforms_admin.column_selector_no_fields : wpforms_admin.column_selector_no_meta;
$( this )
.addClass( 'wpforms-multiselect-checkbox-optgroup-' + appendix )
.after( `<span class="wpforms-multiselect-checkbox-no-results wpforms-multiselect-checkbox-no-results-${ appendix } wpforms-hidden">${ noResultsText }</span>` );
} );
},
/**
* Add cog icon to the table header.
*
* @since 1.8.6
*
* @return {jQuery} Cog icon object.
*/
initCogIcon() {
const $lastColumnHeader = el.$table.find( 'thead th:not(.hidden):last' );
if ( ! $lastColumnHeader.length ) {
return $();
}
if ( el.$cog ) {
$lastColumnHeader.append( el.$cog );
return el.$cog;
}
const cogId = selectors.cogIcon.replace( '#', '' );
const $cog = $( `<a href="#" title="${ wpforms_admin.column_selector_title }" id="${ cogId }"><i class="fa fa-cog" aria-hidden="true"></i></a>` );
$lastColumnHeader.append( $cog );
return $cog;
},
/*
* Click on the cog icon.
*
* @since 1.8.6
*
* @param {object} event Event object.
*/
onClickCog( event ) {
event.preventDefault();
},
/*
* Save changes.
*
* @since 1.8.6
*
* @param {object} event Event object.
*/
onSaveChanges( event ) {
event.preventDefault();
const data = {
nonce: wpforms_admin.nonce,
action: el.$menu.find( 'input[name="action"]' ).val(),
form_id: el.$menu.find( 'input[name="form_id"]' ).val(), // eslint-disable-line camelcase
columns: app.getMenuColumnsOrder(),
};
app.closeMenu();
$.post( wpforms_admin.ajax_url, data )
.done( function( response ) {
if ( ! response.success ) {
app.displayErrorModal( response.data || wpforms_admin.unknown_error );
return;
}
window.location.reload();
} )
.fail( function() {
app.displayErrorModal( wpforms_admin.server_error );
} );
},
/**
* Toggle multiselect columns menu.
*
* @since 1.8.6
*
* @param {Object} event Event object.
*/
onMenuToggle( event ) {
$( selectors.cogIcon ).toggleClass( 'active', event.detail.isOpen );
// Hide no results messages.
el.$menu.find( '.wpforms-multiselect-checkbox-no-results' ).addClass( 'wpforms-hidden' );
app.positionMultiselectColumnsMenu();
},
/**
* Position the multiselect columns menu just under the cog icon.
*
* @since 1.8.6
*/
positionMultiselectColumnsMenu() {
if ( ! el.$cog.length ) {
return;
}
el.$menu.css( {
top: el.$cog.offset().top - $( '#wpbody-content' ).offset().top + el.$cog.outerHeight() + 6,
} );
},
/**
* Detect if the custom styled scrollbar is needed.
*
* @since 1.8.6
*
* @return {boolean} True when needed.
*/
isCustomScrollbarNeeded() {
const ua = navigator.userAgent;
return ( ua.includes( 'Windows' ) || ua.includes( 'Linux' ) ) &&
( ua.includes( 'Chrome' ) || ua.includes( 'Firefox' ) );
},
};
return app;
}( document, window, jQuery ) );
// Initialize.
WPFormsAdminListTableExt.init();
File diff suppressed because one or more lines are too long
@@ -0,0 +1,196 @@
/* global define */
/* eslint-disable */
/**
* XOR, or exclusive or, is a logical bitwise operation that stands for "exclusive or."
* In the context of binary numbers, XOR compares corresponding bits of two operands and
* produces a new result. The XOR operation returns true (or 1) for bits where the operands differ.
*
* Note: This class is a simple obfuscation technique and should not be used for securing sensitive data.
*
* Here's the truth table for XOR:
*
* A | B | A XOR B
* -----------------
* 0 | 0 | 0
* 0 | 1 | 1
* 1 | 0 | 1
* 1 | 1 | 0
*
* In binary, XOR is often denoted by the symbol ^.
* Here's an example of XOR operation on binary numbers:
*
* 1101 (13 in decimal)
* ^ 1010 (10 in decimal)
* ------------------------
* 0111 (7 in decimal)
*
* Example Usage:
*
* // Instantiate the plugin with a custom encryption key.
* const xorInstance = new WPFormsXOR({
* key: 55, // Use any number as the encryption key.
* });
*
* // Example object to encrypt.
* const dataToEncrypt = {
* age: 30,
* name: 'Sullie',
* city: 'Texas',
* };
*
* // Encrypt the object.
* const encryptedValue = xorInstance.encrypt(dataToEncrypt);
* console.log('Encrypted:', encryptedValue);
*
* // Decrypt the string.
* const decryptedObject = xorInstance.decrypt(encryptedValue);
* console.log('Decrypted:', decryptedObject);
*/
/* eslint-enable */
( function( root, factory ) {
const pluginName = 'WPFormsXOR';
if ( typeof define === 'function' && define.amd ) {
define( [], factory( pluginName ) );
} else if ( typeof exports === 'object' ) {
module.exports = factory( pluginName );
} else {
root[ pluginName ] = factory( pluginName );
}
// eslint-disable-next-line max-lines-per-function
}( this, function( pluginName ) {
// eslint-disable-next-line strict
'use strict';
/**
* Plugin Error Object.
*
* @since 1.8.6
*
* @class PluginError
*
* @augments Error
*/
class PluginError extends Error {
/**
* Constructor.
*
* @since 1.8.6
*
* @param {string} message The error message.
*/
constructor( message ) {
super( message );
this.name = pluginName;
}
}
/**
* Plugin Object.
*
* @since 1.8.6
*
* @class Plugin
*/
class Plugin {
// Default settings.
static defaults = {
// The encryption key is a crucial component in encryption algorithms,
// including the XOR encryption used in the provided code.
// The key is a value used to control the transformation
// of the data during encryption and decryption.
key: 42, // You can use any number.
};
/**
* Constructor.
*
* @since 1.8.6
*
* @param {Object} args The argument object.
*/
constructor( args ) {
// Merge the default settings with the provided settings.
this.args = Object.assign( {}, Plugin.defaults, args );
}
/**
* Encrypt an object using XOR encryption.
*
* @since 1.8.6
*
* @param {Object} obj The object to encrypt.
*
* @return {string} The encrypted object as a string.
*/
encrypt( obj ) {
// Bail if the input is not an object.
if ( typeof obj !== 'object' ) {
throw new PluginError( 'Invalid input. Expected an object for encryption.' );
}
// Initialize an empty string to store the encrypted result.
let result = '';
try {
// Convert the object to a JSON string.
const jsonString = JSON.stringify( obj );
// Iterate through each character of the JSON string.
for ( let i = 0; i < jsonString.length; i++ ) {
// XOR each character with the encryption key and append to the result.
// eslint-disable-next-line no-bitwise
result += String.fromCharCode( jsonString.charCodeAt( i ) ^ this.args.key );
}
} catch ( error ) {
// Throw a PluginError if there's an issue during JSON stringification.
throw new PluginError( 'Error during encryption. Unable to stringify the object.' );
}
return result;
}
/**
* Decrypt a string using XOR encryption.
*
* @since 1.8.6
*
* @param {string} encryptedString The encrypted string.
*
* @return {Object} The decrypted object.
*/
decrypt( encryptedString = '' ) {
// Bail if the input is not a string.
if ( typeof encryptedString !== 'string' ) {
throw new PluginError( 'Invalid input. Expected a string for decryption.' );
}
// Bail if there is no encrypted string.
if ( ! encryptedString ) {
return {}; // Return an empty object.
}
let result = '';
try {
// Iterate through each character of the encrypted string.
for ( let i = 0; i < encryptedString.length; i++ ) {
// XOR each character with the decryption key and append to the result.
// eslint-disable-next-line no-bitwise
result += String.fromCharCode( encryptedString.charCodeAt( i ) ^ this.args.key );
}
// Parse the decrypted result as JSON or return an empty object if parsing fails.
return JSON.parse( result || '{}' );
} catch ( error ) {
// Throw an error if there's an issue during decryption or parsing.
throw new PluginError( 'Error during decryption. Unable to parse decrypted data.' );
}
}
}
return Plugin;
} ) );
+1
View File
@@ -0,0 +1 @@
((t,r)=>{var e="WPFormsXOR";"function"==typeof define&&define.amd?define([],r(e)):"object"==typeof exports?module.exports=r(e):t[e]=r(e)})(this,function(r){class n extends Error{constructor(t){super(t),this.name=r}}return class e{static defaults={key:42};constructor(t){this.args=Object.assign({},e.defaults,t)}encrypt(t){if("object"!=typeof t)throw new n("Invalid input. Expected an object for encryption.");let r="";try{var e=JSON.stringify(t);for(let t=0;t<e.length;t++)r+=String.fromCharCode(e.charCodeAt(t)^this.args.key)}catch(t){throw new n("Error during encryption. Unable to stringify the object.")}return r}decrypt(r=""){if("string"!=typeof r)throw new n("Invalid input. Expected a string for decryption.");if(!r)return{};let e="";try{for(let t=0;t<r.length;t++)e+=String.fromCharCode(r.charCodeAt(t)^this.args.key);return JSON.parse(e||"{}")}catch(t){throw new n("Error during decryption. Unable to parse decrypted data.")}}}});