diff --git a/CHANGELOG.md b/CHANGELOG.md index c5957360f..eb5e6f163 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,16 @@ # Changelog # +## v1.3.4 (August 11, 2025) ## + +- Fix: Video Editor UI issues + +## v1.3.3 (August 11, 2025) ## + +- Tweak: Improved video player controls and HLS URL handling +- Tweak: Added Video SEO support and duplicate prevention for GoDAM Tab videos +- Tweak: Player-specific video speed and quality settings +- Fix: Resolved share button and JavaScript registration issues + ## v1.3.2 (August 6, 2025) ## - Fix: Resolved PHP Warning on admin pages diff --git a/README.md b/README.md index 384fe503c..26a6084ef 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ ![Banner V3](https://github.com/user-attachments/assets/73cfb2c2-0779-44c2-85e7-67e812dd82b6) # GoDAM - Organize WordPress Media Library & File Manager with Unlimited Folders for Images, Videos & more -Contributors: [rtcamp](https://profiles.wordpress.org/rtcamp), [elifvish](https://profiles.wordpress.org/elifvish), [subodhrajpopat](https://profiles.wordpress.org/subodhrajpopat), [kuldipchaudhary](https://profiles.wordpress.org/kuldipchaudhary), [prachigarg19](https://profiles.wordpress.org/prachigarg19), [juzar](https://profiles.wordpress.org/juzar), [geekofshire](https://profiles.wordpress.org/geekofshire), [nazmulhassann20](https://profiles.wordpress.org/nazmulhassann20), [abhinavbelhekar03](https://profiles.wordpress.org/abhinavbelhekar03), [gautam23](https://profiles.wordpress.org/gautam23) +Contributors: [rtcamp](https://profiles.wordpress.org/rtcamp), [elifvish](https://profiles.wordpress.org/elifvish), [subodhrajpopat](https://profiles.wordpress.org/subodhrajpopat), [kuldipchaudhary](https://profiles.wordpress.org/kuldipchaudhary), [prachigarg19](https://profiles.wordpress.org/prachigarg19), [juzar](https://profiles.wordpress.org/juzar), [geekofshire](https://profiles.wordpress.org/geekofshire), [nazmulhassann20](https://profiles.wordpress.org/nazmulhassann20), [abhinavbelhekar03](https://profiles.wordpress.org/abhinavbelhekar03), [gautam23](https://profiles.wordpress.org/gautam23), [mukulsingh27](https://profiles.wordpress.org/mukulsingh27), [hbhalodia](https://profiles.wordpress.org/hbhalodia) Tags: transcoder, video, media library, folders, file manager @@ -11,7 +11,7 @@ Tested up to: 6.8.1 Requires PHP: 7.4 -Stable tag: 1.3.2 +Stable tag: 1.3.4 License: [GPLv2 or later](http://www.gnu.org/licenses/gpl-2.0.html) diff --git a/admin/godam-transcoder-actions.php b/admin/godam-transcoder-actions.php index 55dbeb42c..917aadfdf 100644 --- a/admin/godam-transcoder-actions.php +++ b/admin/godam-transcoder-actions.php @@ -58,18 +58,21 @@ function rtgodam_add_transcoded_url_field( $form_fields, $post ) { 'helps' => __( 'The URL of the transcoded file is generated automatically and cannot be edited.', 'godam' ), ); - $form_fields['hls_transcoded_url'] = array( - 'label' => __( 'Transcoded CDN URL (HLS)', 'godam' ), - 'input' => 'html', - 'html' => sprintf( - '', - (int) $post->ID, - (int) $post->ID, - esc_url( $hls_transcoded_url ) - ), - 'value' => esc_url( $hls_transcoded_url ), - 'helps' => __( 'The HLS URL of the transcoded file is generated automatically and cannot be edited.', 'godam' ), - ); + // Show the HLS transcoded URL field only for video files. + if ( strpos( $mime_type, 'video/' ) === 0 ) { + $form_fields['hls_transcoded_url'] = array( + 'label' => __( 'Transcoded CDN URL (HLS)', 'godam' ), + 'input' => 'html', + 'html' => sprintf( + '', + (int) $post->ID, + (int) $post->ID, + esc_url( $hls_transcoded_url ) + ), + 'value' => esc_url( $hls_transcoded_url ), + 'helps' => __( 'The HLS URL of the transcoded file is generated automatically and cannot be edited.', 'godam' ), + ); + } } else { // Display locked field with upsell message for free users. $form_fields['transcoded_url'] = array( diff --git a/assets/src/blocks/godam-player/components/VideoSEOModal.js b/assets/src/blocks/godam-player/components/VideoSEOModal.js index f7a58f4a0..af00d3bc6 100644 --- a/assets/src/blocks/godam-player/components/VideoSEOModal.js +++ b/assets/src/blocks/godam-player/components/VideoSEOModal.js @@ -19,21 +19,32 @@ import { __ } from '@wordpress/i18n'; * Internal dependencies */ import './video-seo-modal.scss'; -import { getFirstNonEmpty, appendTimezoneOffsetToUTC, isObjectEmpty } from '../utils'; +import { isObjectEmpty } from '../utils'; -export default function VideoSEOModal( { isOpen, setIsOpen, attachmentData, attributes, setAttributes } ) { +/** + * Video SEO Modal component + * + * @param {*} param0 + * @param {boolean} param0.isOpen - Whether the modal is open + * @param {Function} param0.setIsOpen - Function to set modal open state + * @param {Object} param0.attributes - Block attributes + * @param {Function} param0.setAttributes - Function to set block attributes + * + * @return {JSX.Element|null} returns the Video SEO Modal component or null if not open + */ +export default function VideoSEOModal( { isOpen, setIsOpen, attributes, setAttributes } ) { const [ videoData, setVideoData ] = useState( {} ); useEffect( () => { - if ( attachmentData && ! isObjectEmpty( attachmentData ) ) { + if ( attributes.seo && ! isObjectEmpty( attributes.seo ) ) { const initialVideoData = { - contentUrl: getFirstNonEmpty( attributes?.seo?.contentUrl, attachmentData?.meta?.rtgodam_transcoded_url, attachmentData?.source_url ), - headline: getFirstNonEmpty( attributes?.seo?.headline, attachmentData?.title?.rendered ), - description: getFirstNonEmpty( attributes?.seo?.description, attachmentData?.description?.rendered ), - uploadDate: getFirstNonEmpty( attributes?.seo?.uploadDate, appendTimezoneOffsetToUTC( attachmentData?.date_gmt ) ), - duration: getFirstNonEmpty( attributes?.seo?.duration, attachmentData?.video_duration_iso8601 ), - thumbnailUrl: getFirstNonEmpty( attributes?.seo?.thumbnailUrl, attachmentData?.meta?.rtgodam_media_video_thumbnail ), - isFamilyFriendly: getFirstNonEmpty( attributes?.seo?.isFamilyFriendly, true ), + contentUrl: attributes?.seo?.contentUrl || '', + headline: attributes?.seo?.headline || '', + description: attributes?.seo?.description || '', + uploadDate: attributes?.seo?.uploadDate || '', + duration: attributes?.seo?.duration || '', + thumbnailUrl: attributes?.seo?.thumbnailUrl || '', + isFamilyFriendly: attributes?.seo?.isFamilyFriendly || true, }; setVideoData( initialVideoData ); @@ -46,7 +57,7 @@ export default function VideoSEOModal( { isOpen, setIsOpen, attachmentData, attr } ); } } - }, [ attachmentData, attributes, setAttributes ] ); // Remove isOpen from dependencies + }, [ attributes, setAttributes ] ); // Remove isOpen from dependencies const updateField = ( field, value ) => { setVideoData( { ...videoData, [ field ]: value } ); diff --git a/assets/src/blocks/godam-player/edit.js b/assets/src/blocks/godam-player/edit.js index 8ed095003..6dbb56cd4 100644 --- a/assets/src/blocks/godam-player/edit.js +++ b/assets/src/blocks/godam-player/edit.js @@ -43,6 +43,7 @@ import Video from './VideoJS'; import TracksEditor from './track-uploader'; import { Caption } from './caption'; import VideoSEOModal from './components/VideoSEOModal.js'; +import { appendTimezoneOffsetToUTC, secondsToISO8601 } from './utils/index.js'; const ALLOWED_MEDIA_TYPES = [ 'video' ]; const VIDEO_POSTER_ALLOWED_MEDIA_TYPES = [ 'image' ]; @@ -106,7 +107,6 @@ function VideoEdit( { const [ temporaryURL, setTemporaryURL ] = useState( attributes.blob ); const [ defaultPoster, setDefaultPoster ] = useState( '' ); const [ isSEOModalOpen, setIsSEOModelOpen ] = useState( false ); - const [ videoResponse, setVideoResponse ] = useState( {} ); const [ duration, setDuration ] = useState( 0 ); const dispatch = useDispatch(); @@ -167,8 +167,6 @@ function VideoEdit( { try { const response = await apiFetch( { path: `/wp/v2/media/${ id }` } ); - setVideoResponse( response ); - if ( response.meta.rtgodam_media_video_thumbnail !== '' ) { setDefaultPoster( response.meta.rtgodam_media_video_thumbnail ); } @@ -252,54 +250,87 @@ function VideoEdit( { setDefaultPoster( media.image?.src ); } + if ( media?.origin === 'godam' ) { + setAttributes( { + seo: { + contentUrl: media?.url, + headline: media?.title || '', + description: media?.description || '', + uploadDate: appendTimezoneOffsetToUTC( media?.date || '' ), + duration: secondsToISO8601( media?.duration || '' ), + thumbnailUrl: media?.thumbnail_url || '', + isFamilyFriendly: true, // Default value + }, + } ); + + setAttributes( { + sources: [ + { + src: media.url, + type: media.url.endsWith( '.mov' ) ? 'video/mp4' : media.mime, + }, + ], + } ); + } else { // Fetch transcoded URL from media meta. - ( async () => { - try { - const response = await apiFetch( { path: `/wp/v2/media/${ media.id }` } ); + ( async () => { + try { + const response = await apiFetch( { path: `/wp/v2/media/${ media.id }` } ); - setVideoResponse( response ); + setAttributes( { + seo: { + contentUrl: response.meta?.rtgodam_transcoded_url || response.source_url, + headline: response.title?.rendered || '', + description: response.description?.rendered || '', + uploadDate: appendTimezoneOffsetToUTC( response.date_gmt ), + duration: response.video_duration_iso8601 || '', + thumbnailUrl: response.meta?.rtgodam_media_video_thumbnail || '', + isFamilyFriendly: true, // Default value + }, + } ); - if ( response && response.meta && response.meta.rtgodam_transcoded_url ) { - const transcodedUrl = response.meta.rtgodam_transcoded_url; + if ( response && response.meta && response.meta.rtgodam_transcoded_url ) { + const transcodedUrl = response.meta.rtgodam_transcoded_url; - if ( response.meta.rtgodam_media_video_thumbnail !== '' ) { - setDefaultPoster( response.meta.rtgodam_media_video_thumbnail ); - } + if ( response.meta.rtgodam_media_video_thumbnail !== '' ) { + setDefaultPoster( response.meta.rtgodam_media_video_thumbnail ); + } - setAttributes( { - sources: [ - { - src: transcodedUrl, - type: transcodedUrl.endsWith( '.mpd' ) ? 'application/dash+xml' : media.mime, - }, - { - src: media.url, - type: media.url.endsWith( '.mov' ) ? 'video/mp4' : media.mime, - }, - ], - } ); - } else { + setAttributes( { + sources: [ + { + src: transcodedUrl, + type: transcodedUrl.endsWith( '.mpd' ) ? 'application/dash+xml' : media.mime, + }, + { + src: media.url, + type: media.url.endsWith( '.mov' ) ? 'video/mp4' : media.mime, + }, + ], + } ); + } else { // If meta not present, use media url. + setAttributes( { + sources: [ + { + src: media.url, + type: media.url.endsWith( '.mov' ) ? 'video/mp4' : media.mime, + }, + ], + } ); + } + } catch ( error ) { setAttributes( { sources: [ { src: media.url, - type: media.url.endsWith( '.mov' ) ? 'video/mp4' : media.mime, + type: media.mime, }, ], } ); } - } catch ( error ) { - setAttributes( { - sources: [ - { - src: media.url, - type: media.mime, - }, - ], - } ); - } - } )(); + } )(); + } setTemporaryURL(); } @@ -604,7 +635,6 @@ function VideoEdit( { diff --git a/assets/src/blocks/godam-player/utils/index.js b/assets/src/blocks/godam-player/utils/index.js index 79a8ad1b5..fa2bb5a3f 100644 --- a/assets/src/blocks/godam-player/utils/index.js +++ b/assets/src/blocks/godam-player/utils/index.js @@ -63,6 +63,8 @@ function isObjectEmpty( obj ) { * @return {string} An ISO 8601 duration string in the format 'PTnHnMnS'. */ function secondsToISO8601( seconds ) { + seconds = Math.floor( seconds ); // ensure whole seconds + const duration = { hours: Math.floor( seconds / 3600 ), minutes: Math.floor( ( seconds % 3600 ) / 60 ), diff --git a/assets/src/css/_variables.scss b/assets/src/css/_variables.scss index 2b0990bed..5ab2c208d 100644 --- a/assets/src/css/_variables.scss +++ b/assets/src/css/_variables.scss @@ -1,6 +1,7 @@ $godam-primary-gradient: linear-gradient(83.85deg, #AB3A6C -9.3%, #E6533A 120.31%); $godam-primary-color: rgba(194, 0, 0, 1); $godam-light-color: rgba(255, 244, 244, 1); +$godam-primary-text-color: #AB3A6C; $godam-gray-color: rgba(243, 245, 247, 1); $godam-black-color: rgba(33, 33, 33, 1); diff --git a/assets/src/css/admin.scss b/assets/src/css/admin.scss index 8513e93c9..6cd9d6d27 100644 --- a/assets/src/css/admin.scss +++ b/assets/src/css/admin.scss @@ -1,4 +1,6 @@ +@use './variables'; + @import url(https://vjs.zencdn.net/8.3.0/video-js.css); @import "_common"; // stylelint-disable-line scss/load-no-partial-leading-underscore @@ -11,6 +13,9 @@ $LIGHT_BLUE: #2196F3; --wp-components-color-accent: #ab3a6c; } +#wpbody-content { + container: wpBodyContent / inline-size; +} /* Write your Sass code here for admin. @@ -581,3 +586,149 @@ $media-frame-width: calc(300px + 2 * 24px); color: #ab3a6c; } } + +/* +Video Editor and Media Library's Annual plan offer banner styles + +@todo: Remove container queries when the media-library static sidebar is converted to an overlay sidebar +to ensure proper responsiveness and avoid layout issues. +*/ +.annual-plan-offer-banner { + border: none; + margin: 0; + margin-left: -2rem; + background: variables.$godam-primary-gradient; + display: flex; + justify-content: center; + position: relative; + margin-bottom: 0 !important; + + #godam-particle-canvas { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + z-index: 0; + pointer-events: none; + } + + &__content { + position: relative; + z-index: 1; + padding: 24px; + width: 100%; + max-width: 1024px; + display: flex; + justify-content: space-between; + align-items: center; + gap: 2rem; + margin-right: 30px; + + @media (max-width: 782px) { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + padding: 18px; + padding-left: 0; + } + + @container wpBodyContent (max-width: 640px) { + flex-direction: column; + align-items: flex-start; + gap: 1rem; + padding: 18px; + padding-left: 0; + } + } + + &__title { + margin: 0; + font-size: 1.5rem; + color: #fff; + font-weight: 700; + line-height: 1.2; + letter-spacing: 0.5px; + + @media (max-width: 782px) { + font-size: 1.25rem; + } + + @container wpBodyContent (max-width: 640px) { + font-size: 1.25rem; + } + } + + &__description { + margin: 10px 0 0 !important; + padding: 0 !important; + font-size: 1rem; + color: #ececec; + line-height: 1.2; + font-weight: 200; + letter-spacing: 0.3px; + + @media (max-width: 782px) { + font-size: 0.875rem; + margin-top: 8px !important; + } + + @container wpBodyContent (max-width: 640px) { + font-size: 0.875rem; + margin-top: 8px !important; + } + } + + &__cta-container { + display: flex; + align-items: center; + } + + &__cta { + background-color: #fff; + color: variables.$godam-primary-text-color; + padding: 10px 16px; + border-radius: 8px; + font-weight: 600; + text-decoration: none; + white-space: nowrap; + text-transform: uppercase; + box-shadow: 0 0 50px 2px rgba(255, 255, 255, 1); + + @media (max-width: 782px) { + padding: 6px 12px; + box-shadow: 0 0 50px 0 rgba(255, 255, 255, 1); + } + + @container wpBodyContent (max-width: 640px) { + padding: 6px 12px; + box-shadow: 0 0 50px 0 rgba(255, 255, 255, 1); + } + + &:hover { + color: variables.$godam-primary-text-color; + } + } + + &__dismiss { + position: absolute; + top: 8px; + right: 10px; + background: none; + border: none; + color: #fff; + font-size: 1.5rem; + cursor: pointer; + padding: 0.1rem .5rem 0.3rem .5rem; + line-height: 1; + z-index: 10; + transition: all 0.3s ease; + margin-left: 16px; + + &:hover { + background-color: #fff; + color: variables.$godam-primary-text-color; + border-radius: 4px; + } + } +} diff --git a/assets/src/css/media-library.scss b/assets/src/css/media-library.scss index 5b71bea38..097fdb7a5 100644 --- a/assets/src/css/media-library.scss +++ b/assets/src/css/media-library.scss @@ -332,33 +332,3 @@ .elementor-editor-active .attachments-browser #media-date-range-filter { background-color: unset; } - -.annual-plan-offer-banner { - border: none; - margin: 0; - margin-left: -2rem; - background: linear-gradient(90deg, #AB3A6C, #E6533A); - display: flex; - justify-content: center; - position: relative; - - &__img { - width: 100%; - height: auto; - max-width: 1000px; - } - - &__dismiss { - position: absolute; - top: 0; - right: 0; - background: none; - border: none; - color: #fff; - font-size: 1.5rem; - cursor: pointer; - padding: 0.25rem 0.5rem; - line-height: 1; - z-index: 10; - } -} diff --git a/assets/src/js/admin.js b/assets/src/js/admin.js index 9e771d2b7..d7ea7f8aa 100644 --- a/assets/src/js/admin.js +++ b/assets/src/js/admin.js @@ -2,6 +2,7 @@ * Write your JS code here for admin. */ +// Utility function to join URL paths window.pathJoin = function( parts, sep = '/' ) { return parts .map( ( part, index ) => { @@ -14,7 +15,10 @@ window.pathJoin = function( parts, sep = '/' ) { .join( sep ); }; -document.addEventListener( 'DOMContentLoaded', function() { +/** + * Toggle postboxes in admin UI + */ +function initTogglePostboxes() { const toggleButtons = document.querySelectorAll( '#easydam-tools-widget .handlediv' ); toggleButtons.forEach( ( button ) => { @@ -29,4 +33,112 @@ document.addEventListener( 'DOMContentLoaded', function() { } } ); } ); -} ); +} + +/** + * Particle class for canvas-based particle effect + */ +class Particle { + constructor( ctx, width, height ) { + this.ctx = ctx; + this.width = width; + this.height = height; + this.x = Math.random() * width; + this.y = Math.random() * height; + this.radius = ( Math.random() * 1.5 ) + 1; + this.vx = ( Math.random() * 0.5 ) - 0.25; + this.vy = ( Math.random() * 0.5 ) - 0.25; + } + + // Draw the particle + draw() { + this.ctx.beginPath(); + this.ctx.arc( this.x, this.y, this.radius, 0, Math.PI * 2 ); + this.ctx.fillStyle = 'rgba(255, 255, 255, 0.6)'; + this.ctx.fill(); + } + + // Update the particle position + update() { + this.x += this.vx; + this.y += this.vy; + + if ( this.x < 0 || this.x > this.width ) { + this.vx *= -1; + } + if ( this.y < 0 || this.y > this.height ) { + this.vy *= -1; + } + } +} + +/** + * Manages the particle effect background + */ +class ParticleEffect { + constructor() { + // Initialize canvas and context + this.canvas = document.getElementById( 'godam-particle-canvas' ); + this.banner = document.querySelector( '.annual-plan-offer-banner' ); + + // Bail if canvas and banner do not exist + if ( ! this.canvas || ! this.banner ) { + return; + } + + this.ctx = this.canvas.getContext( '2d' ); + this.particles = []; + this.setupEventListeners(); + this.resizeCanvas(); + this.animate(); + } + + // Set up event listeners for resizing the canvas + setupEventListeners() { + window.addEventListener( 'resize', this.debounce( () => this.resizeCanvas(), 100 ) ); + } + + // Debounce function to limit the rate of function execution + debounce( fn, delay ) { + let timer; + return function() { + clearTimeout( timer ); + timer = setTimeout( fn, delay ); + }; + } + + // Resize the canvas to fit the banner + resizeCanvas() { + this.width = this.canvas.width = this.banner.offsetWidth || window.innerWidth; + this.height = this.canvas.height = this.banner.offsetHeight || 300; + this.initParticles( 40 ); + } + + // Initialize particles based on the canvas size + initParticles( count ) { + const maxParticles = Math.min( count, Math.floor( this.width / 20 ) ); + this.particles = Array.from( { length: maxParticles }, () => new Particle( this.ctx, this.width, this.height ) ); + } + + // Start the animation loop + animate() { + // Bail if canvas and context do not exist + if ( ! this.canvas || ! this.ctx ) { + return; + } + + this.ctx.clearRect( 0, 0, this.width, this.height ); + this.particles.forEach( ( p ) => { + p.update(); + p.draw(); + } ); + requestAnimationFrame( () => this.animate() ); + } +} + +function initAdminUI() { + initTogglePostboxes(); + new ParticleEffect(); +} + +document.addEventListener( 'DOMContentLoaded', initAdminUI ); diff --git a/assets/src/js/godam-player/masterSettings.js b/assets/src/js/godam-player/masterSettings.js index f4a778b55..8b3202ad8 100644 --- a/assets/src/js/godam-player/masterSettings.js +++ b/assets/src/js/godam-player/masterSettings.js @@ -38,6 +38,8 @@ class SettingsButton extends videojs.getComponent( 'MenuButton' ) { constructor( player, options ) { super( player, options ); this.player_ = player; + this.player_.selectedSpeed ??= '1x'; + this.player_.selectedQuality ??= 'Auto'; this.addClass( 'vjs-settings-button' ); this.controlText( 'Settings' ); this.hasQualityItem = false; @@ -78,6 +80,33 @@ class SettingsButton extends videojs.getComponent( 'MenuButton' ) { super.handleClick( event ); } + // Method for the outside click listener + outsideClickListener() { + if ( this.el() ) { + this.menu.unlockShowing(); + this.resetToDefaultMenu(); + } + } + + // Method to add an event listener to close the settings menu when clicked outside + attachOutsideClickListener() { + if ( ! this.el() ) { + return; + } + + // Attach a new outside click listener and store its reference + this._outsideClickListener = this.outsideClickListener.bind( this ); + document.addEventListener( 'click', this._outsideClickListener, { once: true } ); + } + + // Method to detach the outside click listener + detachOutsideClickListener() { + if ( this._outsideClickListener ) { + document.removeEventListener( 'click', this._outsideClickListener ); + this._outsideClickListener = null; + } + } + // Method to reset menu to default state resetToDefaultMenu() { if ( this.originalItems_ ) { @@ -99,10 +128,21 @@ class SettingsButton extends videojs.getComponent( 'MenuButton' ) { this.originalItems_ = null; } } + + // Override the superclass dispose method + dispose() { + this.detachOutsideClickListener(); // Detach the outside click listener + super.dispose(); + } +} + +// Utility function to close menu and reset to default menu +function closeAndResetMenu( menuButton ) { + menuButton.menu.hide(); + menuButton.menu.unlockShowing(); + menuButton.resetToDefaultMenu(); // reset to default menu } -let selectedSpeed = '1x'; -let selectedQuality = 'Auto'; // initiated globally so it can be share between multiple instances of the function. function openSubmenu( menuButton, items, title = '' ) { // Ensure the menu is created if ( ! menuButton.menu ) { @@ -163,9 +203,11 @@ function openSubmenu( menuButton, items, title = '' ) { let isSelected = false; if ( title === 'Speed' ) { - isSelected = selectedSpeed === itemData.label; + const currentSpeed = this.player().selectedSpeed || '1x'; + isSelected = currentSpeed === itemData.label; } else if ( title === 'Quality' ) { - isSelected = selectedQuality === itemData.label; + const currentQuality = this.player().selectedQuality || 'Auto'; + isSelected = currentQuality === itemData.label; } let html = ''; @@ -213,7 +255,7 @@ function openSubmenu( menuButton, items, title = '' ) { for ( let i = 0; i < qualityLevels.length; i++ ) { qualityLevels[ i ].enabled = true; } - selectedQuality = 'Auto'; + this.player().selectedQuality = 'Auto'; } else { // Parse height from label like '720p' const selectedHeight = parseInt( qualityLabel.replace( 'p', '' ), 10 ); @@ -222,34 +264,19 @@ function openSubmenu( menuButton, items, title = '' ) { const level = qualityLevels[ i ]; level.enabled = level.height === selectedHeight; } - selectedQuality = selectedHeight + 'p'; + this.player().selectedQuality = selectedHeight + 'p'; } - menuButton.el_.focus(); // keep menu focused - openSubmenu( menuButton, items, title ); // refresh menu to update ticks + closeAndResetMenu( menuButton ); } handleSpeedSelection( speed ) { // Implement playback speed change const rate = parseFloat( speed.replace( 'x', '' ) ); this.player().playbackRate( rate ); - selectedSpeed = speed; - - document - .querySelectorAll( '.easydam-player.video-js' ) - .forEach( ( videoElement ) => { - try { - const player = videojs.getPlayer( videoElement ); - if ( player ) { - player.playbackRate( rate ); - } - } catch ( error ) { - // silently fail. - } - } ); + this.player().selectedSpeed = speed; - menuButton.el_.focus(); - openSubmenu( menuButton, items, title ); // refresh menu to show new tick + closeAndResetMenu( menuButton ); } } @@ -265,6 +292,11 @@ function openSubmenu( menuButton, items, title = '' ) { // Force menu to update menuButton.menu.show(); + + // Ensure the outside click listener is attached + if ( menuButton && typeof menuButton.attachOutsideClickListener === 'function' ) { + menuButton.attachOutsideClickListener(); + } } class QualityMenuItem extends videojs.getComponent( 'MenuItem' ) { diff --git a/godam.php b/godam.php index 6c4c50239..8d0cfbb75 100644 --- a/godam.php +++ b/godam.php @@ -3,7 +3,7 @@ * Plugin Name: GoDAM * Plugin URI: https://godam.io * Description: Seamlessly manage and deliver your media assets directly from the cloud-based media management. Store assets efficiently, stream them via a CDN, and enhance your website's performance and user experience. Featuring adaptive bit rate streaming, adding interactive layers in videos, and taking full advantage of a digital asset management solution within WordPress. - * Version: 1.3.2 + * Version: 1.3.4 * Requires at least: 6.5 * Requires PHP: 7.4 * Text Domain: godam @@ -43,7 +43,7 @@ /** * The version of the plugin */ - define( 'RTGODAM_VERSION', '1.3.2' ); + define( 'RTGODAM_VERSION', '1.3.4' ); } if ( ! defined( 'RTGODAM_NO_MAIL' ) && defined( 'VIP_GO_APP_ENVIRONMENT' ) ) { diff --git a/inc/classes/class-media-library-ajax.php b/inc/classes/class-media-library-ajax.php index 97dc8fbfd..4be0d635c 100644 --- a/inc/classes/class-media-library-ajax.php +++ b/inc/classes/class-media-library-ajax.php @@ -32,7 +32,6 @@ protected function __construct() { */ public function setup_hooks() { add_filter( 'ajax_query_attachments_args', array( $this, 'filter_media_library_by_taxonomy' ) ); - add_filter( 'ajax_query_attachments_args', array( $this, 'godam_media_library_ajax' ) ); add_action( 'pre_get_posts', array( $this, 'pre_get_post_filter' ) ); add_action( 'restrict_manage_posts', array( $this, 'restrict_manage_media_filter' ) ); @@ -47,103 +46,6 @@ public function setup_hooks() { add_action( 'wp_ajax_godam_dismiss_offer_banner', array( $this, 'dismiss_offer_banner' ) ); } - /** - * Short-circuit the media library AJAX request if the mime type is 'godam'. - * - * @param array $query_args Query arguments. - * @return array - */ - public function godam_media_library_ajax( $query_args ) { - - $api_key = get_option( 'rtgodam-api-key', '' ); - - if ( empty( $api_key ) ) { - return $query_args; - } - - if ( isset( $query_args['post_mime_type'] ) && is_array( $query_args['post_mime_type'] ) ) { - - $post_mime_type = $query_args['post_mime_type'][0]; - $mime_type = ''; - if ( false === strpos( $post_mime_type, 'godam/' ) ) { - return $query_args; - } else { - // mime_type is godam/{mime_type}. - $mime_type = str_replace( 'godam/', '', $post_mime_type ); - $mime_type = explode( '-', $mime_type ); - $mime_type = $mime_type[0]; - if ( 'all' === $mime_type ) { - $mime_type = ''; - } - } - - $api_url = RTGODAM_API_BASE . '/api/method/godam_core.api.file.get_list_of_files_with_api_key'; - - - $order_by = 'creation asc'; - if ( isset( $query_args['order'] ) && 'DESC' === $query_args['order'] ) { - $order_by = 'creation desc'; - } - - $request_args = array( - 'api_key' => $api_key, - 'order_by' => $order_by, - ); - - if ( ! empty( $mime_type ) ) { - if ( 'video' === $mime_type ) { - $request_args['job_type'] = 'stream'; - } else { - $request_args['job_type'] = $mime_type; - } - } - - if ( isset( $query_args['s'] ) && ! empty( $query_args['s'] ) ) { - $request_args['search'] = $query_args['s']; - } - - if ( isset( $query_args['posts_per_page'] ) && ! empty( $query_args['paged'] ) ) { - $request_args['page_size'] = intval( $query_args['posts_per_page'] ); - $request_args['page'] = intval( $query_args['paged'] ); - } - - $api_url = add_query_arg( - $request_args, - $api_url - ); - - $response = wp_remote_get( - $api_url, - array( - 'headers' => array( - 'Content-Type' => 'application/json', - ), - ) - ); - - if ( is_wp_error( $response ) ) { - return $query_args; - } - - $body = json_decode( wp_remote_retrieve_body( $response ) ); - - if ( ! is_object( $body ) || ! isset( $body->message ) || ! isset( $body->message->files ) ) { - return $query_args; - } - - $response = $body->message->files; - - foreach ( $response as $key => $item ) { - $response[ $key ] = $this->prepare_godam_media_item( $item ); - } - - wp_send_json_success( $response ); - - } else { - return $query_args; - } - } - /** * Prepare a single Godam media item to match WordPress media format. * @@ -160,19 +62,22 @@ public function prepare_godam_media_item( $item ) { $result = array( 'id' => $item['name'], - 'title' => pathinfo( $item['orignal_file_name'], PATHINFO_FILENAME ) ?? $item['name'], + 'title' => isset( $item['orignal_file_name'] ) ? pathinfo( $item['orignal_file_name'], PATHINFO_FILENAME ) : $item['name'], 'filename' => $item['orignal_file_name'] ?? $item['name'], - 'url' => 'image' === $item['job_type'] ? $item['file_origin'] : ( $item['transcoded_file_path'] ?? $item['file_origin'] ), - 'mime' => 'application/dash+xml' ?? '', + 'url' => ( $item['job_type'] ?? '' ) === 'image' ? ( $item['file_origin'] ?? '' ) : ( $item['transcoded_file_path'] ?? $item['file_origin'] ?? '' ), + 'mime' => 'application/dash+xml', 'type' => $item['job_type'] ?? '', - 'subtype' => explode( '/', $item['mime_type'] )[1] ?? 'jpg', - 'status' => $item['status'], - 'date' => strtotime( $item['creation'] ) * 1000, - 'modified' => strtotime( $item['modified'] ) * 1000, - 'filesizeInBytes' => $item['file_size'], - 'filesizeHumanReadable' => size_format( $item['file_size'] ), + 'subtype' => ( isset( $item['mime_type'] ) && strpos( $item['mime_type'], '/' ) !== false ) ? explode( '/', $item['mime_type'] )[1] : 'jpg', + 'status' => $item['status'] ?? '', + 'date' => isset( $item['creation'] ) ? strtotime( $item['creation'] ) * 1000 : 0, + 'modified' => isset( $item['modified'] ) ? strtotime( $item['modified'] ) * 1000 : 0, + 'filesizeInBytes' => $item['file_size'] ?? 0, + 'filesizeHumanReadable' => isset( $item['file_size'] ) ? size_format( $item['file_size'] ) : '', 'owner' => $item['owner'] ?? '', 'label' => $item['file_label'] ?? '', + 'origin' => 'godam', + 'thumbnail_url' => $item['thumbnail_url'] ?? '', + 'duration' => $item['playtime'] ?? '', ); if ( 'stream' === $item['job_type'] ) { @@ -636,28 +541,52 @@ public function media_library_offer_banner() { $host = wp_parse_url( home_url(), PHP_URL_HOST ); $banner_html = sprintf( - '
- - Annual Plan Offer Banner - - + '
+ +
+
+

