From c91867bf56af8135ecab3f1d781caaa2d06c2084 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Rabbi=20Islam=20Rony=20=E2=9A=A1=EF=B8=8F?= <35329385+RabbiIslamRony@users.noreply.github.com> Date: Sun, 9 Aug 2026 16:29:30 +0600 Subject: [PATCH] improve: add listing image focal point controls --- assets/src/js/global/add-listing.js | 19 + .../js/global/components/image-focal-point.js | 838 ++++++++++++++++++ assets/src/scss/component/_add-listing.scss | 433 +++++++++ .../component/listings/_listing-card.scss | 9 + includes/asset-loader/localized_data.php | 24 +- includes/classes/class-add-listing.php | 32 +- .../classes/class-submission-controller.php | 36 +- includes/helper-functions.php | 340 +++++++ includes/model/ListingDashboard.php | 25 +- includes/model/Listings.php | 31 +- includes/model/SingleListing.php | 15 +- .../builder-preset-fields.php | 6 + .../Version1/class-listings-controller.php | 1 + .../Version2/class-listings-controller.php | 1 + templates/archive/fields/google-map.php | 10 +- templates/archive/fields/openstreet-map.php | 12 +- templates/archive/fields/thumb-card.php | 73 +- .../listing-form/fields/image_upload.php | 11 +- 18 files changed, 1810 insertions(+), 106 deletions(-) create mode 100644 assets/src/js/global/components/image-focal-point.js diff --git a/assets/src/js/global/add-listing.js b/assets/src/js/global/add-listing.js index b0b09330bc..e2b1a88070 100644 --- a/assets/src/js/global/add-listing.js +++ b/assets/src/js/global/add-listing.js @@ -15,6 +15,11 @@ import { watchFieldChanges as watchFieldChangesBase, } from './components/conditional-logic'; import debounce from './components/debounce'; +import { + appendImageFocalPointData, + initImageFocalPointDeepLink, + initImageFocalPointPicker, +} from './components/image-focal-point'; /* eslint-disable */ const $ = jQuery; @@ -561,11 +566,17 @@ $(function () { }); EzUploader.init(); + initImageFocalPointPicker( + EzUploader, + localized_data.i18n_text + ); // mediaUploaders[i].media_uploader.init(); } } } + initImageFocalPointDeepLink(); + let FORM_ON_PROCESSING = false; let has_media = true; let quickLoginModalSuccessCallback = null; @@ -816,6 +827,14 @@ $(function () { }); } + mediaUploaders.forEach(function (uploaderEntry) { + appendImageFocalPointData( + form_data, + uploaderEntry, + uploadedImages + ); + }); + // categories var categories = $form.find('#at_biz_dir-categories').val(); if (Array.isArray(categories) && categories.length) { diff --git a/assets/src/js/global/components/image-focal-point.js b/assets/src/js/global/components/image-focal-point.js new file mode 100644 index 0000000000..dc6a09d796 --- /dev/null +++ b/assets/src/js/global/components/image-focal-point.js @@ -0,0 +1,838 @@ +const DEFAULT_FOCAL_POINT = { x: 50, y: 50 }; + +let modalIndex = 0; + +function clamp( value ) { + return Math.min( 100, Math.max( 0, Number( value ) || 0 ) ); +} + +function normalizeFocalPoint( focalPoint = DEFAULT_FOCAL_POINT ) { + return { + x: Math.round( clamp( focalPoint.x ) * 100 ) / 100, + y: Math.round( clamp( focalPoint.y ) * 100 ) / 100, + }; +} + +function isDefaultFocalPoint( focalPoint = DEFAULT_FOCAL_POINT ) { + const normalizedPoint = normalizeFocalPoint( focalPoint ); + + return ( + normalizedPoint.x === DEFAULT_FOCAL_POINT.x && + normalizedPoint.y === DEFAULT_FOCAL_POINT.y + ); +} + +function isEnabled( value ) { + return ! [ false, 0, '0', 'false' ].includes( value ); +} + +function getUploaderConfig( mediaUploader ) { + const container = mediaUploader?.container?.[ 0 ]; + + if ( ! container ) { + return {}; + } + + try { + return JSON.parse( container.getAttribute( 'data-uploader' ) || '{}' ); + } catch ( error ) { + return {}; + } +} + +function getMetaById( mediaUploader, id ) { + return mediaUploader.filesMeta.find( + ( fileMeta ) => String( fileMeta.id ) === String( id ) + ); +} + +function createButton( className, label ) { + const button = document.createElement( 'button' ); + button.type = 'button'; + button.className = className; + button.textContent = label; + + return button; +} + +function getText( dictionary, key, fallback ) { + return dictionary?.[ key ] || fallback; +} + +function normalizePreviewDisplay( display = {} ) { + const allowedModes = [ 'cover', 'contain', 'full' ]; + const allowedSizeMethods = [ 'px', 'ratio' ]; + const allowedBackgrounds = [ 'blur', 'color' ]; + + return { + mode: allowedModes.includes( display.mode ) ? display.mode : 'cover', + sizeBy: allowedSizeMethods.includes( display.size_by ) + ? display.size_by + : 'px', + width: Math.max( 1, Number( display.width ) || 360 ), + height: Math.max( 1, Number( display.height ) || 300 ), + backgroundType: allowedBackgrounds.includes( display.background_type ) + ? display.background_type + : 'blur', + backgroundColor: display.background_color || '#fff', + }; +} + +function getModeHelp( dictionary, mode ) { + const help = { + contain: getText( + dictionary, + 'image_focus_help_contain', + 'The complete image remains visible. Focus controls its alignment and is also used on cropped thumbnail surfaces.' + ), + cover: getText( + dictionary, + 'image_focus_help_cover', + 'Focus controls which part of the Preview image remains visible when the card crops it.' + ), + full: getText( + dictionary, + 'image_focus_help_full', + 'The Original archive image is uncropped. Focus is saved for maps, dashboard thumbnails, and other cropped surfaces.' + ), + }; + + return help[ mode ] || help.cover; +} + +function createFocalPointToolbar( dictionary, display ) { + const toolbar = document.createElement( 'div' ); + toolbar.className = 'directorist-image-focus-toolbar'; + toolbar.hidden = true; + toolbar.setAttribute( + 'aria-label', + getText( dictionary, 'image_focus_status_label', 'Image focus' ) + ); + + const summary = document.createElement( 'div' ); + summary.className = 'directorist-image-focus-toolbar__summary'; + + const heading = document.createElement( 'div' ); + heading.className = 'directorist-image-focus-toolbar__heading'; + + const label = document.createElement( 'strong' ); + label.className = 'directorist-image-focus-toolbar__label'; + label.textContent = getText( + dictionary, + 'image_focus_status_label', + 'Image focus' + ); + + const status = document.createElement( 'span' ); + status.className = 'directorist-image-focus-toolbar__status'; + status.setAttribute( 'aria-live', 'polite' ); + + const description = document.createElement( 'p' ); + description.className = 'directorist-image-focus-toolbar__description'; + description.textContent = getModeHelp( dictionary, display.mode ); + + heading.append( label, status ); + summary.append( heading, description ); + + const actions = document.createElement( 'div' ); + actions.className = 'directorist-image-focus-toolbar__actions'; + + const editButton = createButton( + 'directorist-btn directorist-btn-sm directorist-btn-primary directorist-image-focus-toolbar__edit', + getText( dictionary, 'set_image_focus', 'Set image focus' ) + ); + const resetButton = createButton( + 'directorist-btn directorist-btn-sm directorist-btn-outline-primary directorist-image-focus-toolbar__reset', + getText( dictionary, 'image_focus_reset', 'Reset to center' ) + ); + + actions.append( editButton, resetButton ); + toolbar.append( summary, actions ); + + return { + editButton, + resetButton, + status, + toolbar, + }; +} + +function createFocalPointModal( dictionary, display ) { + modalIndex += 1; + + const titleId = `directorist-image-focus-title-${ modalIndex }`; + const modal = document.createElement( 'div' ); + modal.className = 'directorist-image-focus-modal'; + modal.hidden = true; + modal.setAttribute( 'role', 'dialog' ); + modal.setAttribute( 'aria-modal', 'true' ); + modal.setAttribute( 'aria-labelledby', titleId ); + + const backdrop = document.createElement( 'div' ); + backdrop.className = 'directorist-image-focus-modal__backdrop'; + + const dialog = document.createElement( 'div' ); + dialog.className = 'directorist-image-focus-modal__dialog'; + + const header = document.createElement( 'div' ); + header.className = 'directorist-image-focus-modal__header'; + + const headingWrap = document.createElement( 'div' ); + const title = document.createElement( 'h2' ); + title.id = titleId; + title.className = 'directorist-image-focus-modal__title'; + title.textContent = getText( + dictionary, + 'image_focus_title', + 'Image focal point' + ); + + const description = document.createElement( 'p' ); + description.className = 'directorist-image-focus-modal__description'; + description.textContent = getModeHelp( dictionary, display.mode ); + + headingWrap.append( title, description ); + + const closeButton = createButton( + 'directorist-image-focus-modal__close', + '×' + ); + closeButton.setAttribute( + 'aria-label', + getText( dictionary, 'image_focus_close', 'Close focal point editor' ) + ); + + header.append( headingWrap, closeButton ); + + const body = document.createElement( 'div' ); + body.className = 'directorist-image-focus-modal__body'; + + const editorColumn = document.createElement( 'div' ); + editorColumn.className = 'directorist-image-focus-modal__editor'; + + const stage = createButton( 'directorist-image-focus-stage', '' ); + stage.setAttribute( + 'aria-label', + getText( + dictionary, + 'image_focus_move', + 'Move the focal point. Use the arrow keys for precise adjustment.' + ) + ); + + const stageImage = document.createElement( 'img' ); + stageImage.className = 'directorist-image-focus-stage__image'; + stageImage.alt = ''; + + const marker = document.createElement( 'span' ); + marker.className = 'directorist-image-focus-stage__marker'; + marker.setAttribute( 'aria-hidden', 'true' ); + stage.append( stageImage, marker ); + + const controls = document.createElement( 'div' ); + controls.className = 'directorist-image-focus-controls'; + + const xLabel = document.createElement( 'label' ); + xLabel.className = 'directorist-image-focus-controls__label'; + const xLabelText = document.createElement( 'span' ); + xLabelText.textContent = getText( + dictionary, + 'image_focus_horizontal', + 'Horizontal position' + ); + const xValue = document.createElement( 'output' ); + const xRange = document.createElement( 'input' ); + xRange.type = 'range'; + xRange.min = '0'; + xRange.max = '100'; + xRange.step = '1'; + xRange.className = 'directorist-image-focus-controls__range'; + xLabel.append( xLabelText, xValue, xRange ); + + const yLabel = document.createElement( 'label' ); + yLabel.className = 'directorist-image-focus-controls__label'; + const yLabelText = document.createElement( 'span' ); + yLabelText.textContent = getText( + dictionary, + 'image_focus_vertical', + 'Vertical position' + ); + const yValue = document.createElement( 'output' ); + const yRange = document.createElement( 'input' ); + yRange.type = 'range'; + yRange.min = '0'; + yRange.max = '100'; + yRange.step = '1'; + yRange.className = 'directorist-image-focus-controls__range'; + yLabel.append( yLabelText, yValue, yRange ); + + controls.append( xLabel, yLabel ); + editorColumn.append( stage, controls ); + + const previewColumn = document.createElement( 'div' ); + previewColumn.className = 'directorist-image-focus-modal__preview-column'; + + const previewTitle = document.createElement( 'h3' ); + previewTitle.className = 'directorist-image-focus-modal__preview-title'; + previewTitle.textContent = getText( + dictionary, + 'image_focus_preview', + 'Listing card preview' + ); + + const preview = document.createElement( 'div' ); + preview.className = `directorist-image-focus-preview directorist-image-focus-preview--${ display.mode }`; + + if ( display.mode !== 'full' ) { + preview.style.aspectRatio = `${ display.width } / ${ display.height }`; + } + + if ( display.mode === 'contain' && display.backgroundType === 'color' ) { + preview.style.backgroundColor = display.backgroundColor; + } + + const previewBackground = document.createElement( 'img' ); + previewBackground.className = 'directorist-image-focus-preview__background'; + previewBackground.alt = ''; + + const previewImage = document.createElement( 'img' ); + previewImage.className = 'directorist-image-focus-preview__image'; + previewImage.alt = ''; + + if ( display.mode === 'contain' && display.backgroundType === 'blur' ) { + preview.appendChild( previewBackground ); + } + + preview.appendChild( previewImage ); + + const presets = document.createElement( 'div' ); + presets.className = 'directorist-image-focus-presets'; + + [ + { + label: getText( dictionary, 'image_focus_top', 'Top' ), + x: 50, + y: 0, + }, + { + label: getText( dictionary, 'image_focus_center', 'Center' ), + x: 50, + y: 50, + }, + { + label: getText( dictionary, 'image_focus_bottom', 'Bottom' ), + x: 50, + y: 100, + }, + ].forEach( ( preset ) => { + const button = createButton( + 'directorist-image-focus-presets__button', + preset.label + ); + button.dataset.x = preset.x; + button.dataset.y = preset.y; + presets.appendChild( button ); + } ); + + const resetButton = createButton( + 'directorist-image-focus-modal__reset', + getText( dictionary, 'image_focus_reset', 'Reset to center' ) + ); + + previewColumn.append( previewTitle, preview, presets, resetButton ); + body.append( editorColumn, previewColumn ); + + const footer = document.createElement( 'div' ); + footer.className = 'directorist-image-focus-modal__footer'; + const cancelButton = createButton( + 'directorist-btn directorist-btn-sm directorist-btn-outline-primary', + getText( dictionary, 'image_focus_cancel', 'Cancel' ) + ); + const doneButton = createButton( + 'directorist-btn directorist-btn-sm directorist-btn-primary', + getText( dictionary, 'image_focus_done', 'Done' ) + ); + footer.append( cancelButton, doneButton ); + + dialog.append( header, body, footer ); + modal.append( backdrop, dialog ); + document.body.appendChild( modal ); + + return { + backdrop, + cancelButton, + closeButton, + doneButton, + marker, + modal, + previewBackground, + previewImage, + presets, + resetButton, + stage, + stageImage, + xRange, + xValue, + yRange, + yValue, + }; +} + +/** + * Add the focal-point editor to an initialized EZ Media Uploader instance. + * + * @param {Object} mediaUploader EZ Media Uploader instance. + * @param {Object} dictionary Localized UI strings. + * @return {void} + */ +export function initImageFocalPointPicker( mediaUploader, dictionary = {} ) { + const config = getUploaderConfig( mediaUploader ); + + if ( + ! Object.prototype.hasOwnProperty.call( + config, + 'enable_image_focus' + ) || + ! isEnabled( config.enable_image_focus ) || + ! mediaUploader?.container?.[ 0 ] + ) { + return; + } + + const savedFocalPoints = config.image_focal_points || {}; + const display = normalizePreviewDisplay( config.focus_preview_display ); + + mediaUploader.filesMeta.forEach( ( fileMeta ) => { + if ( + fileMeta.attachmentID && + savedFocalPoints[ fileMeta.attachmentID ] + ) { + const savedFocalPoint = normalizeFocalPoint( + savedFocalPoints[ fileMeta.attachmentID ] + ); + + if ( ! isDefaultFocalPoint( savedFocalPoint ) ) { + fileMeta.directoristFocalPoint = savedFocalPoint; + } + } + } ); + + mediaUploader.directoristImageFocusEnabled = true; + + const ui = createFocalPointModal( dictionary, display ); + const toolbarUi = createFocalPointToolbar( dictionary, display ); + let activeMeta = null; + let activeTrigger = null; + let previewMeta = null; + let workingPoint = { ...DEFAULT_FOCAL_POINT }; + let isDragging = false; + + mediaUploader.container?.[ 0 ]?.insertAdjacentElement( + 'afterend', + toolbarUi.toolbar + ); + + function getFocalPoint( fileMeta ) { + return normalizeFocalPoint( + fileMeta?.directoristFocalPoint || DEFAULT_FOCAL_POINT + ); + } + + function setFocalPoint( fileMeta, focalPoint ) { + if ( ! fileMeta ) { + return; + } + + const normalizedPoint = normalizeFocalPoint( focalPoint ); + + if ( isDefaultFocalPoint( normalizedPoint ) ) { + delete fileMeta.directoristFocalPoint; + return; + } + + fileMeta.directoristFocalPoint = normalizedPoint; + } + + function renderWorkingPoint() { + workingPoint = normalizeFocalPoint( workingPoint ); + ui.marker.style.left = `${ workingPoint.x }%`; + ui.marker.style.top = `${ workingPoint.y }%`; + ui.previewImage.style.objectPosition = `${ workingPoint.x }% ${ workingPoint.y }%`; + ui.previewBackground.style.objectPosition = `${ workingPoint.x }% ${ workingPoint.y }%`; + ui.xRange.value = workingPoint.x; + ui.yRange.value = workingPoint.y; + ui.xValue.textContent = `${ workingPoint.x }%`; + ui.yValue.textContent = `${ workingPoint.y }%`; + } + + function updatePointFromPointer( event ) { + const bounds = ui.stage.getBoundingClientRect(); + + if ( ! bounds.width || ! bounds.height ) { + return; + } + + workingPoint = { + x: ( ( event.clientX - bounds.left ) / bounds.width ) * 100, + y: ( ( event.clientY - bounds.top ) / bounds.height ) * 100, + }; + renderWorkingPoint(); + } + + function updateThumbnailPreview( fileMeta, focalPoint ) { + const featuredItem = mediaUploader.container[ 0 ] + .querySelector( '.ezmu__featured_tag' ) + ?.closest( '.ezmu__thumbnail-list-item' ); + + if ( ! featuredItem || ! fileMeta ) { + return; + } + + const itemMeta = getMetaById( mediaUploader, featuredItem.dataset.id ); + + if ( itemMeta !== fileMeta ) { + return; + } + + const thumbnail = featuredItem.querySelector( '.ezmu__thumbnail-img' ); + + if ( thumbnail ) { + const normalizedPoint = normalizeFocalPoint( focalPoint ); + thumbnail.style.objectPosition = `${ normalizedPoint.x }% ${ normalizedPoint.y }%`; + } + } + + function updateToolbar( fileMeta ) { + previewMeta = fileMeta || null; + toolbarUi.toolbar.hidden = ! previewMeta; + + if ( ! previewMeta ) { + return; + } + + const focalPoint = getFocalPoint( previewMeta ); + const hasCustomFocus = ! isDefaultFocalPoint( focalPoint ); + + toolbarUi.status.textContent = getText( + dictionary, + hasCustomFocus + ? 'image_focus_custom_status' + : 'image_focus_default_status', + hasCustomFocus ? 'Custom' : 'Center (default)' + ); + toolbarUi.status.classList.toggle( + 'directorist-image-focus-toolbar__status--custom', + hasCustomFocus + ); + toolbarUi.editButton.textContent = getText( + dictionary, + hasCustomFocus ? 'edit_image_focus' : 'set_image_focus', + hasCustomFocus ? 'Edit focus' : 'Set image focus' + ); + toolbarUi.resetButton.disabled = ! hasCustomFocus; + updateThumbnailPreview( previewMeta, focalPoint ); + } + + function closeModal( commit = false ) { + if ( commit && activeMeta ) { + setFocalPoint( activeMeta, workingPoint ); + updateToolbar( activeMeta ); + } + + ui.modal.hidden = true; + document.body.classList.remove( 'directorist-image-focus-modal-open' ); + + if ( activeTrigger ) { + activeTrigger.focus(); + } + + activeMeta = null; + activeTrigger = null; + } + + function openModal( fileMeta, trigger ) { + const thumbnailItem = Array.from( + mediaUploader.container[ 0 ].querySelectorAll( + '.ezmu__thumbnail-list-item' + ) + ).find( + ( item ) => + getMetaById( mediaUploader, item.dataset.id ) === fileMeta + ); + const renderedThumbnail = thumbnailItem?.querySelector( + '.ezmu__thumbnail-img' + ); + const imageSource = + fileMeta.blob || + fileMeta.url || + renderedThumbnail?.currentSrc || + renderedThumbnail?.src; + + if ( ! imageSource ) { + return; + } + + activeMeta = fileMeta; + activeTrigger = trigger; + workingPoint = getFocalPoint( fileMeta ); + ui.stageImage.onload = () => { + if ( ui.stageImage.naturalWidth && ui.stageImage.naturalHeight ) { + ui.stage.style.aspectRatio = `${ ui.stageImage.naturalWidth } / ${ ui.stageImage.naturalHeight }`; + } + }; + ui.stageImage.src = imageSource; + ui.previewImage.src = imageSource; + ui.previewBackground.src = imageSource; + + if ( ui.stageImage.complete ) { + ui.stageImage.onload(); + } + + renderWorkingPoint(); + ui.modal.hidden = false; + document.body.classList.add( 'directorist-image-focus-modal-open' ); + ui.closeButton.focus(); + } + + function decorateFeaturedImage() { + const featuredTag = mediaUploader.container[ 0 ].querySelector( + '.ezmu__featured_tag' + ); + + if ( ! featuredTag ) { + updateToolbar( null ); + return; + } + + const item = featuredTag.closest( '.ezmu__thumbnail-list-item' ); + const fileMeta = item + ? getMetaById( mediaUploader, item.dataset.id ) + : null; + + if ( ! fileMeta ) { + updateToolbar( null ); + return; + } + + updateToolbar( fileMeta ); + } + + toolbarUi.editButton.addEventListener( 'click', () => { + if ( previewMeta ) { + openModal( previewMeta, toolbarUi.editButton ); + } + } ); + + toolbarUi.resetButton.addEventListener( 'click', () => { + if ( ! previewMeta ) { + return; + } + + setFocalPoint( previewMeta, DEFAULT_FOCAL_POINT ); + updateToolbar( previewMeta ); + } ); + + ui.stage.addEventListener( 'pointerdown', ( event ) => { + isDragging = true; + ui.stage.setPointerCapture?.( event.pointerId ); + updatePointFromPointer( event ); + } ); + + ui.stage.addEventListener( 'pointermove', ( event ) => { + if ( isDragging ) { + updatePointFromPointer( event ); + } + } ); + + [ 'pointerup', 'pointercancel' ].forEach( ( eventName ) => { + ui.stage.addEventListener( eventName, () => { + isDragging = false; + } ); + } ); + + ui.stage.addEventListener( 'keydown', ( event ) => { + const step = event.shiftKey ? 5 : 1; + const keyChanges = { + ArrowDown: { x: 0, y: step }, + ArrowLeft: { x: -step, y: 0 }, + ArrowRight: { x: step, y: 0 }, + ArrowUp: { x: 0, y: -step }, + }; + + if ( ! keyChanges[ event.key ] ) { + return; + } + + event.preventDefault(); + workingPoint.x += keyChanges[ event.key ].x; + workingPoint.y += keyChanges[ event.key ].y; + renderWorkingPoint(); + } ); + + ui.xRange.addEventListener( 'input', () => { + workingPoint.x = ui.xRange.value; + renderWorkingPoint(); + } ); + + ui.yRange.addEventListener( 'input', () => { + workingPoint.y = ui.yRange.value; + renderWorkingPoint(); + } ); + + ui.presets.addEventListener( 'click', ( event ) => { + const button = event.target.closest( 'button[data-x][data-y]' ); + + if ( ! button ) { + return; + } + + workingPoint = { x: button.dataset.x, y: button.dataset.y }; + renderWorkingPoint(); + } ); + + ui.resetButton.addEventListener( 'click', () => { + workingPoint = { ...DEFAULT_FOCAL_POINT }; + renderWorkingPoint(); + } ); + + ui.doneButton.addEventListener( 'click', () => closeModal( true ) ); + ui.cancelButton.addEventListener( 'click', () => closeModal() ); + ui.closeButton.addEventListener( 'click', () => closeModal() ); + ui.backdrop.addEventListener( 'click', () => closeModal() ); + + ui.modal.addEventListener( 'keydown', ( event ) => { + if ( event.key === 'Escape' ) { + event.preventDefault(); + closeModal(); + return; + } + + if ( event.key !== 'Tab' ) { + return; + } + + const focusable = Array.from( + ui.modal.querySelectorAll( + 'button:not([disabled]), input:not([disabled]), [tabindex]:not([tabindex="-1"])' + ) + ).filter( ( element ) => ! element.hidden ); + + if ( ! focusable.length ) { + return; + } + + const first = focusable[ 0 ]; + const last = focusable[ focusable.length - 1 ]; + + if ( + event.shiftKey && + ui.modal.ownerDocument.activeElement === first + ) { + event.preventDefault(); + last.focus(); + } else if ( + ! event.shiftKey && + ui.modal.ownerDocument.activeElement === last + ) { + event.preventDefault(); + first.focus(); + } + } ); + + const observer = new window.MutationObserver( decorateFeaturedImage ); + observer.observe( mediaUploader.thumbnailArea, { + childList: true, + subtree: true, + } ); + + decorateFeaturedImage(); +} + +/** + * Focus the image controls when a listing owner follows the dashboard shortcut. + * + * @return {void} + */ +export function initImageFocalPointDeepLink() { + const params = new window.URLSearchParams( window.location.search ); + + if ( params.get( 'directorist_edit_focus' ) !== '1' ) { + return; + } + + const imageField = document.querySelector( + '#directorist-add-listing-form .directorist-form-image-upload-field' + ); + + if ( ! imageField ) { + return; + } + + window.setTimeout( () => { + imageField.scrollIntoView( { behavior: 'smooth', block: 'center' } ); + imageField.classList.add( 'directorist-image-focus-target' ); + + const editButton = imageField.querySelector( + '.directorist-image-focus-toolbar__edit:not([hidden])' + ); + + editButton?.focus( { preventScroll: true } ); + + window.setTimeout( () => { + imageField.classList.remove( 'directorist-image-focus-target' ); + }, 1800 ); + }, 350 ); +} + +/** + * Append the selected preview image and its focal point to listing form data. + * + * @param {FormData} formData Listing form data. + * @param {Object} uploaderEntry Directorist uploader entry. + * @param {Array} uploadedImages Newly uploaded temporary images. + * @return {void} + */ +export function appendImageFocalPointData( + formData, + uploaderEntry, + uploadedImages = [] +) { + const mediaUploader = uploaderEntry?.media_uploader; + + if ( + uploaderEntry?.uploaders_data?.meta_name !== 'listing_img' || + ! mediaUploader?.directoristImageFocusEnabled || + ! mediaUploader.filesMeta?.length + ) { + return; + } + + const previewMeta = mediaUploader.filesMeta[ 0 ]; + const focalPoint = normalizeFocalPoint( + previewMeta.directoristFocalPoint || DEFAULT_FOCAL_POINT + ); + let previewSource = ''; + + if ( previewMeta.attachmentID ) { + previewSource = `attachment:${ previewMeta.attachmentID }`; + } else if ( previewMeta.file ) { + const uploadedPreview = uploadedImages.find( + ( image ) => image.file === previewMeta.file + ); + + if ( uploadedPreview?.uploadedFile ) { + previewSource = `temp:${ uploadedPreview.uploadedFile }`; + } + } + + formData.append( 'listing_image_focal_x', focalPoint.x ); + formData.append( 'listing_image_focal_y', focalPoint.y ); + formData.append( + 'listing_image_focal_is_custom', + isDefaultFocalPoint( focalPoint ) ? '0' : '1' + ); + + if ( previewSource ) { + formData.append( 'listing_image_preview_source', previewSource ); + } +} diff --git a/assets/src/scss/component/_add-listing.scss b/assets/src/scss/component/_add-listing.scss index fb970ee486..b5835baee2 100644 --- a/assets/src/scss/component/_add-listing.scss +++ b/assets/src/scss/component/_add-listing.scss @@ -940,6 +940,439 @@ } } +.directorist-image-focus-toolbar { + display: flex; + align-items: center; + justify-content: space-between; + gap: 18px; + margin-top: 16px; + padding: 16px; + border: 1px solid var(--directorist-color-border-gray); + border-radius: 10px; + background: var(--directorist-color-white); + transition: + border-color 0.2s ease, + box-shadow 0.2s ease; + + &[hidden] { + display: none; + } + + @include e(summary) { + min-width: 0; + } + + @include e(heading) { + display: flex; + align-items: center; + gap: 10px; + } + + @include e(label) { + color: var(--directorist-color-dark); + font-size: 15px; + line-height: 1.4; + } + + @include e(status) { + display: inline-flex; + align-items: center; + min-height: 24px; + padding: 3px 9px; + border-radius: 999px; + background: var(--directorist-color-bg-gray); + color: var(--directorist-color-body); + font-size: 12px; + font-weight: 600; + line-height: 1.2; + + @include m(custom) { + background: rgba(var(--directorist-color-primary-rgb), 0.1); + color: var(--directorist-color-primary); + } + } + + @include e(description) { + margin: 5px 0 0; + color: var(--directorist-color-body); + font-size: 13px; + line-height: 1.5; + } + + @include e(actions) { + display: flex; + align-items: center; + gap: 8px; + flex-shrink: 0; + + .directorist-btn { + white-space: nowrap; + + &:disabled { + opacity: 0.5; + cursor: not-allowed; + } + } + } +} + +.directorist-form-image-upload-field.directorist-image-focus-target { + .directorist-image-focus-toolbar { + border-color: var(--directorist-color-primary); + box-shadow: 0 0 0 3px rgba(var(--directorist-color-primary-rgb), 0.12); + } +} + +body.directorist-image-focus-modal-open { + overflow: hidden; +} + +.directorist-image-focus-modal { + position: fixed; + inset: 0; + z-index: 999999; + display: flex; + align-items: center; + justify-content: center; + padding: 24px; + + &[hidden] { + display: none; + } + + @include e(backdrop) { + position: absolute; + inset: 0; + background: rgba(var(--directorist-color-dark-rgb), 0.62); + backdrop-filter: blur(2px); + } + + @include e(dialog) { + position: relative; + z-index: 1; + width: min(920px, 100%); + max-height: calc(100vh - 48px); + overflow: auto; + border-radius: 14px; + background: var(--directorist-color-white); + box-shadow: 0 24px 70px rgba(var(--directorist-color-dark-rgb), 0.28); + color: var(--directorist-color-dark); + } + + @include e(header) { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: 24px; + padding: 24px 28px 18px; + border-bottom: 1px solid var(--directorist-color-border-gray); + } + + @include e(title) { + margin: 0; + color: var(--directorist-color-dark); + font-size: 22px; + font-weight: 600; + line-height: 1.3; + } + + @include e(description) { + max-width: 650px; + margin: 6px 0 0; + color: var(--directorist-color-body); + font-size: 14px; + line-height: 1.5; + } + + @include e(close) { + display: inline-flex; + align-items: center; + justify-content: center; + flex: 0 0 36px; + width: 36px; + height: 36px; + padding: 0; + border: 1px solid var(--directorist-color-border-gray); + border-radius: 50%; + background: transparent; + color: var(--directorist-color-dark); + font-size: 25px; + font-weight: 300; + line-height: 1; + cursor: pointer; + + &:hover, + &:focus-visible { + border-color: var(--directorist-color-primary); + color: var(--directorist-color-primary); + outline: none; + } + } + + @include e(body) { + display: grid; + grid-template-columns: minmax(0, 1.45fr) minmax(230px, 0.55fr); + gap: 28px; + padding: 28px; + } + + @include e(preview-column) { + min-width: 0; + } + + @include e(preview-title) { + margin: 0 0 12px; + color: var(--directorist-color-dark); + font-size: 15px; + font-weight: 600; + } + + @include e(reset) { + width: 100%; + padding: 8px 10px; + border: 0; + background: transparent; + color: var(--directorist-color-primary); + font-size: 13px; + font-weight: 500; + cursor: pointer; + + &:hover, + &:focus-visible { + text-decoration: underline; + outline: none; + } + } + + @include e(footer) { + display: flex; + justify-content: flex-end; + gap: 10px; + padding: 18px 28px 24px; + border-top: 1px solid var(--directorist-color-border-gray); + } +} + +.directorist-image-focus-stage { + position: relative; + display: block; + width: 100%; + max-height: 55vh; + padding: 0; + overflow: hidden; + border: 0; + border-radius: 10px; + background-color: var(--directorist-color-bg-gray); + line-height: 0; + cursor: crosshair; + touch-action: none; + + &:focus-visible { + outline: 3px solid rgba(var(--directorist-color-primary-rgb), 0.28); + outline-offset: 3px; + } + + @include e(image) { + display: block; + width: 100%; + height: 100%; + object-fit: contain; + pointer-events: none; + user-select: none; + } + + @include e(marker) { + position: absolute; + width: 30px; + height: 30px; + border: 3px solid var(--directorist-color-white); + border-radius: 50%; + background: var(--directorist-color-primary); + box-shadow: 0 2px 9px rgba(var(--directorist-color-dark-rgb), 0.4); + pointer-events: none; + transform: translate(-50%, -50%); + + &::before, + &::after { + position: absolute; + content: ""; + background: var(--directorist-color-white); + } + + &::before { + top: 50%; + left: 5px; + right: 5px; + height: 2px; + transform: translateY(-50%); + } + + &::after { + top: 5px; + bottom: 5px; + left: 50%; + width: 2px; + transform: translateX(-50%); + } + } +} + +.directorist-image-focus-controls { + display: grid; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 18px; + margin-top: 18px; + + @include e(label) { + display: grid; + grid-template-columns: 1fr auto; + align-items: center; + gap: 6px 10px; + margin: 0; + color: var(--directorist-color-dark); + font-size: 13px; + font-weight: 500; + + output { + color: var(--directorist-color-body); + font-variant-numeric: tabular-nums; + } + } + + @include e(range) { + grid-column: 1 / -1; + width: 100%; + accent-color: var(--directorist-color-primary); + } +} + +.directorist-image-focus-preview { + position: relative; + width: 100%; + overflow: hidden; + border-radius: 10px; + background: var(--directorist-color-bg-gray); + box-shadow: inset 0 0 0 1px var(--directorist-color-border-gray); + + @include m(contain) { + .directorist-image-focus-preview__image { + object-fit: contain; + } + } + + @include m(full) { + background: transparent; + + .directorist-image-focus-preview__image { + width: auto; + max-width: 100%; + height: auto; + max-height: 420px; + margin: 0 auto; + object-fit: contain; + } + } + + @include e(background) { + position: absolute; + inset: -12px; + display: block; + width: calc(100% + 24px); + height: calc(100% + 24px); + object-fit: cover; + filter: blur(8px); + pointer-events: none; + user-select: none; + } + + @include e(image) { + position: relative; + z-index: 1; + display: block; + width: 100%; + height: 100%; + object-fit: cover; + } +} + +.directorist-image-focus-presets { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 6px; + margin-top: 12px; + + @include e(button) { + padding: 7px 6px; + border: 1px solid var(--directorist-color-border-gray); + border-radius: 6px; + background: var(--directorist-color-white); + color: var(--directorist-color-dark); + font-size: 12px; + font-weight: 500; + cursor: pointer; + + &:hover, + &:focus-visible { + border-color: var(--directorist-color-primary); + color: var(--directorist-color-primary); + outline: none; + } + } +} + +@media only screen and (max-width: 767px) { + + .directorist-image-focus-modal { + align-items: flex-start; + padding: 12px; + + @include e(dialog) { + max-height: calc(100vh - 24px); + } + + @include e(header) { + padding: 18px; + } + + @include e(title) { + font-size: 19px; + } + + @include e(body) { + grid-template-columns: 1fr; + gap: 22px; + padding: 20px 18px; + } + + @include e(footer) { + padding: 16px 18px 20px; + + .directorist-btn { + flex: 1; + } + } + } + + .directorist-image-focus-controls { + grid-template-columns: 1fr; + } + + .directorist-image-focus-toolbar { + align-items: stretch; + flex-direction: column; + gap: 14px; + + @include e(actions) { + align-items: stretch; + flex-direction: column; + + .directorist-btn { + width: 100%; + } + } + } +} + //add listing image upload .directorist-add-listing-wrapper { max-width: 1000px !important; diff --git a/assets/src/scss/component/listings/_listing-card.scss b/assets/src/scss/component/listings/_listing-card.scss index b9813d2dad..898f624d27 100644 --- a/assets/src/scss/component/listings/_listing-card.scss +++ b/assets/src/scss/component/listings/_listing-card.scss @@ -551,6 +551,9 @@ width: 100%; height: 100%; object-fit: cover; + object-position: + var(--directorist-image-focus-x, 50%) + var(--directorist-image-focus-y, 50%); margin: 0; } @@ -1215,6 +1218,12 @@ } } +.directorist-image-focal-point { + object-position: + var(--directorist-image-focus-x, 50%) + var(--directorist-image-focus-y, 50%); +} + // Rating Meta .directorist-rating-meta { padding: 0; diff --git a/includes/asset-loader/localized_data.php b/includes/asset-loader/localized_data.php index 148c115715..e5af42ef7c 100644 --- a/includes/asset-loader/localized_data.php +++ b/includes/asset-loader/localized_data.php @@ -187,7 +187,29 @@ public static function get_add_listings_data() { 'max_location_creation' => esc_attr( $max_loc_creation ), 'max_location_msg' => sprintf( __( 'You can only use %s', 'directorist' ), $max_loc_creation ), 'submission_wait_msg' => esc_html__( 'Please wait, your submission is being processed.', 'directorist' ), - 'image_uploading_msg' => esc_html__( 'Please wait, your selected images being uploaded.', 'directorist' ) + 'image_uploading_msg' => esc_html__( 'Please wait, your selected images being uploaded.', 'directorist' ), + 'set_image_focus' => esc_html__( 'Set image focus', 'directorist' ), + 'edit_image_focus' => esc_html__( 'Edit focus', 'directorist' ), + 'image_focus_status_label' => esc_html__( 'Image focus', 'directorist' ), + 'image_focus_default_status' => esc_html__( 'Center (default)', 'directorist' ), + 'image_focus_custom_status' => esc_html__( 'Custom', 'directorist' ), + 'image_focus_help' => esc_html__( 'Controls which part of the Preview image stays visible in cropped listing cards.', 'directorist' ), + 'image_focus_help_cover' => esc_html__( 'Focus controls which part of the Preview image remains visible when the card crops it.', 'directorist' ), + 'image_focus_help_contain' => esc_html__( 'The complete image remains visible. Focus controls its alignment and is also used on cropped thumbnail surfaces.', 'directorist' ), + 'image_focus_help_full' => esc_html__( 'The Original archive image is uncropped. Focus is saved for maps, dashboard thumbnails, and other cropped surfaces.', 'directorist' ), + 'image_focus_title' => esc_html__( 'Image focal point', 'directorist' ), + 'image_focus_description' => esc_html__( 'Choose the part of the image that should remain visible when the image is cropped.', 'directorist' ), + 'image_focus_preview' => esc_html__( 'Listing card preview', 'directorist' ), + 'image_focus_horizontal' => esc_html__( 'Horizontal position', 'directorist' ), + 'image_focus_vertical' => esc_html__( 'Vertical position', 'directorist' ), + 'image_focus_top' => esc_html__( 'Top', 'directorist' ), + 'image_focus_center' => esc_html__( 'Center', 'directorist' ), + 'image_focus_bottom' => esc_html__( 'Bottom', 'directorist' ), + 'image_focus_reset' => esc_html__( 'Reset to center', 'directorist' ), + 'image_focus_cancel' => esc_html__( 'Cancel', 'directorist' ), + 'image_focus_done' => esc_html__( 'Done', 'directorist' ), + 'image_focus_close' => esc_html__( 'Close focal point editor', 'directorist' ), + 'image_focus_move' => esc_html__( 'Move the focal point. Use the arrow keys for precise adjustment.', 'directorist' ), ]; //get listing is if the screen in edit listing diff --git a/includes/classes/class-add-listing.php b/includes/classes/class-add-listing.php index 2c392c2f83..42244b7c8c 100644 --- a/includes/classes/class-add-listing.php +++ b/includes/classes/class-add-listing.php @@ -570,6 +570,7 @@ public static function upload_images( $listing_id, $posted_data ) { delete_post_thumbnail( $listing_id ); delete_post_meta( $listing_id, '_listing_img' ); delete_post_meta( $listing_id, '_listing_prv_img' ); + delete_post_meta( $listing_id, '_directorist_image_focal_points' ); return; } @@ -589,13 +590,15 @@ public static function upload_images( $listing_id, $posted_data ) { $target_dir = trailingslashit( $upload_dir['path'] ); $uploaded_images = $old_images; $background_processable_images = []; + $new_attachment_ids = []; foreach ( $new_images as $image ) { if ( empty( $image ) ) { continue; } - $image = sanitize_file_name( $image ); + $submitted_image = sanitize_file_name( $image ); + $image = $submitted_image; $filepath = $temp_dir . $image; if ( is_dir( $filepath ) || ! file_exists( $filepath ) ) { @@ -628,18 +631,35 @@ public static function upload_images( $listing_id, $posted_data ) { } $background_processable_images[ $attachment_id ] = $target_dir . $image; + $new_attachment_ids[ $submitted_image ] = $attachment_id; $uploaded_images[] = $attachment_id; } if ( ! empty( $uploaded_images ) ) { - update_post_meta( $listing_id, '_listing_prv_img', $uploaded_images[0] ); - set_post_thumbnail( $listing_id, $uploaded_images[0] ); + $uploaded_images = array_values( array_unique( wp_parse_id_list( $uploaded_images ) ) ); + $preview_source = isset( $posted_data['listing_image_preview_source'] ) ? $posted_data['listing_image_preview_source'] : ''; + $preview_image = directorist_resolve_listing_preview_image_source( $preview_source, $uploaded_images, $new_attachment_ids ); - unset( $uploaded_images[0] ); + if ( $preview_image ) { + $uploaded_images = array_values( array_diff( $uploaded_images, [ $preview_image ] ) ); + array_unshift( $uploaded_images, $preview_image ); + } + + $preview_image = reset( $uploaded_images ); + + update_post_meta( $listing_id, '_listing_prv_img', $preview_image ); + set_post_thumbnail( $listing_id, $preview_image ); - if ( count( $uploaded_images ) ) { - update_post_meta( $listing_id, '_listing_img', $uploaded_images ); + directorist_clean_listing_image_focal_points( $listing_id, $uploaded_images ); + directorist_save_listing_image_focal_point( $listing_id, $preview_image, $posted_data ); + + $gallery_images = array_values( array_slice( $uploaded_images, 1 ) ); + + if ( count( $gallery_images ) ) { + update_post_meta( $listing_id, '_listing_img', $gallery_images ); + } else { + delete_post_meta( $listing_id, '_listing_img' ); } directorist_background_image_process( $background_processable_images ); diff --git a/includes/classes/class-submission-controller.php b/includes/classes/class-submission-controller.php index e06ffff2ee..5eedcda0b2 100644 --- a/includes/classes/class-submission-controller.php +++ b/includes/classes/class-submission-controller.php @@ -897,6 +897,7 @@ public static function upload_images( $listing_id, $posted_data ) { delete_post_thumbnail( $listing_id ); delete_post_meta( $listing_id, '_listing_img' ); delete_post_meta( $listing_id, '_listing_prv_img' ); + delete_post_meta( $listing_id, '_directorist_image_focal_points' ); return; } @@ -916,18 +917,20 @@ public static function upload_images( $listing_id, $posted_data ) { $target_dir = trailingslashit( $upload_dir['path'] ); $uploaded_images = $old_images; $background_processable_images = array(); + $new_attachment_ids = array(); foreach ( $new_images as $image ) { if ( empty( $image ) ) { continue; } - $image = self::get_safe_submitted_filename( $image ); - if ( ! $image ) { + $submitted_image = self::get_safe_submitted_filename( $image ); + if ( ! $submitted_image ) { continue; } - $filepath = $temp_dir . $image; + $image = $submitted_image; + $filepath = $temp_dir . $image; if ( is_dir( $filepath ) || ! file_exists( $filepath ) ) { continue; @@ -967,18 +970,35 @@ public static function upload_images( $listing_id, $posted_data ) { } $background_processable_images[ $attachment_id ] = $target_dir . $image; + $new_attachment_ids[ $submitted_image ] = $attachment_id; $uploaded_images[] = $attachment_id; } if ( ! empty( $uploaded_images ) ) { - update_post_meta( $listing_id, '_listing_prv_img', $uploaded_images[0] ); - set_post_thumbnail( $listing_id, $uploaded_images[0] ); + $uploaded_images = array_values( array_unique( wp_parse_id_list( $uploaded_images ) ) ); + $preview_source = isset( $posted_data['listing_image_preview_source'] ) ? $posted_data['listing_image_preview_source'] : ''; + $preview_image = directorist_resolve_listing_preview_image_source( $preview_source, $uploaded_images, $new_attachment_ids ); - unset( $uploaded_images[0] ); + if ( $preview_image ) { + $uploaded_images = array_values( array_diff( $uploaded_images, [ $preview_image ] ) ); + array_unshift( $uploaded_images, $preview_image ); + } + + $preview_image = reset( $uploaded_images ); + + update_post_meta( $listing_id, '_listing_prv_img', $preview_image ); + set_post_thumbnail( $listing_id, $preview_image ); - if ( count( $uploaded_images ) ) { - update_post_meta( $listing_id, '_listing_img', $uploaded_images ); + directorist_clean_listing_image_focal_points( $listing_id, $uploaded_images ); + directorist_save_listing_image_focal_point( $listing_id, $preview_image, $posted_data ); + + $gallery_images = array_values( array_slice( $uploaded_images, 1 ) ); + + if ( count( $gallery_images ) ) { + update_post_meta( $listing_id, '_listing_img', $gallery_images ); + } else { + delete_post_meta( $listing_id, '_listing_img' ); } directorist_background_image_process( $background_processable_images ); diff --git a/includes/helper-functions.php b/includes/helper-functions.php index 57b3da23c1..b629627f9b 100644 --- a/includes/helper-functions.php +++ b/includes/helper-functions.php @@ -4863,6 +4863,346 @@ function directorist_get_listing_preview_image( $listing_id = 0 ) { return $image; } +/** + * Get the normalized display configuration for listing preview images. + * + * This keeps the archive renderer, focal-point editor, themes, and extensions + * aligned with the existing Preview Image settings without changing how those + * settings are stored. + * + * @since 8.9.3 + * + * @return array + */ +function directorist_get_listing_preview_image_display_config() { + $config = [ + 'mode' => get_directorist_option( 'way_to_show_preview', 'cover' ), + 'size_by' => get_directorist_option( 'prv_container_size_by', 'px' ), + 'width' => get_directorist_option( 'crop_width', 360 ), + 'height' => get_directorist_option( 'crop_height', 300 ), + 'background_type' => get_directorist_option( 'prv_background_type', 'blur' ), + 'background_color' => get_directorist_option( 'prv_background_color', '#fff' ), + 'image_quality' => get_directorist_option( 'preview_image_quality', 'directorist_preview' ), + ]; + + /** + * Filters the listing preview image display configuration. + * + * @since 8.9.3 + * + * @param array $config Listing preview image display configuration. + */ + $config = apply_filters( 'directorist_listing_preview_image_display_config', $config ); + $config = is_array( $config ) ? $config : []; + + $mode = isset( $config['mode'] ) && in_array( $config['mode'], [ 'cover', 'contain', 'full' ], true ) ? $config['mode'] : 'cover'; + $size_by = isset( $config['size_by'] ) && in_array( $config['size_by'], [ 'px', 'ratio' ], true ) ? $config['size_by'] : 'px'; + $background_type = isset( $config['background_type'] ) && in_array( $config['background_type'], [ 'blur', 'color' ], true ) ? $config['background_type'] : 'blur'; + $width = isset( $config['width'] ) && is_numeric( $config['width'] ) ? absint( $config['width'] ) : 360; + $height = isset( $config['height'] ) && is_numeric( $config['height'] ) ? absint( $config['height'] ) : 300; + $width = $width ? $width : 360; + $height = $height ? $height : 300; + $background = isset( $config['background_color'] ) && is_scalar( $config['background_color'] ) ? sanitize_text_field( (string) $config['background_color'] ) : '#fff'; + $image_quality = isset( $config['image_quality'] ) && is_scalar( $config['image_quality'] ) ? sanitize_key( (string) $config['image_quality'] ) : 'directorist_preview'; + $style = []; + + if ( 'ratio' === $size_by ) { + $style['padding-top'] = ( $height / $width * 100 ) . '%'; + } elseif ( 'full' !== $mode ) { + $style['height'] = $height . 'px'; + } + + if ( 'full' !== $mode && 'color' === $background_type ) { + $style['background-color'] = $background; + } + + $container_style = ''; + foreach ( $style as $property => $value ) { + $container_style .= $property . ':' . $value . ';'; + } + + return [ + 'mode' => $mode, + 'size_by' => $size_by, + 'width' => $width, + 'height' => $height, + 'background_type' => $background_type, + 'background_color' => $background, + 'image_quality' => $image_quality, + 'container_class' => 'directorist-card-' . $mode, + 'container_style' => safecss_filter_attr( $container_style ), + ]; +} + +/** + * Sanitize listing image focal point coordinates. + * + * @since 8.9.3 + * + * @param array $focal_point Image focal point coordinates. + * @return array + */ +function directorist_sanitize_listing_image_focal_point( $focal_point = [] ) { + $focal_point = is_array( $focal_point ) ? $focal_point : []; + $x = isset( $focal_point['x'] ) && is_numeric( $focal_point['x'] ) ? (float) $focal_point['x'] : 50; + $y = isset( $focal_point['y'] ) && is_numeric( $focal_point['y'] ) ? (float) $focal_point['y'] : 50; + + return [ + 'x' => min( 100, max( 0, round( $x, 2 ) ) ), + 'y' => min( 100, max( 0, round( $y, 2 ) ) ), + ]; +} + +/** + * Get all image focal points saved for a listing. + * + * Focal points are stored against the listing instead of the attachment so the + * same media-library image can be positioned differently in different listings. + * + * @since 8.9.3 + * + * @param int $listing_id Listing ID. + * @return array + */ +function directorist_get_listing_image_focal_points( $listing_id = 0 ) { + $listing_id = absint( $listing_id ); + $focal_points = $listing_id ? get_post_meta( $listing_id, '_directorist_image_focal_points', true ) : []; + $focal_points = is_array( $focal_points ) ? $focal_points : []; + $sanitized = []; + + foreach ( $focal_points as $attachment_id => $focal_point ) { + $attachment_id = absint( $attachment_id ); + + if ( ! $attachment_id ) { + continue; + } + + $sanitized[ $attachment_id ] = directorist_sanitize_listing_image_focal_point( $focal_point ); + } + + return $sanitized; +} + +/** + * Get the focal point for a listing image. + * + * @since 8.9.3 + * + * @param int $listing_id Listing ID. + * @param int $attachment_id Attachment ID. + * @return array + */ +function directorist_get_listing_image_focal_point( $listing_id = 0, $attachment_id = 0 ) { + $listing_id = absint( $listing_id ); + $attachment_id = absint( $attachment_id ); + $focal_points = directorist_get_listing_image_focal_points( $listing_id ); + $focal_point = isset( $focal_points[ $attachment_id ] ) ? $focal_points[ $attachment_id ] : [ 'x' => 50, 'y' => 50 ]; + + /** + * Filters a listing image focal point before it is rendered. + * + * @since 8.9.3 + * + * @param array $focal_point Focal point coordinates. + * @param int $listing_id Listing ID. + * @param int $attachment_id Attachment ID. + */ + $focal_point = apply_filters( 'directorist_listing_image_focal_point', $focal_point, $listing_id, $attachment_id ); + + return directorist_sanitize_listing_image_focal_point( $focal_point ); +} + +/** + * Get CSS custom properties for a listing image focal point. + * + * @since 8.9.3 + * + * @param int $listing_id Listing ID. + * @param int $attachment_id Attachment ID. + * @return string + */ +function directorist_get_listing_image_focal_point_style( $listing_id = 0, $attachment_id = 0 ) { + $focal_point = directorist_get_listing_image_focal_point( $listing_id, $attachment_id ); + $x = rtrim( rtrim( number_format( $focal_point['x'], 2, '.', '' ), '0' ), '.' ); + $y = rtrim( rtrim( number_format( $focal_point['y'], 2, '.', '' ), '0' ), '.' ); + + return sprintf( '--directorist-image-focus-x:%1$s%%;--directorist-image-focus-y:%2$s%%;', $x, $y ); +} + +/** + * Save a focal point for one image in a listing. + * + * @since 8.9.3 + * + * @param int $listing_id Listing ID. + * @param int $attachment_id Attachment ID. + * @param array $focal_point Focal point coordinates. + * @return bool + */ +function directorist_update_listing_image_focal_point( $listing_id, $attachment_id, $focal_point ) { + $listing_id = absint( $listing_id ); + $attachment_id = absint( $attachment_id ); + + if ( ! $listing_id || ! $attachment_id ) { + return false; + } + + $focal_points = directorist_get_listing_image_focal_points( $listing_id ); + $focal_points[ $attachment_id ] = directorist_sanitize_listing_image_focal_point( $focal_point ); + + return (bool) update_post_meta( $listing_id, '_directorist_image_focal_points', $focal_points ); +} + +/** + * Delete the focal point for one image in a listing. + * + * @since 8.9.3 + * + * @param int $listing_id Listing ID. + * @param int $attachment_id Attachment ID. + * @return bool + */ +function directorist_delete_listing_image_focal_point( $listing_id, $attachment_id ) { + $listing_id = absint( $listing_id ); + $attachment_id = absint( $attachment_id ); + + if ( ! $listing_id || ! $attachment_id ) { + return false; + } + + $focal_points = directorist_get_listing_image_focal_points( $listing_id ); + + if ( ! isset( $focal_points[ $attachment_id ] ) ) { + return true; + } + + unset( $focal_points[ $attachment_id ] ); + + if ( empty( $focal_points ) ) { + delete_post_meta( $listing_id, '_directorist_image_focal_points' ); + return true; + } + + return (bool) update_post_meta( $listing_id, '_directorist_image_focal_points', $focal_points ); +} + +/** + * Check whether the focal-point control is enabled for a listing's image field. + * + * @since 8.9.3 + * + * @param int $listing_id Listing ID. + * @return bool + */ +function directorist_is_listing_image_focal_point_enabled( $listing_id = 0 ) { + $directory_id = directorist_get_listing_directory( absint( $listing_id ) ); + $image_field = $directory_id ? directorist_get_listing_form_field( $directory_id, 'image_upload' ) : []; + $admin_only = isset( $image_field['only_for_admin'] ) && ! in_array( $image_field['only_for_admin'], [ false, 0, '0', 'false', '' ], true ); + + if ( empty( $image_field ) || $admin_only ) { + return false; + } + + if ( ! isset( $image_field['enable_image_focus'] ) ) { + return true; + } + + return ! in_array( $image_field['enable_image_focus'], [ false, 0, '0', 'false' ], true ); +} + +/** + * Remove focal points for images that are no longer attached to a listing. + * + * @since 8.9.3 + * + * @param int $listing_id Listing ID. + * @param array $attachment_ids Attachment IDs that remain in the listing. + * @return void + */ +function directorist_clean_listing_image_focal_points( $listing_id, $attachment_ids = [] ) { + $listing_id = absint( $listing_id ); + $attachment_ids = array_filter( wp_parse_id_list( $attachment_ids ) ); + + if ( ! $listing_id ) { + return; + } + + $focal_points = directorist_get_listing_image_focal_points( $listing_id ); + $focal_points = array_intersect_key( $focal_points, array_flip( $attachment_ids ) ); + + if ( empty( $focal_points ) ) { + delete_post_meta( $listing_id, '_directorist_image_focal_points' ); + return; + } + + update_post_meta( $listing_id, '_directorist_image_focal_points', $focal_points ); +} + +/** + * Resolve the preview image selected by the frontend image uploader. + * + * @since 8.9.3 + * + * @param string $source Preview source token. + * @param array $attachment_ids Available attachment IDs. + * @param array $new_attachment_ids Temporary file names mapped to attachment IDs. + * @return int + */ +function directorist_resolve_listing_preview_image_source( $source, $attachment_ids = [], $new_attachment_ids = [] ) { + $source = sanitize_text_field( (string) $source ); + $attachment_ids = array_filter( wp_parse_id_list( $attachment_ids ) ); + + if ( ! $source || strpos( $source, ':' ) === false ) { + return 0; + } + + list( $source_type, $source_value ) = explode( ':', $source, 2 ); + $preview_image_id = 0; + + if ( 'attachment' === $source_type ) { + $preview_image_id = absint( $source_value ); + } elseif ( 'temp' === $source_type ) { + $source_value = sanitize_file_name( $source_value ); + $preview_image_id = isset( $new_attachment_ids[ $source_value ] ) ? absint( $new_attachment_ids[ $source_value ] ) : 0; + } + + return in_array( $preview_image_id, $attachment_ids, true ) ? $preview_image_id : 0; +} + +/** + * Save focal point coordinates submitted for the selected preview image. + * + * @since 8.9.3 + * + * @param int $listing_id Listing ID. + * @param int $attachment_id Preview attachment ID. + * @param array $posted_data Listing submission data. + * @return void + */ +function directorist_save_listing_image_focal_point( $listing_id, $attachment_id, $posted_data = [] ) { + if ( ! array_key_exists( 'listing_image_focal_x', $posted_data ) || ! array_key_exists( 'listing_image_focal_y', $posted_data ) ) { + return; + } + + if ( array_key_exists( 'listing_image_focal_is_custom', $posted_data ) ) { + $is_custom = ! in_array( $posted_data['listing_image_focal_is_custom'], [ false, 0, '0', 'false', '' ], true ); + + if ( ! $is_custom ) { + directorist_delete_listing_image_focal_point( $listing_id, $attachment_id ); + return; + } + } + + directorist_update_listing_image_focal_point( + $listing_id, + $attachment_id, + [ + 'x' => $posted_data['listing_image_focal_x'], + 'y' => $posted_data['listing_image_focal_y'], + ] + ); +} + /** * Get listing gallery images. * diff --git a/includes/model/ListingDashboard.php b/includes/model/ListingDashboard.php index 98a915eb35..2400d307bf 100644 --- a/includes/model/ListingDashboard.php +++ b/includes/model/ListingDashboard.php @@ -257,9 +257,12 @@ public function get_listing_type() { public function get_listing_thumbnail() { $id = get_the_ID(); $type = directorist_get_listing_directory( $id ); + $thumbnail_id = 0; + $thumbnail_img = ''; $default_image_src = Helper::default_preview_image_src( $type ); - $image_quality = get_directorist_option( 'preview_image_quality', 'directorist_preview' ); + $display_config = directorist_get_listing_preview_image_display_config(); + $image_quality = $display_config['image_quality']; $listing_prv_img = directorist_get_listing_preview_image( $id ); $listing_img = directorist_get_listing_gallery_images( $id ); @@ -287,8 +290,9 @@ public function get_listing_thumbnail() { $image_alt = get_post_meta( $thumbnail_id, '_wp_attachment_image_alt', true ); $image_alt = ( ! empty( $image_alt ) ) ? esc_attr( $image_alt ) : esc_html( get_the_title( $thumbnail_id ) ); $image_alt = ( ! empty( $image_alt ) ) ? $image_alt : esc_html( get_the_title() ); + $image_style = esc_attr( directorist_get_listing_image_focal_point_style( $id, $thumbnail_id ) ); - return "$image_alt"; + return "$image_alt"; } public function fav_listing_items() { @@ -705,6 +709,21 @@ public function get_action_dropdown_item() { ]; } + if ( directorist_get_listing_preview_image( $post_id ) && directorist_is_listing_image_focal_point_enabled( $post_id ) ) { + $focus_url = add_query_arg( + 'directorist_edit_focus', + '1', + ATBDP_Permalink::get_edit_listing_page_link( $post_id ) + ); + $dropdown_items['image_focus'] = [ + 'class' => '', + 'data_attr' => '', + 'link' => $focus_url, + 'icon' => directorist_icon( 'las la-crosshairs', false ), + 'label' => __( 'Edit image focus', 'directorist' ), + ]; + } + // Add unfeature option if listing is featured. if ( apply_filters( 'directorist_can_unfeature_listing', $this->can_unfeature(), $this ) ) { $dropdown_items['unfeature'] = [ @@ -726,4 +745,4 @@ public function get_action_dropdown_item() { return apply_filters( 'directorist_dashboard_listing_action_items_end', $dropdown_items, $post_id, $this ); } -} \ No newline at end of file +} diff --git a/includes/model/Listings.php b/includes/model/Listings.php index 17a3ac4636..d65df5db11 100644 --- a/includes/model/Listings.php +++ b/includes/model/Listings.php @@ -1723,6 +1723,9 @@ public function openstreet_map_card_data() { $ls_data['gallery_img'] = atbdp_get_image_source( $ls_data['listing_img'][0], 'medium' ); } + $ls_data['image_attachment_id'] = ! empty( $ls_data['listing_prv_img'] ) ? absint( $ls_data['listing_prv_img'] ) : absint( $ls_data['listing_img'][0] ?? 0 ); + $ls_data['image_focal_point_style'] = directorist_get_listing_image_focal_point_style( $listings_id, $ls_data['image_attachment_id'] ); + $cat_icon = directorist_icon( $this->loop_map_cat_icon(), false ); $ls_data['cat_icon'] = $cat_icon; @@ -1806,8 +1809,8 @@ public function load_google_map() { $cat_icon = directorist_icon( $this->loop_map_cat_icon(), false ); $ls_data['cat_icon'] = wp_json_encode( $cat_icon ); - $listing_type = directorist_get_listing_directory( $listings_id ); - $ls_data['default_img'] = Helper::default_preview_image_src( $listing_type ); + $listing_type = directorist_get_listing_directory( $listings_id ); + $ls_data['default_image'] = Helper::default_preview_image_src( $listing_type ); if ( ! empty( $ls_data['listing_prv_img'] ) ) { $ls_data['prv_image'] = atbdp_get_image_source( $ls_data['listing_prv_img'], 'large' ); @@ -1817,6 +1820,9 @@ public function load_google_map() { $ls_data['gallery_img'] = atbdp_get_image_source( $ls_data['listing_img'][0], 'medium' ); } + $ls_data['image_attachment_id'] = ! empty( $ls_data['listing_prv_img'] ) ? absint( $ls_data['listing_prv_img'] ) : absint( $ls_data['listing_img'][0] ?? 0 ); + $ls_data['image_focal_point_style'] = directorist_get_listing_image_focal_point_style( $listings_id, $ls_data['image_attachment_id'] ); + if ( ! empty( $ls_data['manual_lat'] ) && ! empty( $ls_data['manual_lng'] ) ) { $opt['ls_data'] = $ls_data; $content = Helper::get_template_contents( 'archive/fields/google-map', $opt ); @@ -1872,8 +1878,9 @@ function loop_get_the_thumbnail( $class = '' ) { $default_image_src = Helper::default_preview_image_src( $this->current_listing_type ); - $id = get_the_ID(); - $image_quality = get_directorist_option( 'preview_image_quality', 'directorist_preview' ); + $id = get_the_ID(); + $display_config = directorist_get_listing_preview_image_display_config(); + $image_quality = $display_config['image_quality']; $listing_prv_img = directorist_get_listing_preview_image( $id ); $listing_img = directorist_get_listing_gallery_images( $id ); @@ -1886,7 +1893,8 @@ function loop_get_the_thumbnail( $class = '' ) { if ( empty( $thumbnail_img_id ) ) { $thumbnail_img_id = $default_image_src; $image_alt = esc_html( get_the_title( $id ) ); - $image = "$image_alt"; + $image_style = esc_attr( directorist_get_listing_image_focal_point_style( $id ) ); + $image = "$image_alt"; if ( ! $this->disable_single_listing ) { $image = $link_start . $image . $link_end; } @@ -1902,10 +1910,12 @@ function loop_get_the_thumbnail( $class = '' ) { $image_count = count( $thumbnail_img_id ); if ( 1 === (int) $image_count ) { - $image_src = atbdp_get_image_source( reset( $thumbnail_img_id ), $image_quality ); - $image_alt = get_post_meta( reset( $thumbnail_img_id ), '_wp_attachment_image_alt', true ); - $image_alt = ( ! empty( $image_alt ) ) ? esc_attr( $image_alt ) : esc_html( get_the_title( reset( $thumbnail_img_id ) ) ); - $image = "$image_alt"; + $attachment_id = reset( $thumbnail_img_id ); + $image_src = atbdp_get_image_source( $attachment_id, $image_quality ); + $image_alt = get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ); + $image_alt = ( ! empty( $image_alt ) ) ? esc_attr( $image_alt ) : esc_html( get_the_title( $attachment_id ) ); + $image_style = esc_attr( directorist_get_listing_image_focal_point_style( $id, $attachment_id ) ); + $image = "$image_alt"; if ( ! $this->disable_single_listing ) { $image = $link_start . $image . $link_end; } @@ -1919,7 +1929,8 @@ function loop_get_the_thumbnail( $class = '' ) { $image_src = atbdp_get_image_source( $img_id, $image_quality ); $image_alt = get_post_meta( $img_id, '_wp_attachment_image_alt', true ); $image_alt = ! empty( $image_alt ) ? esc_attr( $image_alt ) : esc_html( get_the_title( $img_id ) ); - $image = "$image_alt"; + $image_style = esc_attr( directorist_get_listing_image_focal_point_style( $id, $img_id ) ); + $image = "$image_alt"; if ( ! $this->disable_single_listing ) { $image = $link_start . $image . $link_end; diff --git a/includes/model/SingleListing.php b/includes/model/SingleListing.php index 377c707c8e..d6a7112001 100644 --- a/includes/model/SingleListing.php +++ b/includes/model/SingleListing.php @@ -1393,16 +1393,16 @@ public function map_data() { $default_image = Helper::default_preview_image_src( $listing_type ); $listing_title = get_the_title( $id ); $img_url = ''; + $attachment_id = 0; if ( ! empty( $listing_prv_img ) ) { - $cropped_image = atbdp_image_cropping( $listing_prv_img, 150, 150, true, 100 ); - $img_url = ! empty( $cropped_image['url'] ) ? $cropped_image['url'] : atbdp_get_image_source( $listing_prv_img, 'small' ); + $attachment_id = absint( $listing_prv_img ); + $img_url = atbdp_get_image_source( $attachment_id, 'medium' ); } if ( empty( $img_url ) && ! empty( $listing_img[0] ) ) { - $gallery_image_id = (int) $listing_img[0]; - $cropped_image = atbdp_image_cropping( $gallery_image_id, 150, 150, true, 100 ); - $img_url = ! empty( $cropped_image['url'] ) ? $cropped_image['url'] : atbdp_get_image_source( $gallery_image_id, 'small' ); + $attachment_id = absint( $listing_img[0] ); + $img_url = atbdp_get_image_source( $attachment_id, 'medium' ); } if ( empty( $img_url ) ) { @@ -1410,9 +1410,10 @@ public function map_data() { } $image = sprintf( - "
%s
", + "
%s
", esc_url( $img_url ), - esc_attr( $listing_title ) + esc_attr( $listing_title ), + esc_attr( directorist_get_listing_image_focal_point_style( $id, $attachment_id ) ) ); if ( empty( $display_image_map ) ) { $image = ''; diff --git a/includes/modules/multi-directory-setup/builder-preset-fields.php b/includes/modules/multi-directory-setup/builder-preset-fields.php index 3ba59356e0..abf634867e 100644 --- a/includes/modules/multi-directory-setup/builder-preset-fields.php +++ b/includes/modules/multi-directory-setup/builder-preset-fields.php @@ -907,6 +907,12 @@ function directorist_get_conditional_logic_field( array $args = [] ) { 'label' => __( 'Total Upload Size in MB', 'directorist' ), 'value' => 2, ], + 'enable_image_focus' => [ + 'type' => 'toggle', + 'label' => __( 'Image Focal Point', 'directorist' ), + 'description' => __( 'Allow listing owners to choose which part of the preview image remains visible when it is cropped.', 'directorist' ), + 'value' => true, + ], 'required' => [ 'type' => 'toggle', 'label' => __( 'Required', 'directorist' ), diff --git a/includes/rest-api/Version1/class-listings-controller.php b/includes/rest-api/Version1/class-listings-controller.php index 68c9e0f5d3..0fff48bcc8 100644 --- a/includes/rest-api/Version1/class-listings-controller.php +++ b/includes/rest-api/Version1/class-listings-controller.php @@ -767,6 +767,7 @@ protected function get_images( $listing, $context ) { 'name' => get_the_title( $attachment_id ), 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ), 'position' => (int) $position, + 'focal_point' => directorist_get_listing_image_focal_point( $listing->ID, $attachment_id ), ); } diff --git a/includes/rest-api/Version2/class-listings-controller.php b/includes/rest-api/Version2/class-listings-controller.php index 1bcf4ddab0..f628e56732 100644 --- a/includes/rest-api/Version2/class-listings-controller.php +++ b/includes/rest-api/Version2/class-listings-controller.php @@ -719,6 +719,7 @@ protected function get_images( $listing, $context ) { 'name' => get_the_title( $attachment_id ), 'alt' => get_post_meta( $attachment_id, '_wp_attachment_image_alt', true ), 'position' => (int) $position, + 'focal_point' => directorist_get_listing_image_focal_point( $listing->ID, $attachment_id ), ); } diff --git a/templates/archive/fields/google-map.php b/templates/archive/fields/google-map.php index 5e877da9e0..f18816a18e 100644 --- a/templates/archive/fields/google-map.php +++ b/templates/archive/fields/google-map.php @@ -2,8 +2,10 @@ /** * @author wpWax * @since 6.6 - * @version 7.7.0 + * @version 8.9.3 */ + +$image_focal_point_style = $ls_data['image_focal_point_style'] ?? directorist_get_listing_image_focal_point_style( $ls_data['post_id'] ?? get_the_ID() ); ?>
@@ -26,17 +28,17 @@ } if ( ! empty( $ls_data['listing_prv_img'] ) ) { ?> - ' alt=''> + ' alt='' style=''> - ' alt=''> + ' alt='' style=''> - ' alt=''> + ' alt='' style=''>
@@ -21,17 +23,17 @@ } if ( ! empty( $ls_data['listing_prv_img'] ) ) { ?> - ' alt=''> + ' alt='' style=''> - ' alt=''> + ' alt='' style=''> - ' alt=''> + ' alt='' style=''>
-
\ No newline at end of file + diff --git a/templates/archive/fields/thumb-card.php b/templates/archive/fields/thumb-card.php index 21a4b67566..b49effb55f 100644 --- a/templates/archive/fields/thumb-card.php +++ b/templates/archive/fields/thumb-card.php @@ -2,73 +2,26 @@ /** * @author wpWax * @since 6.6 - * @version 8.0 + * @version 8.9.3 */ if ( ! defined( 'ABSPATH' ) ) exit; -$is_blur = get_directorist_option( 'prv_background_type', 'blur' ); -$is_blur = ( 'blur' === $is_blur ? true : false ); -$container_size_by = get_directorist_option( 'prv_container_size_by', 'px' ); -$by_ratio = ( 'px' === $container_size_by ) ? false : true; -$image_size = get_directorist_option( 'way_to_show_preview', 'cover' ); -$ratio_width = get_directorist_option( 'crop_width', 360 ); -$ratio_height = get_directorist_option( 'crop_height', 300 ); -$blur_background = $is_blur; -$background_color = get_directorist_option( 'prv_background_color', '#fff' ); - -// Style -$style_component = []; - -if ( $by_ratio ) { - $padding_top_value = (int) $ratio_height / (int) $ratio_width * 100; - $style_component[ 'padding-top' ] = "{$padding_top_value}%"; -} else { - $height_value = (int) $ratio_height; - $style_component[ 'height' ] = "{$height_value}px"; -} -if ( $image_size !== 'full' && ! $blur_background ) { - $style_component[ 'background-color' ] = $background_color; -} -if ( $image_size === 'full' ) { - unset( $style_component[ 'height' ] ); -} - -$style = ''; -foreach ( $style_component as $style_prop => $style_value ) { - $style .= "{$style_prop}: {$style_value};"; -} +$display_config = directorist_get_listing_preview_image_display_config(); +$image_size = $display_config['mode']; +$style = $display_config['container_style']; +$blur_background = 'blur' === $display_config['background_type']; $front_wrap_html = "
" . $listings->loop_get_the_thumbnail( 'directorist-thumnail-card-front-img' ) . "
"; $back_wrap_html = "
" . $listings->loop_get_the_thumbnail( 'directorist-thumnail-card-back-img' ) . "
"; - - -$blur_bg = ( $blur_background ) ? $back_wrap_html : ''; - -// Card Contain -$card_contain_wrap = "
"; -$image_contain_html = $card_contain_wrap . $blur_bg . $front_wrap_html . "
"; - -// Card Cover -$card_cover_wrap = "
"; -$image_cover_html = $card_cover_wrap . $front_wrap_html . "
"; - -// Card Full -$card_full_wrap = "
"; -$image_full_html = $card_full_wrap . $front_wrap_html . "
"; - -$the_html = $image_cover_html; -switch ( $image_size ) { - case 'cover': - $the_html = $image_cover_html; - break; - case 'contain': - $the_html = $image_contain_html; - break; - case 'full': - $the_html = $image_full_html; - break; -} +$card_content = ( 'contain' === $image_size && $blur_background ) ? $back_wrap_html . $front_wrap_html : $front_wrap_html; + +$the_html = sprintf( + '
%3$s
', + esc_attr( $display_config['container_class'] ), + esc_attr( $style ), + $card_content +); $link_start = ''; diff --git a/templates/listing-form/fields/image_upload.php b/templates/listing-form/fields/image_upload.php index 503de0f4ce..359e404736 100644 --- a/templates/listing-form/fields/image_upload.php +++ b/templates/listing-form/fields/image_upload.php @@ -2,12 +2,13 @@ /** * @author wpWax * @since 6.6 - * @version 8.6 + * @version 8.9.3 */ if ( ! defined( 'ABSPATH' ) ) exit; -$listing_img = atbdp_get_listing_attachment_ids( $listing_form->get_add_listing_id() ); +$listing_id = $listing_form->get_add_listing_id(); +$listing_img = atbdp_get_listing_attachment_ids( $listing_id ); $maximum = ! empty( $data['max'] ) ? $data['max'] : $data['max_image_limit']; $unlimited = ! empty( $data['unlimited'] ) ? $data['unlimited'] : ''; $limit = $unlimited ? '0' : $maximum; @@ -17,6 +18,9 @@ $max_total_file_size_kb = $max_total_file_size * 1024; $required = (bool) $data['required']; $select_files_label = ! empty( $data['select_files_label'] ) ? $data['select_files_label'] : __( 'Select Files', 'directorist' ); +$enable_image_focus = ! isset( $data['enable_image_focus'] ) || ! in_array( $data['enable_image_focus'], [ false, 0, '0', 'false' ], true ); +$image_focal_points = directorist_get_listing_image_focal_points( $listing_id ); +$focus_preview_display = directorist_get_listing_preview_image_display_config(); $accepted_mime_types = directorist_get_mime_types( 'image', 'extension' ); $accepted_mime_types_upper = array_map( 'strtoupper', $accepted_mime_types ); @@ -30,6 +34,9 @@ 'max_total_img_size' => $max_total_file_size_kb, 'is_required' => $required, 'max_size_per_img' => $max_file_size_kb, + 'enable_image_focus' => $enable_image_focus, + 'image_focal_points' => $image_focal_points, + 'focus_preview_display' => $focus_preview_display, ], $listing_form, $data