Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions assets/dev/js/admin/admin.js
Original file line number Diff line number Diff line change
Expand Up @@ -118,6 +118,22 @@ import FloatingButtonsHandler from 'elementor/modules/floating-buttons/assets/js
} );
} );

$( '.e-notice--cta.e-notice--dismissible[data-notice_id="plugin_image_optimization"] a.e-button--cta' ).on( 'click', function() {
elementorCommon.ajax.addRequest( 'elementor_image_optimization_campaign', {
data: {
source: 'io-wp-media-library-install',
},
} );
} );

$( '.e-a-apps .e-a-item[data-plugin="image-optimization/image-optimization.php"] a.e-btn' ).on( 'click', function() {
elementorCommon.ajax.addRequest( 'elementor_image_optimization_campaign', {
data: {
source: 'io-esetting-addons-install',
},
} );
} );

$( '#elementor-clear-cache-button' ).on( 'click', function( event ) {
event.preventDefault();
var $thisButton = $( this );
Expand Down
1 change: 1 addition & 0 deletions assets/dev/js/admin/hints/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@
dismissId: event.target.closest( '.e-hint__container' ).dataset.event,
},
} );

this.hideHint( event );
},

Expand Down
7 changes: 7 additions & 0 deletions assets/dev/js/editor/controls/gallery.js
Original file line number Diff line number Diff line change
Expand Up @@ -258,6 +258,13 @@ ControlMediaItemView = ControlBaseDataView.extend( {
if ( actionURL ) {
window.open( actionURL, '_blank' );
}

elementorCommon.ajax.addRequest( 'elementor_image_optimization_campaign', {
data: {
source: 'io-editor-gallery-install',
},
} );

this.hidePromotion();
},

Expand Down
7 changes: 7 additions & 0 deletions assets/dev/js/editor/controls/media.js
Original file line number Diff line number Diff line change
Expand Up @@ -221,6 +221,13 @@ ControlMediaItemView = ControlMultipleBaseItemView.extend( {
if ( ! eventName ) {
eventName = this.getDismissPromotionEventName();
}

elementorCommon.ajax.addRequest( 'elementor_image_optimization_campaign', {
data: {
source: 'io-editor-image-install',
},
} );

// Prevent opening the same promotion again in current editor session.
elementor.config.user.dismissed_editor_notices.push( eventName );
},
Expand Down
8 changes: 7 additions & 1 deletion assets/dev/js/editor/utils/helpers.js
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import ColorPicker from './color-picker';
import DocumentHelper from 'elementor-editor/document/helper-bc';
import ContainerHelper from 'elementor-editor-utils/container-helper';
import DOMPurify from 'dompurify';
import DOMPurify, { isValidAttribute } from 'dompurify';

const allowedHTMLWrapperTags = [
'article',
Expand Down Expand Up @@ -704,4 +704,10 @@ module.exports = {
sanitize( value, options ) {
return DOMPurify.sanitize( value, options );
},

sanitizeUrl( url ) {
const isValidUrl = !! url ? isValidAttribute( 'a', 'href', url ) : false;

return isValidUrl ? url : '';
},
};
20 changes: 20 additions & 0 deletions core/admin/admin.php
Original file line number Diff line number Diff line change
Expand Up @@ -878,6 +878,8 @@ public function __construct() {
add_action( 'in_plugin_update_message-' . ELEMENTOR_PLUGIN_BASE, function( $plugin_data ) {
$this->version_update_warning( ELEMENTOR_VERSION, $plugin_data['new_version'] );
} );

add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_hints' ] );
}

/**
Expand Down Expand Up @@ -987,4 +989,22 @@ private function maybe_enqueue_hints() {

wp_enqueue_script( 'media-hints' );
}

public function register_ajax_hints( $ajax_manager ) {
$ajax_manager->register_ajax_action( 'elementor_image_optimization_campaign', [ $this, 'ajax_set_image_optimization_campaign' ] );
}

public function ajax_set_image_optimization_campaign( $request ) {
if ( empty( $request['source'] ) ) {
return;
}

$campaign_data = [
'source' => sanitize_key( $request['source'] ),
'campaign' => 'io-plg',
'medium' => 'wp-dash',
];

set_transient( 'elementor_image_optimization_campaign', $campaign_data, 30 * DAY_IN_SECONDS );
}
}
2 changes: 1 addition & 1 deletion includes/widgets/heading.php
Original file line number Diff line number Diff line change
Expand Up @@ -390,7 +390,7 @@ protected function content_template() {
let title = elementor.helpers.sanitize( settings.title, { ALLOW_DATA_ATTR: false } );

if ( '' !== settings.link.url ) {
title = '<a href="' + _.escape( settings.link.url ) + '">' + title + '</a>';
title = '<a href="' + elementor.helpers.sanitizeUrl( settings.link.url ) + '">' + title + '</a>';
}

view.addRenderAttribute( 'title', 'class', [ 'elementor-heading-title' ] );
Expand Down
2 changes: 1 addition & 1 deletion includes/widgets/icon-box.php
Original file line number Diff line number Diff line change
Expand Up @@ -789,7 +789,7 @@ protected function content_template() {
view.addRenderAttribute( 'icon', 'class', 'elementor-icon elementor-animation-' + settings.hover_animation );

if ( hasLink ) {
view.addRenderAttribute( 'link', 'href', settings.link.url );
view.addRenderAttribute( 'link', 'href', elementor.helpers.sanitizeUrl( settings.link.url ) );
view.addRenderAttribute( 'icon', 'tabindex', '-1' );
}

Expand Down
2 changes: 1 addition & 1 deletion includes/widgets/icon-list.php
Original file line number Diff line number Diff line change
Expand Up @@ -806,7 +806,7 @@ protected function content_template() {

<li {{{ view.getRenderAttributeString( 'list_item' ) }}}>
<# if ( item.link && item.link.url ) { #>
<a href="{{ item.link.url }}">
<a href="{{ elementor.helpers.sanitizeUrl( item.link.url ) }}">
<# } #>
<# if ( item.icon || item.selected_icon.value ) { #>
<span class="elementor-icon-list-icon">
Expand Down
2 changes: 1 addition & 1 deletion includes/widgets/icon.php
Original file line number Diff line number Diff line change
Expand Up @@ -486,7 +486,7 @@ protected function content_template() {
return;
}

const link = settings.link.url ? 'href="' + _.escape( settings.link.url ) + '"' : '',
const link = settings.link.url ? 'href="' + elementor.helpers.sanitizeUrl( settings.link.url ) + '"' : '',
iconHTML = elementor.helpers.renderIcon( view, settings.selected_icon, { 'aria-hidden': true }, 'i' , 'object' ),
migrated = elementor.helpers.isIconMigrated( settings, 'selected_icon' ),
iconTag = link ? 'a' : 'div';
Expand Down
4 changes: 2 additions & 2 deletions includes/widgets/image-box.php
Original file line number Diff line number Diff line change
Expand Up @@ -700,7 +700,7 @@ protected function content_template() {
var imageHtml = '<img src="' + _.escape( image_url ) + '" class="elementor-animation-' + _.escape( settings.hover_animation ) + '" />';

if ( settings.link.url ) {
imageHtml = '<a href="' + _.escape( settings.link.url ) + '" tabindex="-1">' + imageHtml + '</a>';
imageHtml = '<a href="' + elementor.helpers.sanitizeUrl( settings.link.url ) + '" tabindex="-1">' + imageHtml + '</a>';
}

html += '<figure class="elementor-image-box-img">' + imageHtml + '</figure>';
Expand All @@ -714,7 +714,7 @@ protected function content_template() {
titleSizeTag = elementor.helpers.validateHTMLTag( settings.title_size );

if ( settings.link.url ) {
title_html = '<a href="' + _.escape( settings.link.url ) + '">' + title_html + '</a>';
title_html = '<a href="' + elementor.helpers.sanitizeUrl( settings.link.url ) + '">' + title_html + '</a>';
}

view.addRenderAttribute( 'title_text', 'class', 'elementor-image-box-title' );
Expand Down
2 changes: 1 addition & 1 deletion includes/widgets/image.php
Original file line number Diff line number Diff line change
Expand Up @@ -822,7 +822,7 @@ protected function content_template() {
}

if ( link_url ) {
#><a class="elementor-clickable" data-elementor-open-lightbox="{{ settings.open_lightbox }}" href="{{ link_url }}"><#
#><a class="elementor-clickable" data-elementor-open-lightbox="{{ settings.open_lightbox }}" href="{{ elementor.helpers.sanitizeUrl( link_url ) }}"><#
}
#><img src="{{ image_url }}" class="{{ imgClass }}" /><#

Expand Down
2 changes: 1 addition & 1 deletion includes/widgets/social-icons.php
Original file line number Diff line number Diff line change
Expand Up @@ -660,7 +660,7 @@ protected function content_template() {
social = elementor.helpers.getSocialNetworkNameFromIcon( item.social_icon, item.social, false, migrated );
#>
<span class="elementor-grid-item">
<a class="elementor-icon elementor-social-icon elementor-social-icon-{{ social }} elementor-animation-{{ settings.hover_animation }} elementor-repeater-item-{{item._id}}" href="{{ link }}">
<a class="elementor-icon elementor-social-icon elementor-social-icon-{{ social }} elementor-animation-{{ settings.hover_animation }} elementor-repeater-item-{{item._id}}" href="{{ elementor.helpers.sanitizeUrl( link ) }}">
<span class="elementor-screen-only">{{{ social }}}</span>
<#
iconsHTML[ index ] = elementor.helpers.renderIcon( view, item.social_icon, {}, 'i', 'object' );
Expand Down
2 changes: 1 addition & 1 deletion includes/widgets/testimonial.php
Original file line number Diff line number Diff line change
Expand Up @@ -567,7 +567,7 @@ protected function content_template() {

var imageHtml = '<img src="' + _.escape( imageUrl ) + '" alt="testimonial" />';
if ( settings.link.url ) {
imageHtml = '<a href="' + _.escape( settings.link.url ) + '">' + imageHtml + '</a>';
imageHtml = '<a href="' + elementor.helpers.sanitizeUrl( settings.link.url ) + '">' + imageHtml + '</a>';
}
}

Expand Down
2 changes: 1 addition & 1 deletion includes/widgets/traits/button-trait.php
Original file line number Diff line number Diff line change
Expand Up @@ -577,7 +577,7 @@ protected function content_template() {
view.addRenderAttribute( 'button', 'class', 'elementor-button' );

if ( '' !== settings.link.url ) {
view.addRenderAttribute( 'button', 'href', settings.link.url );
view.addRenderAttribute( 'button', 'href', elementor.helpers.sanitizeUrl( settings.link.url ) );
view.addRenderAttribute( 'button', 'class', 'elementor-button-link' );
} else {
view.addRenderAttribute( 'button', 'role', 'button' );
Expand Down
6 changes: 5 additions & 1 deletion modules/ai/assets/js/editor/api/index.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,10 @@
const request = ( endpoint, data = {}, immediately = false, signal ) => {
if ( Object.keys( data ).length ) {
data.context = window.elementorAiCurrentContext;
if ( window.elementorAiCurrentContext ) {
data.context = window.elementorAiCurrentContext;
} else {
data.context = window.elementorWpAiCurrentContext;
}
}

return new Promise( ( resolve, reject ) => {
Expand Down
47 changes: 47 additions & 0 deletions modules/ai/assets/js/gutenberg/edit-text-with-ai.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { useState } from '@wordpress/element';
import { __ } from '@wordpress/i18n';
import { AiText } from './text-with-ai';
import { AIIcon } from '@elementor/icons';
import React from 'react';
const { ToolbarButton } = wp.components;
const { BlockControls } = wp.blockEditor;

export const EditTextWithAi = ( props ) => {
const [ shouldRenderAiApp, setShouldRenderAiApp ] = useState( false );
const BlockEdit = props.blockEdit;

const supportedBlocks = [ 'core/paragraph', 'core/heading' ];

if ( ! supportedBlocks.includes( props.name ) ) {
return <BlockEdit { ...props } />;
}

if ( shouldRenderAiApp ) {
return <AiText onClose={ () => setShouldRenderAiApp( false ) }
blockName={ props.name }
initialValue={ props.attributes.content ? String( props.attributes.content ) : '' }
blockClientId={ props.clientId }
/>;
}

return (
<>
<BlockControls>
<ToolbarButton
icon={ <AIIcon color="secondary" /> }
label={ __( 'Edit with Elementor AI', 'elementor' ) }
onClick={ () => setShouldRenderAiApp( true ) }
/>
</BlockControls>
{ <BlockEdit { ...props } /> }
</>
);
};

EditTextWithAi.propTypes = {
name: PropTypes.string,
blockEdit: PropTypes.func,
attributes: PropTypes.object,
clientId: PropTypes.string,
};

2 changes: 1 addition & 1 deletion modules/ai/assets/js/gutenberg/excerpt.js
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const GenerateExcerptWithAI = () => {
};

return (
<div style={ { paddingBottom: '0.6em' } }>
<div style={ { paddingTop: '0.6em' } }>
<RequestIdsProvider>
<Icon className={ 'eicon-ai' } />
<AiLink onClick={ handleButtonClick }>{ __( 'Generate with Elementor AI', 'elementor' ) }</AiLink>
Expand Down
77 changes: 62 additions & 15 deletions modules/ai/assets/js/gutenberg/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { createRoot } from '@wordpress/element';
import GenerateExcerptWithAI from './excerpt';
import GenerateFeaturedImageWithAI from './featured-image';
import { GenerateTextWithAi } from './text-with-ai';
import React from 'react';
import { EditTextWithAi } from './edit-text-with-ai';

( function() {
'use strict';
Expand All @@ -16,16 +18,7 @@ import { GenerateTextWithAi } from './text-with-ai';
if ( excerptPanel && ! document.querySelector( '.e-excerpt-ai' ) ) {
const rootElement = document.createElement( 'div' );
rootElement.classList.add( 'e-excerpt-ai' );
// Find the existing link with class "components-external-link"
const existingExcerptLink = excerptPanel.querySelector( '.components-external-link' );
if ( existingExcerptLink ) {
existingExcerptLink.classList.add( 'existing-excerpt-link' );
// Append the new link before the existing one
excerptPanel.insertBefore( rootElement, existingExcerptLink );
} else {
// Append the new link to the excerpt panel
excerptPanel.appendChild( rootElement );
}
excerptPanel.appendChild( rootElement );

const urlSearchParams = new URLSearchParams( window.location.search );
elementorCommon?.ajax?.addRequestConstant( 'editor_post_id', urlSearchParams?.get( 'post' ) );
Expand All @@ -48,14 +41,16 @@ import { GenerateTextWithAi } from './text-with-ai';
}
};

const addTextWithAI = ( blockName ) => {
const textPanel = document.querySelector( '.block-editor-block-card__content' );
if ( textPanel && ! document.querySelector( '.e-text-ai' ) ) {
const addTextWithAI = ( blockName, blockClientId ) => {
const textPanel = document.querySelector( '.block-editor-block-card__description, .block-editor-block-card__content' );
if ( textPanel && ! document.querySelector( `.e-text-ai[data-client-id="${ blockClientId }"]` ) ) {
removeAiIndicator();
const rootElement = document.createElement( 'div' );
rootElement.classList.add( 'e-text-ai' );
rootElement.setAttribute( 'data-client-id', blockClientId );
textPanel.appendChild( rootElement );
const root = createRoot( rootElement );
root.render( <GenerateTextWithAi blockName={ blockName } /> );
root.render( <GenerateTextWithAi blockName={ blockName } blockClientId={ blockClientId } /> );
}
};

Expand All @@ -78,7 +73,7 @@ import { GenerateTextWithAi } from './text-with-ai';
const addAiIndicatorToTextBlock = ( blockNames ) => {
const selectedBlock = wp.data.select( 'core/block-editor' )?.getSelectedBlock();
if ( selectedBlock && blockNames.some( ( name ) => selectedBlock.name.includes( name ) ) ) {
addTextWithAI( selectedBlock.name );
addTextWithAI( selectedBlock.name, selectedBlock.clientId );
} else {
removeAiIndicator();
}
Expand All @@ -89,5 +84,57 @@ import { GenerateTextWithAi } from './text-with-ai';
addAiIndicator( 'featured-image', addGenerateFeaturedImageWithAI );
addAiIndicatorToTextBlock( [ 'paragraph', 'heading' ] );
} );

const observer = new MutationObserver( ( mutationsList ) => {
for ( const mutation of mutationsList ) {
if ( 'childList' === mutation.type ) {
if ( document.querySelector( '.editor-post-excerpt' ) ) {
addGenerateExcerptWithAI();
}
}
}
} );

observer.observe( document.body, { childList: true, subtree: true } );
window.addEventListener( 'beforeunload', () => {
observer.disconnect();
} );
} );
} )( jQuery );

( function( wp ) {
const { addFilter } = wp.hooks;

const addAiButtonToToolbar = ( BlockEdit ) => {
return ( props ) => {
return <EditTextWithAi { ...props } blockEdit={ BlockEdit } />;
};
};

addFilter( 'editor.BlockEdit', 'elementor-ai-toolbar-button', addAiButtonToToolbar );
} )( window.wp );

( function() {
'use strict';

const setElementorWpAiCurrentContext = () => {
const selectedBlock = wp.data.select( 'core/block-editor' ).getSelectedBlock();
if ( selectedBlock ) {
const blockName = 'core/heading' === selectedBlock.name ? 'heading' : selectedBlock.name;
window.elementorWpAiCurrentContext = {
widgetType: blockName,
controlName: blockName,
};
} else {
window.elementorWpAiCurrentContext = null;
}
};

wp.data.subscribe( setElementorWpAiCurrentContext );

const clearElementorAiCurrentContext = () => {
window.elementorWpAiCurrentContext = null;
};

window.addEventListener( 'beforeunload', clearElementorAiCurrentContext );
} )();
Loading