%1$s

+

%2$s

+
+ +
+
', - esc_url( 'https://godam.io/pricing?utm_campaign=annual-plan&utm_source=' . $host . '&utm_medium=plugin&utm_content=banner' ), - esc_url( RTGODAM_URL . '/assets/src/images/annual-plan-offer-banner.png' ) + esc_html__( 'Pay for 10 months and get 2 months free with our annual plan.', 'godam' ), + esc_html__( 'Elevate your media management, transcoding, storage, delivery and more.', 'godam' ), + esc_url( RTGODAM_IO_API_BASE . '/pricing?utm_campaign=annual-plan&utm_source=' . $host . '&utm_medium=plugin&utm_content=banner' ), + esc_html__( 'Buy Now', 'godam' ) ); echo wp_kses( $banner_html, array( 'div' => array( 'class' => array() ), + 'canvas' => array( 'id' => array() ), + 'h3' => array( 'class' => array() ), + 'p' => array( 'class' => array() ), 'a' => array( - 'href' => array(), - 'class' => array(), - ), - 'img' => array( - 'src' => array(), - 'class' => array(), - 'alt' => array(), + 'href' => array(), + 'class' => array(), + 'target' => array(), + 'rel' => array(), + 'title' => array(), ), 'button' => array( 'type' => array(), @@ -665,7 +594,6 @@ public function media_library_offer_banner() { ), ) ); - } } } diff --git a/inc/classes/class-pages.php b/inc/classes/class-pages.php index f10e2ad5b..67f5f6f5d 100644 --- a/inc/classes/class-pages.php +++ b/inc/classes/class-pages.php @@ -366,6 +366,7 @@ public function admin_enqueue_scripts() { 'currentUserRoles' => wp_get_current_user()->roles, // Current user roles. 'validApiKey' => rtgodam_is_api_key_valid(), 'adminUrl' => admin_url(), + 'godamBaseUrl' => RTGODAM_IO_API_BASE, 'gfActive' => $is_gf_active, 'cf7Active' => $is_cf7_active, 'wpformsActive' => $is_wpforms_active, diff --git a/inc/classes/class-seo.php b/inc/classes/class-seo.php index 7be179070..a265cfe1c 100644 --- a/inc/classes/class-seo.php +++ b/inc/classes/class-seo.php @@ -105,7 +105,7 @@ private function extract_video_seo_schema_from_block( $block ) { $schemas[] = $block['attrs']['seo']; } } - + if ( ! empty( $block['innerBlocks'] ) ) { foreach ( $block['innerBlocks'] as $inner_block ) { $schemas = array_merge( @@ -114,7 +114,7 @@ private function extract_video_seo_schema_from_block( $block ) { ); } } - + return $schemas; } @@ -128,14 +128,14 @@ private function extract_video_seo_schema_from_block( $block ) { public function add_video_duration_for_video_seo( $response, $post ) { if ( 'video' === $post->post_mime_type || str_starts_with( $post->post_mime_type, 'video/' ) ) { $meta = wp_get_attachment_metadata( $post->ID ); - + if ( ! empty( $meta['length'] ) && is_numeric( $meta['length'] ) ) { $duration_seconds = (int) $meta['length']; - + $hours = floor( $duration_seconds / 3600 ); $minutes = floor( ( $duration_seconds % 3600 ) / 60 ); $seconds = $duration_seconds % 60; - + $iso_duration = 'PT'; if ( $hours > 0 ) { $iso_duration .= $hours . 'H'; @@ -146,11 +146,11 @@ public function add_video_duration_for_video_seo( $response, $post ) { if ( $seconds > 0 || 'PT' === $iso_duration ) { $iso_duration .= $seconds . 'S'; } - + $response->data['video_duration_iso8601'] = $iso_duration; } } - + return $response; } @@ -172,31 +172,31 @@ public function add_video_seo_schema() { if ( ! is_singular() ) { return; } - + global $post; - + $raw = get_post_meta( $post->ID, self::VIDEO_SEO_SCHEMA_META_KEY, true ); - + // Normalize and validate raw data. if ( empty( $raw ) ) { return; } - + if ( ! is_array( $raw ) ) { $raw = maybe_unserialize( $raw ); } - + if ( ! is_array( $raw ) || empty( $raw ) ) { return; } - + $schemas = array(); - + foreach ( $raw as $video ) { if ( ! is_array( $video ) ) { continue; } - + $schema = array( '@context' => 'https://schema.org', '@type' => 'VideoObject', @@ -206,22 +206,22 @@ public function add_video_seo_schema() { 'uploadDate' => sanitize_text_field( $video['uploadDate'] ?? '' ), 'isFamilyFriendly' => isset( $video['isFamilyFriendly'] ) ? (bool) $video['isFamilyFriendly'] : true, ); - + if ( ! empty( $video['thumbnailUrl'] ) ) { $schema['thumbnailUrl'] = esc_url_raw( $video['thumbnailUrl'] ); } - + if ( ! empty( $video['duration'] ) ) { $schema['duration'] = sanitize_text_field( $video['duration'] ); } - + $schemas[] = $schema; } if ( empty( $schemas ) ) { return; } - + // Output a single