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
@@ -0,0 +1,789 @@
/* global wpforms_education, WPFormsBuilder, wpf */
/**
* WPForms Education Core.
*
* @since 1.6.6
*/
// noinspection ES6ConvertVarToLetConst
/**
* @param wpforms_education.activate_confirm
* @param wpforms_education.activate_prompt
* @param wpforms_education.activating
* @param wpforms_education.addon_activated
* @param wpforms_education.addon_error
* @param wpforms_education.addon_incompatible.title
* @param wpforms_education.addon_incompatible.button_text
* @param wpforms_education.addon_incompatible.button_url
* @param wpforms_education.ajax_url
* @param wpforms_education.can_activate_addons
* @param wpforms_education.can_install_addons
* @param wpforms_education.cancel
* @param wpforms_education.close
* @param wpforms_education.install_confirm
* @param wpforms_education.install_prompt
* @param wpforms_education.installing
* @param wpforms_education.nonce
* @param wpforms_education.ok
* @param wpforms_education.plugin_activated
* @param wpforms_education.save_confirm
* @param wpforms_education.save_prompt
* @param wpforms_education.saving
* @param wpforms_education.thanks_for_interest
* @param wpforms_education.upgrade
* @param wpforms_education.upgrade.modal
* @param wpforms_education.upgrade.url
* @param wpforms_education.upgrade.url_template
*/
var WPFormsEducation = window.WPFormsEducation || {}; // eslint-disable-line no-var
WPFormsEducation.core = window.WPFormsEducation.core || ( function( document, window, $ ) {
/**
* Spinner markup.
*
* @since 1.7.0
*
* @type {string}
*/
const spinner = '<i class="wpforms-loading-spinner wpforms-loading-white wpforms-loading-inline"></i>';
/**
* Public functions and properties.
*
* @since 1.6.6
*
* @type {Object}
*/
const app = {
/**
* Start the engine.
*
* @since 1.6.6
*/
init() {
$( app.ready );
},
/**
* Document ready.
*
* @since 1.6.6
*/
ready() {
app.events();
},
/**
* Register JS events.
*
* @since 1.6.6
*/
events() {
app.dismissEvents();
app.openModalButtonClick();
app.setDykColspan();
app.gotoAdvancedTabClick();
app.proFieldDelete();
},
/**
* Open education modal.
*
* @since 1.7.0
*/
openModalButtonClick() {
$( document )
.on( 'click', '.education-modal:not(.wpforms-add-fields-button)', app.openModalButtonHandler )
.on( 'mousedown', '.education-modal.wpforms-add-fields-button', app.openModalButtonHandler )
.on( 'click', '.education-action-button', app.actionButtonHandler );
},
/**
* Action button click handler.
*
* @since 1.9.5
*
* @param {Event} event Event.
*/
actionButtonHandler( event ) {
event.preventDefault();
const $this = $( this );
const action = $this.data( 'action' );
// Currently, only the upgrade action is supported.
if ( action !== 'upgrade' ) {
return;
}
const utmContent = $this.data( 'utm-content' );
const type = $this.data( 'license' );
window.open( WPFormsEducation.core.getUpgradeURL( utmContent, type ), '_blank' );
},
/**
* Open education modal handler.
*
* @since 1.8.0
* @since 1.9.6.1 Added `$element` parameter.
*
* @param {Event} event Event.
* @param {jQuery} $element jQuery element.
*/
openModalButtonHandler( event, $element = null ) {
event.preventDefault();
const $this = $element || $( this );
switch ( $this.data( 'action' ) ) {
case 'activate':
app.activateModal( $this );
break;
case 'install':
app.installModal( $this );
break;
case 'incompatible':
app.incompatibleModal( $this );
break;
}
},
/**
* Hide Pro fields notice when all disabled fields deleted.
*
* @since 1.9.4
*/
proFieldDelete() {
$( '#wpforms-builder' ).on(
'wpformsFieldDelete',
function() {
if ( ! $( '.wpforms-field-wrap .wpforms-field-is-pro' ).length ) {
$( '.wpforms-preview .wpforms-pro-fields-notice' ).addClass( 'wpforms-hidden' );
}
}
);
},
/**
* Dismiss button events.
*
* @since 1.6.6
*/
dismissEvents() {
$( document ).on( 'click', '.wpforms-dismiss-container .wpforms-dismiss-button', function() {
const $this = $( this ),
$cont = $this.closest( '.wpforms-dismiss-container' ),
data = {
action: 'wpforms_education_dismiss',
nonce: wpforms_education.nonce,
section: $this.data( 'section' ),
page: typeof window.pagenow === 'string' ? window.pagenow : '',
};
let $out = $cont.find( '.wpforms-dismiss-out' );
if ( $cont.hasClass( 'wpforms-dismiss-out' ) ) {
$out = $cont;
}
if ( $out.length > 0 ) {
$out.addClass( 'out' );
setTimeout(
function() {
$cont.remove();
},
300
);
} else {
$cont.remove();
}
$.post( wpforms_education.ajax_url, data );
} );
},
/**
* Calculate and dynamically set the DYK block cell colspan attribute.
*
* @since 1.7.3
*/
setDykColspan() {
$( '#adv-settings' ).on(
'change',
'input.hide-column-tog',
function() {
const $dykCell = $( '.wpforms-dyk td' ),
colCount = $( '.wp-list-table thead .manage-column' ).not( '.hidden' ).length;
$dykCell.attr( 'colspan', colCount );
}
);
},
/**
* Go to Advanced tab when click on the link in Calculations educational notice.
*
* @since 1.8.4.1
*/
gotoAdvancedTabClick() {
$( document )
.on( 'click', '.wpforms-educational-alert.wpforms-calculations a', function( e ) {
const $a = $( this );
if ( $a.attr( 'href' ) !== '#advanced-tab' ) {
return;
}
e.preventDefault();
$a.closest( '.wpforms-field-option' )
.find( '.wpforms-field-option-group-advanced .wpforms-field-option-group-toggle' )
.trigger( 'click' );
} );
},
/**
* Get UTM content for different elements.
*
* @since 1.6.9
*
* @param {jQuery} $el Element.
*
* @return {string} UTM content string.
*/
getUTMContentValue( $el ) {
// UTM content for Fields.
if ( $el.hasClass( 'wpforms-add-fields-button' ) ) {
return $el.data( 'utm-content' ) + ' Field';
}
// UTM content for Templates.
if ( $el.hasClass( 'wpforms-template-select' ) ) {
return app.slugToUTMContent( $el.data( 'slug' ) );
}
// UTM content for Addons (sidebar).
if ( $el.hasClass( 'wpforms-panel-sidebar-section' ) ) {
return app.slugToUTMContent( $el.data( 'slug' ) ) + ' Addon';
}
// UTM content by default with fallback `data-name`.
return $el.data( 'utm-content' ) || $el.data( 'name' );
},
/**
* Convert slug to UTM content.
*
* @since 1.6.9
*
* @param {string} slug Slug.
*
* @return {string} UTM content string.
*/
slugToUTMContent( slug ) {
if ( ! slug ) {
return '';
}
return slug.toString()
// Replace all non-alphanumeric characters with space.
.replace( /[^a-z\d ]/gi, ' ' )
// Uppercase each word.
.replace( /\b[a-z]/g, function( char ) {
return char.toUpperCase();
} );
},
/**
* Get upgrade URL according to the UTM content and license type.
*
* @since 1.6.9
*
* @param {string} utmContent UTM content.
* @param {string} type Feature license type: pro or elite.
*
* @return {string} Upgrade URL.
*/
getUpgradeURL( utmContent, type ) {
let baseURL = wpforms_education.upgrade[ type ].url;
if ( utmContent.toLowerCase().indexOf( 'template' ) > -1 ) {
baseURL = wpforms_education.upgrade[ type ].url_template;
}
if ( utmContent.toLowerCase().indexOf( 'themes' ) > -1 ) {
baseURL = wpforms_education.upgrade[ type ].url_themes;
}
// Test if the base URL already contains `?`.
let appendChar = /(\?)/.test( baseURL ) ? '&' : '?';
// If the upgrade link is changed by partners, appendChar has to be encoded.
if ( baseURL.indexOf( 'https://wpforms.com' ) === -1 ) {
appendChar = encodeURIComponent( appendChar );
}
return baseURL + appendChar + 'utm_content=' + encodeURIComponent( utmContent.trim() );
},
/**
* Upgrade modal second state.
*
* @since 1.6.6
*
* @param {string} type Feature license type: pro or elite.
*/
upgradeModalThankYou: ( type ) => {
$.alert( {
title: wpforms_education.thanks_for_interest,
content: wpforms_education.upgrade[ type ].modal,
icon: 'fa fa-info-circle',
type: 'blue',
boxWidth: '565px',
buttons: {
confirm: {
text: wpforms_education.ok,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
/**
* Get spinner markup.
*
* @since 1.7.6
*
* @return {string} Spinner markup.
*/
getSpinner: () => {
return spinner;
},
/**
* Addon activate modal.
*
* @since 1.7.0
*
* @param {jQuery} $button jQuery button element.
*/
activateModal( $button ) {
const feature = $button.data( 'name' ),
message = $button.data( 'message' );
const canActivateAddons = wpforms_education.can_activate_addons;
$.alert( {
title: false,
content: message ? message : wpforms_education.activate_prompt.replace( /%name%/g, feature ),
icon: 'fa fa-info-circle',
type: 'blue',
buttons: {
confirm: {
text: wpforms_education.activate_confirm,
btnClass: 'btn-confirm' + ( ! canActivateAddons ? ' hidden' : '' ),
keys: [ 'enter' ],
isHidden: ! canActivateAddons,
action() {
this.$$confirm
.prop( 'disabled', true )
.html( spinner + wpforms_education.activating );
this.$$cancel
.prop( 'disabled', true );
app.activateAddon( $button, this );
return false;
},
},
cancel: {
text: wpforms_education.cancel,
action() {
/**
* Trigger event when modal is closed.
* This event is used to handle any custom logic when the modal is closed.
*
* @since 1.9.6.1
*
* @param {jQuery} $button jQuery button element.
*/
$( document ).trigger( 'wpformsEducationModalClose', $button );
},
},
},
} );
},
/**
* Activate addon via AJAX.
*
* @since 1.7.0
*
* @param {jQuery} $button jQuery button element.
* @param {Object} previousModal Previous modal instance.
*/
activateAddon( $button, previousModal ) {
const path = $button.data( 'path' ),
pluginType = $button.data( 'type' ),
nonce = $button.data( 'nonce' ),
hideOnSuccess = $button.data( 'hide-on-success' );
$.post(
wpforms_education.ajax_url,
{
action: 'wpforms_activate_addon',
nonce,
plugin: path,
type: pluginType,
},
function( res ) {
previousModal.close();
if ( res.success ) {
if ( hideOnSuccess ) {
$button.hide();
}
app.saveModal( pluginType === 'plugin' ? wpforms_education.plugin_activated : wpforms_education.addon_activated );
} else {
$.alert( {
title: false,
content: res.data,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_education.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
}
}
);
},
/**
* Ask user if they would like to save form and refresh form builder.
*
* @since 1.7.0
*
* @param {string} title Modal title.
* @param {string|boolean} content Modal content.
* @param {Object} args Additional arguments.
*/
saveModal( title, content = false, args = undefined ) {
title = title || wpforms_education.addon_activated;
content = content || wpforms_education.save_prompt;
$.alert( {
title: title.replace( /\.$/, '' ), // Remove a dot in the title end.
content,
icon: 'fa fa-check-circle',
type: 'green',
buttons: {
confirm: {
text: args?.saveConfirm || wpforms_education.save_confirm,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
if ( typeof WPFormsBuilder === 'undefined' ) {
location.reload();
return;
}
this.$$confirm
.prop( 'disabled', true )
.html( spinner + wpforms_education.saving );
this.$$cancel
.prop( 'disabled', true );
if ( WPFormsBuilder.formIsSaved() ) {
app.redirect( args?.redirectUrl );
return;
}
const saveForm = WPFormsBuilder.formSave( false );
if ( ! saveForm ) {
return true;
}
saveForm.done( function() {
app.redirect( args?.redirectUrl );
} );
return false;
},
},
cancel: {
text: wpforms_education.close,
action() {
/**
* Triggers an event to notify that the education save modal has been closed.
*
* @since 1.9.6.1
*/
$( document ).trigger( 'wpformsEducationSaveModalClose' );
},
},
},
} );
},
/**
* Redirect to URL or reload the page.
*
* @since 1.9.2
*
* @param {string} url Redirect URL.
*/
redirect( url ) {
if ( url ) {
location.href = url;
} else {
location.reload();
}
},
/**
* Addon install modal.
*
* @since 1.7.0
*
* @param {jQuery} $button jQuery button element.
*/
installModal( $button ) {
const feature = $button.data( 'name' ),
url = $button.data( 'url' );
if ( ! url || '' === url ) {
wpf.debug( `Couldn't install the ${ feature } addon: Empty install URL.` );
return;
}
const canInstallAddons = wpforms_education.can_install_addons,
message = $button.data( 'message' );
$.alert( {
title: false,
content: message ? message : wpforms_education.install_prompt.replace( /%name%/g, feature ),
icon: 'fa fa-info-circle',
type: 'blue',
boxWidth: '425px',
buttons: {
confirm: {
text: wpforms_education.install_confirm,
btnClass: 'btn-confirm' + ( ! canInstallAddons ? ' hidden' : '' ),
keys: [ 'enter' ],
isHidden: ! canInstallAddons,
action() {
this.$$confirm.prop( 'disabled', true )
.html( spinner + wpforms_education.installing );
this.$$cancel
.prop( 'disabled', true );
app.installAddon( $button, this );
return false;
},
},
cancel: {
text: wpforms_education.cancel,
action() {
$( document ).trigger( 'wpformsEducationModalClose', $button );
},
},
},
} );
},
/**
* Inform customer about incompatible addon modal.
*
* @since 1.9.4
*
* @param {jQuery} $button jQuery button element.
*/
incompatibleModal( $button ) {
const title = wpforms_education.addon_incompatible.title;
const content = $button.data( 'message' ) || wpforms_education.addon_error;
$.alert( {
title,
content,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_education.addon_incompatible.button_text,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
action() {
if ( typeof WPFormsBuilder === 'undefined' ) {
app.redirect( wpforms_education.addon_incompatible.button_url );
return false;
}
this.$$confirm
.prop( 'disabled', true )
.html( spinner + this.$$confirm.text() );
this.$$cancel
.prop( 'disabled', true );
if ( WPFormsBuilder.formIsSaved() ) {
app.redirect( wpforms_education.addon_incompatible.button_url );
return false;
}
const saveForm = WPFormsBuilder.formSave( false );
if ( ! saveForm ) {
return false;
}
saveForm.done( function() {
app.redirect( wpforms_education.addon_incompatible.button_url );
} );
return false;
},
},
cancel: {
text: wpforms_education.cancel,
action() {
$( document ).trigger( 'wpformsEducationModalClose', $button );
},
},
},
} );
},
/**
* Install addon via AJAX.
*
* @since 1.7.0
*
* @param {jQuery} $button Button object.
* @param {Object} previousModal Previous modal instance.
*/
installAddon( $button, previousModal ) {
const url = $button.data( 'url' ),
pluginType = $button.data( 'type' ),
nonce = $button.data( 'nonce' ),
hideOnSuccess = $button.data( 'hide-on-success' );
$.post(
wpforms_education.ajax_url,
{
action: 'wpforms_install_addon',
nonce,
plugin: url,
type: pluginType,
},
function( res ) {
previousModal.close();
if ( res.success ) {
if ( hideOnSuccess ) {
$button.hide();
}
app.saveModal( res.data.msg );
} else {
let message = res.data;
if ( 'object' === typeof res.data ) {
message = wpforms_education.addon_error;
}
$.alert( {
title: false,
content: message,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_education.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
}
}
);
},
/**
* Get upgrade modal width.
*
* @since 1.7.3
*
* @param {boolean} isVideoModal Upgrade a modal type (with video or not).
*
* @return {string} Modal width in pixels.
*/
getUpgradeModalWidth( isVideoModal ) {
const windowWidth = $( window ).width();
if ( windowWidth <= 300 ) {
return '250px';
}
if ( windowWidth <= 750 ) {
return '350px';
}
if ( ! isVideoModal || windowWidth <= 1024 ) {
return '550px';
}
return windowWidth > 1070 ? '1040px' : '994px';
},
/**
* Error modal.
*
* @since 1.7.6
*
* @param {string} title Modal title.
* @param {string} content Modal content.
*/
errorModal( title, content ) {
$.alert( {
title: title || false,
content,
icon: 'fa fa-exclamation-circle',
type: 'orange',
buttons: {
confirm: {
text: wpforms_education.close,
btnClass: 'btn-confirm',
keys: [ 'enter' ],
},
},
} );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery ) );
WPFormsEducation.core.init();
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
File diff suppressed because one or more lines are too long
@@ -0,0 +1,476 @@
/* global wpforms_edit_post_education */
// noinspection ES6ConvertVarToLetConst
/**
* WPForms Edit Post Education function.
*
* @since 1.8.1
*/
// eslint-disable-next-line no-var, no-unused-vars
var WPFormsEditPostEducation = window.WPFormsEditPostEducation || ( function( document, window, $ ) {
// The identifiers for the Redux stores.
const coreEditSite = 'core/edit-site',
coreEditor = 'core/editor',
coreBlockEditor = 'core/block-editor',
coreNotices = 'core/notices',
// Heading block name.
coreHeading = 'core/heading';
/**
* Public functions and properties.
*
* @since 1.8.1
*
* @type {Object}
*/
const app = {
/**
* Determine if the notice was shown before.
*
* @since 1.8.1
*/
isNoticeVisible: false,
/**
* Identifier for the plugin and notice.
*
* @since 1.9.5
*/
pluginId: 'wpforms-edit-post-product-education-guide',
/**
* Start the engine.
*
* @since 1.8.1
*/
init() {
$( window ).on( 'load', function() {
// In the case of jQuery 3.+, we need to wait for a ready event first.
if ( typeof $.ready.then === 'function' ) {
$.ready.then( app.load );
} else {
app.load();
}
} );
},
/**
* Page load.
*
* @since 1.8.1
* @since 1.9.5 Added compatibility for the Site Editor.
*/
load() {
if ( ! app.isGutenbergEditor() ) {
app.maybeShowClassicNotice();
app.bindClassicEvents();
return;
}
app.maybeShowGutenbergNotice();
// "core/edit-site" store available only in the Site Editor.
if ( !! wp.data.select( coreEditSite ) ) {
app.subscribeForSiteEditor();
return;
}
app.subscribeForBlockEditor();
},
/**
* This method listens for changes in the WordPress data store and performs the following actions:
* - Monitors the editor title and focus mode to detect changes.
* - Dismisses a custom notice if the focus mode is disabled and the notice is visible.
* - Shows a custom Gutenberg notice if the title or focus mode changes.
*
* @since 1.9.5
*/
subscribeForSiteEditor() {
// Store the initial editor title and focus mode state.
let prevTitle = app.getEditorTitle();
let prevFocusMode = null;
const { subscribe, select, dispatch } = wp.data;
// Listen for changes in the WordPress data store.
subscribe( () => {
// Fetch the current editor mode setting.
// If true - Site Editor canvas is opened, and you can edit something.
// If false - you should see the sidebar with navigation and preview
// with selected template or page.
const { focusMode } = select( coreEditor ).getEditorSettings();
// If focus mode is disabled and a notice is visible, remove the notice.
// This is essential because user can switch pages / templates
// without a page-reload.
if ( ! focusMode && app.isNoticeVisible ) {
app.isNoticeVisible = false;
prevFocusMode = focusMode;
dispatch( coreNotices ).removeNotice( app.pluginId );
}
const title = app.getEditorTitle();
// If neither the title nor the focus mode has changed, do nothing.
if ( prevTitle === title && prevFocusMode === focusMode ) {
return;
}
// Update the previous title and focus mode values for the next subscription cycle.
prevTitle = title;
prevFocusMode = focusMode;
// Show a custom Gutenberg notice if conditions are met.
app.maybeShowGutenbergNotice();
} );
},
/**
* Subscribes to changes in the WordPress block editor and monitors the editor's title.
* When the title changes, it triggers a process to potentially display a Gutenberg notice.
* The subscription is automatically stopped if the notice becomes visible.
*
* @since 1.9.5
*/
subscribeForBlockEditor() {
let prevTitle = app.getEditorTitle();
const { subscribe } = wp.data;
// Subscribe to WordPress data changes.
const unsubscribe = subscribe( () => {
const title = app.getEditorTitle();
// Check if the title has changed since the previous value.
if ( prevTitle === title ) {
return;
}
// Update the previous title to the current title.
prevTitle = title;
app.maybeShowGutenbergNotice();
// If the notice is visible, stop the WordPress data subscription.
if ( app.isNoticeVisible ) {
unsubscribe();
}
} );
},
/**
* Retrieves the title of the post currently being edited. If in the Site Editor,
* it attempts to fetch the title from the topmost heading block. Otherwise, it
* retrieves the title attribute of the edited post.
*
* @since 1.9.5
*
* @return {string} The post title or an empty string if no title is found.
*/
getEditorTitle() {
const { select } = wp.data;
// Retrieve the title for Post Editor.
if ( ! select( coreEditSite ) ) {
return select( coreEditor ).getEditedPostAttribute( 'title' );
}
if ( app.isEditPostFSE() ) {
return app.getPostTitle();
}
return app.getTopmostHeadingTitle();
},
/**
* Retrieves the content of the first heading block.
*
* @since 1.9.5
*
* @return {string} The topmost heading content or null if not found.
*/
getTopmostHeadingTitle() {
const { select } = wp.data;
const headings = select( coreBlockEditor ).getBlocksByName( coreHeading );
if ( ! headings.length ) {
return '';
}
const headingBlock = select( coreBlockEditor ).getBlock( headings[ 0 ] );
return headingBlock?.attributes?.content?.text ?? '';
},
/**
* Determines if the current editing context is for a post type in the Full Site Editor (FSE).
*
* @since 1.9.5
*
* @return {boolean} True if the current context represents a post type in the FSE, otherwise false.
*/
isEditPostFSE() {
const { select } = wp.data;
const { context } = select( coreEditSite ).getPage();
return !! context?.postType;
},
/**
* Retrieves the title of a post based on its type and ID from the current editing context.
*
* @since 1.9.5
*
* @return {string} The title of the post.
*/
getPostTitle() {
const { select } = wp.data;
const { context } = select( coreEditSite ).getPage();
// Use `getEditedEntityRecord` instead of `getEntityRecord`
// to fetch the live, updated data for the post being edited.
const { title = '' } = select( 'core' ).getEditedEntityRecord(
'postType',
context.postType,
context.postId
) || {};
return title;
},
/**
* Bind events for Classic Editor.
*
* @since 1.8.1
*/
bindClassicEvents() {
const $document = $( document );
if ( ! app.isNoticeVisible ) {
$document.on( 'input', '#title', _.debounce( app.maybeShowClassicNotice, 1000 ) );
}
$document.on( 'click', '.wpforms-edit-post-education-notice-close', app.closeNotice );
},
/**
* Determine if the editor is Gutenberg.
*
* @since 1.8.1
*
* @return {boolean} True if the editor is Gutenberg.
*/
isGutenbergEditor() {
return typeof wp !== 'undefined' && typeof wp.blocks !== 'undefined';
},
/**
* Create a notice for Gutenberg.
*
* @since 1.8.1
*/
showGutenbergNotice() {
wp.data.dispatch( coreNotices ).createInfoNotice(
wpforms_edit_post_education.gutenberg_notice.template,
app.getGutenbergNoticeSettings()
);
// The notice component doesn't have a way to add HTML id or class to the notice.
// Also, the notice became visible with a delay on old Gutenberg versions.
const hasNotice = setInterval( function() {
const noticeBody = $( '.wpforms-edit-post-education-notice-body' );
if ( ! noticeBody.length ) {
return;
}
const $notice = noticeBody.closest( '.components-notice' );
$notice.addClass( 'wpforms-edit-post-education-notice' );
$notice.find( '.is-secondary, .is-link' ).removeClass( 'is-secondary' ).removeClass( 'is-link' ).addClass( 'is-primary' );
// We can't use onDismiss callback as it was introduced in WordPress 6.0 only.
const dismissButton = $notice.find( '.components-notice__dismiss' );
if ( dismissButton ) {
dismissButton.on( 'click', function() {
app.updateUserMeta();
} );
}
clearInterval( hasNotice );
}, 100 );
},
/**
* Get settings for the Gutenberg notice.
*
* @since 1.8.1
*
* @return {Object} Notice settings.
*/
getGutenbergNoticeSettings() {
const noticeSettings = {
id: app.pluginId,
isDismissible: true,
HTML: true,
__unstableHTML: true,
actions: [
{
className: 'wpforms-edit-post-education-notice-guide-button',
variant: 'primary',
label: wpforms_edit_post_education.gutenberg_notice.button,
},
],
};
if ( ! wpforms_edit_post_education.gutenberg_guide ) {
noticeSettings.actions[ 0 ].url = wpforms_edit_post_education.gutenberg_notice.url;
return noticeSettings;
}
const { Guide } = wp.components,
{ useState } = wp.element,
{ registerPlugin, unregisterPlugin } = wp.plugins;
const GutenbergTutorial = function() {
const [ isOpen, setIsOpen ] = useState( true );
if ( ! isOpen ) {
return null;
}
return (
// eslint-disable-next-line react/react-in-jsx-scope
<Guide
className="edit-post-welcome-guide"
onFinish={ () => {
unregisterPlugin( app.pluginId );
setIsOpen( false );
} }
pages={ app.getGuidePages() }
/>
);
};
noticeSettings.actions[ 0 ].onClick = () => registerPlugin( app.pluginId, { render: GutenbergTutorial } );
return noticeSettings;
},
/**
* Get Guide pages in proper format.
*
* @since 1.8.1
*
* @return {Array} Guide Pages.
*/
getGuidePages() {
const pages = [];
wpforms_edit_post_education.gutenberg_guide.forEach( function( page ) {
pages.push(
{
/* eslint-disable react/react-in-jsx-scope */
content: (
<>
<h1 className="edit-post-welcome-guide__heading">{ page.title }</h1>
<p className="edit-post-welcome-guide__text">{ page.content }</p>
</>
),
image: <img className="edit-post-welcome-guide__image" src={ page.image } alt={ page.title } />,
/* eslint-enable react/react-in-jsx-scope */
}
);
} );
return pages;
},
/**
* Show notice if the page title matches some keywords for Classic Editor.
*
* @since 1.8.1
*/
maybeShowClassicNotice() {
if ( app.isNoticeVisible ) {
return;
}
if ( app.isTitleMatchKeywords( $( '#title' ).val() ) ) {
app.isNoticeVisible = true;
$( '.wpforms-edit-post-education-notice' ).removeClass( 'wpforms-hidden' );
}
},
/**
* Show notice if the page title matches some keywords for Gutenberg Editor.
*
* @since 1.8.1
*/
maybeShowGutenbergNotice() {
if ( app.isNoticeVisible ) {
return;
}
const title = app.getEditorTitle();
if ( app.isTitleMatchKeywords( title ) ) {
app.isNoticeVisible = true;
app.showGutenbergNotice();
}
},
/**
* Determine if the title matches keywords.
*
* @since 1.8.1
*
* @param {string} titleValue Page title value.
*
* @return {boolean} True if the title matches some keywords.
*/
isTitleMatchKeywords( titleValue ) {
const expectedTitleRegex = new RegExp( /\b(contact|form)\b/i );
return expectedTitleRegex.test( titleValue );
},
/**
* Close a notice.
*
* @since 1.8.1
*/
closeNotice() {
$( this ).closest( '.wpforms-edit-post-education-notice' ).remove();
app.updateUserMeta();
},
/**
* Update user meta and don't show the notice next time.
*
* @since 1.8.1
*/
updateUserMeta() {
$.post(
wpforms_edit_post_education.ajax_url,
{
action: 'wpforms_education_dismiss',
nonce: wpforms_edit_post_education.education_nonce,
section: 'edit-post-notice',
}
);
},
};
return app;
}( document, window, jQuery ) );
WPFormsEditPostEducation.init();
@@ -0,0 +1,127 @@
/**
* PDF Education.
*
* @since 1.9.7.3
*
* @param {Window} window The global window object.
* @param {jQuery} $ The jQuery object.
*/
( function( window, $ ) {
const app = {
/**
* The whole popup element.
*
* @since 1.9.7.3
*
* @type {jQuery}
*/
$popup: null,
/**
* The builder element.
*
* @since 1.9.7.3
*
* @type {jQuery}
*/
$builder: null,
/**
* The close button element inside the popup. Closes popup.
*
* @since 1.9.7.3
*
* @type {jQuery}
*/
$close: null,
/**
* The button element inside the popup. Triggers the PDF panel.
*
* @since 1.9.7.3
*
* @type {jQuery}
*/
$switchButton: null,
/**
* The notification section element.
* This is what a user sees when clicking on Builder > Settings > Notifications.
*
* @since 1.9.7.3
*
* @type {jQuery}
*/
$notifications: null,
/**
* The PDF panel element.
* This is what a user sees when clicking on Builder > Settings > PDF.
*
* @since 1.9.7.3
*
* @type {jQuery}
*/
$pdfPanel: null,
/**
* Initializes the app.
*
* @since 1.9.7.3
*/
init() {
$( app.ready );
},
/**
* Document ready.
*
* @since 1.9.7.3
*/
ready() {
app.$popup = $( '#wpforms-pdf-popup' );
app.$close = app.$popup.find( '.close-popup' );
app.$switchButton = app.$popup.find( 'button.education-modal[data-target="wpforms-pdf"]' );
app.$notifications = $( '.wpforms-panel-content-section.wpforms-panel-content-section-notifications' );
app.$builder = $( '#wpforms-builder' );
app.$pdfPanel = $( '.wpforms-panel-sidebar-section.wpforms-panel-sidebar-section-pdf' );
app.run();
},
/**
* Runs the app.
*
* @since 1.9.7.3
*/
run() {
/*
* User clicked on one of the subsections in Builder > Settings.
*/
app.$builder.on( 'wpformsPanelSectionSwitch', function( e, section ) {
if ( section === 'default' || ! section ) {
return;
}
app.$popup.toggle( section === 'notifications' );
} );
/*
* User clicked on the left dark sidebar in Builder.
*/
app.$builder.on( 'wpformsPanelSwitched', () => {
app.$popup.toggle( app.$notifications.is( ':visible' ) );
} );
/*
* User clicked on the 'Try it Out' button.
*/
app.$switchButton.on( 'click', function( e ) {
e.preventDefault();
app.$pdfPanel.click();
} );
},
};
app.init();
}( window, jQuery ) );
+1
View File
@@ -0,0 +1 @@
(n=>{let i={$popup:null,$builder:null,$close:null,$switchButton:null,$notifications:null,$pdfPanel:null,init(){n(i.ready)},ready(){i.$popup=n("#wpforms-pdf-popup"),i.$close=i.$popup.find(".close-popup"),i.$switchButton=i.$popup.find('button.education-modal[data-target="wpforms-pdf"]'),i.$notifications=n(".wpforms-panel-content-section.wpforms-panel-content-section-notifications"),i.$builder=n("#wpforms-builder"),i.$pdfPanel=n(".wpforms-panel-sidebar-section.wpforms-panel-sidebar-section-pdf"),i.run()},run(){i.$builder.on("wpformsPanelSectionSwitch",function(n,o){"default"!==o&&o&&i.$popup.toggle("notifications"===o)}),i.$builder.on("wpformsPanelSwitched",()=>{i.$popup.toggle(i.$notifications.is(":visible"))}),i.$switchButton.on("click",function(n){n.preventDefault(),i.$pdfPanel.click()})}};i.init()})((window,jQuery));
@@ -0,0 +1,133 @@
/* eslint-disable camelcase */
/* global ajaxurl, wpforms_education_pointers_payment */
// noinspection ES6ConvertVarToLetConst
/**
* Module for handling education pointers related to payments in WPForms.
*
* @since 1.8.8
*/
// eslint-disable-next-line no-var
var WPFormsPointersPayment = window.WPFormsPointersPayment || ( function( document, window, $, l10n ) {
/**
* Elements holder.
*
* @since 1.8.8
*
* @type {Object}
*/
const el = {};
/**
* Runtime variables.
*
* @since 1.8.8
*
* @type {Object}
*/
const vars = {
/**
* Unique ID for the pointer.
*
* @since 1.8.8
*/
pointerId: l10n.pointer,
/**
* Cryptographic token for validating authorized Ajax data exchange.
*
* @since 1.8.8
*/
nonce: l10n.nonce,
};
/**
* Public functions and properties.
*
* @since 1.8.8
*/
const app = {
/**
* Start the engine.
*
* @since 1.8.8
*/
init() {
$( app.ready );
},
/**
* Document ready.
*
* @since 1.8.8
*/
ready() {
app.setup();
app.bindEvents();
},
/**
* Setup. Prepare some variables.
*
* @since 1.8.8
*/
setup() {
// Cache DOM elements.
el.$document = $( document );
},
/**
* Bind events.
*
* @since 1.8.8
*/
bindEvents() {
el.$document.on( 'click', '#toplevel_page_wpforms-overview [href$="-payments"], #wpforms-education-pointers-payments', app.handleOnClick );
},
/**
* Callback for clicking on the action link.
*
* @since 1.8.8
*
* @param {Object} event An event which takes place in the DOM.
*/
handleOnClick( event ) {
// Prevent the default action.
event.preventDefault();
const $this = $( this );
// Get the href attribute.
const href = $this.attr( 'href' );
// Return early if href is missing.
if ( ! href ) {
return;
}
// Hide the pointer before redirecting.
$this.closest( '.wp-pointer-content' ).parent().hide();
// Send AJAX request.
$.post(
ajaxurl,
{
pointer_id: vars.pointerId,
_ajax_nonce: vars.nonce,
action: 'wpforms_education_pointers_engagement',
}
).done( function() {
window.location.href = href;
} );
},
};
// Provide access to public functions/properties.
return app;
}( document, window, jQuery, wpforms_education_pointers_payment ) );
// Initialize.
WPFormsPointersPayment.init();
@@ -0,0 +1 @@
var WPFormsPointersPayment=window.WPFormsPointersPayment||((e,t,o,n)=>{let i={},r={pointerId:n.pointer,nonce:n.nonce},a={init(){o(a.ready)},ready(){a.setup(),a.bindEvents()},setup(){i.$document=o(e)},bindEvents(){i.$document.on("click",'#toplevel_page_wpforms-overview [href$="-payments"], #wpforms-education-pointers-payments',a.handleOnClick)},handleOnClick(e){e.preventDefault();e=o(this);let n=e.attr("href");n&&(e.closest(".wp-pointer-content").parent().hide(),o.post(ajaxurl,{pointer_id:r.pointerId,_ajax_nonce:r.nonce,action:"wpforms_education_pointers_engagement"}).done(function(){t.location.href=n}))}};return a})(document,window,jQuery,wpforms_education_pointers_payment);WPFormsPointersPayment.init();