first commit

This commit is contained in:
Ryan Ariana
2024-05-06 11:04:37 +07:00
commit aee061ddba
7322 changed files with 2918816 additions and 0 deletions

View File

@@ -0,0 +1,143 @@
<?php
namespace Elementor\Modules\AdminBar;
use Elementor\Core\Base\Document;
use Elementor\Core\Base\App as BaseApp;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseApp {
/**
* @var Document[]
*/
private $documents = [];
/**
* @return bool
*/
public static function is_active() {
return is_admin_bar_showing();
}
/**
* @return string
*/
public function get_name() {
return 'admin-bar';
}
/**
* Collect the documents that was rendered in the current page.
*
* @param Document $document
* @param $is_excerpt
*/
public function add_document_to_admin_bar( Document $document, $is_excerpt ) {
if (
$is_excerpt ||
! $document::get_property( 'show_on_admin_bar' ) ||
! $document->is_editable_by_current_user()
) {
return;
}
$this->documents[ $document->get_main_id() ] = $document;
}
/**
* Scripts for module.
*/
public function enqueue_scripts() {
if ( empty( $this->documents ) ) {
return;
}
// Should load 'elementor-admin-bar' before 'admin-bar'
wp_dequeue_script( 'admin-bar' );
wp_enqueue_script(
'elementor-admin-bar',
$this->get_js_assets_url( 'elementor-admin-bar' ),
[ 'elementor-frontend-modules' ],
ELEMENTOR_VERSION,
true
);
// This is a core script of WordPress, it is not required to pass the 'ver' argument.
wp_enqueue_script( // phpcs:ignore WordPress.WP.EnqueuedResourceParameters
'admin-bar',
null,
[ 'elementor-admin-bar' ],
false,
true
);
$this->print_config( 'elementor-admin-bar' );
}
/**
* Creates admin bar menu items config.
*
* @return array
*/
public function get_init_settings() {
$settings = [];
if ( ! empty( $this->documents ) ) {
$settings['elementor_edit_page'] = $this->get_edit_button_config();
}
/**
* Admin bar settings in the frontend.
*
* Register admin_bar config to parse later in the frontend and add to the admin bar with JS.
*
* @since 3.0.0
*
* @param array $settings the admin_bar config
*/
$settings = apply_filters( 'elementor/frontend/admin_bar/settings', $settings );
return $settings;
}
/**
* Creates the config for 'Edit with elementor' menu item.
*
* @return array
*/
private function get_edit_button_config() {
$queried_object_id = get_queried_object_id();
$href = null;
if ( is_singular() && isset( $this->documents[ $queried_object_id ] ) ) {
$href = $this->documents[ $queried_object_id ]->get_edit_url();
unset( $this->documents[ $queried_object_id ] );
}
return [
'id' => 'elementor_edit_page',
'title' => esc_html__( 'Edit with Elementor', 'elementor' ),
'href' => $href,
'children' => array_map( function ( $document ) {
return [
'id' => "elementor_edit_doc_{$document->get_main_id()}",
'title' => $document->get_post()->post_title,
'sub_title' => $document::get_title(),
'href' => $document->get_edit_url(),
];
}, $this->documents ),
];
}
/**
* Module constructor.
*/
public function __construct() {
add_action( 'elementor/frontend/before_get_builder_content', [ $this, 'add_document_to_admin_bar' ], 10, 2 );
add_action( 'wp_footer', [ $this, 'enqueue_scripts' ], 11 /* after third party scripts */ );
}
}

View File

@@ -0,0 +1,150 @@
<?php
namespace Elementor\Modules\AdminTopBar;
use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager;
use Elementor\Plugin;
use Elementor\Core\Base\App as BaseApp;
use Elementor\Core\Experiments\Manager;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseApp {
/**
* @return bool
*/
public static function is_active() {
return is_admin();
}
/**
* @return string
*/
public function get_name() {
return 'admin-top-bar';
}
private function render_admin_top_bar() {
?>
<div id="e-admin-top-bar-root">
</div>
<?php
}
/**
* Enqueue admin scripts
*/
private function enqueue_scripts() {
wp_enqueue_style( 'elementor-admin-top-bar-fonts', 'https://fonts.googleapis.com/css2?family=Roboto:wght@400;500;700&display=swap', [], ELEMENTOR_VERSION );
wp_enqueue_style( 'elementor-admin-top-bar', $this->get_css_assets_url( 'admin-top-bar', null, 'default', true ), [], ELEMENTOR_VERSION );
/**
* Before admin top bar enqueue scripts.
*
* Fires before Elementor admin top bar scripts are enqueued.
*
* @since 3.19.0
*/
do_action( 'elementor/admin_top_bar/before_enqueue_scripts', $this );
wp_enqueue_script( 'elementor-admin-top-bar', $this->get_js_assets_url( 'admin-top-bar' ), [
'elementor-common',
'react',
'react-dom',
'tipsy',
], ELEMENTOR_VERSION, true );
wp_set_script_translations( 'elementor-admin-top-bar', 'elementor' );
$min_suffix = Utils::is_script_debug() ? '' : '.min';
wp_enqueue_script( 'tipsy', ELEMENTOR_ASSETS_URL . 'lib/tipsy/tipsy' . $min_suffix . '.js', [
'jquery',
], '1.0.0', true );
$this->print_config();
}
private function add_frontend_settings() {
$settings = [];
$settings['is_administrator'] = current_user_can( 'manage_options' );
// TODO: Find a better way to add apps page url to the admin top bar.
$settings['apps_url'] = admin_url( 'admin.php?page=elementor-apps' );
$settings['promotion'] = [
'text' => __( 'Upgrade Now', 'elementor' ),
'url' => 'https://go.elementor.com/wp-dash-admin-top-bar-upgrade/',
];
$settings['promotion'] = Filtered_Promotions_Manager::get_filtered_promotion_data(
$settings['promotion'],
'elementor/admin_top_bar/go_pro_promotion',
'url'
);
$current_screen = get_current_screen();
/** @var \Elementor\Core\Common\Modules\Connect\Apps\Library $library */
$library = Plugin::$instance->common->get_component( 'connect' )->get_app( 'library' );
if ( $library ) {
$settings = array_merge( $settings, [
'is_user_connected' => $library->is_connected(),
'connect_url' => $library->get_admin_url( 'authorize', [
'utm_source' => 'top-bar',
'utm_medium' => 'wp-dash',
'utm_campaign' => 'connect-account',
'utm_content' => $current_screen->id,
'source' => 'generic',
] ),
] );
}
$this->set_settings( $settings );
do_action( 'elementor/admin-top-bar/init', $this );
}
private function is_top_bar_active() {
$current_screen = get_current_screen();
if ( ! $current_screen ) {
return false;
}
$is_elementor_page = strpos( $current_screen->id ?? '', 'elementor' ) !== false;
$is_elementor_post_type_page = strpos( $current_screen->post_type ?? '', 'elementor' ) !== false;
return apply_filters(
'elementor/admin-top-bar/is-active',
$is_elementor_page || $is_elementor_post_type_page,
$current_screen
);
}
/**
* Module constructor.
*/
public function __construct() {
parent::__construct();
add_action( 'current_screen', function () {
if ( ! $this->is_top_bar_active() ) {
return;
}
$this->add_frontend_settings();
add_action( 'in_admin_header', function () {
$this->render_admin_top_bar();
} );
add_action( 'admin_enqueue_scripts', function () {
$this->enqueue_scripts();
} );
} );
}
}

View File

@@ -0,0 +1,660 @@
<?php
namespace Elementor\Modules\Ai\Connect;
use Elementor\Core\Common\Modules\Connect\Apps\Library;
use Elementor\Modules\Ai\Module;
use Elementor\Utils as ElementorUtils;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Ai extends Library {
const API_URL = 'https://my.elementor.com/api/v2/ai/';
const STYLE_PRESET = 'style_preset';
const IMAGE_TYPE = 'image_type';
const IMAGE_STRENGTH = 'image_strength';
const ASPECT_RATIO = 'ratio';
const IMAGE_RESOLUTION = 'image_resolution';
const PROMPT = 'prompt';
public function get_title() {
return esc_html__( 'AI', 'elementor' );
}
protected function get_api_url() {
return static::API_URL . '/';
}
public function get_usage() {
return $this->ai_request(
'POST',
'status/check',
[
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function get_cached_usage() {
$cache_key = 'elementor_ai_usage';
$cache_time = 24 * HOUR_IN_SECONDS;
$usage = get_site_transient( $cache_key );
if ( ! $usage ) {
$usage = $this->get_usage();
set_site_transient( $cache_key, $usage, $cache_time );
}
return $usage;
}
public function get_remote_config() {
return $this->ai_request(
'GET',
'remote-config/config',
[
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
/**
* get_file_payload
* @param $filename
* @param $file_type
* @param $file_path
* @param $boundary
*
* @return string
*/
private function get_file_payload( $filename, $file_type, $file_path, $boundary ) {
$name = $filename ?? basename( $file_path );
$mine_type = 'image' === $file_type ? image_type_to_mime_type( exif_imagetype( $file_path ) ) : $file_type;
$payload = '';
// Upload the file
$payload .= '--' . $boundary;
$payload .= "\r\n";
$payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"; filename="' . esc_attr( $name ) . '"' . "\r\n";
$payload .= 'Content-Type: ' . $mine_type . "\r\n";
$payload .= "\r\n";
$payload .= file_get_contents( $file_path );
$payload .= "\r\n";
return $payload;
}
private function get_upload_request_body( $body, $file, $boundary, $file_name = '' ) {
$payload = '';
// add all body fields as standard POST fields:
foreach ( $body as $name => $value ) {
$payload .= '--' . $boundary;
$payload .= "\r\n";
$payload .= 'Content-Disposition: form-data; name="' . esc_attr( $name ) . '"' . "\r\n\r\n";
$payload .= $value;
$payload .= "\r\n";
}
if ( is_array( $file ) ) {
foreach ( $file as $key => $file_data ) {
$payload .= $this->get_file_payload( $file_data['name'], $file_data['type'], $file_data['path'], $boundary );
}
} else {
$image_mime = image_type_to_mime_type( exif_imagetype( $file ) );
// @todo: add validation for supported image types
if ( empty( $file_name ) ) {
$file_name = basename( $file );
}
$payload .= $this->get_file_payload( $file_name, $image_mime, $file, $boundary );
}
$payload .= '--' . $boundary . '--';
return $payload;
}
private function ai_request( $method, $endpoint, $body, $file = false, $file_name = '', $format = 'default' ) {
$headers = [
'x-elementor-ai-version' => '2',
];
if ( $file ) {
$boundary = wp_generate_password( 24, false );
$body = $this->get_upload_request_body( $body, $file, $boundary, $file_name );
// add content type header
$headers['Content-Type'] = 'multipart/form-data; boundary=' . $boundary;
} elseif ( 'json' === $format ) {
$headers['Content-Type'] = 'application/json';
$body = wp_json_encode( $body );
}
return $this->http_request(
$method,
$endpoint,
[
'timeout' => 100,
'headers' => $headers,
'body' => $body,
],
[
'return_type' => static::HTTP_RETURN_TYPE_ARRAY,
'with_error_data' => true,
]
);
}
public function set_get_started() {
return $this->ai_request(
'POST',
'status/get-started',
[
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function set_status_feedback( $response_id ) {
return $this->ai_request(
'POST',
'status/feedback/' . $response_id,
[
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function set_used_gallery_image( $image_id ) {
return $this->ai_request(
'POST',
'status/used-gallery-image/' . $image_id,
[
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function get_completion_text( $prompt, $context, $request_ids ) {
return $this->ai_request(
'POST',
'text/completion',
[
'prompt' => $prompt,
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
/**
* get_image_prompt_enhanced
* @param $prompt
*
* @return mixed|\WP_Error
*/
public function get_image_prompt_enhanced( $prompt, $context, $request_ids ) {
return $this->ai_request(
'POST',
'text/enhance-image-prompt',
[
'prompt' => $prompt,
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function get_edit_text( $data, $context, $request_ids ) {
return $this->ai_request(
'POST',
'text/edit',
[
'input' => $data['payload']['input'],
'instruction' => $data['payload']['instruction'],
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function get_custom_code( $data, $context, $request_ids ) {
return $this->ai_request(
'POST',
'text/custom-code',
[
'prompt' => $data['payload']['prompt'],
'language' => $data['payload']['language'],
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function get_custom_css( $data, $context, $request_ids ) {
return $this->ai_request(
'POST',
'text/custom-css',
[
'prompt' => $data['payload']['prompt'],
'html_markup' => $data['payload']['html_markup'],
'element_id' => $data['payload']['element_id'],
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
/**
* get_text_to_image
* @param $prompt
* @param $prompt_settings
*
* @return mixed|\WP_Error
*/
public function get_text_to_image( $data, $context, $request_ids ) {
return $this->ai_request(
'POST',
'image/text-to-image',
[
self::PROMPT => $data['payload']['prompt'],
self::IMAGE_TYPE => $data['payload']['settings'][ self::IMAGE_TYPE ] . '/' . $data['payload']['settings'][ self::STYLE_PRESET ],
self::ASPECT_RATIO => $data['payload']['settings'][ self::ASPECT_RATIO ],
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
/**
* get_image_to_image
* @param $image_data
*
* @return mixed|\WP_Error
* @throws \Exception
*/
public function get_image_to_image( $image_data, $context, $request_ids ) {
$image_file = get_attached_file( $image_data['attachment_id'] );
if ( ! $image_file ) {
throw new \Exception( 'Image file not found' );
}
$result = $this->ai_request(
'POST',
'image/image-to-image',
[
self::PROMPT => $image_data[ self::PROMPT ],
self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ],
self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ],
self::ASPECT_RATIO => $image_data['promptSettings'][ self::ASPECT_RATIO ],
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
],
$image_file,
'image'
);
return $result;
}
/**
* get_image_to_image_upscale
* @param $image_data
*
* @return mixed|\WP_Error
* @throws \Exception
*/
public function get_image_to_image_upscale( $image_data, $context, $request_ids ) {
$image_file = get_attached_file( $image_data['attachment_id'] );
if ( ! $image_file ) {
throw new \Exception( 'Image file not found' );
}
$result = $this->ai_request(
'POST',
'image/image-to-image/upscale',
[
self::IMAGE_RESOLUTION => $image_data['promptSettings']['upscale_to'],
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
],
$image_file,
'image'
);
return $result;
}
/**
* get_image_to_image_remove_background
* @param $image_data
*
* @return mixed|\WP_Error
* @throws \Exception
*/
public function get_image_to_image_remove_background( $image_data, $context, $request_ids ) {
$image_file = get_attached_file( $image_data['attachment_id'] );
if ( ! $image_file ) {
throw new \Exception( 'Image file not found' );
}
$result = $this->ai_request(
'POST',
'image/image-to-image/remove-background',
[
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
],
$image_file,
'image'
);
return $result;
}
/**
* get_image_to_image_remove_text
* @param $image_data
*
* @return mixed|\WP_Error
* @throws \Exception
*/
public function get_image_to_image_replace_background( $image_data, $context, $request_ids ) {
$image_file = get_attached_file( $image_data['attachment_id'] );
if ( ! $image_file ) {
throw new \Exception( 'Image file not found' );
}
$result = $this->ai_request(
'POST',
'image/image-to-image/replace-background',
[
self::PROMPT => $image_data[ self::PROMPT ],
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
],
$image_file,
'image'
);
return $result;
}
/**
* store_temp_file
* used to store a temp file for the AI request and deletes it once the request is done
* @param $file_content
* @param $file_ext
*
* @return string
*/
private function store_temp_file( $file_content, $file_ext = '' ) {
$temp_file = str_replace( '.tmp', '', wp_tempnam() . $file_ext );
file_put_contents( $temp_file, $file_content );
// make sure the temp file is deleted on shutdown
register_shutdown_function( function () use ( $temp_file ) {
if ( file_exists( $temp_file ) ) {
unlink( $temp_file );
}
} );
return $temp_file;
}
/**
* get_image_to_image_out_painting
* @param $image_data
*
* @return mixed|\WP_Error
* @throws \Exception
*/
public function get_image_to_image_out_painting( $image_data, $context, $request_ids ) {
$img_content = str_replace( ' ', '+', $image_data['mask'] );
$img_content = substr( $img_content, strpos( $img_content, ',' ) + 1 );
$img_content = base64_decode( $img_content );
$mask_file = $this->store_temp_file( $img_content, '.png' );
if ( ! $mask_file ) {
throw new \Exception( 'Expended Image file not found' );
}
$result = $this->ai_request(
'POST',
'image/image-to-image/outpainting',
[
self::PROMPT => $image_data[ self::PROMPT ],
self::IMAGE_TYPE => '',
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
],
[
[
'name' => 'image',
'type' => 'image',
'path' => $mask_file,
],
]
);
return $result;
}
/**
* get_image_to_image_mask
* @param $image_data
*
* @return mixed|\WP_Error
* @throws \Exception
*/
public function get_image_to_image_mask( $image_data, $context, $request_ids ) {
$image_file = get_attached_file( $image_data['attachment_id'] );
$mask_file = $this->store_temp_file( $image_data['mask'], '.svg' );
if ( ! $image_file ) {
throw new \Exception( 'Image file not found' );
}
if ( ! $mask_file ) {
throw new \Exception( 'Mask file not found' );
}
$result = $this->ai_request(
'POST',
'image/image-to-image/inpainting',
[
self::PROMPT => $image_data[ self::PROMPT ],
self::IMAGE_TYPE => $image_data['promptSettings'][ self::IMAGE_TYPE ] . '/' . $image_data['promptSettings'][ self::STYLE_PRESET ],
self::IMAGE_STRENGTH => $image_data['promptSettings'][ self::IMAGE_STRENGTH ],
'context' => wp_json_encode( $context ),
'ids' => $request_ids,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
],
[
[
'name' => 'image',
'type' => 'image',
'path' => $image_file,
],
[
'name' => 'mask_image',
'type' => 'image/svg+xml',
'path' => $mask_file,
],
]
);
return $result;
}
public function generate_layout( $data, $context ) {
$endpoint = 'generate/layout';
$body = [
'prompt' => $data['prompt'],
'variationType' => (int) $data['variationType'],
'ids' => $data['ids'],
];
if ( ! empty( $data['prevGeneratedIds'] ) ) {
$body['generatedBaseTemplatesIds'] = $data['prevGeneratedIds'];
}
if ( ! empty( $data['attachments'] ) ) {
$attachment = $data['attachments'][0];
switch ( $attachment['type'] ) {
case 'json':
$endpoint = 'generate/generate-json-variation';
$body['json'] = [
'type' => 'elementor',
'elements' => [ $attachment['content'] ],
'label' => $attachment['label'],
'source' => $attachment['source'],
];
break;
case 'url':
$endpoint = 'generate/html-to-elementor';
$html = wp_json_encode( $attachment['content'] );
$body['html'] = $html;
break;
}
}
$context['currentContext'] = $data['currentContext'];
$context['features'] = [
'supportedFeatures' => [],
];
if ( ElementorUtils::has_pro() ) {
$context['features']['subscriptions'] = [ 'Pro' ];
}
if ( Plugin::$instance->experiments->is_feature_active( 'container_grid' ) ) {
$context['features']['supportedFeatures'][] = 'Grid';
}
if ( Plugin::instance()->experiments->get_active_features()['nested-elements'] ) {
$context['features']['supportedFeatures'][] = 'NestedElements';
}
$metadata = [
'context' => $context,
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
'config' => [
'generate' => [
'all' => true,
],
],
];
$body = array_merge( $body, $metadata );
// Temp hack for platforms that filters the http_request_args, and it breaks JSON requests.
remove_all_filters( 'http_request_args' );
return $this->ai_request(
'POST',
$endpoint,
$body,
false,
'',
'json'
);
}
public function get_layout_prompt_enhanced( $prompt, $enhance_type, $context ) {
return $this->ai_request(
'POST',
'generate/enhance-prompt',
[
'prompt' => $prompt,
'enhance_type' => $enhance_type,
'context' => wp_json_encode( $context ),
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function get_history_by_type( $type, $page, $limit, $context = [] ) {
$endpoint = Module::HISTORY_TYPE_ALL === $type
? 'history'
: add_query_arg( [
'page' => $page,
'limit' => $limit,
], "history/{$type}" );
return $this->ai_request(
'POST',
$endpoint,
[
'context' => wp_json_encode( $context ),
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function delete_history_item( $id, $context = [] ) {
return $this->ai_request(
'DELETE', 'history/' . $id,
[
'context' => wp_json_encode( $context ),
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
public function toggle_favorite_history_item( $id, $context = [] ) {
return $this->ai_request(
'POST', sprintf( 'history/%s/favorite', $id ),
[
'context' => wp_json_encode( $context ),
'api_version' => ELEMENTOR_VERSION,
'site_lang' => get_bloginfo( 'language' ),
]
);
}
protected function init() {}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,76 @@
<?php
namespace Elementor\Modules\Announcements\Classes;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Announcement {
/**
* @var array
*/
protected $raw_data;
/**
* @var array
*/
protected $triggers;
public function __construct( array $data ) {
$this->raw_data = $data;
$this->set_triggers();
}
/**
* @return array
*/
protected function get_triggers(): array {
return $this->triggers;
}
protected function set_triggers() {
$triggers = $this->raw_data['triggers'] ?? [];
foreach ( $triggers as $trigger ) {
$this->triggers[] = Utils::get_trigger_object( $trigger );
}
}
/**
* is_active
* @return bool
*/
public function is_active(): bool {
$triggers = $this->get_triggers();
if ( empty( $triggers ) ) {
return true;
}
foreach ( $triggers as $trigger ) {
if ( ! $trigger->is_active() ) {
return false;
}
}
return true;
}
public function after_triggered() {
foreach ( $this->get_triggers() as $trigger ) {
if ( $trigger->is_active() ) {
$trigger->after_triggered();
}
}
}
/**
* @return array
*/
public function get_prepared_data(): array {
$raw_data = $this->raw_data;
unset( $raw_data['triggers'] );
return $raw_data;
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Elementor\Modules\Announcements\Classes;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
abstract class Trigger_Base {
/**
* @var string
*/
protected $name = 'trigger-base';
/**
* @return string
*/
public function get_name(): string {
return $this->name;
}
/**
* @return bool
*/
public function is_active(): bool {
return true;
}
public function after_triggered() {
}
}

View File

@@ -0,0 +1,38 @@
<?php
namespace Elementor\Modules\Announcements\Classes;
use Elementor\Modules\Announcements\Triggers\{
IsFlexContainerInactive, AiStarted
};
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Utils {
/**
* get_trigger_object
*
* @param $trigger
*
* @return IsFlexContainerInactive|false
*/
public static function get_trigger_object( $trigger ) {
$object_trigger = apply_filters( 'elementor/announcements/trigger_object', false, $trigger );
if ( false !== $object_trigger ) {
return $object_trigger;
}
//@TODO - replace with trigger manager
switch ( $trigger['action'] ) {
case 'isFlexContainerInactive':
return new IsFlexContainerInactive();
case 'aiStared':
return new AiStarted();
default:
return false;
}
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace Elementor\Modules\Announcements;
use Elementor\Core\Base\App as BaseApp;
use Elementor\Modules\Announcements\Classes\Announcement;
use Elementor\Settings as ElementorSettings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseApp {
/**
* @return bool
*/
public static function is_active(): bool {
return is_admin();
}
/**
* @return string
*/
public function get_name(): string {
return 'announcements';
}
/**
* Render wrapper for the app to load.
*/
private function render_app_wrapper() {
?>
<div id="e-announcements-root"></div>
<?php
}
/**
* Enqueue app scripts.
*/
private function enqueue_scripts() {
wp_enqueue_script(
'announcements-app',
$this->get_js_assets_url( 'announcements-app' ),
[
'wp-i18n',
],
ELEMENTOR_VERSION,
true
);
wp_set_script_translations( 'announcements-app', 'elementor' );
$this->print_config( 'announcements-app' );
}
/**
* Get initialization settings to use in frontend.
*
* @return array[]
*/
protected function get_init_settings(): array {
$active_announcements = $this->get_active_announcements();
$additional_settings = [];
foreach ( $active_announcements as $announcement ) {
$additional_settings[] = $announcement->get_prepared_data();
//@TODO - replace with ajax request from the front after actually triggered
$announcement->after_triggered();
}
return [
'announcements' => $additional_settings,
];
}
/**
* Enqueue the module styles.
*/
public function enqueue_styles() {
wp_enqueue_style(
'announcements-app',
$this->get_css_assets_url( 'modules/announcements/announcements' ),
[],
ELEMENTOR_VERSION
);
}
/**
* Retrieve all announcement in raw format ( array ).
*
* @return array[]
*/
private function get_raw_announcements(): array {
$raw_announcements = [
[
'title' => 'Unlock the Power of Elementor AI ',
'description' => '<p>Design a website true to your brand with natively integrated AI tools.</p>
<ul>
<li>Generate containers using text or any website you reference from the web and get a wireframe layout to start with. Use the container variations capability to bring the wireframe to life with design and content.</li>
<li>Let AI write or edit your text in the context of your brand, tone of voice and optimal length. Also generate custom code or CSS that seamlessly integrates into your website.</li>
<li>Create one-of-a-kind images, add, or erase content from existing images or expand them beyond their original size and aspect ratio.</li>
<li>Use Elementors AI History Panel to efficiently access previously-generated text, code or image prompts, and ensure consistency across your site.</li>
</ul>',
'media' => [
'type' => 'image',
'src' => ELEMENTOR_ASSETS_URL . 'images/announcement.png?' . ELEMENTOR_VERSION,
],
'cta' => [
[
'label' => 'Continue',
'variant' => 'primary',
'target' => '_blank',
],
[
'label' => 'Learn More',
'target' => '_blank',
'url' => 'https://go.elementor.com/whats-new-popup-learn-elementor-ai/',
],
],
'triggers' => [
[
'action' => 'aiStared',
],
],
],
];
// DO NOT USE THIS FILTER
return apply_filters( 'elementor/announcements/raw_announcements', $raw_announcements );
}
/**
* Retrieve all announcement objects.
*
* @return array
*/
private function get_announcements(): array {
$announcements = [];
foreach ( $this->get_raw_announcements() as $announcement_data ) {
$announcements[] = new Announcement( $announcement_data );
}
return $announcements;
}
/**
* Retrieve all active announcement objects.
*
* @return array
*/
private function get_active_announcements(): array {
$active_announcements = [];
foreach ( $this->get_announcements() as $announcement ) {
if ( $announcement->is_active() ) {
$active_announcements[] = $announcement;
}
}
return $active_announcements;
}
public function __construct() {
parent::__construct();
add_action( 'elementor/init', [ $this, 'on_elementor_init' ] );
}
public function on_elementor_init() {
if ( empty( $this->get_active_announcements() ) ) {
return;
}
add_action( 'elementor/editor/footer', function () {
$this->render_app_wrapper();
} );
add_action( 'elementor/editor/after_enqueue_scripts', function () {
$this->enqueue_scripts();
$this->enqueue_styles();
} );
}
}

View File

@@ -0,0 +1,28 @@
<?php
namespace Elementor\Modules\Announcements\Triggers;
use Elementor\Modules\Announcements\Classes\Trigger_Base;
use Elementor\User;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class AiStarted extends Trigger_Base {
/**
* @var string
*/
protected $name = 'ai-get-started-announcement';
public function after_triggered() {
User::set_introduction_viewed( [ 'introductionKey' => $this->name ] );
}
/**
* @return bool
*/
public function is_active(): bool {
return ! User::get_introduction_meta( 'ai_get_started' ) && ! User::get_introduction_meta( $this->name );
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Elementor\Modules\Announcements\Triggers;
use Elementor\Modules\Announcements\Classes\Trigger_Base;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class IsFlexContainerInactive extends Trigger_Base {
const USER_META_KEY = 'announcements_user_counter';
/**
* @var string
*/
protected $name = 'is-flex-container-inactive';
/**
* @return int
*/
protected function get_view_count(): int {
$user_counter = $this->get_user_announcement_count();
return ! empty( $user_counter ) ? (int) $user_counter : 0;
}
public function after_triggered() {
$new_counter = $this->get_view_count() + 1;
update_user_meta( get_current_user_id(), self::USER_META_KEY, $new_counter );
}
/**
* @return bool
*/
public function is_active(): bool {
$is_feature_active = Plugin::$instance->experiments->is_feature_active( 'container' );
$counter = $this->get_user_announcement_count();
return ! $is_feature_active && (int) $counter < 1;
}
/**
* @return string
*/
private function get_user_announcement_count(): string {
return get_user_meta( get_current_user_id(), self::USER_META_KEY, true );
}
}

View File

@@ -0,0 +1,191 @@
<?php
namespace Elementor\Modules\Apps;
use Elementor\Core\Isolation\Wordpress_Adapter;
use Elementor\Core\Isolation\Plugin_Status_Adapter;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Admin_Apps_Page {
const APPS_URL = 'https://assets.elementor.com/apps/v1/apps.json';
private static ?Wordpress_Adapter $wordpress_adapter = null;
private static ?Plugin_Status_Adapter $plugin_status_adapter = null;
public static function render() {
?>
<div class="wrap e-a-apps">
<div class="e-a-page-title">
<h2><?php echo esc_html__( 'Popular Add-ons, New Possibilities.', 'elementor' ); ?></h2>
<p><?php echo esc_html__( 'Boost your web-creation process with add-ons, plugins, and more tools specially selected to unleash your creativity, increase productivity, and enhance your Elementor-powered website.', 'elementor' ); ?>*<br>
<a href="https://go.elementor.com/wp-dash-apps-about-apps-page/" target="_blank"><?php echo esc_html__( 'Learn more about this page.', 'elementor' ); ?></a>
</p>
</div>
<div class="e-a-list">
<?php self::render_plugins_list(); ?>
</div>
<div class="e-a-page-footer">
<p>*<?php echo esc_html__( 'Please note that certain tools and services on this page are developed by third-party companies and are not part of Elementor\'s suite of products or support. Before using them, we recommend independently evaluating them. Additionally, when clicking on their action buttons, you may be redirected to an external website.', 'elementor' ); ?></p>
</div>
</div>
<?php
}
private static function render_plugins_list() {
$plugins = self::get_plugins();
foreach ( $plugins as $plugin ) {
self::render_plugin_item( $plugin );
}
}
private static function get_plugins() : array {
if ( ! self::$wordpress_adapter ) {
self::$wordpress_adapter = new Wordpress_Adapter();
}
if ( ! self::$plugin_status_adapter ) {
self::$plugin_status_adapter = new Plugin_Status_Adapter( self::$wordpress_adapter );
}
$apps = static::get_remote_apps();
return static::filter_apps( $apps );
}
private static function get_remote_apps() {
$apps = wp_remote_get( static::APPS_URL );
if ( is_wp_error( $apps ) ) {
return [];
}
$apps = json_decode( wp_remote_retrieve_body( $apps ), true );
if ( empty( $apps['apps'] ) || ! is_array( $apps['apps'] ) ) {
return [];
}
return $apps['apps'];
}
private static function filter_apps( $apps ) {
$filtered_apps = [];
foreach ( $apps as $app ) {
if ( static::is_wporg_app( $app ) ) {
$app = static::filter_wporg_app( $app );
}
if ( static::is_ecom_app( $app ) ) {
$app = static::filter_ecom_app( $app );
}
if ( empty( $app ) ) {
continue;
}
$filtered_apps[] = $app;
}
return $filtered_apps;
}
private static function is_wporg_app( $app ) {
return isset( $app['type'] ) && 'wporg' === $app['type'];
}
private static function filter_wporg_app( $app ) {
if ( self::$wordpress_adapter->is_plugin_active( $app['file_path'] ) ) {
return null;
}
if ( self::$plugin_status_adapter->is_plugin_installed( $app['file_path'] ) ) {
if ( current_user_can( 'activate_plugins' ) ) {
$app['action_label'] = 'Activate';
$app['action_url'] = self::$plugin_status_adapter->get_activate_plugin_url( $app['file_path'] );
} else {
$app['action_label'] = 'Cannot Activate';
$app['action_url'] = '#';
}
} else {
if ( current_user_can( 'install_plugins' ) ) {
$app['action_label'] = 'Install';
$app['action_url'] = self::$plugin_status_adapter->get_install_plugin_url( $app['file_path'] );
} else {
$app['action_label'] = 'Cannot Install';
$app['action_url'] = '#';
}
}
return $app;
}
private static function is_ecom_app( $app ) {
return isset( $app['type'] ) && 'ecom' === $app['type'];
}
private static function filter_ecom_app( $app ) {
if ( self::$wordpress_adapter->is_plugin_active( $app['file_path'] ) ) {
return null;
}
if ( ! self::$plugin_status_adapter->is_plugin_installed( $app['file_path'] ) ) {
return $app;
}
if ( current_user_can( 'activate_plugins' ) ) {
$app['action_label'] = 'Activate';
$app['action_url'] = self::$plugin_status_adapter->get_activate_plugin_url( $app['file_path'] );
} else {
$app['action_label'] = 'Cannot Activate';
$app['action_url'] = '#';
}
$app['target'] = '_self';
return $app;
}
private static function get_images_url() {
return ELEMENTOR_URL . 'modules/apps/images/';
}
private static function is_elementor_pro_installed() {
return defined( 'ELEMENTOR_PRO_VERSION' );
}
private static function render_plugin_item( $plugin ) {
?>
<div class="e-a-item">
<div class="e-a-heading">
<img class="e-a-img" src="<?php echo esc_url( $plugin['image'] ); ?>" alt="<?php echo esc_attr( $plugin['name'] ); ?>">
<?php if ( ! empty( $plugin['badge'] ) ) : ?>
<span class="e-a-badge"><?php echo esc_html( $plugin['badge'] ); ?></span>
<?php endif; ?>
</div>
<h3 class="e-a-title"><?php echo esc_html( $plugin['name'] ); ?></h3>
<p class="e-a-author"><?php esc_html_e( 'By', 'elementor' ); ?> <a href="<?php echo esc_url( $plugin['author_url'] ); ?>" target="_blank"><?php echo esc_html( $plugin['author'] ); ?></a></p>
<div class="e-a-desc">
<p><?php echo esc_html( $plugin['description'] ); ?></p>
<?php if ( ! empty( $plugin['offering'] ) ) : ?>
<p class="e-a-offering"><?php echo esc_html( $plugin['offering'] ); ?></p>
<?php endif; ?>
</div>
<p class="e-a-actions">
<?php if ( ! empty( $plugin['learn_more_url'] ) ) : ?>
<a class="e-a-learn-more" href="<?php echo esc_url( $plugin['learn_more_url'] ); ?>" target="_blank"><?php echo esc_html__( 'Learn More', 'elementor' ); ?></a>
<?php endif; ?>
<a href="<?php echo esc_url( $plugin['action_url'] ); ?>" class="e-btn e-accent" target="<?php echo isset( $plugin['target'] ) ? esc_attr( $plugin['target'] ) : '_blank'; ?>"><?php echo esc_html( $plugin['action_label'] ); ?></a>
</p>
</div>
<?php
}
}

View File

@@ -0,0 +1,36 @@
<?php
namespace Elementor\Modules\Apps;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Admin_Menu_Apps implements Admin_Menu_Item_With_Page {
public function is_visible() {
return true;
}
public function get_parent_slug() {
return Settings::PAGE_ID;
}
public function get_label() {
return esc_html__( 'Add-ons', 'elementor' );
}
public function get_page_title() {
return esc_html__( 'Add-ons', 'elementor' );
}
public function get_capability() {
return 'manage_options';
}
public function render() {
Admin_Apps_Page::render();
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Elementor\Modules\Apps;
use Elementor\Core\Upgrade\Manager as Upgrade_Manager;
use Elementor\User;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Admin_Pointer {
const RELEASE_VERSION = '3.15.0';
const CURRENT_POINTER_SLUG = 'e-apps';
public static function add_hooks() {
add_action( 'admin_print_footer_scripts-index.php', [ __CLASS__, 'admin_print_script' ] );
}
public static function admin_print_script() {
if ( static::is_dismissed() || static::is_new_installation() ) {
return;
}
wp_enqueue_script( 'wp-pointer' );
wp_enqueue_style( 'wp-pointer' );
$pointer_content = '<h3>' . esc_html__( 'New! Popular Add-ons', 'elementor' ) . '</h3>';
$pointer_content .= '<p>' . esc_html__( 'Discover our collection of plugins and add-ons carefully selected to enhance your Elementor website and unleash your creativity.', 'elementor' ) . '</p>';
$pointer_content .= sprintf(
'<p><a class="button button-primary" href="%s">%s</a></p>',
admin_url( 'admin.php?page=' . Module::PAGE_ID ),
esc_html__( 'Explore Add-ons', 'elementor' )
)
?>
<script>
jQuery( document ).ready( function( $ ) {
$( '#toplevel_page_elementor' ).pointer( {
content: '<?php echo $pointer_content; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>',
position: {
edge: <?php echo is_rtl() ? "'right'" : "'left'"; ?>,
align: 'center'
},
close: function() {
elementorCommon.ajax.addRequest( 'introduction_viewed', {
data: {
introductionKey: '<?php echo esc_attr( static::CURRENT_POINTER_SLUG ); ?>',
},
} );
}
} ).pointer( 'open' );
} );
</script>
<?php
}
private static function is_dismissed() {
return User::get_introduction_meta( static::CURRENT_POINTER_SLUG );
}
private static function is_new_installation() {
return Upgrade_Manager::install_compare( static::RELEASE_VERSION, '>=' );
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Elementor\Modules\Apps;
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
const PAGE_ID = 'elementor-apps';
public function get_name() {
return 'apps';
}
public function __construct() {
parent::__construct();
Admin_Pointer::add_hooks();
add_action( 'elementor/admin/menu/register', function( Admin_Menu_Manager $admin_menu ) {
$admin_menu->register( static::PAGE_ID, new Admin_Menu_Apps() );
}, 115 );
add_action( 'elementor/admin/menu/after_register', function ( Admin_Menu_Manager $admin_menu, array $hooks ) {
if ( ! empty( $hooks[ static::PAGE_ID ] ) ) {
add_action( "admin_print_scripts-{$hooks[ static::PAGE_ID ]}", [ $this, 'enqueue_assets' ] );
}
}, 10, 2 );
add_filter( 'elementor/finder/categories', function( array $categories ) {
$categories['site']['items']['apps'] = [
'title' => esc_html__( 'Add-ons', 'elementor' ),
'url' => admin_url( 'admin.php?page=' . static::PAGE_ID ),
'icon' => 'apps',
'keywords' => [ 'apps', 'addon', 'plugin', 'extension', 'integration' ],
];
return $categories;
} );
// Add the Elementor Apps link to the plugin install action links.
add_filter( 'install_plugins_tabs', [ $this, 'add_elementor_plugin_install_action_link' ] );
add_action( 'install_plugins_pre_elementor', [ $this, 'maybe_open_elementor_tab' ] );
add_action( 'admin_print_styles-plugin-install.php', [ $this, 'add_plugins_page_styles' ] );
}
public function enqueue_assets() {
add_filter( 'admin_body_class', [ $this, 'body_status_classes' ] );
wp_enqueue_style(
'elementor-apps',
$this->get_css_assets_url( 'modules/apps/admin' ),
[],
ELEMENTOR_VERSION
);
}
public function body_status_classes( $admin_body_classes ) {
$admin_body_classes .= ' elementor-apps-page';
return $admin_body_classes;
}
public function add_elementor_plugin_install_action_link( $tabs ) {
$tabs['elementor'] = esc_html__( 'For Elementor', 'elementor' );
return $tabs;
}
public function maybe_open_elementor_tab() {
if ( ! isset( $_GET['tab'] ) || 'elementor' !== $_GET['tab'] ) {
return;
}
$elementor_url = add_query_arg( [
'page' => static::PAGE_ID,
'tab' => 'elementor',
'ref' => 'plugins',
], admin_url( 'admin.php' ) );
wp_safe_redirect( $elementor_url );
exit;
}
public function add_plugins_page_styles() {
?>
<style>
.plugin-install-elementor > a::after {
content: "";
display: inline-block;
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.33321 3H12.9999V7.66667H11.9999V4.70711L8.02009 8.68689L7.31299 7.97978L11.2928 4H8.33321V3Z' fill='%23646970'/%3E%3Cpath d='M6.33333 4.1665H4.33333C3.8731 4.1665 3.5 4.5396 3.5 4.99984V11.6665C3.5 12.1267 3.8731 12.4998 4.33333 12.4998H11C11.4602 12.4998 11.8333 12.1267 11.8333 11.6665V9.6665' stroke='%23646970'/%3E%3C/svg%3E%0A");
width: 16px;
height: 16px;
background-repeat: no-repeat;
vertical-align: text-top;
margin-left: 2px;
}
.plugin-install-elementor:hover > a::after {
background-image: url("data:image/svg+xml,%3Csvg width='16' height='16' viewBox='0 0 16 16' fill='none' xmlns='http://www.w3.org/2000/svg'%3E%3Cpath fill-rule='evenodd' clip-rule='evenodd' d='M8.33321 3H12.9999V7.66667H11.9999V4.70711L8.02009 8.68689L7.31299 7.97978L11.2928 4H8.33321V3Z' fill='%23135E96'/%3E%3Cpath d='M6.33333 4.1665H4.33333C3.8731 4.1665 3.5 4.5396 3.5 4.99984V11.6665C3.5 12.1267 3.8731 12.4998 4.33333 12.4998H11C11.4602 12.4998 11.8333 12.1267 11.8333 11.6665V9.6665' stroke='%23135E96'/%3E%3C/svg%3E%0A");
}
</style>
<?php
}
}

View File

@@ -0,0 +1,156 @@
<?php
namespace Elementor\Modules\CompatibilityTag;
use Elementor\Plugin;
use Elementor\Core\Utils\Version;
use Elementor\Core\Utils\Collection;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Modules\System_Info\Module as System_Info;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
abstract class Base_Module extends BaseModule {
const MODULE_NAME = 'compatibility-tag';
/**
* @var Compatibility_Tag
*/
private $compatibility_tag_service;
/**
* @return string
*/
public function get_name() {
return static::MODULE_NAME;
}
/**
* @return Compatibility_Tag
*/
private function get_compatibility_tag_service() {
if ( ! $this->compatibility_tag_service ) {
$this->compatibility_tag_service = new Compatibility_Tag( $this->get_plugin_header() );
}
return $this->compatibility_tag_service;
}
/**
* Add allowed headers to plugins.
*
* @param array $headers
* @param $compatibility_tag_header
*
* @return array
*/
protected function enable_elementor_headers( array $headers, $compatibility_tag_header ) {
$headers[] = $compatibility_tag_header;
return $headers;
}
/**
* @return Collection
*/
protected function get_plugins_to_check() {
return $this->get_plugins_with_header();
}
/**
* Append a compatibility message to the update plugin warning.
*
* @param array $args
*
* @throws \Exception
*/
protected function on_plugin_update_message( array $args ) {
$new_version = Version::create_from_string( $args['new_version'] );
if ( $new_version->compare( '=', $args['Version'], Version::PART_MAJOR_2 ) ) {
return;
}
$plugins = $this->get_plugins_to_check();
$plugins_compatibility = $this->get_compatibility_tag_service()->check( $new_version, $plugins->keys() );
$plugins = $plugins->filter( function ( $data, $plugin_name ) use ( $plugins_compatibility ) {
return Compatibility_Tag::COMPATIBLE !== $plugins_compatibility[ $plugin_name ];
} );
if ( $plugins->is_empty() ) {
return;
}
include __DIR__ . '/views/plugin-update-message-compatibility.php';
}
/**
* Get all plugins with specific header.
*
* @return Collection
*/
private function get_plugins_with_header() {
return Plugin::$instance->wp
->get_plugins()
->filter( function ( array $plugin ) {
return ! empty( $plugin[ $this->get_plugin_header() ] );
} );
}
/**
* @return string
*/
abstract protected function get_plugin_header();
/**
* @return string
*/
abstract protected function get_plugin_label();
/**
* @return string
*/
abstract protected function get_plugin_name();
/**
* @return string
*/
abstract protected function get_plugin_version();
/**
* Base_Module constructor.
*
* @throws \Exception
*/
public function __construct() {
add_filter( 'extra_plugin_headers', function ( array $headers ) {
return $this->enable_elementor_headers( $headers, $this->get_plugin_header() );
} );
add_action( 'in_plugin_update_message-' . $this->get_plugin_name(), function ( array $args ) {
$this->on_plugin_update_message( $args );
}, 11 /* After the warning message for backup */ );
add_action( 'elementor/system_info/get_allowed_reports', function () {
$plugin_short_name = basename( $this->get_plugin_name(), '.php' );
System_Info::add_report(
"{$plugin_short_name}_compatibility",
[
'file_name' => __DIR__ . '/compatibility-tag-report.php',
'class_name' => __NAMESPACE__ . '\Compatibility_Tag_Report',
'fields' => [
'compatibility_tag_service' => $this->get_compatibility_tag_service(),
'plugin_label' => $this->get_plugin_label(),
'plugin_version' => Version::create_from_string( $this->get_plugin_version() ),
'plugins_to_check' => $this->get_plugins_to_check()
->only( get_option( 'active_plugins' ) )
->keys(),
],
]
);
} );
}
}

View File

@@ -0,0 +1,176 @@
<?php
namespace Elementor\Modules\CompatibilityTag;
use Elementor\Plugin;
use Elementor\Core\Utils\Version;
use Elementor\Core\Utils\Collection;
use Elementor\Modules\System_Info\Reporters\Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Compatibility_Tag_Report extends Base {
/**
* @var Compatibility_Tag
*/
protected $compatibility_tag_service;
/**
* @var Version
*/
protected $plugin_version;
/**
* @var string
*/
protected $plugin_label;
/**
* @var array
*/
protected $plugins_to_check;
/**
* Compatibility_Tag_Report constructor.
*
* @param $properties
*/
public function __construct( $properties ) {
parent::__construct( $properties );
$this->compatibility_tag_service = $this->_properties['fields']['compatibility_tag_service'];
$this->plugin_label = $this->_properties['fields']['plugin_label'];
$this->plugin_version = $this->_properties['fields']['plugin_version'];
$this->plugins_to_check = $this->_properties['fields']['plugins_to_check'];
}
/**
* The title of the report
*
* @return string
*/
public function get_title() {
return $this->plugin_label . ' - Compatibility Tag';
}
/**
* Report fields
*
* @return string[]
*/
public function get_fields() {
return [
'report_data' => '',
];
}
/**
* Report data.
*
* @return string[]
* @throws \Exception
*/
public function get_report_data() {
$compatibility_status = $this->compatibility_tag_service->check(
$this->plugin_version,
$this->plugins_to_check
);
return [
'value' => $compatibility_status,
];
}
public function get_html_report_data() {
$compatibility_status = $this->compatibility_tag_service->check(
$this->plugin_version,
$this->plugins_to_check
);
$compatibility_status = $this->get_html_from_compatibility_status( $compatibility_status );
return [
'value' => $compatibility_status,
];
}
public function get_raw_report_data() {
$compatibility_status = $this->compatibility_tag_service->check(
$this->plugin_version,
$this->plugins_to_check
);
$compatibility_status = $this->get_raw_from_compatibility_status( $compatibility_status );
return [
'value' => $compatibility_status,
];
}
/**
* Merge compatibility status with the plugins data.
*
* @param array $compatibility_status
*
* @return Collection
*/
private function merge_compatibility_status_with_plugins( array $compatibility_status ) {
$labels = $this->get_report_labels();
$compatibility_status = ( new Collection( $compatibility_status ) )
->map( function ( $value ) use ( $labels ) {
$status = isset( $labels[ $value ] ) ? $labels[ $value ] : esc_html__( 'Unknown', 'elementor' );
return [ 'compatibility_status' => $status ];
} );
return Plugin::$instance->wp
->get_plugins()
->only( $compatibility_status->keys() )
->merge_recursive( $compatibility_status );
}
/**
* Format compatibility status into HTML.
*
* @param array $compatibility_status
*
* @return string
*/
private function get_html_from_compatibility_status( array $compatibility_status ) {
return $this->merge_compatibility_status_with_plugins( $compatibility_status )
->map( function ( array $plugin ) {
return "<tr><td> {$plugin['Name']} </td><td> {$plugin['compatibility_status']} </td></tr>";
} )
->implode( '' );
}
/**
* Format compatibility status into raw string.
*
* @param array $compatibility_status
*
* @return string
*/
private function get_raw_from_compatibility_status( array $compatibility_status ) {
return PHP_EOL . $this->merge_compatibility_status_with_plugins( $compatibility_status )
->map( function ( array $plugin ) {
return "\t{$plugin['Name']}: {$plugin['compatibility_status']}";
} )
->implode( PHP_EOL );
}
/**
* @return array
*/
private function get_report_labels() {
return [
Compatibility_Tag::COMPATIBLE => esc_html__( 'Compatible', 'elementor' ),
Compatibility_Tag::INCOMPATIBLE => esc_html__( 'Incompatible', 'elementor' ),
Compatibility_Tag::HEADER_NOT_EXISTS => esc_html__( 'Compatibility not specified', 'elementor' ),
Compatibility_Tag::INVALID_VERSION => esc_html__( 'Compatibility unknown', 'elementor' ),
Compatibility_Tag::PLUGIN_NOT_EXISTS => esc_html__( 'Error', 'elementor' ),
];
}
}

View File

@@ -0,0 +1,83 @@
<?php
namespace Elementor\Modules\CompatibilityTag;
use Elementor\Plugin;
use Elementor\Core\Utils\Version;
use Elementor\Core\Base\Base_Object;
use Elementor\Core\Utils\Collection;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Compatibility_Tag extends Base_Object {
const PLUGIN_NOT_EXISTS = 'plugin_not_exists';
const HEADER_NOT_EXISTS = 'header_not_exists';
const INVALID_VERSION = 'invalid_version';
const INCOMPATIBLE = 'incompatible';
const COMPATIBLE = 'compatible';
/**
* @var string Holds the header that should be checked.
*/
private $header;
/**
* Compatibility_Tag constructor.
*
* @param string $header
*/
public function __construct( $header ) {
$this->header = $header;
}
/**
* Return if plugins is compatible or not.
*
* @param Version $version
* @param array $plugins_names
*
* @return array
* @throws \Exception
*/
public function check( Version $version, array $plugins_names ) {
return ( new Collection( $plugins_names ) )
->map_with_keys( function ( $plugin_name ) use ( $version ) {
return [ $plugin_name => $this->is_compatible( $version, $plugin_name ) ];
} )
->all();
}
/**
* Check single plugin if is compatible or not.
*
* @param Version $version
* @param $plugin_name
*
* @return string
* @throws \Exception
*/
private function is_compatible( Version $version, $plugin_name ) {
$plugins = Plugin::$instance->wp->get_plugins();
if ( ! isset( $plugins[ $plugin_name ] ) ) {
return self::PLUGIN_NOT_EXISTS;
}
$requested_plugin = $plugins[ $plugin_name ];
if ( empty( $requested_plugin[ $this->header ] ) ) {
return self::HEADER_NOT_EXISTS;
}
if ( ! Version::is_valid_version( $requested_plugin[ $this->header ] ) ) {
return self::INVALID_VERSION;
}
if ( $version->compare( '>', $requested_plugin[ $this->header ], Version::PART_MAJOR_2 ) ) {
return self::INCOMPATIBLE;
}
return self::COMPATIBLE;
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Elementor\Modules\CompatibilityTag;
use Elementor\Plugin;
use Elementor\Core\Utils\Version;
use Elementor\Core\Utils\Collection;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Inspired By WooCommerce.
*
* @link https://github.com/woocommerce/woocommerce/blob/master/includes/admin/plugin-updates/class-wc-plugin-updates.php
*/
class Module extends Base_Module {
/**
* This is the header used by extensions to show testing.
*
* @var string
*/
const PLUGIN_VERSION_TESTED_HEADER = 'Elementor tested up to';
/**
* @return string
*/
protected function get_plugin_header() {
return static::PLUGIN_VERSION_TESTED_HEADER;
}
/**
* @return string
*/
protected function get_plugin_label() {
return esc_html__( 'Elementor', 'elementor' );
}
/**
* @return string
*/
protected function get_plugin_name() {
return ELEMENTOR_PLUGIN_BASE;
}
/**
* @return string
*/
protected function get_plugin_version() {
return ELEMENTOR_VERSION;
}
/**
* @return Collection
*/
protected function get_plugins_to_check() {
return parent::get_plugins_to_check()
->merge( $this->get_plugins_with_plugin_title_in_their_name() );
}
/**
* Get all the plugins that has the name of the current plugin in their name.
*
* @return Collection
*/
private function get_plugins_with_plugin_title_in_their_name() {
return Plugin::$instance->wp
->get_plugins()
->except( [
'elementor/elementor.php',
'elementor-beta/elementor-beta.php',
'block-builder/block-builder.php',
] )
->filter( function ( array $data ) {
return false !== strpos( strtolower( $data['Name'] ), 'elementor' );
} );
}
}

View File

@@ -0,0 +1,68 @@
<?php
use Elementor\Core\Utils\Version;
use Elementor\Core\Utils\Collection;
use Elementor\Modules\CompatibilityTag\Base_Module;
use Elementor\Modules\CompatibilityTag\Compatibility_Tag;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Those variables were declared in 'in_plugin_update_message' method that included the current view file.
*
* @var Base_Module $this
* @var Version $new_version
* @var Collection $plugins
* @var array $plugins_compatibility
*/
?>
<hr class="e-major-update-warning__separator" />
<div class="e-major-update-warning">
<div class="e-major-update-warning__icon">
<i class="eicon-info-circle"></i>
</div>
<div>
<div class="e-major-update-warning__message">
<strong>
<?php echo esc_html__( 'Compatibility Alert', 'elementor' ); ?>
</strong> -
<?php
echo sprintf(
/* translators: 1: Plugin name, 2: Plugin version. */
esc_html__( 'Some of the plugins youre using have not been tested with the latest version of %1$s (%2$s). To avoid issues, make sure they are all up to date and compatible before updating %1$s.', 'elementor' ),
esc_html( $this->get_plugin_label() ),
$new_version->__toString() // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
);
?>
</div>
<br />
<table class="e-compatibility-update-table">
<tr>
<th><?php echo esc_html__( 'Plugin', 'elementor' ); ?></th>
<th><?php
/* translators: %s: Elementor plugin name. */
echo sprintf( esc_html__( 'Tested up to %s version', 'elementor' ), esc_html( $this->get_plugin_label() ) );
?></th>
</tr>
<?php foreach ( $plugins as $plugin_name => $plugin_data ) : ?>
<?php
if (
in_array( $plugins_compatibility[ $plugin_name ], [
Compatibility_Tag::PLUGIN_NOT_EXISTS,
Compatibility_Tag::HEADER_NOT_EXISTS,
Compatibility_Tag::INVALID_VERSION,
], true )
) {
$plugin_data[ $this->get_plugin_header() ] = esc_html__( 'Unknown', 'elementor' );
}
?>
<tr>
<td><?php echo esc_html( $plugin_data['Name'] ); ?></td>
<td><?php echo esc_html( $plugin_data[ $this->get_plugin_header() ] ); ?></td>
</tr>
<?php endforeach ?>
</table>
</div>
</div>

View File

@@ -0,0 +1,170 @@
<?php
namespace Elementor\Modules\ContainerConverter;
use Elementor\Controls_Manager;
use Elementor\Controls_Stack;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends \Elementor\Core\Base\Module {
// Event name dispatched by the buttons.
const EVENT_NAME = 'elementorContainerConverter:convert';
/**
* Retrieve the module name.
*
* @return string
*/
public function get_name() {
return 'container-converter';
}
/**
* Determine whether the module is active.
*
* @return bool
*/
public static function is_active() {
return Plugin::$instance->experiments->is_feature_active( 'container' );
}
/**
* Enqueue the module scripts.
*
* @return void
*/
public function enqueue_scripts() {
wp_enqueue_script(
'container-converter',
$this->get_js_assets_url( 'container-converter' ),
[ 'elementor-editor' ],
ELEMENTOR_VERSION,
true
);
}
/**
* Enqueue the module styles.
*
* @return void
*/
public function enqueue_styles() {
wp_enqueue_style(
'container-converter',
$this->get_css_assets_url( 'modules/container-converter/editor' ),
[],
ELEMENTOR_VERSION
);
}
/**
* Add a convert button to sections.
*
* @param \Elementor\Controls_Stack $controls_stack
*
* @return void
*/
protected function add_section_convert_button( Controls_Stack $controls_stack ) {
if ( ! Plugin::$instance->editor->is_edit_mode() ) {
return;
}
$controls_stack->start_injection( [
'of' => '_title',
] );
$controls_stack->add_control(
'convert_to_container',
[
'type' => Controls_Manager::BUTTON,
'label' => esc_html__( 'Convert to container', 'elementor' ),
'text' => esc_html__( 'Convert', 'elementor' ),
'button_type' => 'default',
'description' => esc_html__( 'Copies all of the selected sections and columns and pastes them in a container beneath the original.', 'elementor' ),
'separator' => 'after',
'event' => static::EVENT_NAME,
]
);
$controls_stack->end_injection();
}
/**
* Add a convert button to page settings.
*
* @param \Elementor\Controls_Stack $controls_stack
*
* @return void
*/
protected function add_page_convert_button( Controls_Stack $controls_stack ) {
if ( ! Plugin::$instance->editor->is_edit_mode() || ! $this->page_contains_sections( $controls_stack ) || ! Plugin::$instance->role_manager->user_can( 'design' ) ) {
return;
}
$controls_stack->start_injection( [
'of' => 'post_title',
'at' => 'before',
] );
$controls_stack->add_control(
'convert_to_container',
[
'type' => Controls_Manager::BUTTON,
'label' => esc_html__( 'Convert to container', 'elementor' ),
'text' => esc_html__( 'Convert', 'elementor' ),
'button_type' => 'default',
'description' => esc_html__( 'Copies all of the selected sections and columns and pastes them in a container beneath the original.', 'elementor' ),
'separator' => 'after',
'event' => static::EVENT_NAME,
]
);
$controls_stack->end_injection();
}
/**
* Checks if document has any Section elements.
*
* @param \Elementor\Controls_Stack $controls_stack
*
* @return bool
*/
protected function page_contains_sections( $controls_stack ) {
$data = $controls_stack->get_elements_data();
if ( ! is_array( $data ) ) {
return false;
}
foreach ( $data as $element ) {
if ( isset( $element['elType'] ) && 'section' === $element['elType'] ) {
return true;
}
}
return false;
}
/**
* Initialize the Container-Converter module.
*
* @return void
*/
public function __construct() {
add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'elementor/editor/after_enqueue_styles', [ $this, 'enqueue_styles' ] );
add_action( 'elementor/element/section/section_layout/after_section_end', function ( Controls_Stack $controls_stack ) {
$this->add_section_convert_button( $controls_stack );
} );
add_action( 'elementor/documents/register_controls', function ( Controls_Stack $controls_stack ) {
$this->add_page_convert_button( $controls_stack );
} );
}
}

View File

@@ -0,0 +1,10 @@
<?php
namespace Elementor\Modules\ContentSanitizer\Interfaces;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
interface Sanitizable {
public function sanitize( $content );
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Elementor\Modules\ContentSanitizer;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
const WIDGET_TO_SANITIZE = 'heading';
public function __construct() {
parent::__construct();
add_filter( 'elementor/document/save/data', [ $this, 'sanitize_content' ], 10, 2 );
}
public function get_name() {
return 'content-sanitizer';
}
public function sanitize_content( $data, $document ) : array {
if ( current_user_can( 'manage_options' ) || empty( $data['elements'] ) ) {
return $data;
}
if ( ! $this->is_widget_present( $data ) ) {
return $data;
}
return Plugin::$instance->db->iterate_data( $data, function ( $element ) {
if ( $this->is_target_widget( $element ) ) {
$element['settings']['title'] = Plugin::$instance->widgets_manager->get_widget_types( self::WIDGET_TO_SANITIZE )->sanitize( $element['settings']['title'] );
}
return $element;
});
}
private function is_target_widget( $element ) {
return self::WIDGET_TO_SANITIZE === $element['widgetType'];
}
private function is_widget_present( array $elements ): bool {
$json = wp_json_encode( $elements );
return false !== strpos( $json, '"widgetType":"' . self::WIDGET_TO_SANITIZE . '"' );
}
}

View File

@@ -0,0 +1,369 @@
<?php
namespace Elementor\Modules\DevTools;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Deprecation {
const SOFT_VERSIONS_COUNT = 4;
const HARD_VERSIONS_COUNT = 8;
private $current_version = null;
private $soft_deprecated_notices = [];
public function __construct( $current_version ) {
$this->current_version = $current_version;
}
public function get_settings() {
return [
'soft_notices' => $this->soft_deprecated_notices,
'soft_version_count' => self::SOFT_VERSIONS_COUNT,
'hard_version_count' => self::HARD_VERSIONS_COUNT,
'current_version' => ELEMENTOR_VERSION,
];
}
/**
* Get total of major.
*
* Since `get_total_major` cannot determine how much really versions between 2.9.0 and 3.3.0 if there is 2.10.0 version for example,
* versions with major2 more then 9 will be added to total.
*
* @since 3.1.0
*
* @param array $parsed_version
*
* @return int
*/
public function get_total_major( $parsed_version ) {
$major1 = $parsed_version['major1'];
$major2 = $parsed_version['major2'];
$major2 = $major2 > 9 ? 9 : $major2;
$minor = 0;
$total = intval( "{$major1}{$major2}{$minor}" );
if ( $total > 99 ) {
$total = $total / 10;
} else {
$total = intval( $total / 10 );
}
if ( $parsed_version['major2'] > 9 ) {
$total += $parsed_version['major2'] - 9;
}
return $total;
}
/**
* Get next version.
*
* @since 3.1.0
*
* @param string $version
* @param int $count
*
* @return string|false
*/
public function get_next_version( $version, $count = 1 ) {
$version = $this->parse_version( $version );
if ( ! $version ) {
return false;
}
$version['total'] = $this->get_total_major( $version ) + $count;
$total = $version['total'];
if ( $total > 9 ) {
$version['major1'] = intval( $total / 10 );
$version['major2'] = $total % 10;
} else {
$version['major1'] = 0;
$version['major2'] = $total;
}
$version['minor'] = 0;
return $this->implode_version( $version );
}
/**
* Implode parsed version to string version.
*
* @since 3.1.0
*
* @param array $parsed_version
*
* @return string
*/
public function implode_version( $parsed_version ) {
$major1 = $parsed_version['major1'];
$major2 = $parsed_version['major2'];
$minor = $parsed_version['minor'];
return "{$major1}.{$major2}.{$minor}";
}
/**
* Parse to an informative array.
*
* @since 3.1.0
*
* @param string $version
*
* @return array|false
*/
public function parse_version( $version ) {
$version_explode = explode( '.', $version );
$version_explode_count = count( $version_explode );
if ( $version_explode_count < 3 || $version_explode_count > 4 ) {
trigger_error( 'Invalid Semantic Version string provided' );
return false;
}
list( $major1, $major2, $minor ) = $version_explode;
$result = [
'major1' => intval( $major1 ),
'major2' => intval( $major2 ),
'minor' => intval( $minor ),
];
if ( $version_explode_count > 3 ) {
$result['build'] = $version_explode[3];
}
return $result;
}
/**
* Compare two versions, result is equal to diff of major versions.
* Notice: If you want to compare between 2.9.0 and 3.3.0, and there is also a 2.10.0 version, you cannot get the right comparison
* Since $this->deprecation->get_total_major cannot determine how much really versions between 2.9.0 and 3.3.0.
*
* @since 3.1.0
*
* @param {string} $version1
* @param {string} $version2
*
* @return int|false
*/
public function compare_version( $version1, $version2 ) {
$version1 = self::parse_version( $version1 );
$version2 = self::parse_version( $version2 );
if ( $version1 && $version2 ) {
$versions = [ &$version1, &$version2 ];
foreach ( $versions as &$version ) {
$version['total'] = self::get_total_major( $version );
}
return $version1['total'] - $version2['total'];
}
return false;
}
/**
* Check Deprecation
*
* Checks whether the given entity is valid. If valid, this method checks whether the deprecation
* should be soft (browser console notice) or hard (use WordPress' native deprecation methods).
*
* @since 3.1.0
*
* @param string $entity - The Deprecated entity (the function/hook itself)
* @param string $version
* @param string $replacement Optional
* @param string $base_version Optional. Default is `null`
*
* @return bool|void
* @throws \Exception
*/
private function check_deprecation( $entity, $version, $replacement, $base_version = null ) {
if ( null === $base_version ) {
$base_version = $this->current_version;
}
$diff = $this->compare_version( $base_version, $version );
if ( false === $diff ) {
throw new \Exception( 'Invalid deprecation diff.' );
}
$print_deprecated = false;
if ( defined( 'WP_DEBUG' ) && WP_DEBUG && $diff <= self::SOFT_VERSIONS_COUNT ) {
// Soft deprecated.
if ( ! isset( $this->soft_deprecated_notices[ $entity ] ) ) {
$this->soft_deprecated_notices[ $entity ] = [
$version,
$replacement,
];
}
if ( defined( 'ELEMENTOR_DEBUG' ) && ELEMENTOR_DEBUG ) {
$print_deprecated = true;
}
}
return $print_deprecated;
}
/**
* Deprecated Function
*
* Handles the deprecation process for functions.
*
* @since 3.1.0
*
* @param string $function
* @param string $version
* @param string $replacement Optional. Default is ''
* @param string $base_version Optional. Default is `null`
* @throws \Exception
*/
public function deprecated_function( $function, $version, $replacement = '', $base_version = null ) {
$print_deprecated = $this->check_deprecation( $function, $version, $replacement, $base_version );
if ( $print_deprecated ) {
// PHPCS - We need to echo special characters because they can exist in function calls.
_deprecated_function( $function, esc_html( $version ), $replacement ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
}
/**
* Deprecated Hook
*
* Handles the deprecation process for hooks.
*
* @since 3.1.0
*
* @param string $hook
* @param string $version
* @param string $replacement Optional. Default is ''
* @param string $base_version Optional. Default is `null`
* @throws \Exception
*/
public function deprecated_hook( $hook, $version, $replacement = '', $base_version = null ) {
$print_deprecated = $this->check_deprecation( $hook, $version, $replacement, $base_version );
if ( $print_deprecated ) {
_deprecated_hook( esc_html( $hook ), esc_html( $version ), esc_html( $replacement ) );
}
}
/**
* Deprecated Argument
*
* Handles the deprecation process for function arguments.
*
* @since 3.1.0
*
* @param string $argument
* @param string $version
* @param string $replacement
* @param string $message
* @throws \Exception
*/
public function deprecated_argument( $argument, $version, $replacement = '', $message = '' ) {
$print_deprecated = $this->check_deprecation( $argument, $version, $replacement );
if ( $print_deprecated ) {
$message = empty( $message ) ? '' : ' ' . $message;
// These arguments are escaped because they are printed later, and are not escaped when printed.
$error_message_args = [ esc_html( $argument ), esc_html( $version ) ];
if ( $replacement ) {
/* translators: 1: Function argument, 2: Elementor version number, 3: Replacement argument name. */
$translation_string = esc_html__( 'The %1$s argument is deprecated since version %2$s! Use %3$s instead.', 'elementor' );
$error_message_args[] = $replacement;
} else {
/* translators: 1: Function argument, 2: Elementor version number. */
$translation_string = esc_html__( 'The %1$s argument is deprecated since version %2$s!', 'elementor' );
}
trigger_error(
vsprintf(
// PHPCS - $translation_string is already escaped above.
$translation_string, // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
// PHPCS - $error_message_args is an array.
$error_message_args // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
) . esc_html( $message ),
E_USER_DEPRECATED
);
}
}
/**
* Do Deprecated Action
*
* A method used to run deprecated actions through Elementor's deprecation process.
*
* @since 3.1.0
*
* @param string $hook
* @param array $args
* @param string $version
* @param string $replacement
* @param null|string $base_version
*
* @throws \Exception
*/
public function do_deprecated_action( $hook, $args, $version, $replacement = '', $base_version = null ) {
if ( ! has_action( $hook ) ) {
return;
}
$this->deprecated_hook( $hook, $version, $replacement, $base_version );
do_action_ref_array( $hook, $args );
}
/**
* Apply Deprecated Filter
*
* A method used to run deprecated filters through Elementor's deprecation process.
*
* @since 3.2.0
*
* @param string $hook
* @param array $args
* @param string $version
* @param string $replacement
* @param null|string $base_version
*
* @return mixed
* @throws \Exception
*/
public function apply_deprecated_filter( $hook, $args, $version, $replacement = '', $base_version = null ) {
if ( ! has_action( $hook ) ) {
// `$args` should be an array, but in order to keep BC, we need to support non-array values.
if ( is_array( $args ) ) {
return $args[0] ?? null;
}
return $args;
}
// BC - See the comment above.
if ( ! is_array( $args ) ) {
$args = [ $args ];
}
// Avoid associative arrays.
$args = array_values( $args );
$this->deprecated_hook( $hook, $version, $replacement, $base_version );
return apply_filters_ref_array( $hook, $args );
}
}

View File

@@ -0,0 +1,56 @@
<?php
namespace Elementor\Modules\DevTools;
use Elementor\Core\Base\App;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Fix issue with 'Potentially polymorphic call. The code may be inoperable depending on the actual class instance passed as the argument.'.
* Its tells to the editor that instance() return right module. instead of base module.
* @method Module instance()
*/
class Module extends App {
/**
* @var Deprecation
*/
public $deprecation;
public function __construct() {
$this->deprecation = new Deprecation( ELEMENTOR_VERSION );
add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'register_scripts' ] );
add_action( 'admin_enqueue_scripts', [ $this, 'register_scripts' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'register_scripts' ] );
add_action( 'elementor/frontend/after_register_scripts', [ $this, 'register_scripts' ] );
add_action( 'elementor/common/after_register_scripts', [ $this, 'register_scripts' ] );
}
public function get_name() {
return 'dev-tools';
}
public function register_scripts() {
wp_register_script(
'elementor-dev-tools',
$this->get_js_assets_url( 'dev-tools' ),
[],
ELEMENTOR_VERSION,
true
);
$this->print_config( 'elementor-dev-tools' );
}
protected function get_init_settings() {
return [
'isDebug' => ( defined( 'WP_DEBUG' ) && WP_DEBUG ),
'urls' => [
'assets' => ELEMENTOR_ASSETS_URL,
],
'deprecation' => $this->deprecation->get_settings(),
];
}
}

View File

@@ -0,0 +1,165 @@
<?php
namespace Elementor\Modules\DynamicTags;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\DynamicTags\Base_Tag;
use Elementor\Core\DynamicTags\Manager;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor dynamic tags module.
*
* Elementor dynamic tags module handler class is responsible for registering
* and managing Elementor dynamic tags modules.
*
* @since 2.0.0
*/
class Module extends BaseModule {
/**
* Base dynamic tag group.
*/
const BASE_GROUP = 'base';
/**
* Dynamic tags text category.
*/
const TEXT_CATEGORY = 'text';
/**
* Dynamic tags URL category.
*/
const URL_CATEGORY = 'url';
/**
* Dynamic tags image category.
*/
const IMAGE_CATEGORY = 'image';
/**
* Dynamic tags media category.
*/
const MEDIA_CATEGORY = 'media';
/**
* Dynamic tags post meta category.
*/
const POST_META_CATEGORY = 'post_meta';
/**
* Dynamic tags gallery category.
*/
const GALLERY_CATEGORY = 'gallery';
/**
* Dynamic tags number category.
*/
const NUMBER_CATEGORY = 'number';
/**
* Dynamic tags number category.
*/
const COLOR_CATEGORY = 'color';
/**
* Dynamic tags datetime category.
*/
const DATETIME_CATEGORY = 'datetime';
/**
* Dynamic tags module constructor.
*
* Initializing Elementor dynamic tags module.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
$this->register_groups();
add_action( 'elementor/dynamic_tags/register', [ $this, 'register_tags' ] );
}
/**
* Get module name.
*
* Retrieve the dynamic tags module name.
*
* @since 2.0.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'dynamic_tags';
}
/**
* Get classes names.
*
* Retrieve the dynamic tag classes names.
*
* @since 2.0.0
* @access public
*
* @return array Tag dynamic tag classes names.
*/
public function get_tag_classes_names() {
return [];
}
/**
* Get groups.
*
* Retrieve the dynamic tag groups.
*
* @since 2.0.0
* @access public
*
* @return array Tag dynamic tag groups.
*/
public function get_groups() {
return [
self::BASE_GROUP => [
'title' => 'Base Tags',
],
];
}
/**
* Register groups.
*
* Add all the available tag groups.
*
* @since 2.0.0
* @access private
*/
private function register_groups() {
foreach ( $this->get_groups() as $group_name => $group_settings ) {
Plugin::$instance->dynamic_tags->register_group( $group_name, $group_settings );
}
}
/**
* Register tags.
*
* Add all the available dynamic tags.
*
* @since 2.0.0
* @access public
*
* @param Manager $dynamic_tags
*/
public function register_tags( $dynamic_tags ) {
foreach ( $this->get_tag_classes_names() as $tag_class ) {
/** @var Base_Tag $class_name */
$class_name = $this->get_reflection()->getNamespaceName() . '\Tags\\' . $tag_class;
$dynamic_tags->register( new $class_name() );
}
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Elementor\Modules\EditorEvents;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Common\Modules\Connect\Apps\Base_App;
use Elementor\Tracker;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
const ELEMENTOR_EDITOR_EVENTS_DATA_SYSTEM_URL = 'https://my.elementor.com/api/v1/editor-events';
public function get_name() {
return 'editor-events';
}
public static function get_editor_events_config() {
$can_send_events = false;
$settings = [
'can_send_events' => $can_send_events,
'elementor_version' => ELEMENTOR_VERSION,
'site_url' => hash( 'sha256', get_site_url() ),
'wp_version' => get_bloginfo( 'version' ),
'user_agent' => esc_html( Utils::get_super_global_value( $_SERVER, 'HTTP_USER_AGENT' ) ),
'site_language' => get_locale(),
'site_key' => get_option( Base_App::OPTION_CONNECT_SITE_KEY ),
'subscription_id' => null,
];
if ( $can_send_events ) {
$settings['data_system_url'] = self::ELEMENTOR_EDITOR_EVENTS_DATA_SYSTEM_URL;
}
return $settings;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Elementor\Modules\ElementManager;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Admin_Menu_App implements Admin_Menu_Item_With_Page {
public function is_visible() {
return true;
}
public function get_parent_slug() {
return Settings::PAGE_ID;
}
public function get_label() {
return esc_html__( 'Element Manager', 'elementor' );
}
public function get_page_title() {
return esc_html__( 'Element Manager', 'elementor' );
}
public function get_capability() {
return 'manage_options';
}
public function render() {
echo '<div class="wrap">';
echo '<h3 class="wp-heading-inline">' . esc_html__( 'Element Manager', 'elementor' ) . '</h3>';
echo '<div id="elementor-element-manager-wrap"></div>';
echo '</div>';
}
}

View File

@@ -0,0 +1,183 @@
<?php
namespace Elementor\Modules\ElementManager;
use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager;
use Elementor\Modules\Usage\Module as Usage_Module;
use Elementor\Api;
use Elementor\Plugin;
use Elementor\User;
use Elementor\Utils;
use Elementor\Core\Utils\Promotions\Validate_Promotion;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Ajax {
const ELEMENT_MANAGER_PROMOTION_URL = 'https://go.elementor.com/go-pro-element-manager/';
const FREE_TO_PRO_PERMISSIONS_PROMOTION_URL = 'https://go.elementor.com/go-pro-element-manager-permissions/';
const PRO_TO_ADVANCED_PERMISSIONS_PROMOTION_URL = 'https://go.elementor.com/go-pro-advanced-element-manager-permissions/';
public function register_endpoints() {
add_action( 'wp_ajax_elementor_element_manager_get_admin_app_data', [ $this, 'ajax_get_admin_page_data' ] );
add_action( 'wp_ajax_elementor_element_manager_save_disabled_elements', [ $this, 'ajax_save_disabled_elements' ] );
add_action( 'wp_ajax_elementor_element_manager_get_widgets_usage', [ $this, 'ajax_get_widgets_usage' ] );
}
public function ajax_get_admin_page_data() {
$this->verify_permission();
$this->force_enabled_all_elements();
$widgets = [];
$plugins = [];
foreach ( Plugin::$instance->widgets_manager->get_widget_types() as $widget ) {
$widget_title = sanitize_user( $widget->get_title() );
if ( empty( $widget_title ) || ! $widget->show_in_panel() ) {
continue;
}
$plugin_name = $this->get_plugin_name_from_widget_instance( $widget );
if ( ! in_array( $plugin_name, $plugins ) ) {
$plugins[] = $plugin_name;
}
$widgets[] = [
'name' => $widget->get_name(),
'plugin' => $plugin_name,
'title' => $widget_title,
'icon' => $widget->get_icon(),
];
}
$notice_id = 'e-element-manager-intro-1';
$data = [
'disabled_elements' => Options::get_disabled_elements(),
'promotion_widgets' => [],
'widgets' => $widgets,
'plugins' => $plugins,
'notice_data' => [
'notice_id' => $notice_id,
'is_viewed' => User::is_user_notice_viewed( $notice_id ),
],
'promotion_data' => [
'manager_permissions' => [
'pro' => $this->get_element_manager_promotion(
[
'text' => esc_html__( 'Upgrade Now', 'elementor' ),
'url' => self::FREE_TO_PRO_PERMISSIONS_PROMOTION_URL,
],
'pro_permissions'
),
'advanced' => $this->get_element_manager_promotion(
[
'text' => esc_html__( 'Upgrade Now', 'elementor' ),
'url' => self::PRO_TO_ADVANCED_PERMISSIONS_PROMOTION_URL,
],
'advanced_permissions'
),
],
'element_manager' => $this->get_element_manager_promotion(
[
'text' => esc_html__( 'Upgrade Now', 'elementor' ),
'url' => self::ELEMENT_MANAGER_PROMOTION_URL,
],
'element_manager'
),
],
];
if ( ! Utils::has_pro() ) {
$data['promotion_widgets'] = Api::get_promotion_widgets();
}
$data['additional_data'] = apply_filters( 'elementor/element_manager/admin_app_data/additional_data', [] );
wp_send_json_success( $data );
}
private function get_element_manager_promotion( $promotion_data, $filter_id ): array {
return Filtered_Promotions_Manager::get_filtered_promotion_data( $promotion_data, 'elementor/element_manager/admin_app_data/promotion_data/' . $filter_id, 'url' );
}
private function verify_permission() {
if ( ! current_user_can( 'manage_options' ) ) {
wp_send_json_error( esc_html__( 'You do not have permission to edit these settings.', 'elementor' ) );
}
$nonce = Utils::get_super_global_value( $_POST, 'nonce' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( empty( $nonce ) || ! wp_verify_nonce( $nonce, 'e-element-manager-app' ) ) {
wp_send_json_error( esc_html__( 'Invalid nonce.', 'elementor' ) );
}
}
private function force_enabled_all_elements() {
remove_all_filters( 'elementor/widgets/is_widget_enabled' );
}
private function get_plugin_name_from_widget_instance( $widget ) {
if ( in_array( 'wordpress', $widget->get_categories() ) ) {
return esc_html__( 'WordPress Widgets', 'elementor' );
}
$class_reflection = new \ReflectionClass( $widget );
$plugin_basename = plugin_basename( $class_reflection->getFileName() );
$plugin_directory = strtok( $plugin_basename, '/' );
$plugins_data = get_plugins( '/' . $plugin_directory );
$plugin_data = array_shift( $plugins_data );
return $plugin_data['Name'] ?? esc_html__( 'Unknown', 'elementor' );
}
public function ajax_save_disabled_elements() {
$this->verify_permission();
$elements = Utils::get_super_global_value( $_POST, 'widgets' ); // phpcs:ignore WordPress.Security.NonceVerification.Missing
if ( empty( $elements ) ) {
wp_send_json_error( esc_html__( 'No elements to save.', 'elementor' ) );
}
$disabled_elements = json_decode( $elements );
if ( ! is_array( $disabled_elements ) ) {
wp_send_json_error( esc_html__( 'Unexpected elements data.', 'elementor' ) );
}
Options::update_disabled_elements( $disabled_elements );
do_action( 'elementor/element_manager/save_disabled_elements' );
wp_send_json_success();
}
public function ajax_get_widgets_usage() {
$this->verify_permission();
/** @var Usage_Module $usage_module */
$usage_module = Usage_Module::instance();
$usage_module->recalc_usage();
$widgets_usage = [];
foreach ( $usage_module->get_formatted_usage( 'raw' ) as $data ) {
foreach ( $data['elements'] as $element => $count ) {
if ( ! isset( $widgets_usage[ $element ] ) ) {
$widgets_usage[ $element ] = 0;
}
$widgets_usage[ $element ] += $count;
}
}
wp_send_json_success( $widgets_usage );
}
}

View File

@@ -0,0 +1,95 @@
<?php
namespace Elementor\Modules\ElementManager;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
use Elementor\Widget_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
const PAGE_ID = 'elementor-element-manager';
public function get_name() {
return 'element-manager';
}
public function __construct() {
parent::__construct();
$ajax = new Ajax();
$ajax->register_endpoints();
add_action( 'elementor/admin/menu/register', function( Admin_Menu_Manager $admin_menu ) {
$admin_menu->register( static::PAGE_ID, new Admin_Menu_App() );
}, 25 );
add_action( 'elementor/admin/menu/after_register', function ( Admin_Menu_Manager $admin_menu, array $hooks ) {
if ( ! empty( $hooks[ static::PAGE_ID ] ) ) {
add_action( "admin_print_scripts-{$hooks[ static::PAGE_ID ]}", [ $this, 'enqueue_assets' ] );
add_action( "admin_footer-{$hooks[ static::PAGE_ID ]}", [ $this, 'print_styles' ], 1000 );
}
}, 10, 2 );
add_filter( 'elementor/widgets/is_widget_enabled', function( $should_register, Widget_Base $widget_instance ) {
return ! Options::is_element_disabled( $widget_instance->get_name() );
}, 10, 2 );
add_filter( 'elementor/system-info/usage/settings', function( $usage ) {
$disabled_elements = Options::get_disabled_elements();
if ( ! empty( $disabled_elements ) ) {
$usage['disabled_elements'] = implode( ', ', $disabled_elements );
}
return $usage;
} );
add_filter( 'elementor/tracker/send_tracking_data_params', function( $params ) {
$disabled_elements = Options::get_disabled_elements();
if ( ! empty( $disabled_elements ) ) {
$params['usages']['disabled_elements'] = $disabled_elements;
}
return $params;
} );
}
public function enqueue_assets() {
wp_enqueue_script(
'e-element-manager-app',
$this->get_js_assets_url( 'element-manager-admin' ),
[
'wp-element',
'wp-components',
'wp-dom-ready',
'wp-i18n',
],
ELEMENTOR_VERSION
);
wp_localize_script( 'e-element-manager-app', 'eElementManagerConfig', [
'nonce' => wp_create_nonce( 'e-element-manager-app' ),
'ajaxurl' => admin_url( 'admin-ajax.php' ),
] );
wp_set_script_translations( 'e-element-manager-app', 'elementor' );
wp_enqueue_style( 'wp-components' );
wp_enqueue_style( 'wp-format-library' );
}
public function print_styles() {
?>
<style>
.components-button.is-secondary:disabled {
box-shadow: inset 0 0 0 1px #949494;
}
</style>
<?php
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Elementor\Modules\ElementManager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Options {
public static function get_disabled_elements() {
return (array) get_option( 'elementor_disabled_elements', [] );
}
public static function update_disabled_elements( $elements ) {
update_option( 'elementor_disabled_elements', (array) $elements );
}
public static function is_element_disabled( $element_name ) {
return in_array( $element_name, self::get_disabled_elements() );
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace Elementor\Modules\ElementsColorPicker;
use Elementor\Core\Experiments\Manager;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Experiments\Manager as Experiments_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
/**
* Retrieve the module name.
*
* @return string
*/
public function get_name() {
return 'elements-color-picker';
}
/**
* Enqueue the `Color-Thief` library to pick colors from images.
*
* @return void
*/
public function enqueue_scripts() {
wp_enqueue_script(
'color-thief',
$this->get_js_assets_url( 'color-thief', 'assets/lib/color-thief/', true ),
[ 'elementor-editor' ],
ELEMENTOR_VERSION,
true
);
}
/**
* Module constructor - Initialize the Eye-Dropper module.
*
* @return void
*/
public function __construct() {
add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
}
}

View File

@@ -0,0 +1,84 @@
<?php
namespace Elementor\Modules\Favorites;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use Elementor\Data\V2\Base\Controller as Controller_Base;
use Elementor\Plugin;
class Controller extends Controller_Base {
public function get_name() {
return 'favorites';
}
public function create_item( $request ) {
$module = $this->get_module();
$type = $request->get_param( 'id' );
$favorite = $request->get_param( 'favorite' );
$module->update( $type, $favorite, $module::ACTION_MERGE );
return $module->get( $type );
}
public function delete_item( $request ) {
$module = $this->get_module();
$type = $request->get_param( 'id' );
$favorite = $request->get_param( 'favorite' );
$module->update( $type, $favorite, $module::ACTION_DELETE );
return $module->get( $type );
}
public function create_item_permissions_check( $request ) {
return current_user_can( 'edit_posts' );
}
public function delete_item_permissions_check( $request ) {
return $this->create_item_permissions_check( $request );
}
/**
* Get the favorites module instance.
*
* @return Module
*/
protected function get_module() {
return Plugin::instance()->modules_manager->get_modules( 'favorites' );
}
public function register_endpoints() {
$this->index_endpoint->register_item_route( \WP_REST_Server::CREATABLE, [
'id_arg_type_regex' => '[\w]+',
'id' => [
'description' => 'Type of favorites.',
'type' => 'string',
'required' => true,
],
'favorite' => [
'description' => 'The favorite slug to create.',
'type' => 'string',
'required' => true,
],
] );
$this->index_endpoint->register_item_route( \WP_REST_Server::DELETABLE, [
'id_arg_type_regex' => '[\w]+',
'id' => [
'description' => 'Type of favorites.',
'type' => 'string',
'required' => true,
],
'favorite' => [
'description' => 'The favorite slug to delete.',
'type' => 'string',
'required' => true,
],
] );
}
}

View File

@@ -0,0 +1,42 @@
<?php
namespace Elementor\Modules\Favorites;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use Elementor\Core\Utils\Collection;
use Elementor\Core\Utils\Static_Collection;
abstract class Favorites_Type extends Static_Collection {
public function __construct( array $items = [] ) {
parent::__construct( $items, true );
}
/**
* Get the name of the type.
*
* @return mixed
*/
abstract public function get_name();
/**
* Prepare favorites before taking any action.
*
* @param Collection|array|string $favorites
*
* @return array
*/
public function prepare( $favorites ) {
if ( $favorites instanceof Collection ) {
$favorites = $favorites->values();
}
if ( ! is_array( $favorites ) ) {
return [ $favorites ];
}
return $favorites;
}
}

View File

@@ -0,0 +1,248 @@
<?php
namespace Elementor\Modules\Favorites;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Experiments\Manager;
use Elementor\Modules\Favorites\Types\Widgets;
use Elementor\Plugin;
use http\Exception\InvalidArgumentException;
use WP_Error;
class Module extends BaseModule {
/**
* List of registered favorites type.
*
* @var Favorites_Type[]
*/
protected $types = [];
const OPTION_NAME = 'elementor_editor_user_favorites';
/**
* The name of the merge action.
*
* @var string
*/
const ACTION_MERGE = 'merge';
/**
* The name of the delete action.
*
* @var string
*/
const ACTION_DELETE = 'delete';
/**
* Favorites module constructor.
*/
public function __construct() {
// Register default types
$this->register( Widgets::class );
$this->populate();
Plugin::instance()->data_manager_v2->register_controller( new Controller() );
add_filter( 'elementor/tracker/send_tracking_data_params', [ $this, 'add_tracking_data' ] );
}
/**
* Add usage data related to favorites.
*
* @param $params
*
* @return array
*/
public function add_tracking_data( $params ) {
$params['usages']['favorites'] = $this->get();
return $params;
}
public function get_name() {
return 'favorites';
}
/**
* Get user favorites by type.
*
* @param string[]|string $type
*
* @return array
*/
public function get( $type = null ) {
if ( null === $type ) {
$type = array_keys( $this->types );
}
if ( is_array( $type ) ) {
return array_intersect_key(
$this->combined(),
array_flip( (array) $type )
);
}
return $this->type_instance( $type )
->values();
}
/**
* Merge new user favorites to a type.
*
* @param string $type
* @param array|string $favorites
* @param bool $store
*
* @return array|bool
*/
public function merge( $type, $favorites, $store = true ) {
return $this->update( $type, $favorites, static::ACTION_MERGE, $store );
}
/**
* Delete existing favorites from a type.
*
* @param string $type
* @param array|string $favorites
* @param bool $store
*
* @return array|int
*/
public function delete( $type, $favorites, $store = true ) {
return $this->update( $type, $favorites, static::ACTION_DELETE, $store );
}
/**
* Update favorites on a type by merging or deleting from it.
*
* @param $type
* @param $favorites
* @param $action
* @param bool $store
*
* @return array|boolean
*/
public function update( $type, $favorites, $action, $store = true ) {
$type_instance = $this->type_instance( $type );
$favorites = $type_instance->prepare( $favorites );
switch ( $action ) {
case static::ACTION_MERGE:
$type_instance->merge( $favorites );
break;
case static::ACTION_DELETE:
$type_instance->filter(
function( $value ) use ( $favorites ) {
return ! in_array( $value, $favorites, true );
}
);
break;
default:
$this->action_doesnt_exists( $action );
}
if ( $store && ! $this->store() ) {
return false;
}
return $type_instance->values();
}
/**
* Get registered favorites type instance.
*
* @param string $type
*
* @return Favorites_Type
*/
public function type_instance( $type ) {
return $this->types[ $type ];
}
/**
* Register a new type class.
*
* @param string $class
*/
public function register( $class ) {
$type_instance = new $class();
$this->types[ $type_instance->get_name() ] = $type_instance;
}
/**
* Returns all available types keys.
*
* @return string[]
*/
public function available() {
return array_keys( $this->types );
}
/**
* Combine favorites from all types into a single array.
*
* @return array
*/
protected function combined() {
$all = [];
foreach ( $this->types as $type ) {
$favorites = $type->values();
if ( ! empty( $favorites ) ) {
$all[ $type->get_name() ] = $favorites;
}
}
return $all;
}
/**
* Populate all type classes with the stored data.
*/
protected function populate() {
$combined = $this->retrieve();
foreach ( $this->types as $key => $type ) {
if ( isset( $combined[ $key ] ) ) {
$type->merge( $combined[ $key ] );
}
}
}
/**
* Retrieve stored user favorites types.
*
* @return mixed|false
*/
protected function retrieve() {
return get_user_option( static::OPTION_NAME );
}
/**
* Update all changes to user favorites type.
*
* @return int|bool
*/
protected function store() {
return update_user_option( get_current_user_id(), static::OPTION_NAME, $this->combined() );
}
/**
* Throw action doesn't exist exception.
*
* @param string $action
*/
public function action_doesnt_exists( $action ) {
throw new \InvalidArgumentException( sprintf(
"Action '%s' to apply on favorites doesn't exists",
$action
) );
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Elementor\Modules\Favorites\Types;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
use Elementor\Modules\Favorites\Favorites_Type;
use Elementor\Plugin;
class Widgets extends Favorites_Type {
const CATEGORY_SLUG = 'favorites';
/**
* Widgets favorites type constructor.
*/
public function __construct( array $items = [] ) {
parent::__construct( $items );
add_action( 'elementor/document/before_get_config', [ $this, 'update_widget_categories' ], 10, 1 );
}
public function get_name() {
return 'widgets';
}
public function prepare( $favorites ) {
return array_intersect( parent::prepare( $favorites ), $this->get_available() );
}
/**
* Get all available widgets.
*
* @return string[]
*/
public function get_available() {
return array_merge(
array_keys(
Plugin::instance()->widgets_manager->get_widget_types()
),
array_keys(
Plugin::instance()->elements_manager->get_element_types()
)
);
}
/**
* Update the categories of a widget inside a filter.
*
* @param $document
*/
public function update_widget_categories( $document ) {
foreach ( $this->values() as $favorite ) {
$widget = Plugin::$instance->widgets_manager->get_widget_types( $favorite );
// If it's not a widget, maybe it's an element.
if ( ! $widget ) {
$widget = Plugin::$instance->elements_manager->get_element_types( $favorite );
}
if ( $widget ) {
$widget->set_config( 'categories', [ static::CATEGORY_SLUG ] );
}
}
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Elementor\Modules\GeneratorTag;
use Elementor\Plugin;
use Elementor\Settings;
use Elementor\Core\Base\Module as BaseModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
public function get_name() {
return 'generator-tag';
}
public function __construct() {
parent::__construct();
add_action( 'wp_head', [ $this, 'render_generator_tag' ] );
add_action( 'elementor/admin/after_create_settings/' . Settings::PAGE_ID, [ $this, 'register_admin_settings' ], 100 );
}
public function render_generator_tag() {
if ( '1' === get_option( 'elementor_meta_generator_tag' ) ) {
return;
}
$generator_content = $this->get_generator_content();
echo '<meta name="generator" content="' . esc_attr( $generator_content ) . '">' . PHP_EOL;
}
private function get_generator_content(): string {
$active_features = $this->get_active_features();
$settings = $this->get_generator_tag_settings();
$tags = [
'Elementor ' . ELEMENTOR_VERSION,
];
if ( ! empty( $active_features ) ) {
$tags[] = 'features: ' . implode( ', ', $active_features );
}
if ( ! empty( $settings ) ) {
$tags[] = 'settings: ' . implode( ', ', $settings );
}
return implode( '; ', $tags );
}
private function get_active_features(): array {
$active_features = [];
foreach ( Plugin::$instance->experiments->get_active_features() as $feature_slug => $feature ) {
if ( isset( $feature['generator_tag'] ) && $feature['generator_tag'] ) {
$active_features[] = $feature_slug;
}
}
return $active_features;
}
private function get_generator_tag_settings(): array {
return apply_filters( 'elementor/generator_tag/settings', [] );
}
public function register_admin_settings( Settings $settings ) {
$settings->add_field(
Settings::TAB_ADVANCED,
Settings::TAB_ADVANCED,
'meta_generator_tag',
[
'label' => esc_html__( 'Generator Tag', 'elementor' ),
'field_args' => [
'type' => 'select',
'std' => '',
'options' => [
'' => esc_html__( 'Enable', 'elementor' ),
'1' => esc_html__( 'Disable', 'elementor' ),
],
'desc' => esc_html__( 'A generator tag is a meta element that indicates the attributes used to create a webpage. It is used for analytical purposes.', 'elementor' ),
],
]
);
}
}

View File

@@ -0,0 +1,231 @@
<?php
namespace Elementor\Modules\Gutenberg;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Experiments\Manager as Experiments_Manager;
use Elementor\Plugin;
use Elementor\User;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
protected $is_gutenberg_editor_active = false;
/**
* @since 2.1.0
* @access public
*/
public function get_name() {
return 'gutenberg';
}
/**
* @since 2.1.0
* @access public
* @static
*/
public static function is_active() {
return function_exists( 'register_block_type' );
}
/**
* @since 2.1.0
* @access public
*/
public function register_elementor_rest_field() {
register_rest_field( get_post_types( '', 'names' ),
'gutenberg_elementor_mode', [
'update_callback' => function( $request_value, $object ) {
if ( ! User::is_current_user_can_edit( $object->ID ) ) {
return false;
}
$document = Plugin::$instance->documents->get( $object->ID );
if ( ! $document ) {
return false;
}
$document->set_is_built_with_elementor( false );
return true;
},
]
);
}
/**
* @since 2.1.0
* @access public
*/
public function enqueue_assets() {
$document = Plugin::$instance->documents->get( get_the_ID() );
if ( ! $document || ! $document->is_editable_by_current_user() ) {
return;
}
$this->is_gutenberg_editor_active = true;
$suffix = defined( 'SCRIPT_DEBUG' ) && SCRIPT_DEBUG ? '' : '.min';
wp_enqueue_script( 'elementor-gutenberg', ELEMENTOR_ASSETS_URL . 'js/gutenberg' . $suffix . '.js', [ 'jquery' ], ELEMENTOR_VERSION, true );
$elementor_settings = [
'isElementorMode' => $document->is_built_with_elementor(),
'editLink' => $document->get_edit_url(),
];
Utils::print_js_config( 'elementor-gutenberg', 'ElementorGutenbergSettings', $elementor_settings );
}
/**
* @since 2.1.0
* @access public
*/
public function print_admin_js_template() {
if ( ! $this->is_gutenberg_editor_active ) {
return;
}
?>
<script id="elementor-gutenberg-button-switch-mode" type="text/html">
<div id="elementor-switch-mode">
<button id="elementor-switch-mode-button" type="button" class="button button-primary button-large">
<span class="elementor-switch-mode-on"><?php echo esc_html__( '&#8592; Back to WordPress Editor', 'elementor' ); ?></span>
<span class="elementor-switch-mode-off">
<i class="eicon-elementor-square" aria-hidden="true"></i>
<?php echo esc_html__( 'Edit with Elementor', 'elementor' ); ?>
</span>
</button>
</div>
</script>
<script id="elementor-gutenberg-panel" type="text/html">
<div id="elementor-editor">
<div id="elementor-go-to-edit-page-link">
<button id="elementor-editor-button" class="button button-primary button-hero">
<i class="eicon-elementor-square" aria-hidden="true"></i>
<?php echo esc_html__( 'Edit with Elementor', 'elementor' ); ?>
</button>
<div class="elementor-loader-wrapper">
<div class="elementor-loader">
<div class="elementor-loader-boxes">
<div class="elementor-loader-box"></div>
<div class="elementor-loader-box"></div>
<div class="elementor-loader-box"></div>
<div class="elementor-loader-box"></div>
</div>
</div>
<div class="elementor-loading-title"><?php echo esc_html__( 'Loading', 'elementor' ); ?></div>
</div>
</div>
</div>
</script>
<script id="elementor-gutenberg-button-tmpl" type="text/html">
<div id="elementor-edit-button-gutenberg">
<button id="elementor-edit-mode-button" type="button" class="button button-primary button-large">
<span class="elementor-edit-mode-gutenberg">
<i class="eicon-elementor-square" aria-hidden="true"></i>
<?php echo esc_html__( 'Edit with Elementor', 'elementor' ); ?>
</span>
</button>
</div>
</script>
<?php
}
/**
* @since 2.1.0
* @access public
*/
public function __construct() {
add_action( 'rest_api_init', [ $this, 'register_elementor_rest_field' ] );
add_action( 'enqueue_block_editor_assets', [ $this, 'enqueue_assets' ] );
add_action( 'admin_footer', [ $this, 'print_admin_js_template' ] );
add_action( 'wp_enqueue_scripts', [ $this, 'dequeue_assets' ], 999 );
}
public function dequeue_assets() {
if ( ! static::is_optimized_gutenberg_loading_enabled() ) {
return;
}
if ( ! static::should_dequeue_gutenberg_assets() ) {
return;
}
wp_dequeue_style( 'wp-block-library' );
wp_dequeue_style( 'wp-block-library-theme' );
wp_dequeue_style( 'wc-block-style' );
wp_dequeue_style( 'wc-blocks-style' );
}
/**
* Check whether the "Optimized Gutenberg Loading" settings is enabled.
*
* The 'elementor_optimized_gutenberg_loading' option can be enabled/disabled from the Elementor settings.
* For BC, when the option has not been saved in the database, the default '1' value is returned.
*
* @since 3.21.0
* @access private
*/
private static function is_optimized_gutenberg_loading_enabled() : bool {
return (bool) get_option( 'elementor_optimized_gutenberg_loading', '1' );
}
private static function should_dequeue_gutenberg_assets() : bool {
$post = get_post();
if ( empty( $post->ID ) ) {
return false;
}
if ( ! static::is_built_with_elementor( $post ) ) {
return false;
}
if ( static::is_gutenberg_in_post( $post ) ) {
return false;
}
return true;
}
private static function is_built_with_elementor( $post ) : bool {
$document = Plugin::$instance->documents->get( $post->ID );
if ( ! $document || ! $document->is_built_with_elementor() ) {
return false;
}
return true;
}
private static function is_gutenberg_in_post( $post ) : bool {
if ( has_blocks( $post ) ) {
return true;
}
if ( static::current_theme_is_fse_theme() ) {
return true;
}
return false;
}
private static function current_theme_is_fse_theme() : bool {
if ( function_exists( 'wp_is_block_theme' ) ) {
return (bool) wp_is_block_theme();
}
if ( function_exists( 'gutenberg_is_fse_theme' ) ) {
return (bool) gutenberg_is_fse_theme();
}
return false;
}
}

View File

@@ -0,0 +1,74 @@
<?php
namespace Elementor\Modules\History;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor history module.
*
* Elementor history module handler class is responsible for registering and
* managing Elementor history modules.
*
* @since 1.7.0
*/
class Module extends BaseModule {
/**
* Get module name.
*
* Retrieve the history module name.
*
* @since 1.7.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'history';
}
/**
* Localize settings.
*
* Add new localized settings for the history module.
*
* Fired by `elementor/editor/localize_settings` filter.
*
* @since 1.7.0
* @deprecated 3.1.0
* @access public
*
* @return array Localized settings.
*/
public function localize_settings() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' );
return [];
}
/**
* @since 2.3.0
* @access public
*/
public function add_templates() {
Plugin::$instance->common->add_template( __DIR__ . '/views/history-panel-template.php' );
Plugin::$instance->common->add_template( __DIR__ . '/views/revisions-panel-template.php' );
}
/**
* History module constructor.
*
* Initializing Elementor history module.
*
* @since 1.7.0
* @access public
*/
public function __construct() {
add_action( 'elementor/editor/init', [ $this, 'add_templates' ] );
}
}

View File

@@ -0,0 +1,416 @@
<?php
namespace Elementor\Modules\History;
use Elementor\Core\Base\Document;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
use Elementor\Core\Files\CSS\Post as Post_CSS;
use Elementor\Plugin;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor history revisions manager.
*
* Elementor history revisions manager handler class is responsible for
* registering and managing Elementor revisions manager.
*
* @since 1.7.0
*/
class Revisions_Manager {
/**
* Maximum number of revisions to display.
*/
const MAX_REVISIONS_TO_DISPLAY = 50;
/**
* Authors list.
*
* Holds all the authors.
*
* @access private
*
* @var array
*/
private static $authors = [];
/**
* History revisions manager constructor.
*
* Initializing Elementor history revisions manager.
*
* @since 1.7.0
* @access public
*/
public function __construct() {
self::register_actions();
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function handle_revision() {
add_filter( 'wp_save_post_revision_check_for_changes', '__return_false' );
}
/**
* @since 2.0.0
* @access public
* @static
*
* @param $post_content
* @param $post_id
*
* @return string
*/
public static function avoid_delete_auto_save( $post_content, $post_id ) {
// Add a temporary string in order the $post will not be equal to the $autosave
// in edit-form-advanced.php:210
$document = Plugin::$instance->documents->get( $post_id );
if ( $document && $document->is_built_with_elementor() ) {
$post_content .= '<!-- Created with Elementor -->';
}
return $post_content;
}
/**
* @since 2.0.0
* @access public
* @static
*/
public static function remove_temp_post_content() {
global $post;
$document = Plugin::$instance->documents->get( $post->ID );
if ( ! $document || ! $document->is_built_with_elementor() ) {
return;
}
$post->post_content = str_replace( '<!-- Created with Elementor -->', '', $post->post_content );
}
/**
* @since 1.7.0
* @access public
* @static
*
* @param int $post_id
* @param array $query_args
* @param bool $parse_result
*
* @return array
*/
public static function get_revisions( $post_id = 0, $query_args = [], $parse_result = true ) {
$post = get_post( $post_id );
if ( ! $post || empty( $post->ID ) ) {
return [];
}
$revisions = [];
$default_query_args = [
'posts_per_page' => self::MAX_REVISIONS_TO_DISPLAY,
'meta_key' => '_elementor_data',
];
$query_args = array_merge( $default_query_args, $query_args );
$posts = wp_get_post_revisions( $post->ID, $query_args );
if ( ! wp_revisions_enabled( $post ) ) {
$autosave = Utils::get_post_autosave( $post->ID );
if ( $autosave ) {
if ( $parse_result ) {
array_unshift( $posts, $autosave );
} else {
array_unshift( $posts, $autosave->ID );
}
}
}
if ( $parse_result ) {
array_unshift( $posts, $post );
} else {
array_unshift( $posts, $post->ID );
return $posts;
}
$current_time = current_time( 'timestamp' );
/** @var \WP_Post $revision */
foreach ( $posts as $revision ) {
$date = date_i18n( _x( 'M j @ H:i', 'revision date format', 'elementor' ), strtotime( $revision->post_modified ) );
$human_time = human_time_diff( strtotime( $revision->post_modified ), $current_time );
if ( $revision->ID === $post->ID ) {
$type = 'current';
$type_label = esc_html__( 'Current Version', 'elementor' );
} elseif ( false !== strpos( $revision->post_name, 'autosave' ) ) {
$type = 'autosave';
$type_label = esc_html__( 'Autosave', 'elementor' );
} else {
$type = 'revision';
$type_label = esc_html__( 'Revision', 'elementor' );
}
if ( ! isset( self::$authors[ $revision->post_author ] ) ) {
self::$authors[ $revision->post_author ] = [
'avatar' => get_avatar( $revision->post_author, 22 ),
'display_name' => get_the_author_meta( 'display_name', $revision->post_author ),
];
}
$revisions[] = [
'id' => $revision->ID,
'author' => self::$authors[ $revision->post_author ]['display_name'],
'timestamp' => strtotime( $revision->post_modified ),
'date' => sprintf(
/* translators: 1: Human readable time difference, 2: Date. */
esc_html__( '%1$s ago (%2$s)', 'elementor' ),
'<time>' . $human_time . '</time>',
'<time>' . $date . '</time>'
),
'type' => $type,
'typeLabel' => $type_label,
'gravatar' => self::$authors[ $revision->post_author ]['avatar'],
];
}
return $revisions;
}
/**
* @since 1.9.2
* @access public
* @static
*/
public static function update_autosave( $autosave_data ) {
self::save_revision( $autosave_data['ID'] );
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function save_revision( $revision_id ) {
$parent_id = wp_is_post_revision( $revision_id );
if ( $parent_id ) {
Plugin::$instance->db->safe_copy_elementor_meta( $parent_id, $revision_id );
}
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function restore_revision( $parent_id, $revision_id ) {
$parent = Plugin::$instance->documents->get( $parent_id );
$revision = Plugin::$instance->documents->get( $revision_id );
if ( ! $parent || ! $revision ) {
return;
}
$is_built_with_elementor = $revision->is_built_with_elementor();
$parent->set_is_built_with_elementor( $is_built_with_elementor );
if ( ! $is_built_with_elementor ) {
return;
}
Plugin::$instance->db->copy_elementor_meta( $revision_id, $parent_id );
$post_css = Post_CSS::create( $parent_id );
$post_css->update();
}
/**
* @since 2.3.0
* @access public
* @static
*
* @param $data
*
* @return array
* @throws \Exception
*/
public static function ajax_get_revision_data( array $data ) {
if ( ! isset( $data['id'] ) ) {
throw new \Exception( 'You must set the revision ID.' );
}
$revision = Plugin::$instance->documents->get_with_permissions( $data['id'] );
return [
'settings' => $revision->get_settings(),
'elements' => $revision->get_elements_data(),
];
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function add_revision_support_for_all_post_types() {
$post_types = get_post_types_by_support( 'elementor' );
foreach ( $post_types as $post_type ) {
add_post_type_support( $post_type, 'revisions' );
}
}
/**
* @since 2.0.0
* @access public
* @static
* @param array $return_data
* @param Document $document
*
* @return array
*/
public static function on_ajax_save_builder_data( $return_data, $document ) {
$post_id = $document->get_main_id();
$latest_revisions = self::get_revisions(
$post_id, [
'posts_per_page' => 1,
]
);
$all_revision_ids = self::get_revisions(
$post_id, [
'fields' => 'ids',
], false
);
// Send revisions data only if has revisions.
if ( ! empty( $latest_revisions ) ) {
$current_revision_id = self::current_revision_id( $post_id );
$return_data = array_replace_recursive( $return_data, [
'config' => [
'document' => [
'revisions' => [
'current_id' => $current_revision_id,
],
],
],
'latest_revisions' => $latest_revisions,
'revisions_ids' => $all_revision_ids,
] );
}
return $return_data;
}
/**
* @since 1.7.0
* @access public
* @static
*/
public static function db_before_save( $status, $has_changes ) {
if ( $has_changes ) {
self::handle_revision();
}
}
public static function document_config( $settings, $post_id ) {
$settings['revisions'] = [
'enabled' => ( $post_id && wp_revisions_enabled( get_post( $post_id ) ) ),
'current_id' => self::current_revision_id( $post_id ),
];
return $settings;
}
/**
* Localize settings.
*
* Add new localized settings for the revisions manager.
*
* Fired by `elementor/editor/editor_settings` filter.
*
* @since 1.7.0
* @deprecated 3.1.0
* @access public
* @static
*/
public static function editor_settings() {
Plugin::$instance->modules_manager->get_modules( 'dev-tools' )->deprecation->deprecated_function( __METHOD__, '3.1.0' );
return [];
}
/**
* @throws \Exception
*/
public static function ajax_get_revisions( $data ) {
Plugin::$instance->documents->check_permissions( $data['editor_post_id'] );
return self::get_revisions();
}
/**
* @since 2.3.0
* @access public
* @static
*/
public static function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'get_revisions', [ __CLASS__, 'ajax_get_revisions' ] );
$ajax->register_ajax_action( 'get_revision_data', [ __CLASS__, 'ajax_get_revision_data' ] );
}
/**
* @since 1.7.0
* @access private
* @static
*/
private static function register_actions() {
add_action( 'wp_restore_post_revision', [ __CLASS__, 'restore_revision' ], 10, 2 );
add_action( 'init', [ __CLASS__, 'add_revision_support_for_all_post_types' ], 9999 );
add_filter( 'elementor/document/config', [ __CLASS__, 'document_config' ], 10, 2 );
add_action( 'elementor/db/before_save', [ __CLASS__, 'db_before_save' ], 10, 2 );
add_action( '_wp_put_post_revision', [ __CLASS__, 'save_revision' ] );
add_action( 'wp_creating_autosave', [ __CLASS__, 'update_autosave' ] );
add_action( 'elementor/ajax/register_actions', [ __CLASS__, 'register_ajax_actions' ] );
// Hack to avoid delete the auto-save revision in WP editor.
add_filter( 'edit_post_content', [ __CLASS__, 'avoid_delete_auto_save' ], 10, 2 );
add_action( 'edit_form_after_title', [ __CLASS__, 'remove_temp_post_content' ] );
if ( wp_doing_ajax() ) {
add_filter( 'elementor/documents/ajax_save/return_data', [ __CLASS__, 'on_ajax_save_builder_data' ], 10, 2 );
}
}
/**
* @since 1.9.0
* @access private
* @static
*/
private static function current_revision_id( $post_id ) {
$current_revision_id = $post_id;
$autosave = Utils::get_post_autosave( $post_id );
if ( is_object( $autosave ) ) {
$current_revision_id = $autosave->ID;
}
return $current_revision_id;
}
}

View File

@@ -0,0 +1,37 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?>
<script type="text/template" id="tmpl-elementor-panel-history-page">
<div id="elementor-panel-elements-navigation" class="elementor-panel-navigation">
<button class="elementor-component-tab elementor-panel-navigation-tab" data-tab="actions"><?php echo esc_html__( 'Actions', 'elementor' ); ?></button>
<button class="elementor-component-tab elementor-panel-navigation-tab" data-tab="revisions"><?php echo esc_html__( 'Revisions', 'elementor' ); ?></button>
</div>
<div id="elementor-panel-history-content"></div>
</script>
<script type="text/template" id="tmpl-elementor-panel-history-tab">
<div id="elementor-history-list"></div>
<div class="elementor-history-revisions-message"><?php echo esc_html__( 'Switch to Revisions tab for older versions', 'elementor' ); ?></div>
</script>
<script type="text/template" id="tmpl-elementor-panel-history-no-items">
<img class="elementor-nerd-box-icon" src="<?php
// PHPCS - Safe Elementor SVG
echo ELEMENTOR_ASSETS_URL . 'images/information.svg'; // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" loading="lazy" alt="<?php echo esc_attr__( 'Elementor', 'elementor' ); ?>" />
<div class="elementor-nerd-box-title"><?php echo esc_html__( 'No History Yet', 'elementor' ); ?></div>
<div class="elementor-nerd-box-message"><?php echo esc_html__( 'Once you start working, you\'ll be able to redo / undo any action you make in the editor.', 'elementor' ); ?></div>
</script>
<script type="text/template" id="tmpl-elementor-panel-history-item">
<div class="elementor-history-item__details">
<span class="elementor-history-item__title">{{{ title }}}</span>
<span class="elementor-history-item__subtitle">{{{ subTitle }}}</span>
<span class="elementor-history-item__action">{{{ action }}}</span>
</div>
<div class="elementor-history-item__icon">
<span class="eicon" aria-hidden="true"></span>
</div>
</script>

View File

@@ -0,0 +1,74 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
?>
<script type="text/template" id="tmpl-elementor-panel-revisions">
<div class="elementor-panel-box">
<div class="elementor-panel-revisions-buttons">
<button class="elementor-button e-btn-txt e-revision-discard" disabled>
<?php echo esc_html__( 'Discard', 'elementor' ); ?>
</button>
<button class="elementor-button e-revision-save" disabled>
<?php echo esc_html__( 'Apply', 'elementor' ); ?>
</button>
</div>
</div>
<div class="elementor-panel-box">
<div id="elementor-revisions-list" class="elementor-panel-box-content"></div>
</div>
</script>
<script type="text/template" id="tmpl-elementor-panel-revisions-no-revisions">
<#
var no_revisions_1 = '<?php echo esc_html__( 'Revision history lets you save your previous versions of your work, and restore them any time.', 'elementor' ); ?>',
no_revisions_2 = '<?php echo esc_html__( 'Start designing your page and you will be able to see the entire revision history here.', 'elementor' ); ?>',
revisions_disabled_1 = '<?php echo esc_html__( 'It looks like the post revision feature is unavailable in your website.', 'elementor' ); ?>',
revisions_disabled_2 = '<?php printf(
/* translators: %1$s Link open tag, %2$s: Link close tag. */
esc_html__( 'Learn more about %1$sWordPress revisions%2$s', 'elementor' ),
'<a target="_blank" href="https://go.elementor.com/wordpress-revisions/">',
'</a>'
); ?>';
#>
<img class="elementor-nerd-box-icon" src="<?php
// PHPCS - Safe Elementor SVG
echo ELEMENTOR_ASSETS_URL . 'images/information.svg' // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?>" loading="lazy" alt="<?php echo esc_attr__( 'Elementor', 'elementor' ); ?>" />
<div class="elementor-nerd-box-title"><?php echo esc_html__( 'No Revisions Saved Yet', 'elementor' ); ?></div>
<div class="elementor-nerd-box-message">{{{ elementor.config.document.revisions.enabled ? no_revisions_1 : revisions_disabled_1 }}}</div>
<div class="elementor-nerd-box-message">{{{ elementor.config.document.revisions.enabled ? no_revisions_2 : revisions_disabled_2 }}}</div>
</script>
<script type="text/template" id="tmpl-elementor-panel-revisions-loading">
<i class="eicon-loading eicon-animation-spin" aria-hidden="true"></i>
</script>
<script type="text/template" id="tmpl-elementor-panel-revisions-revision-item">
<button class="elementor-revision-item__wrapper {{ type }}">
<div class="elementor-revision-item__gravatar">{{{ gravatar }}}</div>
<div class="elementor-revision-item__details">
<div class="elementor-revision-date" title="{{{ new Date( timestamp * 1000 ) }}}">{{{ date }}}</div>
<div class="elementor-revision-meta">
<span>{{{ typeLabel }}}</span>
<?php echo esc_html__( 'By', 'elementor' ); ?> {{{ author }}}
<span>(#{{{ id }}})</span>&nbsp;
</div>
</div>
<div class="elementor-revision-item__tools">
<i class="elementor-revision-item__tools-spinner eicon-loading eicon-animation-spin" aria-hidden="true"></i>
<# if ( 'current' === type ) { #>
<i class="elementor-revision-item__tools-current eicon-check" aria-hidden="true"></i>
<span class="elementor-screen-only"><?php echo esc_html__( 'Published', 'elementor' ); ?></span>
<# } #>
<!-- <# if ( 'revision' === type ) { #>-->
<!-- <i class="eicon-undo" aria-hidden="true"></i>-->
<!-- <span class="elementor-screen-only">--><?php //echo esc_html__( 'Restore', 'elementor' ); ?><!--</span>-->
<!-- <# } #>-->
</div>
</button>
</script>

View File

@@ -0,0 +1,65 @@
<?php
namespace Elementor\Modules\Home;
use Elementor\Modules\Home\Classes\Transformations_Manager;
class API {
const HOME_SCREEN_DATA_URL = 'https://assets.elementor.com/home-screen/v1/home-screen.json';
public static function get_home_screen_items( $force_request = false ): array {
$home_screen_data = self::get_transient( '_elementor_home_screen_data' );
if ( $force_request || false === $home_screen_data ) {
$home_screen_data = static::fetch_data();
static::set_transient( '_elementor_home_screen_data', $home_screen_data, '+1 hour' );
}
return self::transform_home_screen_data( $home_screen_data );
}
private static function transform_home_screen_data( $json_data ): array {
$transformers = new Transformations_Manager( $json_data );
return $transformers->run_transformations();
}
private static function fetch_data(): array {
$response = wp_remote_get( self::HOME_SCREEN_DATA_URL );
if ( is_wp_error( $response ) ) {
return [];
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $data['home-screen'] ) || ! is_array( $data['home-screen'] ) ) {
return [];
}
return $data['home-screen'];
}
private static function get_transient( $cache_key ) {
$cache = get_option( $cache_key );
if ( empty( $cache['timeout'] ) ) {
return false;
}
if ( current_time( 'timestamp' ) > $cache['timeout'] ) {
return false;
}
return json_decode( $cache['value'], true );
}
private static function set_transient( $cache_key, $value, $expiration = '+12 hours' ): bool {
$data = [
'timeout' => strtotime( $expiration, current_time( 'timestamp' ) ),
'value' => json_encode( $value ),
];
return update_option( $cache_key, $data, false );
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Elementor\Modules\Home\Classes;
use Elementor\Core\Isolation\Wordpress_Adapter;
use Elementor\Core\Isolation\Plugin_Status_Adapter;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Transformations_Manager {
private static $cached_data = [];
private const TRANSFORMATIONS = [
'Create_New_Page_Url',
'Filter_Plugins',
'Filter_Get_Started_By_License',
'Filter_Sidebar_Upgrade_By_License',
'Create_Site_Settings_Url',
'Filter_Condition_Introduction_Meta',
];
protected array $home_screen_data;
protected Wordpress_Adapter $wordpress_adapter;
protected Plugin_Status_Adapter $plugin_status_adapter;
protected array $transformation_classes = [];
public function __construct( $home_screen_data ) {
$this->home_screen_data = $home_screen_data;
$this->wordpress_adapter = new Wordpress_Adapter();
$this->plugin_status_adapter = new Plugin_Status_Adapter( $this->wordpress_adapter );
$this->transformation_classes = $this->get_transformation_classes();
}
public function run_transformations(): array {
if ( ! empty( self::$cached_data ) ) {
return self::$cached_data;
}
$transformations = self::TRANSFORMATIONS;
foreach ( $transformations as $transformation_id ) {
$this->home_screen_data = $this->transformation_classes[ $transformation_id ]->transform( $this->home_screen_data );
}
self::$cached_data = $this->home_screen_data;
return $this->home_screen_data;
}
private function get_transformation_classes(): array {
$classes = [];
$transformations = self::TRANSFORMATIONS;
$arguments = [
'wordpress_adapter' => $this->wordpress_adapter,
'plugin_status_adapter' => $this->plugin_status_adapter,
];
foreach ( $transformations as $transformation_id ) {
$class_name = '\\Elementor\\Modules\\Home\\Transformations\\' . $transformation_id;
$classes[ $transformation_id ] = new $class_name( $arguments );
}
return $classes;
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Elementor\Modules\Home;
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
use Elementor\Core\Base\App as BaseApp;
use Elementor\Core\Experiments\Manager as Experiments_Manager;
use Elementor\Settings;
use Elementor\Plugin;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseApp {
const PAGE_ID = 'home_screen';
public function get_name(): string {
return 'home';
}
public function __construct() {
parent::__construct();
$this->register_layout_experiment();
if ( ! $this->is_experiment_active() ) {
return;
}
add_action( 'elementor/admin/menu/after_register', function ( Admin_Menu_Manager $admin_menu, array $hooks ) {
$hook_suffix = 'toplevel_page_elementor';
add_action( "admin_print_scripts-{$hook_suffix}", [ $this, 'enqueue_home_screen_scripts' ] );
}, 10, 2 );
add_filter( 'elementor/document/urls/edit', [ $this, 'add_active_document_to_edit_link' ] );
}
public function enqueue_home_screen_scripts(): void {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
$min_suffix = Utils::is_script_debug() ? '' : '.min';
wp_enqueue_script(
'e-home-screen',
ELEMENTOR_ASSETS_URL . 'js/e-home-screen' . $min_suffix . '.js',
[
'react',
'react-dom',
'elementor-common',
'elementor-v2-ui',
],
ELEMENTOR_VERSION,
true
);
wp_set_script_translations( 'e-home-screen', 'elementor' );
wp_localize_script(
'e-home-screen',
'elementorHomeScreenData',
$this->get_app_js_config()
);
}
public function is_experiment_active(): bool {
return Plugin::$instance->experiments->is_feature_active( self::PAGE_ID );
}
public function add_active_document_to_edit_link( $edit_link ) {
$active_document = Utils::get_super_global_value( $_GET, 'active-document' ) ?? null;
$active_tab = Utils::get_super_global_value( $_GET, 'active-tab' ) ?? null;
if ( $active_document ) {
$edit_link = add_query_arg( 'active-document', $active_document, $edit_link );
}
if ( $active_tab ) {
$edit_link = add_query_arg( 'active-tab', $active_tab, $edit_link );
}
return $edit_link;
}
private function register_layout_experiment(): void {
Plugin::$instance->experiments->add_feature( [
'name' => static::PAGE_ID,
'title' => esc_html__( 'Elementor Home Screen', 'elementor' ),
'description' => esc_html__( 'Default Elementor menu page.', 'elementor' ),
'hidden' => true,
'default' => Experiments_Manager::STATE_ACTIVE,
] );
}
private function get_app_js_config(): array {
return API::get_home_screen_items();
}
public static function get_elementor_settings_page_id(): string {
return Plugin::$instance->experiments->is_feature_active( self::PAGE_ID )
? 'elementor-settings'
: Settings::PAGE_ID;
}
}

View File

@@ -0,0 +1,19 @@
<?php
namespace Elementor\Modules\Home\Transformations\Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Transformations_Abstract {
protected $wordpress_adapter;
protected $plugin_status_adapter;
public function __construct( $args ) {
$this->wordpress_adapter = $args['wordpress_adapter'] ?? null;
$this->plugin_status_adapter = $args['plugin_status_adapter'] ?? null;
}
abstract public function transform( array $home_screen_data ): array;
}

View File

@@ -0,0 +1,18 @@
<?php
namespace Elementor\Modules\Home\Transformations;
use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Create_New_Page_Url extends Transformations_Abstract {
public function transform( array $home_screen_data ): array {
$home_screen_data['create_new_page_url'] = Plugin::$instance->documents->get_create_new_post_url( 'page' );
return $home_screen_data;
}
}

View File

@@ -0,0 +1,101 @@
<?php
namespace Elementor\Modules\Home\Transformations;
use Elementor\Core\Base\Document;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Create_Site_Settings_Url extends Base\Transformations_Abstract {
const URL_TYPE = 'site_settings';
const SITE_SETTINGS_ITEMS = [ 'Site Settings', 'Site Logo', 'Global Colors', 'Global Fonts' ];
public function transform( array $home_screen_data ): array {
if ( empty( $home_screen_data['get_started'] ) ) {
return $home_screen_data;
}
$site_settings_url_config = $this->get_site_settings_url_config();
$home_screen_data['get_started']['repeater'] = array_map( function( $repeater_item ) use ( $site_settings_url_config ) {
if ( ! in_array( $repeater_item['title'], static::SITE_SETTINGS_ITEMS, true ) ) {
return $repeater_item;
}
if ( ! empty( $repeater_item['tab_id'] ) ) {
$site_settings_url_config['url'] = add_query_arg( [ 'active-tab' => $repeater_item['tab_id'] ], $site_settings_url_config['url'] );
}
return array_merge( $repeater_item, $site_settings_url_config );
}, $home_screen_data['get_started']['repeater'] );
return $home_screen_data;
}
private function get_site_settings_url_config(): array {
$existing_elementor_page = $this->get_elementor_page();
$site_settings_url = ! empty( $existing_elementor_page )
? $this->get_elementor_edit_url( $existing_elementor_page->ID )
: $this->get_elementor_create_new_page_url();
return [
'new_page' => empty( $existing_elementor_page ),
'url' => $site_settings_url,
'type' => static::URL_TYPE,
];
}
private function get_elementor_create_new_page_url(): string {
$active_kit_id = Plugin::$instance->kits_manager->get_active_id();
if ( empty( $active_kit_id ) ) {
return Plugin::$instance->documents->get_create_new_post_url( 'page' );
}
return add_query_arg( [ 'active-document' => $active_kit_id ], Plugin::$instance->documents->get_create_new_post_url( 'page' ) );
}
private function get_elementor_edit_url( int $post_id ): string {
$active_kit_id = Plugin::$instance->kits_manager->get_active_id();
$document = Plugin::$instance->documents->get( $post_id );
if ( ! $document ) {
return '';
}
return add_query_arg( [ 'active-document' => $active_kit_id ], $document->get_edit_url() );
}
private function get_elementor_page() {
$args = [
'meta_key' => Document::BUILT_WITH_ELEMENTOR_META_KEY,
'sort_order' => 'asc',
'sort_column' => 'post_date',
];
$pages = get_pages( $args );
if ( empty( $pages ) ) {
return null;
}
$show_page_on_front = 'page' === get_option( 'show_on_front' );
if ( ! $show_page_on_front ) {
return $pages[0];
}
$home_page_id = get_option( 'page_on_front' );
foreach ( $pages as $page ) {
if ( (string) $page->ID === $home_page_id ) {
return $page;
}
}
return $pages[0];
}
}

View File

@@ -0,0 +1,67 @@
<?php
namespace Elementor\Modules\Home\Transformations;
use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract;
use Elementor\User;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Filter_Condition_Introduction_Meta extends Transformations_Abstract {
public array $introduction_meta_data;
public function __construct( $args ) {
parent::__construct( $args );
$this->introduction_meta_data = User::get_introduction_meta() ?? [];
}
public function transform( array $home_screen_data ): array {
$introduction_meta_conditions = $this->get_introduction_meta_conditions( $home_screen_data );
$active_addons = $this->get_activated_addons( $introduction_meta_conditions );
$home_screen_data['add_ons']['repeater'] = $this->get_inactive_addons( $home_screen_data, $active_addons );
return $home_screen_data;
}
private function get_introduction_meta_conditions( $home_screen_data ): array {
$add_ons = $home_screen_data['add_ons']['repeater'];
$conditions = [];
foreach ( $add_ons as $add_on ) {
if ( array_key_exists( 'condition', $add_on ) && 'introduction_meta' === $add_on['condition']['key'] ) {
$conditions[ $add_on['title'] ] = $add_on['condition']['value'];
}
}
return $conditions;
}
private function get_activated_addons( $conditions ): array {
$active_addons = [];
foreach ( $conditions as $add_on_title => $introduction_meta_value ) {
if ( ! empty( $this->introduction_meta_data[ $introduction_meta_value ] ) ) {
$active_addons[] = $add_on_title;
}
}
return $active_addons;
}
private function get_inactive_addons( $home_screen_data, $active_addons ): array {
$add_ons = $home_screen_data['add_ons']['repeater'];
$inactive_add_ons = [];
foreach ( $add_ons as $add_on ) {
if ( ! in_array( $add_on['title'], $active_addons ) ) {
$inactive_add_ons[] = $add_on;
}
}
return $inactive_add_ons;
}
}

View File

@@ -0,0 +1,39 @@
<?php
namespace Elementor\Modules\Home\Transformations;
use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Filter_Get_Started_By_License extends Transformations_Abstract {
public bool $has_pro;
public function __construct( $args ) {
parent::__construct( $args );
$this->has_pro = Utils::has_pro();
}
private function valid_item( $item ) {
$has_pro_json_not_free = $this->has_pro && 'pro' === $item['license'][0];
$is_not_pro_json_not_pro = ! $this->has_pro && 'free' === $item['license'][0];
return $has_pro_json_not_free || $is_not_pro_json_not_pro;
}
public function transform( array $home_screen_data ): array {
$new_get_started = [];
foreach ( $home_screen_data['get_started'] as $index => $item ) {
if ( $this->valid_item( $item ) ) {
$new_get_started[] = $item;
}
}
$home_screen_data['get_started'] = reset( $new_get_started );
return $home_screen_data;
}
}

View File

@@ -0,0 +1,89 @@
<?php
namespace Elementor\Modules\Home\Transformations;
use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Filter_Plugins extends Transformations_Abstract {
const PLUGIN_IS_NOT_INSTALLED_FROM_WPORG = 'not-installed-wporg';
const PLUGIN_IS_NOT_INSTALLED_NOT_FROM_WPORG = 'not-installed-not-wporg';
const PLUGIN_IS_INSTALLED_NOT_ACTIVATED = 'installed-not-activated';
const PLUGIN_IS_ACTIVATED = 'activated';
public function transform( array $home_screen_data ): array {
$home_screen_data['add_ons']['repeater'] = $this->get_add_ons_installation_status( $home_screen_data['add_ons']['repeater'] );
return $home_screen_data;
}
private function is_plugin( $add_on ): bool {
return 'link' !== $add_on['type'];
}
private function get_add_ons_installation_status( array $add_ons ): array {
$transformed_add_ons = [];
foreach ( $add_ons as $add_on ) {
if ( $this->is_plugin( $add_on ) ) {
$this->handle_plugin_add_on( $add_on, $transformed_add_ons );
} else {
$transformed_add_ons[] = $add_on;
}
}
return $transformed_add_ons;
}
private function get_plugin_installation_status( $add_on ): string {
$plugin_path = $add_on['file_path'];
if ( ! $this->plugin_status_adapter->is_plugin_installed( $plugin_path ) ) {
if ( 'wporg' === $add_on['type'] ) {
return self::PLUGIN_IS_NOT_INSTALLED_FROM_WPORG;
}
return self::PLUGIN_IS_NOT_INSTALLED_NOT_FROM_WPORG;
}
if ( $this->wordpress_adapter->is_plugin_active( $plugin_path ) ) {
return self::PLUGIN_IS_ACTIVATED;
}
return self::PLUGIN_IS_INSTALLED_NOT_ACTIVATED;
}
private function handle_plugin_add_on( array $add_on, array &$transformed_add_ons ): void {
$installation_status = $this->get_plugin_installation_status( $add_on );
if ( self::PLUGIN_IS_ACTIVATED === $installation_status ) {
return;
}
switch ( $this->get_plugin_installation_status( $add_on ) ) {
case self::PLUGIN_IS_NOT_INSTALLED_NOT_FROM_WPORG:
break;
case self::PLUGIN_IS_NOT_INSTALLED_FROM_WPORG:
$installation_url = $this->plugin_status_adapter->get_install_plugin_url( $add_on['file_path'] );
$add_on['url'] = html_entity_decode( $installation_url );
$add_on['target'] = '_self';
break;
case self::PLUGIN_IS_INSTALLED_NOT_ACTIVATED:
$activation_url = $this->plugin_status_adapter->get_activate_plugin_url( $add_on['file_path'] );
$add_on['url'] = html_entity_decode( $activation_url );
$add_on['button_label'] = esc_html__( 'Activate', 'elementor' );
$add_on['target'] = '_self';
break;
}
$transformed_add_ons[] = $add_on;
}
}

View File

@@ -0,0 +1,46 @@
<?php
namespace elementor\modules\home\transformations;
use Elementor\Modules\Home\Transformations\Base\Transformations_Abstract;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Filter_Sidebar_Upgrade_By_License extends Transformations_Abstract {
public bool $has_pro;
public function __construct( $args ) {
parent::__construct( $args );
$this->has_pro = Utils::has_pro();
}
private function valid_item( $item ) {
$has_pro_json_not_free = $this->has_pro && 'pro' === $item['license'][0];
$is_not_pro_json_not_pro = ! $this->has_pro && 'free' === $item['license'][0];
$should_show = ! isset( $item['show'] ) || 'true' === $item['show'];
return $has_pro_json_not_free && $should_show || $is_not_pro_json_not_pro && $should_show;
}
public function transform( array $home_screen_data ): array {
$new_sidebar_upgrade = [];
foreach ( $home_screen_data['sidebar_upgrade'] as $index => $item ) {
if ( $this->valid_item( $item ) ) {
$new_sidebar_upgrade[] = $item;
}
}
if ( empty( $new_sidebar_upgrade ) ) {
unset( $home_screen_data['sidebar_upgrade'] );
return $home_screen_data;
}
$home_screen_data['sidebar_upgrade'] = reset( $new_sidebar_upgrade );
return $home_screen_data;
}
}

View File

@@ -0,0 +1,364 @@
<?php
namespace Elementor\Modules\ImageLoadingOptimization;
use Elementor\Core\Base\Module as BaseModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends BaseModule {
/**
* @var int Minimum square-pixels threshold.
*/
private $min_priority_img_pixels = 50000;
/**
* @var int The number of content media elements to not lazy-load.
*/
private $omit_threshold = 3;
/**
* @var array Keep a track of images for which loading optimization strategy were computed.
*/
private static $image_visited = [];
/**
* Get Module name.
*/
public function get_name() {
return 'image-loading-optimization';
}
/**
* Constructor.
*/
public function __construct() {
if ( ! static::is_optimized_image_loading_enabled() ) {
return;
}
parent::__construct();
// Stop wp core logic.
add_action( 'init', [ $this, 'stop_core_fetchpriority_high_logic' ] );
add_filter( 'wp_lazy_loading_enabled', '__return_false' );
// Run optimization logic on header.
add_action( 'get_header', [ $this, 'set_buffer' ] );
// Ensure buffer is flushed (if any) before the content logic.
add_filter( 'the_content', [ $this, 'flush_header_buffer' ], 0 );
// Run optimization logic on content.
add_filter( 'wp_content_img_tag', [ $this, 'loading_optimization_image' ] );
}
/**
* Check whether the "Optimized Image Loading" settings is enabled.
*
* The 'optimized_image_loading' option can be enabled/disabled from the Elementor settings.
*
* @since 3.21.0
* @access private
*/
private static function is_optimized_image_loading_enabled(): bool {
return '1' === get_option( 'elementor_optimized_image_loading', '1' );
}
/**
* Stop WordPress core fetchpriority logic by setting the wp_high_priority_element_flag flag to false.
*/
public function stop_core_fetchpriority_high_logic() {
// wp_high_priority_element_flag was only introduced in 6.3.0
if ( function_exists( 'wp_high_priority_element_flag' ) ) {
wp_high_priority_element_flag( false );
}
}
/**
* Set buffer to handle header and footer content.
*/
public function set_buffer() {
ob_start( [ $this, 'handle_buffer_content' ] );
}
/**
* This function ensure that buffer if any is flushed before the content is called.
* This function behaves more like an action than a filter.
*
* @param string $content the content.
* @return string We simply return the content from parameter.
*/
public function flush_header_buffer( $content ) {
$buffer_status = ob_get_status();
if ( ! empty( $buffer_status ) &&
1 === $buffer_status['type'] &&
get_class( $this ) . '::handle_buffer_content' === $buffer_status['name'] ) {
ob_end_flush();
}
return $content;
}
/**
* Callback to handle image optimization logic on buffered content.
*
* @param string $buffer Buffered content.
* @return string Content with optimized images.
*/
public function handle_buffer_content( $buffer ) {
return $this->filter_images( $buffer );
}
/**
* Check for image in the content provided and apply optimization logic on them.
*
* @param string $content Content to be analyzed.
* @return string Content with optimized images.
*/
private function filter_images( $content ) {
return preg_replace_callback(
'/<img\s[^>]+>/',
function ( $matches ) {
return $this->loading_optimization_image( $matches[0] );
},
$content
);
}
/**
* Apply loading optimization logic on the image.
*
* @param mixed $image Original image tag.
* @return string Optimized image.
*/
public function loading_optimization_image( $image ) {
if ( isset( self::$image_visited[ $image ] ) ) {
return self::$image_visited[ $image ];
}
$optimized_image = $this->add_loading_optimization_attrs( $image );
self::$image_visited[ $image ] = $optimized_image;
return $optimized_image;
}
/**
* Adds optimization attributes to an `img` HTML tag.
*
* @param string $image The HTML `img` tag where the attribute should be added.
* @return string Converted `img` tag with optimization attributes added.
*/
private function add_loading_optimization_attrs( $image ) {
$width = preg_match( '/ width=["\']([0-9]+)["\']/', $image, $match_width ) ? (int) $match_width[1] : null;
$height = preg_match( '/ height=["\']([0-9]+)["\']/', $image, $match_height ) ? (int) $match_height[1] : null;
$loading_val = preg_match( '/ loading=["\']([A-Za-z]+)["\']/', $image, $match_loading ) ? $match_loading[1] : null;
$fetchpriority_val = preg_match( '/ fetchpriority=["\']([A-Za-z]+)["\']/', $image, $match_fetchpriority ) ? $match_fetchpriority[1] : null;
// Images should have height and dimension width for the loading optimization attributes to be added.
if ( ! str_contains( $image, ' width="' ) || ! str_contains( $image, ' height="' ) ) {
return $image;
}
$optimization_attrs = $this->get_loading_optimization_attributes(
[
'width' => $width,
'height' => $height,
'loading' => $loading_val,
'fetchpriority' => $fetchpriority_val,
]
);
if ( ! empty( $optimization_attrs['fetchpriority'] ) ) {
$image = str_replace( '<img', '<img fetchpriority="' . esc_attr( $optimization_attrs['fetchpriority'] ) . '"', $image );
}
if ( ! empty( $optimization_attrs['loading'] ) ) {
$image = str_replace( '<img', '<img loading="' . esc_attr( $optimization_attrs['loading'] ) . '"', $image );
}
return $image;
}
/**
* Return loading Loading optimization attributes for a image with give attribute.
*
* @param array $attr Existing image attributes.
* @return array Loading optimization attributes.
*/
private function get_loading_optimization_attributes( $attr ) {
$loading_attrs = [];
// For any resources, width and height must be provided, to avoid layout shifts.
if ( ! isset( $attr['width'], $attr['height'] ) ) {
return $loading_attrs;
}
/*
* The key function logic starts here.
*/
$maybe_in_viewport = null;
$increase_count = false;
$maybe_increase_count = false;
/*
* Logic to handle a `loading` attribute that is already provided.
*
* Copied from `wp_get_loading_optimization_attributes()`.
*/
if ( isset( $attr['loading'] ) ) {
/*
* Interpret "lazy" as not in viewport. Any other value can be
* interpreted as in viewport (realistically only "eager" or `false`
* to force-omit the attribute are other potential values).
*/
if ( 'lazy' === $attr['loading'] ) {
$maybe_in_viewport = false;
} else {
$maybe_in_viewport = true;
}
}
// Logic to handle a `fetchpriority` attribute that is already provided.
$has_fetchpriority_high_attr = ( isset( $attr['fetchpriority'] ) && 'high' === $attr['fetchpriority'] );
/*
* Handle cases where a `fetchpriority="high"` has already been set.
*
* Copied from `wp_get_loading_optimization_attributes()`.
*/
if ( $has_fetchpriority_high_attr ) {
/*
* If the image was already determined to not be in the viewport (e.g.
* from an already provided `loading` attribute), trigger a warning.
* Otherwise, the value can be interpreted as in viewport, since only
* the most important in-viewport image should have `fetchpriority` set
* to "high".
*/
if ( false === $maybe_in_viewport ) {
_doing_it_wrong(
__FUNCTION__,
esc_html__( 'An image should not be lazy-loaded and marked as high priority at the same time.', 'elementor' ),
''
);
/*
* Set `fetchpriority` here for backward-compatibility as we should
* not override what a developer decided, even though it seems
* incorrect.
*/
$loading_attrs['fetchpriority'] = 'high';
} else {
$maybe_in_viewport = true;
}
}
if ( null === $maybe_in_viewport && ! is_admin() ) {
$content_media_count = $this->increase_content_media_count( 0 );
$increase_count = true;
if ( $content_media_count < $this->omit_threshold ) {
$maybe_in_viewport = true;
} else {
$maybe_in_viewport = false;
}
}
if ( $maybe_in_viewport ) {
$loading_attrs = $this->maybe_add_fetchpriority_high_attr( $loading_attrs, $attr );
} else {
$loading_attrs['loading'] = 'lazy';
}
if ( $increase_count ) {
$this->increase_content_media_count();
} elseif ( $maybe_increase_count ) {
if ( $this->get_min_priority_img_pixels() <= $attr['width'] * $attr['height'] ) {
$this->increase_content_media_count();
}
}
return $loading_attrs;
}
/**
* Helper to get the minimum threshold for number of pixels an image needs to have to be considered "priority".
*
* @return int The minimum number of pixels (width * height). Default is 50000.
*/
private function get_min_priority_img_pixels() {
/**
* Filter the minimum pixel threshold used to determine if an image should have fetchpriority="high" applied.
*
* @see https://developer.wordpress.org/reference/hooks/wp_min_priority_img_pixels/
*
* @param int $pixels The minimum number of pixels (with * height).
* @return int The filtered value.
*/
return apply_filters( 'elementor/image-loading-optimization/min_priority_img_pixels', $this->min_priority_img_pixels );
}
/**
* Keeps a count of media image.
*
* @param int $amount Amount by which count must be increased.
* @return int current image count.
*/
private function increase_content_media_count( $amount = 1 ) {
static $content_media_count = 0;
$content_media_count += $amount;
return $content_media_count;
}
/**
* Determines whether to add `fetchpriority='high'` to loading attributes.
*
* @param array $loading_attrs Array of the loading optimization attributes for the element.
* @param array $attr Array of the attributes for the element.
* @return array Updated loading optimization attributes for the element.
*/
private function maybe_add_fetchpriority_high_attr( $loading_attrs, $attr ) {
if ( isset( $attr['fetchpriority'] ) ) {
if ( 'high' === $attr['fetchpriority'] ) {
$loading_attrs['fetchpriority'] = 'high';
$this->high_priority_element_flag( false );
}
return $loading_attrs;
}
// Lazy-loading and `fetchpriority="high"` are mutually exclusive.
if ( isset( $loading_attrs['loading'] ) && 'lazy' === $loading_attrs['loading'] ) {
return $loading_attrs;
}
if ( ! $this->high_priority_element_flag() ) {
return $loading_attrs;
}
if ( $this->get_min_priority_img_pixels() <= $attr['width'] * $attr['height'] ) {
$loading_attrs['fetchpriority'] = 'high';
$this->high_priority_element_flag( false );
}
return $loading_attrs;
}
/**
* Accesses a flag that indicates if an element is a possible candidate for `fetchpriority='high'`.
*
* @param bool $value Optional. Used to change the static variable. Default null.
* @return bool Returns true if high-priority element was marked already, otherwise false.
*/
private function high_priority_element_flag( $value = null ) {
static $high_priority_element = true;
if ( is_bool( $value ) ) {
$high_priority_element = $value;
}
return $high_priority_element;
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Elementor\Modules\KitElementsDefaults\Data;
use Elementor\Modules\KitElementsDefaults\Module;
use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer;
use Elementor\Plugin;
use Elementor\Data\V2\Base\Exceptions\Error_404;
use Elementor\Data\V2\Base\Controller as Base_Controller;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Controller extends Base_Controller {
public function get_name() {
return 'kit-elements-defaults';
}
public function register_endpoints() {
$this->index_endpoint->register_item_route(\WP_REST_Server::EDITABLE, [
'id_arg_name' => 'type',
'id_arg_type_regex' => '[\w\-\_]+',
'type' => [
'type' => 'string',
'description' => 'The type of the element.',
'required' => true,
'validate_callback' => function( $type ) {
return $this->validate_type( $type );
},
],
'settings' => [
'description' => 'All the default values for the requested type',
'required' => true,
'type' => 'object',
'validate_callback' => function( $settings ) {
return is_array( $settings );
},
'sanitize_callback' => function( $settings, \WP_REST_Request $request ) {
$sanitizer = new Settings_Sanitizer(
Plugin::$instance->elements_manager,
array_keys( Plugin::$instance->widgets_manager->get_widget_types() )
);
return $sanitizer
->for( $request->get_param( 'type' ) )
->using( $settings )
->remove_invalid_settings()
->kses_deep()
->get();
},
],
] );
$this->index_endpoint->register_item_route(\WP_REST_Server::DELETABLE, [
'id_arg_name' => 'type',
'id_arg_type_regex' => '[\w\-\_]+',
'type' => [
'type' => 'string',
'description' => 'The type of the element.',
'required' => true,
'validate_callback' => function( $type ) {
return $this->validate_type( $type );
},
],
] );
}
public function get_collection_params() {
return [];
}
public function get_items( $request ) {
$this->validate_kit();
$kit = Plugin::$instance->kits_manager->get_active_kit();
return (object) $kit->get_json_meta( Module::META_KEY );
}
public function update_item( $request ) {
$this->validate_kit();
$kit = Plugin::$instance->kits_manager->get_active_kit();
$data = $kit->get_json_meta( Module::META_KEY );
$data[ $request->get_param( 'type' ) ] = $request->get_param( 'settings' );
$kit->update_json_meta( Module::META_KEY, $data );
return (object) [];
}
public function delete_item( $request ) {
$this->validate_kit();
$kit = Plugin::$instance->kits_manager->get_active_kit();
$data = $kit->get_json_meta( Module::META_KEY );
unset( $data[ $request->get_param( 'type' ) ] );
$kit->update_json_meta( Module::META_KEY, $data );
return (object) [];
}
private function validate_kit() {
$kit = Plugin::$instance->kits_manager->get_active_kit();
$is_valid_kit = $kit && $kit->get_main_id();
if ( ! $is_valid_kit ) {
throw new Error_404( 'Kit doesn\'t exist.' );
}
}
private function validate_type( $param ) {
$element_types = array_keys( Plugin::$instance->elements_manager->get_element_types() );
$widget_types = array_keys( Plugin::$instance->widgets_manager->get_widget_types() );
return in_array(
$param,
array_merge( $element_types, $widget_types ),
true
);
}
public function get_items_permissions_check( $request ) {
return current_user_can( 'edit_posts' );
}
// TODO: Should be removed once the infra will support it.
public function get_item_permissions_check( $request ) {
return $this->get_items_permissions_check( $request );
}
public function update_item_permissions_check( $request ) {
return current_user_can( 'manage_options' );
}
public function delete_item_permissions_check( $request ) {
return current_user_can( 'manage_options' );
}
}

View File

@@ -0,0 +1,27 @@
<?php
namespace Elementor\Modules\KitElementsDefaults\ImportExport;
use Elementor\App\Modules\ImportExport\Processes\Export;
use Elementor\App\Modules\ImportExport\Processes\Import;
use Elementor\Modules\KitElementsDefaults\ImportExport\Runners\Export as Export_Runner;
use Elementor\Modules\KitElementsDefaults\ImportExport\Runners\Import as Import_Runner;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Import_Export {
const FILE_NAME = 'kit-elements-defaults';
public function register() {
// Revert kit is working by default, using the site-settings runner.
add_action( 'elementor/import-export/export-kit', function ( Export $export ) {
$export->register( new Export_Runner() );
} );
add_action( 'elementor/import-export/import-kit', function ( Import $import ) {
$import->register( new Import_Runner() );
} );
}
}

View File

@@ -0,0 +1,71 @@
<?php
namespace Elementor\Modules\KitElementsDefaults\ImportExport\Runners;
use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export;
use Elementor\Plugin;
use Elementor\Core\Utils\Collection;
use Elementor\Modules\KitElementsDefaults\Module;
use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer;
use Elementor\App\Modules\ImportExport\Runners\Export\Export_Runner_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Export extends Export_Runner_Base {
public static function get_name() : string {
return 'elements-default-values';
}
public function should_export( array $data ) {
// Together with site-settings.
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true )
);
}
public function export( array $data ) {
$kit = Plugin::$instance->kits_manager->get_active_kit();
if ( ! $kit ) {
return [
'manifest' => [],
'files' => [],
];
}
$default_values = $kit->get_json_meta( Module::META_KEY );
if ( ! $default_values ) {
return [
'manifest' => [],
'files' => [],
];
}
$sanitizer = new Settings_Sanitizer(
Plugin::$instance->elements_manager,
array_keys( Plugin::$instance->widgets_manager->get_widget_types() )
);
$default_values = ( new Collection( $default_values ) )
->map( function ( $settings, $type ) use ( $sanitizer, $kit ) {
return $sanitizer
->for( $type )
->using( $settings )
->remove_invalid_settings()
->kses_deep()
->prepare_for_export( $kit )
->get();
} )
->all();
return [
'files' => [
'path' => Import_Export::FILE_NAME,
'data' => $default_values,
],
];
}
}

View File

@@ -0,0 +1,69 @@
<?php
namespace Elementor\Modules\KitElementsDefaults\ImportExport\Runners;
use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export;
use Elementor\Plugin;
use Elementor\Core\Utils\Collection;
use Elementor\Modules\KitElementsDefaults\Module;
use Elementor\App\Modules\ImportExport\Utils as ImportExportUtils;
use Elementor\Modules\KitElementsDefaults\Utils\Settings_Sanitizer;
use Elementor\App\Modules\ImportExport\Runners\Import\Import_Runner_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Import extends Import_Runner_Base {
public static function get_name() : string {
return 'elements-default-values';
}
public function should_import( array $data ) {
// Together with site-settings.
return (
isset( $data['include'] ) &&
in_array( 'settings', $data['include'], true ) &&
! empty( $data['site_settings']['settings'] ) &&
! empty( $data['extracted_directory_path'] )
);
}
public function import( array $data, array $imported_data ) {
$kit = Plugin::$instance->kits_manager->get_active_kit();
$file_name = Import_Export::FILE_NAME;
$default_values = ImportExportUtils::read_json_file( "{$data['extracted_directory_path']}/{$file_name}.json" );
if ( ! $kit || ! $default_values ) {
return [];
}
$element_types = array_keys( Plugin::$instance->elements_manager->get_element_types() );
$widget_types = array_keys( Plugin::$instance->widgets_manager->get_widget_types() );
$types = array_merge( $element_types, $widget_types );
$sanitizer = new Settings_Sanitizer(
Plugin::$instance->elements_manager,
$widget_types
);
$default_values = ( new Collection( $default_values ) )
->filter( function ( $settings, $type ) use ( $types ) {
return in_array( $type, $types, true );
} )
->map( function ( $settings, $type ) use ( $sanitizer, $kit ) {
return $sanitizer
->for( $type )
->using( $settings )
->remove_invalid_settings()
->kses_deep()
->prepare_for_import( $kit )
->get();
} )
->all();
$kit->update_json_meta( Module::META_KEY, $default_values );
return $default_values;
}
}

View File

@@ -0,0 +1,54 @@
<?php
namespace Elementor\Modules\KitElementsDefaults;
use Elementor\Core\Experiments\Manager as Experiments_Manager;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Modules\KitElementsDefaults\Data\Controller;
use Elementor\Plugin;
use Elementor\Modules\KitElementsDefaults\ImportExport\Import_Export;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends BaseModule {
const META_KEY = '_elementor_elements_default_values';
public function get_name() {
return 'kit-elements-defaults';
}
private function enqueue_scripts() {
wp_enqueue_script(
'elementor-kit-elements-defaults-editor',
$this->get_js_assets_url( 'kit-elements-defaults-editor' ),
[
'elementor-common',
'elementor-editor-modules',
'elementor-editor-document',
'wp-i18n',
],
ELEMENTOR_VERSION,
true
);
wp_set_script_translations( 'elementor-kit-elements-defaults-editor', 'elementor' );
}
public function __construct() {
parent::__construct();
add_action( 'elementor/editor/before_enqueue_scripts', function () {
$this->enqueue_scripts();
} );
Plugin::$instance->data_manager_v2->register_controller( new Controller() );
( new Usage() )->register();
if ( is_admin() ) {
( new Import_Export() )->register();
}
}
}

View File

@@ -0,0 +1,34 @@
<?php
namespace Elementor\Modules\KitElementsDefaults;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Usage {
public function register() {
add_filter( 'elementor/tracker/send_tracking_data_params', function ( array $params ) {
$params['usages']['kit']['defaults'] = $this->get_usage_data();
return $params;
} );
}
private function get_usage_data() {
$elements_defaults = $this->get_elements_defaults() ?? [];
return [
'count' => count( $elements_defaults ),
'elements' => array_keys( $elements_defaults ),
];
}
private function get_elements_defaults() {
$kit = Plugin::$instance->kits_manager->get_active_kit();
return $kit->get_json_meta( Module::META_KEY );
}
}

View File

@@ -0,0 +1,280 @@
<?php
namespace Elementor\Modules\KitElementsDefaults\Utils;
use Elementor\Core\Breakpoints\Manager as Breakpoints_Manager;
use Elementor\Element_Base;
use Elementor\Elements_Manager;
use Elementor\Core\Base\Document;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Settings_Sanitizer {
const SPECIAL_SETTINGS = [
'__dynamic__',
'__globals__',
];
/**
* @var Elements_Manager
*/
private $elements_manager;
/**
* @var array
*/
private $widget_types;
/**
* @var Element_Base | null
*/
private $pending_element = null;
/**
* @var array | null
*/
private $pending_settings = null;
/**
* @param Elements_Manager $elements_manager
* @param array $widget_types
*/
public function __construct( Elements_Manager $elements_manager, array $widget_types = [] ) {
$this->elements_manager = $elements_manager;
$this->widget_types = $widget_types;
}
/**
* @param $type
*
* @return $this
*/
public function for( $type ) {
$this->pending_element = $this->create_element( $type );
return $this;
}
/**
* @param $settings
*
* @return $this
*/
public function using( $settings ) {
$this->pending_settings = $settings;
return $this;
}
/**
* @return $this
*/
public function reset() {
$this->pending_element = null;
$this->pending_settings = null;
return $this;
}
/**
* @return bool
*/
public function is_prepared() {
return $this->pending_element && is_array( $this->pending_settings );
}
/**
* @return $this
*/
public function remove_invalid_settings() {
if ( ! $this->is_prepared() ) {
return $this;
}
$valid_settings_keys = $this->get_valid_settings_keys(
$this->pending_element->get_controls()
);
$this->pending_settings = $this->filter_invalid_settings(
$this->pending_settings,
array_merge( $valid_settings_keys, self::SPECIAL_SETTINGS )
);
foreach ( self::SPECIAL_SETTINGS as $special_setting ) {
if ( ! isset( $this->pending_settings[ $special_setting ] ) ) {
continue;
}
$this->pending_settings[ $special_setting ] = $this->filter_invalid_settings(
$this->pending_settings[ $special_setting ],
$valid_settings_keys
);
}
return $this;
}
public function kses_deep() {
if ( ! $this->is_prepared() ) {
return $this;
}
$this->pending_settings = map_deep( $this->pending_settings, function( $value ) {
if ( ! is_string( $value ) ) {
return $value;
}
return wp_kses_post( $value );
} );
return $this;
}
/**
* @param Document $document
*
* @return $this
*/
public function prepare_for_export( Document $document ) {
return $this->run_import_export_sanitize_process( $document, 'on_export' );
}
/**
* @param Document $document
*
* @return $this
*/
public function prepare_for_import( Document $document ) {
return $this->run_import_export_sanitize_process( $document, 'on_import' );
}
/**
* @return array
*/
public function get() {
if ( ! $this->is_prepared() ) {
return [];
}
$settings = $this->pending_settings;
$this->reset();
return $settings;
}
/**
* @param string $type
* @param array $settings
*
* @return Element_Base|null
*/
private function create_element( $type ) {
$is_widget = in_array( $type, $this->widget_types, true );
$is_inner_section = 'inner-section' === $type;
if ( $is_inner_section ) {
return $this->elements_manager->create_element_instance( [
'elType' => 'section',
'isInner' => true,
'id' => '0',
] );
}
if ( $is_widget ) {
return $this->elements_manager->create_element_instance( [
'elType' => 'widget',
'widgetType' => $type,
'id' => '0',
] );
}
return $this->elements_manager->create_element_instance( [
'elType' => $type,
'id' => '0',
] );
}
/**
* @param Document $document
* @param $process_type
*
* @return $this
*/
private function run_import_export_sanitize_process( Document $document, $process_type ) {
if ( ! $this->is_prepared() ) {
return $this;
}
$result = $document->process_element_import_export(
$this->pending_element,
$process_type,
[ 'settings' => $this->pending_settings ]
);
if ( empty( $result['settings'] ) ) {
return $this;
}
$this->pending_settings = $result['settings'];
return $this;
}
/**
* Get all the available settings of a specific element, including responsive settings.
*
* @param array $controls
*
* @return array
*/
private function get_valid_settings_keys( $controls ) {
if ( ! $controls ) {
return [];
}
$control_keys = array_keys( $controls );
$optional_responsive_keys = [
Breakpoints_Manager::BREAKPOINT_KEY_MOBILE,
Breakpoints_Manager::BREAKPOINT_KEY_MOBILE_EXTRA,
Breakpoints_Manager::BREAKPOINT_KEY_TABLET,
Breakpoints_Manager::BREAKPOINT_KEY_TABLET_EXTRA,
Breakpoints_Manager::BREAKPOINT_KEY_LAPTOP,
Breakpoints_Manager::BREAKPOINT_KEY_WIDESCREEN,
];
$settings = [];
foreach ( $control_keys as $control_key ) {
// Add the responsive settings.
foreach ( $optional_responsive_keys as $responsive_key ) {
$settings[] = "{$control_key}_{$responsive_key}";
}
// Add the setting itself (not responsive).
$settings[] = $control_key;
}
return $settings;
}
/**
* Remove invalid settings.
*
* @param $settings
* @param $valid_settings_keys
*
* @return array
*/
private function filter_invalid_settings( $settings, $valid_settings_keys ) {
return array_filter(
$settings,
function ( $setting_key ) use ( $valid_settings_keys ) {
return in_array( $setting_key, $valid_settings_keys, true );
},
ARRAY_FILTER_USE_KEY
);
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Elementor\Modules\LandingPages\AdminMenuItems;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Landing_Pages_Empty_View_Menu_Item extends Landing_Pages_Menu_Item implements Admin_Menu_Item_With_Page {
private $render_callback;
public function __construct( callable $render_callback ) {
$this->render_callback = $render_callback;
}
public function render() {
( $this->render_callback )();
}
}

View File

@@ -0,0 +1,32 @@
<?php
namespace Elementor\Modules\LandingPages\AdminMenuItems;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Landing_Pages_Menu_Item implements Admin_Menu_Item {
public function is_visible() {
return true;
}
public function get_parent_slug() {
return Source_Local::ADMIN_MENU_SLUG;
}
public function get_label() {
return esc_html__( 'Landing Pages', 'elementor' );
}
public function get_page_title() {
return esc_html__( 'Landing Pages', 'elementor' );
}
public function get_capability() {
return 'manage_options';
}
}

View File

@@ -0,0 +1,106 @@
<?php
namespace Elementor\Modules\LandingPages\Documents;
use Elementor\Core\DocumentTypes\PageBase;
use Elementor\Modules\LandingPages\Module as Landing_Pages_Module;
use Elementor\Modules\Library\Traits\Library;
use Elementor\Modules\PageTemplates\Module as Page_Templates_Module;
use Elementor\Plugin;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Landing_Page extends PageBase {
// Library Document Trait
use Library;
public static function get_properties() {
$properties = parent::get_properties();
$properties['support_kit'] = true;
$properties['show_in_library'] = true;
$properties['cpt'] = [ Landing_Pages_Module::CPT ];
return $properties;
}
public static function get_type() {
return Landing_Pages_Module::DOCUMENT_TYPE;
}
/**
* @access public
*/
public function get_name() {
return Landing_Pages_Module::DOCUMENT_TYPE;
}
/**
* @access public
* @static
*/
public static function get_title() {
return esc_html__( 'Landing Page', 'elementor' );
}
/**
* @access public
* @static
*/
public static function get_plural_title() {
return esc_html__( 'Landing Pages', 'elementor' );
}
public static function get_create_url() {
return parent::get_create_url() . '#library';
}
/**
* Save Document.
*
* Save an Elementor document.
*
* @since 3.1.0
* @access public
*
* @param $data
*
* @return bool
*/
public function save( $data ) {
// This is for the first time a Landing Page is created. It is done in order to load a new Landing Page with
// 'Canvas' as the default page template.
if ( empty( $data['settings']['template'] ) ) {
$data['settings']['template'] = Page_Templates_Module::TEMPLATE_CANVAS;
}
return parent::save( $data );
}
/**
* Admin Columns Content
*
* @since 3.1.0
*
* @param $column_name
* @access public
*/
public function admin_columns_content( $column_name ) {
if ( 'elementor_library_type' === $column_name ) {
$this->print_admin_column_type();
}
}
protected function get_remote_library_config() {
$config = [
'type' => 'lp',
'default_route' => 'templates/landing-pages',
'autoImportSettings' => true,
];
return array_replace_recursive( parent::get_remote_library_config(), $config );
}
}

View File

@@ -0,0 +1,510 @@
<?php
namespace Elementor\Modules\LandingPages;
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
use Elementor\Core\Admin\Menu\Main as MainMenu;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Documents_Manager;
use Elementor\Core\Experiments\Manager as Experiments_Manager;
use Elementor\Modules\LandingPages\Documents\Landing_Page;
use Elementor\Modules\LandingPages\AdminMenuItems\Landing_Pages_Menu_Item;
use Elementor\Modules\LandingPages\AdminMenuItems\Landing_Pages_Empty_View_Menu_Item;
use Elementor\Modules\LandingPages\Module as Landing_Pages_Module;
use Elementor\Plugin;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
const DOCUMENT_TYPE = 'landing-page';
const CPT = 'e-landing-page';
const ADMIN_PAGE_SLUG = 'edit.php?post_type=' . self::CPT;
private $has_pages = null;
private $trashed_posts;
private $new_lp_url;
private $permalink_structure;
public function get_name() {
return 'landing-pages';
}
/**
* Get Experimental Data
*
* Implementation of this method makes the module an experiment.
*
* @since 3.1.0
*
* @return array
*/
public static function get_experimental_data() {
return [
'name' => 'landing-pages',
'title' => esc_html__( 'Landing Pages', 'elementor' ),
'description' => esc_html__( 'Adds a new Elementor content type that allows creating beautiful landing pages instantly in a streamlined workflow.', 'elementor' ),
'release_status' => Experiments_Manager::RELEASE_STATUS_BETA,
'default' => Experiments_Manager::STATE_ACTIVE,
'new_site' => [
'default_active' => false,
'minimum_installation_version' => '3.1.0',
],
];
}
/**
* Get Trashed Landing Pages Posts
*
* Returns the posts property of a WP_Query run for Landing Pages with post_status of 'trash'.
*
* @since 3.1.0
*
* @return array trashed posts
*/
private function get_trashed_landing_page_posts() {
if ( $this->trashed_posts ) {
return $this->trashed_posts;
}
// `'posts_per_page' => 1` is because this is only used as an indicator to whether there are any trashed landing pages.
$trashed_posts_query = new \WP_Query( [
'no_found_rows' => true,
'post_type' => self::CPT,
'post_status' => 'trash',
'posts_per_page' => 1,
'meta_key' => '_elementor_template_type',
'meta_value' => self::DOCUMENT_TYPE,
] );
$this->trashed_posts = $trashed_posts_query->posts;
return $this->trashed_posts;
}
private function has_landing_pages() {
if ( null !== $this->has_pages ) {
return $this->has_pages;
}
$posts_query = new \WP_Query( [
'no_found_rows' => true,
'post_type' => self::CPT,
'post_status' => 'any',
'posts_per_page' => 1,
'meta_key' => '_elementor_template_type',
'meta_value' => self::DOCUMENT_TYPE,
] );
$this->has_pages = $posts_query->post_count > 0;
return $this->has_pages;
}
/**
* Is Elementor Landing Page.
*
* Check whether the post is an Elementor Landing Page.
*
* @since 3.1.0
* @access public
*
* @param \WP_Post $post Post Object
*
* @return bool Whether the post was built with Elementor.
*/
public function is_elementor_landing_page( $post ) {
return self::CPT === $post->post_type;
}
private function get_menu_args() {
if ( $this->has_landing_pages() ) {
$menu_slug = self::ADMIN_PAGE_SLUG;
$function = null;
} else {
$menu_slug = self::CPT;
$function = [ $this, 'print_empty_landing_pages_page' ];
}
return [
'menu_slug' => $menu_slug,
'function' => $function,
];
}
private function register_admin_menu( MainMenu $menu ) {
$landing_pages_title = esc_html__( 'Landing Pages', 'elementor' );
$menu_args = array_merge( $this->get_menu_args(), [
'page_title' => $landing_pages_title,
'menu_title' => $landing_pages_title,
'index' => 20,
] );
$menu->add_submenu( $menu_args );
}
/**
* Add Submenu Page
*
* Adds the 'Landing Pages' submenu item to the 'Templates' menu item.
*
* @since 3.1.0
*/
private function register_admin_menu_legacy( Admin_Menu_Manager $admin_menu ) {
$menu_args = $this->get_menu_args();
$slug = $menu_args['menu_slug'];
$function = $menu_args['function'];
if ( is_callable( $function ) ) {
$admin_menu->register( $slug, new Landing_Pages_Empty_View_Menu_Item( $function ) );
} else {
$admin_menu->register( $slug, new Landing_Pages_Menu_Item() );
}
}
/**
* Get 'Add New' Landing Page URL
*
* Retrieves the custom URL for the admin dashboard's 'Add New' button in the Landing Pages admin screen. This URL
* creates a new Landing Pages and directly opens the Elementor Editor with the Template Library modal open on the
* Landing Pages tab.
*
* @since 3.1.0
*
* @return string
*/
private function get_add_new_landing_page_url() {
if ( ! $this->new_lp_url ) {
$this->new_lp_url = Plugin::$instance->documents->get_create_new_post_url( self::CPT, self::DOCUMENT_TYPE ) . '#library';
}
return $this->new_lp_url;
}
/**
* Get Empty Landing Pages Page
*
* Prints the HTML content of the page that is displayed when there are no existing landing pages in the DB.
* Added as the callback to add_submenu_page.
*
* @since 3.1.0
*/
public function print_empty_landing_pages_page() {
$template_sources = Plugin::$instance->templates_manager->get_registered_sources();
$source_local = $template_sources['local'];
$trashed_posts = $this->get_trashed_landing_page_posts();
?>
<div class="e-landing-pages-empty">
<?php
/** @var Source_Local $source_local */
$source_local->print_blank_state_template( esc_html__( 'Landing Page', 'elementor' ), $this->get_add_new_landing_page_url(), esc_html__( 'Build Effective Landing Pages for your business\' marketing campaigns.', 'elementor' ) );
if ( ! empty( $trashed_posts ) ) : ?>
<div class="e-trashed-items">
<?php
printf(
/* translators: %1$s Link open tag, %2$s: Link close tag. */
esc_html__( 'Or view %1$sTrashed Items%1$s', 'elementor' ),
'<a href="' . esc_url( admin_url( 'edit.php?post_status=trash&post_type=' . self::CPT ) ) . '">',
'</a>'
);
?>
</div>
<?php endif; ?>
</div>
<?php
}
/**
* Is Current Admin Page Edit LP
*
* Checks whether the current page is a native WordPress edit page for a landing page.
*/
private function is_landing_page_admin_edit() {
$screen = get_current_screen();
if ( 'post' === $screen->base ) {
return $this->is_elementor_landing_page( get_post() );
}
return false;
}
/**
* Admin Localize Settings
*
* Enables adding properties to the globally available elementorAdmin.config JS object in the Admin Dashboard.
* Runs on the 'elementor/admin/localize_settings' filter.
*
* @since 3.1.0
*
* @param $settings
* @return array|null
*/
private function admin_localize_settings( $settings ) {
$additional_settings = [
'urls' => [
'addNewLandingPageUrl' => $this->get_add_new_landing_page_url(),
],
'landingPages' => [
'landingPagesHasPages' => $this->has_landing_pages(),
'isLandingPageAdminEdit' => $this->is_landing_page_admin_edit(),
],
];
return array_replace_recursive( $settings, $additional_settings );
}
/**
* Register Landing Pages CPT
*
* @since 3.1.0
*/
private function register_landing_page_cpt() {
$labels = [
'name' => esc_html__( 'Landing Pages', 'elementor' ),
'singular_name' => esc_html__( 'Landing Page', 'elementor' ),
'add_new' => esc_html__( 'Add New', 'elementor' ),
'add_new_item' => esc_html__( 'Add New Landing Page', 'elementor' ),
'edit_item' => esc_html__( 'Edit Landing Page', 'elementor' ),
'new_item' => esc_html__( 'New Landing Page', 'elementor' ),
'all_items' => esc_html__( 'All Landing Pages', 'elementor' ),
'view_item' => esc_html__( 'View Landing Page', 'elementor' ),
'search_items' => esc_html__( 'Search Landing Pages', 'elementor' ),
'not_found' => esc_html__( 'No landing pages found', 'elementor' ),
'not_found_in_trash' => esc_html__( 'No landing pages found in trash', 'elementor' ),
'parent_item_colon' => '',
'menu_name' => esc_html__( 'Landing Pages', 'elementor' ),
];
$args = [
'labels' => $labels,
'public' => true,
'show_in_menu' => 'edit.php?post_type=elementor_library&tabs_group=library',
'capability_type' => 'page',
'taxonomies' => [ Source_Local::TAXONOMY_TYPE_SLUG ],
'supports' => [ 'title', 'editor', 'comments', 'revisions', 'trackbacks', 'author', 'excerpt', 'page-attributes', 'thumbnail', 'custom-fields', 'post-formats', 'elementor' ],
];
register_post_type( self::CPT, $args );
}
/**
* Remove Post Type Slug
*
* Landing Pages are supposed to act exactly like pages. This includes their URLs being directly under the site's
* domain name. Since "Landing Pages" is a CPT, WordPress automatically adds the landing page slug as a prefix to
* it's posts' permalinks. This method checks if the post's post type is Landing Pages, and if it is, it removes
* the CPT slug from the requested post URL.
*
* Runs on the 'post_type_link' filter.
*
* @since 3.1.0
*
* @param $post_link
* @param $post
* @param $leavename
* @return string|string[]
*/
private function remove_post_type_slug( $post_link, $post, $leavename ) {
// Only try to modify the permalink if the post is a Landing Page.
if ( self::CPT !== $post->post_type || 'publish' !== $post->post_status ) {
return $post_link;
}
// Any slug prefixes need to be removed from the post link.
return get_home_url() . '/' . $post->post_name . '/';
}
/**
* Adjust Landing Page Query
*
* Since Landing Pages are a CPT but should act like pages, the WP_Query that is used to fetch the page from the
* database needs to be adjusted. This method adds the Landing Pages CPT to the list of queried post types, to
* make sure the database query finds the correct Landing Page to display.
* Runs on the 'pre_get_posts' action.
*
* @since 3.1.0
*
* @param \WP_Query $query
*/
private function adjust_landing_page_query( \WP_Query $query ) {
// Only handle actual pages.
if (
! $query->is_main_query()
// If the query is not for a page.
|| ! isset( $query->query['page'] )
// If the query is for a static home/blog page.
|| is_home()
// If the post type comes already set, the main query is probably a custom one made by another plugin.
// In this case we do not want to intervene in order to not cause a conflict.
|| isset( $query->query['post_type'] )
) {
return;
}
// Create the post types property as an array and include the landing pages CPT in it.
$query_post_types = [ 'post', 'page', self::CPT ];
// Since WordPress determined this is supposed to be a page, we'll pre-set the post_type query arg to make sure
// it includes the Landing Page CPT, so when the query is parsed, our CPT will be a legitimate match to the
// Landing Page's permalink (that is directly under the domain, without a CPT slug prefix). In some cases,
// The 'name' property will be set, and in others it is the 'pagename', so we have to cover both cases.
if ( ! empty( $query->query['name'] ) ) {
$query->set( 'post_type', $query_post_types );
} elseif ( ! empty( $query->query['pagename'] ) && false === strpos( $query->query['pagename'], '/' ) ) {
$query->set( 'post_type', $query_post_types );
// We also need to set the name query var since redirect_guess_404_permalink() relies on it.
add_filter( 'pre_redirect_guess_404_permalink', function( $value ) use ( $query ) {
set_query_var( 'name', $query->query['pagename'] );
return $value;
} );
}
}
/**
* Handle 404
*
* This method runs after a page is not found in the database, but before a page is returned as a 404.
* These cases are handled in this filter callback, that runs on the 'pre_handle_404' filter.
*
* In some cases (such as when a site uses custom permalink structures), WordPress's WP_Query does not identify a
* Landing Page's URL as a post belonging to the Landing Page CPT. Some cases are handled successfully by the
* adjust_landing_page_query() method, but some are not and still trigger a 404 process. This method handles such
* cases by overriding the $wp_query global to fetch the correct landing page post entry.
*
* For example, since Landing Pages slugs come directly after the site domain name, WP_Query might parse the post
* as a category page. Since there is no category matching the slug, it triggers a 404 process. In this case, we
* run a query for a Landing Page post with the passed slug ($query->query['category_name']. If a Landing Page
* with the passed slug is found, we override the global $wp_query with the new, correct query.
*
* @param $current_value
* @param $query
* @return false
*/
private function handle_404( $current_value, $query ) {
global $wp_query;
// If another plugin/theme already used this filter, exit here to avoid conflicts.
if ( $current_value ) {
return $current_value;
}
if (
// Make sure we only intervene in the main query.
! $query->is_main_query()
// If a post was found, this is not a 404 case, so do not intervene.
|| ! empty( $query->posts )
// This filter is only meant to deal with wrong queries where the only query var is 'category_name'.
// If there is no 'category_name' query var, do not intervene.
|| empty( $query->query['category_name'] )
// If the query is for a real taxonomy (determined by it including a table to search in, such as the
// wp_term_relationships table), do not intervene.
|| ! empty( $query->tax_query->table_aliases )
) {
return false;
}
// Search for a Landing Page with the same name passed as the 'category name'.
$possible_new_query = new \WP_Query( [
'no_found_rows' => true,
'post_type' => self::CPT,
'name' => $query->query['category_name'],
] );
// Only if such a Landing Page is found, override the query to fetch the correct page.
if ( ! empty( $possible_new_query->posts ) ) {
$wp_query = $possible_new_query; //phpcs:ignore WordPress.WP.GlobalVariablesOverride.Prohibited
}
return false;
}
public function __construct() {
$this->permalink_structure = get_option( 'permalink_structure' );
$this->register_landing_page_cpt();
// If there is a permalink structure set to the site, run the hooks that modify the Landing Pages permalinks to
// match WordPress' native 'Pages' post type.
if ( '' !== $this->permalink_structure ) {
// Landing Pages' post link needs to be modified to be identical to the pages permalink structure. This
// needs to happen in both the admin and the front end, since post links are also used in the admin pages.
add_filter( 'post_type_link', function( $post_link, $post, $leavename ) {
return $this->remove_post_type_slug( $post_link, $post, $leavename );
}, 10, 3 );
// The query itself only has to be manipulated when pages are viewed in the front end.
if ( ! is_admin() || wp_doing_ajax() ) {
add_action( 'pre_get_posts', function ( $query ) {
$this->adjust_landing_page_query( $query );
} );
// Handle cases where visiting a Landing Page's URL returns 404.
add_filter( 'pre_handle_404', function ( $value, $query ) {
return $this->handle_404( $value, $query );
}, 10, 2 );
}
}
add_action( 'elementor/documents/register', function( Documents_Manager $documents_manager ) {
$documents_manager->register_document_type( self::DOCUMENT_TYPE, Landing_Page::get_class_full_name() );
} );
if ( Plugin::$instance->experiments->is_feature_active( 'admin_menu_rearrangement' ) ) {
add_action( 'elementor/admin/menu_registered/elementor', function( MainMenu $menu ) {
$this->register_admin_menu( $menu );
} );
} else {
add_action( 'elementor/admin/menu/register', function( Admin_Menu_Manager $admin_menu ) {
$this->register_admin_menu_legacy( $admin_menu );
}, Source_Local::ADMIN_MENU_PRIORITY + 20 );
}
// Add the custom 'Add New' link for Landing Pages into Elementor's admin config.
add_action( 'elementor/admin/localize_settings', function( array $settings ) {
return $this->admin_localize_settings( $settings );
} );
add_filter( 'elementor/template_library/sources/local/register_taxonomy_cpts', function( array $cpts ) {
$cpts[] = self::CPT;
return $cpts;
} );
// In the Landing Pages Admin Table page - Overwrite Template type column header title.
add_action( 'manage_' . Landing_Pages_Module::CPT . '_posts_columns', function( $posts_columns ) {
/** @var Source_Local $source_local */
$source_local = Plugin::$instance->templates_manager->get_source( 'local' );
return $source_local->admin_columns_headers( $posts_columns );
} );
// In the Landing Pages Admin Table page - Overwrite Template type column row values.
add_action( 'manage_' . Landing_Pages_Module::CPT . '_posts_custom_column', function( $column_name, $post_id ) {
/** @var Landing_Page $document */
$document = Plugin::$instance->documents->get( $post_id );
$document->admin_columns_content( $column_name );
}, 10, 2 );
// Overwrite the Admin Bar's 'New +' Landing Page URL with the link that creates the new LP in Elementor
// with the Template Library modal open.
add_action( 'admin_bar_menu', function( $admin_bar ) {
// Get the Landing Page menu node.
$new_landing_page_node = $admin_bar->get_node( 'new-e-landing-page' );
if ( $new_landing_page_node ) {
$new_landing_page_node->href = $this->get_add_new_landing_page_url();
$admin_bar->add_node( $new_landing_page_node );
}
}, 100 );
}
}

View File

@@ -0,0 +1,107 @@
<?php
namespace Elementor\Modules\LazyLoad;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Experiments\Manager as Experiments_Manager;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends BaseModule {
const EXPERIMENT_NAME = 'e_lazyload';
public function get_name() {
return 'lazyload';
}
public static function get_experimental_data() {
return [
'name' => static::EXPERIMENT_NAME,
'title' => esc_html__( 'Lazy Load Background Images', 'elementor' ),
'tag' => esc_html__( 'Performance', 'elementor' ),
'description' => esc_html__( 'Lazy loading images that are not in the viewport improves initial page load performance and user experience. By activating this experiment all background images except the first one on your page will be lazy loaded to improve your LCP score', 'elementor' ),
'release_status' => Experiments_Manager::RELEASE_STATUS_BETA,
'default' => Experiments_Manager::STATE_INACTIVE,
'new_site' => [
'default_active' => true,
'minimum_installation_version' => '3.21.0',
],
'generator_tag' => true,
];
}
public function __construct() {
parent::__construct();
add_action( 'init', [ $this, 'init' ] );
}
public function init() {
add_action( 'wp_head', function() {
if ( ! $this->should_lazyload() ) {
return;
}
?>
<style>
.e-con.e-parent:nth-of-type(n+4):not(.e-lazyloaded):not(.e-no-lazyload),
.e-con.e-parent:nth-of-type(n+4):not(.e-lazyloaded):not(.e-no-lazyload) * {
background-image: none !important;
}
@media screen and (max-height: 1024px) {
.e-con.e-parent:nth-of-type(n+3):not(.e-lazyloaded):not(.e-no-lazyload),
.e-con.e-parent:nth-of-type(n+3):not(.e-lazyloaded):not(.e-no-lazyload) * {
background-image: none !important;
}
}
@media screen and (max-height: 640px) {
.e-con.e-parent:nth-of-type(n+2):not(.e-lazyloaded):not(.e-no-lazyload),
.e-con.e-parent:nth-of-type(n+2):not(.e-lazyloaded):not(.e-no-lazyload) * {
background-image: none !important;
}
}
</style>
<?php
} );
add_action( 'wp_footer', function() {
if ( ! $this->should_lazyload() ) {
return;
}
?>
<script type='text/javascript'>
const lazyloadRunObserver = () => {
const lazyloadBackgrounds = document.querySelectorAll( `.e-con.e-parent:not(.e-lazyloaded)` );
const lazyloadBackgroundObserver = new IntersectionObserver( ( entries ) => {
entries.forEach( ( entry ) => {
if ( entry.isIntersecting ) {
let lazyloadBackground = entry.target;
if( lazyloadBackground ) {
lazyloadBackground.classList.add( 'e-lazyloaded' );
}
lazyloadBackgroundObserver.unobserve( entry.target );
}
});
}, { rootMargin: '200px 0px 200px 0px' } );
lazyloadBackgrounds.forEach( ( lazyloadBackground ) => {
lazyloadBackgroundObserver.observe( lazyloadBackground );
} );
};
const events = [
'DOMContentLoaded',
'elementor/lazyload/observe',
];
events.forEach( ( event ) => {
document.addEventListener( event, lazyloadRunObserver );
} );
</script>
<?php
} );
}
private function should_lazyload() {
return ! is_admin() && ! Plugin::$instance->preview->is_preview_mode() && ! Plugin::$instance->editor->is_edit_mode();
}
}

View File

@@ -0,0 +1,65 @@
<?php
namespace Elementor\Modules\Library\Documents;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor container library document.
*
* Elementor container library document handler class is responsible for
* handling a document of a container type.
*
* @since 2.0.0
*/
class Container extends Library_Document {
public static function get_properties() {
$properties = parent::get_properties();
$properties['support_kit'] = true;
return $properties;
}
/**
* Get document name.
*
* Retrieve the document name.
*
* @since 2.0.0
* @access public
*
* @return string Document name.
*/
public function get_name() {
return 'container';
}
/**
* Get document title.
*
* Retrieve the document title.
*
* @since 2.0.0
* @access public
* @static
*
* @return string Document title.
*/
public static function get_title() {
return esc_html__( 'Container', 'elementor' );
}
/**
* Get Type
*
* Return the container document type.
*
* @return string
*/
public static function get_type() {
return 'container';
}
}

View File

@@ -0,0 +1,78 @@
<?php
namespace Elementor\Modules\Library\Documents;
use Elementor\Core\Base\Document;
use Elementor\Modules\Library\Traits\Library;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor library document.
*
* Elementor library document handler class is responsible for handling
* a document of the library type.
*
* @since 2.0.0
*/
abstract class Library_Document extends Document {
// Library Document Trait
use Library;
/**
* The taxonomy type slug for the library document.
*/
const TAXONOMY_TYPE_SLUG = 'elementor_library_type';
/**
* Get document properties.
*
* Retrieve the document properties.
*
* @since 2.0.0
* @access public
* @static
*
* @return array Document properties.
*/
public static function get_properties() {
$properties = parent::get_properties();
$properties['admin_tab_group'] = 'library';
$properties['show_in_library'] = true;
$properties['register_type'] = true;
$properties['cpt'] = [ Source_Local::CPT ];
return $properties;
}
/**
* Get initial config.
*
* Retrieve the current element initial configuration.
*
* Adds more configuration on top of the controls list and the tabs assigned
* to the control. This method also adds element name, type, icon and more.
*
* @since 2.9.0
* @access protected
*
* @return array The initial config.
*/
public function get_initial_config() {
$config = parent::get_initial_config();
$config['library'] = [
'save_as_same_type' => true,
];
return $config;
}
public function get_content( $with_css = false ) {
return do_shortcode( parent::get_content( $with_css ) );
}
}

View File

@@ -0,0 +1,73 @@
<?php
namespace Elementor\Modules\Library\Documents;
use Elementor\TemplateLibrary\Source_Local;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor section library document.
*
* Elementor section library document handler class is responsible for
* handling a document of a section type.
*
*/
class Not_Supported extends Library_Document {
/**
* Get document properties.
*
* Retrieve the document properties.
*
* @access public
* @static
*
* @return array Document properties.
*/
public static function get_properties() {
$properties = parent::get_properties();
$properties['admin_tab_group'] = '';
$properties['register_type'] = false;
$properties['is_editable'] = false;
$properties['show_in_library'] = false;
$properties['show_in_finder'] = false;
return $properties;
}
public static function get_type() {
return 'not-supported';
}
/**
* Get document title.
*
* Retrieve the document title.
*
* @access public
* @static
*
* @return string Document title.
*/
public static function get_title() {
return esc_html__( 'Not Supported', 'elementor' );
}
public function save_template_type() {
// Do nothing.
}
public function print_admin_column_type() {
Utils::print_unescaped_internal_string( self::get_title() );
}
public function filter_admin_row_actions( $actions ) {
unset( $actions['view'] );
return $actions;
}
}

View File

@@ -0,0 +1,96 @@
<?php
namespace Elementor\Modules\Library\Documents;
use Elementor\Core\DocumentTypes\Post;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor page library document.
*
* Elementor page library document handler class is responsible for
* handling a document of a page type.
*
* @since 2.0.0
*/
class Page extends Library_Document {
/**
* Get document properties.
*
* Retrieve the document properties.
*
* @since 2.0.0
* @access public
* @static
*
* @return array Document properties.
*/
public static function get_properties() {
$properties = parent::get_properties();
$properties['support_wp_page_templates'] = true;
$properties['support_kit'] = true;
$properties['show_in_finder'] = true;
return $properties;
}
public static function get_type() {
return 'page';
}
/**
* Get document title.
*
* Retrieve the document title.
*
* @since 2.0.0
* @access public
* @static
*
* @return string Document title.
*/
public static function get_title() {
return esc_html__( 'Page', 'elementor' );
}
public static function get_plural_title() {
return esc_html__( 'Pages', 'elementor' );
}
public static function get_add_new_title() {
return esc_html__( 'Add New Page Template', 'elementor' );
}
/**
* @since 2.1.3
* @access public
*/
public function get_css_wrapper_selector() {
return 'body.elementor-page-' . $this->get_main_id();
}
/**
* @since 3.1.0
* @access protected
*/
protected function register_controls() {
parent::register_controls();
Post::register_hide_title_control( $this );
Post::register_style_controls( $this );
}
protected function get_remote_library_config() {
$config = parent::get_remote_library_config();
$config['type'] = 'page';
$config['default_route'] = 'templates/pages';
return $config;
}
}

View File

@@ -0,0 +1,49 @@
<?php
namespace Elementor\Modules\Library\Documents;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor section library document.
*
* Elementor section library document handler class is responsible for
* handling a document of a section type.
*
* @since 2.0.0
*/
class Section extends Library_Document {
public static function get_properties() {
$properties = parent::get_properties();
$properties['support_kit'] = true;
$properties['show_in_finder'] = true;
return $properties;
}
public static function get_type() {
return 'section';
}
/**
* Get document title.
*
* Retrieve the document title.
*
* @since 2.0.0
* @access public
* @static
*
* @return string Document title.
*/
public static function get_title() {
return esc_html__( 'Section', 'elementor' );
}
public static function get_plural_title() {
return esc_html__( 'Sections', 'elementor' );
}
}

View File

@@ -0,0 +1,58 @@
<?php
namespace Elementor\Modules\Library;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Modules\Library\Documents;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Elementor library module.
*
* Elementor library module handler class is responsible for registering and
* managing Elementor library modules.
*
* @since 2.0.0
*/
class Module extends BaseModule {
/**
* Get module name.
*
* Retrieve the library module name.
*
* @since 2.0.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'library';
}
/**
* Library module constructor.
*
* Initializing Elementor library module.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
Plugin::$instance->documents
->register_document_type( 'not-supported', Documents\Not_Supported::get_class_full_name() )
->register_document_type( 'page', Documents\Page::get_class_full_name() )
->register_document_type( 'section', Documents\Section::get_class_full_name() );
$experiments_manager = Plugin::$instance->experiments;
// Register `Container` document type only if the experiment is active.
if ( $experiments_manager->is_feature_active( 'container' ) ) {
Plugin::$instance->documents
->register_document_type( 'container', Documents\Container::get_class_full_name() );
}
}
}

View File

@@ -0,0 +1,41 @@
<?php
namespace Elementor\Modules\Library\Traits;
use Elementor\TemplateLibrary\Source_Local;
/**
* Elementor Library Trait
*
* This trait is used by all Library Documents and Landing Pages.
*
* @since 3.1.0
*/
trait Library {
/**
* Print Admin Column Type
*
* Runs on WordPress' 'manage_{custom post type}_posts_custom_column' hook to modify each row's content.
*
* @since 3.1.0
* @access public
*/
public function print_admin_column_type() {
$admin_filter_url = admin_url( Source_Local::ADMIN_MENU_SLUG . '&elementor_library_type=' . $this->get_name() );
//PHPCS - Not a user input
printf( '<a href="%s">%s</a>', $admin_filter_url, $this->get_title() ); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
/**
* Save document type.
*
* Set new/updated document type.
*
* @since 3.1.0
* @access public
*/
public function save_template_type() {
parent::save_template_type();
wp_set_object_terms( $this->post->ID, $this->get_name(), Source_Local::TAXONOMY_TYPE_SLUG );
}
}

View File

@@ -0,0 +1,145 @@
<?php
namespace Elementor\Modules\Library;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class User_Favorites {
const USER_META_KEY = 'elementor_library_favorites';
/**
* @var int
*/
private $user_id;
/**
* @var array|null
*/
private $cache;
/**
* User_Favorites constructor.
*
* @param $user_id
*/
public function __construct( $user_id ) {
$this->user_id = $user_id;
}
/**
* @param null $vendor
* @param null $resource
* @param false $ignore_cache
*
* @return array
*/
public function get( $vendor = null, $resource = null, $ignore_cache = false ) {
if ( $ignore_cache || empty( $this->cache ) ) {
$this->cache = get_user_meta( $this->user_id, self::USER_META_KEY, true );
}
if ( ! $this->cache || ! is_array( $this->cache ) ) {
return [];
}
if ( $vendor && $resource ) {
$key = $this->get_key( $vendor, $resource );
return isset( $this->cache[ $key ] ) ? $this->cache[ $key ] : [];
}
return $this->cache;
}
/**
* @param $vendor
* @param $resource
* @param $id
*
* @return bool
*/
public function exists( $vendor, $resource, $id ) {
return in_array( $id, $this->get( $vendor, $resource ), true );
}
/**
* @param $vendor
* @param $resource
* @param array $value
*
* @return $this
* @throws \Exception
*/
public function save( $vendor, $resource, $value = [] ) {
$all_favorites = $this->get();
$all_favorites[ $this->get_key( $vendor, $resource ) ] = $value;
$result = update_user_meta( $this->user_id, self::USER_META_KEY, $all_favorites );
if ( false === $result ) {
throw new \Exception( 'Failed to save user favorites.' );
}
$this->cache = $all_favorites;
return $this;
}
/**
* @param $vendor
* @param $resource
* @param $id
*
* @return $this
* @throws \Exception
*/
public function add( $vendor, $resource, $id ) {
$favorites = $this->get( $vendor, $resource );
if ( in_array( $id, $favorites, true ) ) {
return $this;
}
$favorites[] = $id;
$this->save( $vendor, $resource, $favorites );
return $this;
}
/**
* @param $vendor
* @param $resource
* @param $id
*
* @return $this
* @throws \Exception
*/
public function remove( $vendor, $resource, $id ) {
$favorites = $this->get( $vendor, $resource );
if ( ! in_array( $id, $favorites, true ) ) {
return $this;
}
$favorites = array_filter( $favorites, function ( $item ) use ( $id ) {
return $item !== $id;
} );
$this->save( $vendor, $resource, $favorites );
return $this;
}
/**
* @param $vendor
* @param $resource
*
* @return string
*/
private function get_key( $vendor, $resource ) {
return "{$vendor}/{$resource}";
}
}

View File

@@ -0,0 +1,31 @@
<?php
namespace Elementor\Modules\NestedAccordion;
use Elementor\Plugin;
use Elementor\Core\Base\Module as BaseModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends BaseModule {
public static function is_active() {
return Plugin::$instance->experiments->is_feature_active( 'nested-elements' );
}
public function get_name() {
return 'nested-accordion';
}
public function __construct() {
parent::__construct();
add_action( 'elementor/editor/before_enqueue_scripts', function () {
wp_enqueue_script( $this->get_name(), $this->get_js_assets_url( $this->get_name() ), [
'nested-elements',
], ELEMENTOR_VERSION, true );
} );
}
}

View File

@@ -0,0 +1,139 @@
<?php
namespace Elementor\Modules\NestedElements\Base;
use Elementor\Plugin;
use Elementor\Widget_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
/**
* Used to create a new widget that can be nested inside other widgets.
*/
abstract class Widget_Nested_Base extends Widget_Base {
/**
* Get default children elements structure.
*
* @return array
*/
abstract protected function get_default_children_elements();
/**
* Get repeater title setting key name.
*
* @return string
*/
abstract protected function get_default_repeater_title_setting_key();
/**
* Get default children title for the navigator, using `%d` as index in the format.
*
* @note The title in this method is used to set the default title for each created child in nested element.
* for handling the children title for new created widget(s), use `get_default_children_elements()` method,
* eg:
* [
* 'elType' => 'container',
* 'settings' => [
* '_title' => __( 'Tab #1', 'elementor' ),
* ],
* ],
* @return string
*/
protected function get_default_children_title() {
return esc_html__( 'Item #%d', 'elementor' );
}
/**
* Get default children placeholder selector, Empty string, means will be added at the end view.
*
* @return string
*/
protected function get_default_children_placeholder_selector() {
return '';
}
protected function get_default_children_container_placeholder_selector() {
return '';
}
/**
* @inheritDoc
*
* To support nesting.
*/
protected function _get_default_child_type( array $element_data ) {
return Plugin::$instance->elements_manager->get_element_types( $element_data['elType'] );
}
/**
* @inheritDoc
*
* Adding new 'defaults' config for handling children elements.
*/
protected function get_initial_config() {
return array_merge( parent::get_initial_config(), [
'defaults' => [
'elements' => $this->get_default_children_elements(),
'elements_title' => $this->get_default_children_title(),
'elements_placeholder_selector' => $this->get_default_children_placeholder_selector(),
'child_container_placeholder_selector' => $this->get_default_children_container_placeholder_selector(),
'repeater_title_setting' => $this->get_default_repeater_title_setting_key(),
],
'support_nesting' => true,
] );
}
/**
* @inheritDoc
*
* Each element including its children elements.
*/
public function get_raw_data( $with_html_content = false ) {
$elements = [];
$data = $this->get_data();
$children = $this->get_children();
foreach ( $children as $child ) {
$child_raw_data = $child->get_raw_data( $with_html_content );
$elements[] = $child_raw_data;
}
return [
'id' => $this->get_id(),
'elType' => $data['elType'],
'widgetType' => $data['widgetType'],
'settings' => $data['settings'],
'elements' => $elements,
];
}
/**
* Print child, helper method to print the child element.
*
* @param int $index
*/
public function print_child( $index ) {
$children = $this->get_children();
if ( ! empty( $children[ $index ] ) ) {
$children[ $index ]->print_element();
}
}
protected function content_template_single_repeater_item() {}
public function print_template() {
parent::print_template();
if ( $this->get_initial_config()['support_improved_repeaters'] ?? false ) {
?>
<script type="text/html" id="tmpl-elementor-<?php echo esc_attr( $this->get_name() ); ?>-content-single">
<?php $this->content_template_single_repeater_item(); ?>
</script>
<?php
}
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Elementor\Modules\NestedElements\Controls;
use Elementor\Control_Repeater;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Changing the default repeater control behavior for custom item title defaults.
* For custom management of nested repeater controls.
*/
class Control_Nested_Repeater extends Control_Repeater {
const CONTROL_TYPE = 'nested-elements-repeater';
public function get_type() {
return static::CONTROL_TYPE;
}
}

View File

@@ -0,0 +1,52 @@
<?php
namespace Elementor\Modules\NestedElements;
use Elementor\Core\Experiments\Manager as Experiments_Manager;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends \Elementor\Core\Base\Module {
const EXPERIMENT_NAME = 'nested-elements';
public static function get_experimental_data() {
return [
'name' => self::EXPERIMENT_NAME,
'title' => esc_html__( 'Nested Elements', 'elementor' ),
'description' => sprintf(
'%1$s <a href="https://go.elementor.com/wp-dash-nested-elements/" target="_blank">%2$s</a>',
esc_html__( 'Create a rich user experience by layering widgets together inside "Nested" Tabs, etc. When turned on, well automatically enable new nested features. Your old widgets wont be affected.', 'elementor' ),
esc_html__( 'Learn more', 'elementor' )
),
'release_status' => Experiments_Manager::RELEASE_STATUS_BETA,
'default' => Experiments_Manager::STATE_INACTIVE,
'dependencies' => [
'container',
],
'new_site' => [
'default_active' => false,
'minimum_installation_version' => '3.10.0',
],
];
}
public function get_name() {
return 'nested-elements';
}
public function __construct() {
parent::__construct();
add_action( 'elementor/controls/register', function ( $controls_manager ) {
$controls_manager->register( new Controls\Control_Nested_Repeater() );
} );
add_action( 'elementor/editor/before_enqueue_scripts', function () {
wp_enqueue_script( $this->get_name(), $this->get_js_assets_url( $this->get_name() ), [
'elementor-common',
], ELEMENTOR_VERSION, true );
} );
}
}

View File

@@ -0,0 +1,30 @@
<?php
namespace Elementor\Modules\NestedTabs;
use Elementor\Plugin;
use Elementor\Modules\NestedElements\Module as NestedElementsModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends \Elementor\Core\Base\Module {
public static function is_active() {
return Plugin::$instance->experiments->is_feature_active( NestedElementsModule::EXPERIMENT_NAME );
}
public function get_name() {
return 'nested-tabs';
}
public function __construct() {
parent::__construct();
add_action( 'elementor/editor/before_enqueue_scripts', function () {
wp_enqueue_script( $this->get_name(), $this->get_js_assets_url( $this->get_name() ), [
'nested-elements',
], ELEMENTOR_VERSION, true );
} );
}
}

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,66 @@
<?php
namespace Elementor\Modules\Notes;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends BaseModule {
public function get_name() {
return 'notes';
}
/**
* Enqueue the module scripts.
*
* @return void
*/
public function enqueue_scripts() {
wp_enqueue_script(
'elementor-notes',
$this->get_js_assets_url( 'notes' ),
[ 'elementor-editor' ],
ELEMENTOR_VERSION,
true
);
wp_set_script_translations( 'elementor-notes', 'elementor' );
}
/**
* Enqueue the module styles.
*
* @return void
*/
public function enqueue_styles() {
wp_enqueue_style(
'elementor-notes',
$this->get_css_assets_url( 'modules/notes/editor' ),
[ 'elementor-editor' ],
ELEMENTOR_VERSION
);
}
/**
* @return bool
*/
public static function is_active() {
return ! Utils::has_pro();
}
/**
* Initialize the Notes module.
*
* @return void
*/
public function __construct() {
parent::__construct();
add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_scripts' ] );
add_action( 'elementor/editor/after_enqueue_styles', [ $this, 'enqueue_styles' ] );
}
}

View File

@@ -0,0 +1,179 @@
<?php
namespace Elementor\Modules\Notifications;
use Elementor\User;
class API {
const NOTIFICATIONS_URL = 'https://assets.elementor.com/notifications/v1/notifications.json';
public static function get_notifications_by_conditions( $force_request = false ) {
$notifications = static::get_notifications( $force_request );
$filtered_notifications = [];
foreach ( $notifications as $notification ) {
if ( empty( $notification['conditions'] ) ) {
$filtered_notifications = static::add_to_array( $filtered_notifications, $notification );
continue;
}
if ( ! static::check_conditions( $notification['conditions'] ) ) {
continue;
}
$filtered_notifications = static::add_to_array( $filtered_notifications, $notification );
}
return $filtered_notifications;
}
private static function get_notifications( $force_request = false ) {
$notifications = self::get_transient( '_elementor_notifications_data' );
if ( $force_request || false === $notifications ) {
$notifications = static::fetch_data();
static::set_transient( '_elementor_notifications_data', $notifications, '+1 hour' );
}
return $notifications;
}
private static function fetch_data() : array {
$response = wp_remote_get( self::NOTIFICATIONS_URL );
if ( is_wp_error( $response ) ) {
return [];
}
$data = json_decode( wp_remote_retrieve_body( $response ), true );
if ( empty( $data['notifications'] ) || ! is_array( $data['notifications'] ) ) {
return [];
}
return $data['notifications'];
}
private static function add_to_array( $filtered_notifications, $notification ) {
foreach ( $filtered_notifications as $filtered_notification ) {
if ( $filtered_notification['id'] === $notification['id'] ) {
return $filtered_notifications;
}
}
$filtered_notifications[] = $notification;
return $filtered_notifications;
}
private static function check_conditions( $groups ) {
foreach ( $groups as $group ) {
if ( static::check_group( $group ) ) {
return true;
}
}
return false;
}
private static function check_group( $group ) {
$is_or_relation = ! empty( $group['relation'] ) && 'OR' === $group['relation'];
unset( $group['relation'] );
$result = false;
foreach ( $group as $condition ) {
// Reset results for each condition.
$result = false;
switch ( $condition['type'] ) {
case 'wordpress': // phpcs:ignore WordPress.WP.CapitalPDangit.Misspelled
// include an unmodified $wp_version
include ABSPATH . WPINC . '/version.php';
$result = version_compare( $wp_version, $condition['version'], $condition['operator'] );
break;
case 'multisite':
$result = is_multisite() === $condition['multisite'];
break;
case 'language':
$in_array = in_array( get_locale(), $condition['languages'], true );
$result = 'in' === $condition['operator'] ? $in_array : ! $in_array;
break;
case 'plugin':
if ( ! function_exists( 'is_plugin_active' ) ) {
require_once( ABSPATH . 'wp-admin/includes/plugin.php' );
}
$is_plugin_active = is_plugin_active( $condition['plugin'] );
if ( empty( $condition['operator'] ) ) {
$condition['operator'] = '==';
}
$result = '==' === $condition['operator'] ? $is_plugin_active : ! $is_plugin_active;
break;
case 'theme':
$theme = wp_get_theme();
if ( wp_get_theme()->parent() ) {
$theme = wp_get_theme()->parent();
}
if ( $theme->get_template() === $condition['theme'] ) {
$version = $theme->version;
} else {
$version = '';
}
$result = version_compare( $version, $condition['version'], $condition['operator'] );
break;
case 'introduction_meta':
$result = User::get_introduction_meta( $condition['meta'] );
break;
default:
/**
* Filters the notification condition, whether to check the group or not.
*
* The dynamic portion of the hook name, `$condition['type']`, refers to the condition type.
*
* @since 3.19.0
*
* @param bool $result Whether to check the group.
* @param array $condition Notification condition.
*/
$result = apply_filters( "elementor/notifications/condition/{$condition['type']}", $result, $condition );
break;
}
if ( ( $is_or_relation && $result ) || ( ! $is_or_relation && ! $result ) ) {
return $result;
}
}
return $result;
}
private static function get_transient( $cache_key ) {
$cache = get_option( $cache_key );
if ( empty( $cache['timeout'] ) ) {
return false;
}
if ( current_time( 'timestamp' ) > $cache['timeout'] ) {
return false;
}
return json_decode( $cache['value'], true );
}
private static function set_transient( $cache_key, $value, $expiration = '+12 hours' ) {
$data = [
'timeout' => strtotime( $expiration, current_time( 'timestamp' ) ),
'value' => json_encode( $value ),
];
return update_option( $cache_key, $data, false );
}
}

View File

@@ -0,0 +1,97 @@
<?php
namespace Elementor\Modules\Notifications;
use Elementor\Core\Base\Module as BaseModule;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Module extends BaseModule {
public function get_name() {
return 'notification-center';
}
public function __construct() {
parent::__construct();
add_action( 'elementor/admin_top_bar/before_enqueue_scripts', function() {
if ( ! current_user_can( 'manage_options' ) ) {
return;
}
wp_enqueue_script(
'e-admin-notifications',
$this->get_js_assets_url( 'admin-notifications' ),
[
'elementor-v2-ui',
'elementor-v2-icons',
'elementor-v2-query',
'wp-i18n',
],
ELEMENTOR_VERSION,
true
);
wp_localize_script(
'e-admin-notifications',
'elementorNotifications',
$this->get_app_js_config()
);
}, 5 /* Before Elementor's admin enqueue scripts */ );
add_action( 'elementor/editor/v2/scripts/enqueue', [ $this, 'enqueue_editor_scripts' ] );
add_action( 'elementor/editor/after_enqueue_scripts', [ $this, 'enqueue_editor_scripts' ] );
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
}
public function enqueue_editor_scripts() {
$deps = [
'elementor-editor',
'elementor-v2-ui',
'elementor-v2-icons',
'elementor-v2-query',
'wp-i18n',
];
$is_editor_v2 = current_action() === 'elementor/editor/v2/scripts/enqueue';
if ( $is_editor_v2 ) {
$deps[] = 'elementor-v2-editor-app-bar';
}
wp_enqueue_script(
'e-editor-notifications',
$this->get_js_assets_url( 'editor-notifications' ),
$deps,
ELEMENTOR_VERSION,
true
);
wp_localize_script(
'e-editor-notifications',
'elementorNotifications',
$this->get_app_js_config()
);
}
private function get_app_js_config() : array {
return [
'is_unread' => Options::has_unread_notifications(),
];
}
public function register_ajax_actions( $ajax ) {
$ajax->register_ajax_action( 'notifications_get', [ $this, 'ajax_get_notifications' ] );
}
public function ajax_get_notifications() {
$notifications = API::get_notifications_by_conditions( true );
Options::mark_notification_read( $notifications );
return $notifications;
}
}

View File

@@ -0,0 +1,66 @@
<?php
namespace Elementor\Modules\Notifications;
class Options {
public static function has_unread_notifications() : bool {
$current_user = wp_get_current_user();
if ( ! $current_user ) {
return false;
}
$unread_notifications = get_transient( "elementor_unread_notifications_{$current_user->ID}" );
if ( false === $unread_notifications ) {
$notifications = API::get_notifications_by_conditions();
$notifications_ids = wp_list_pluck( $notifications, 'id' );
$unread_notifications = array_diff( $notifications_ids, static::get_notifications_dismissed() );
set_transient( "elementor_unread_notifications_{$current_user->ID}", $unread_notifications, HOUR_IN_SECONDS );
}
return ! empty( $unread_notifications );
}
public static function get_notifications_dismissed() {
$current_user = wp_get_current_user();
if ( ! $current_user ) {
return [];
}
$notifications_dismissed = get_user_meta( $current_user->ID, '_e_notifications_dismissed', true );
if ( ! is_array( $notifications_dismissed ) ) {
$notifications_dismissed = [];
}
return $notifications_dismissed;
}
public static function mark_notification_read( $notifications ) : bool {
$current_user = wp_get_current_user();
if ( ! $current_user ) {
return false;
}
$notifications_dismissed = static::get_notifications_dismissed();
foreach ( $notifications as $notification ) {
if ( ! in_array( $notification['id'], $notifications_dismissed, true ) ) {
$notifications_dismissed[] = $notification['id'];
}
}
$notifications_dismissed = array_unique( $notifications_dismissed );
update_user_meta( $current_user->ID, '_e_notifications_dismissed', $notifications_dismissed );
delete_transient( "elementor_unread_notifications_{$current_user->ID}" );
return true;
}
}

View File

@@ -0,0 +1,432 @@
<?php
namespace Elementor\Modules\PageTemplates;
use Elementor\Controls_Manager;
use Elementor\Core\Base\Document;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Core\Kits\Documents\Kit;
use Elementor\Plugin;
use Elementor\Utils;
use Elementor\Core\DocumentTypes\PageBase as PageBase;
use Elementor\Modules\Library\Documents\Page as LibraryPageDocument;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
/**
* Elementor page templates module.
*
* Elementor page templates module handler class is responsible for registering
* and managing Elementor page templates modules.
*
* @since 2.0.0
*/
class Module extends BaseModule {
/**
* The of the theme.
*/
const TEMPLATE_THEME = 'elementor_theme';
/**
* Elementor Canvas template name.
*/
const TEMPLATE_CANVAS = 'elementor_canvas';
/**
* Elementor Header & Footer template name.
*/
const TEMPLATE_HEADER_FOOTER = 'elementor_header_footer';
/**
* Print callback.
*
* Holds the page template callback content.
*
* @since 2.0.0
* @access protected
*
* @var callable
*/
protected $print_callback;
/**
* Get module name.
*
* Retrieve the page templates module name.
*
* @since 2.0.0
* @access public
*
* @return string Module name.
*/
public function get_name() {
return 'page-templates';
}
/**
* Template include.
*
* Update the path for the Elementor Canvas template.
*
* Fired by `template_include` filter.
*
* @since 2.0.0
* @access public
*
* @param string $template The path of the template to include.
*
* @return string The path of the template to include.
*/
public function template_include( $template ) {
if ( is_singular() ) {
$document = Plugin::$instance->documents->get_doc_for_frontend( get_the_ID() );
if ( $document && $document::get_property( 'support_wp_page_templates' ) ) {
$page_template = $document->get_meta( '_wp_page_template' );
$template_path = $this->get_template_path( $page_template );
if ( self::TEMPLATE_THEME !== $page_template && ! $template_path && $document->is_built_with_elementor() ) {
$kit_default_template = Plugin::$instance->kits_manager->get_current_settings( 'default_page_template' );
$template_path = $this->get_template_path( $kit_default_template );
}
if ( $template_path ) {
$template = $template_path;
Plugin::$instance->inspector->add_log( 'Page Template', Plugin::$instance->inspector->parse_template_path( $template ), $document->get_edit_url() );
}
}
}
return $template;
}
/**
* Add WordPress templates.
*
* Adds Elementor templates to all the post types that support
* Elementor.
*
* Fired by `init` action.
*
* @since 2.0.0
* @access public
*/
public function add_wp_templates_support() {
$post_types = get_post_types_by_support( 'elementor' );
foreach ( $post_types as $post_type ) {
add_filter( "theme_{$post_type}_templates", [ $this, 'add_page_templates' ], 10, 4 );
}
}
/**
* Add page templates.
*
* Add the Elementor page templates to the theme templates.
*
* Fired by `theme_{$post_type}_templates` filter.
*
* @since 2.0.0
* @access public
* @static
*
* @param array $page_templates Array of page templates. Keys are filenames,
* checks are translated names.
*
* @param \WP_Theme $wp_theme
* @param \WP_Post $post
*
* @return array Page templates.
*/
public function add_page_templates( $page_templates, $wp_theme, $post ) {
if ( $post ) {
// FIX ME: Gutenberg not send $post as WP_Post object, just the post ID.
$post_id = ! empty( $post->ID ) ? $post->ID : $post;
$document = Plugin::$instance->documents->get( $post_id );
if ( $document && ! $document::get_property( 'support_wp_page_templates' ) ) {
return $page_templates;
}
}
$page_templates = [
self::TEMPLATE_CANVAS => esc_html__( 'Elementor Canvas', 'elementor' ),
self::TEMPLATE_HEADER_FOOTER => esc_html__( 'Elementor Full Width', 'elementor' ),
self::TEMPLATE_THEME => esc_html__( 'Theme', 'elementor' ),
] + $page_templates;
return $page_templates;
}
/**
* Set print callback.
*
* Set the page template callback.
*
* @since 2.0.0
* @access public
*
* @param callable $callback
*/
public function set_print_callback( $callback ) {
$this->print_callback = $callback;
}
/**
* Print callback.
*
* Prints the page template content using WordPress loop.
*
* @since 2.0.0
* @access public
*/
public function print_callback() {
while ( have_posts() ) :
the_post();
the_content();
endwhile;
}
/**
* Print content.
*
* Prints the page template content.
*
* @since 2.0.0
* @access public
*/
public function print_content() {
if ( ! $this->print_callback ) {
$this->print_callback = [ $this, 'print_callback' ];
}
call_user_func( $this->print_callback );
}
/**
* Get page template path.
*
* Retrieve the path for any given page template.
*
* @since 2.0.0
* @access public
*
* @param string $page_template The page template name.
*
* @return string Page template path.
*/
public function get_template_path( $page_template ) {
$template_path = '';
switch ( $page_template ) {
case self::TEMPLATE_CANVAS:
$template_path = __DIR__ . '/templates/canvas.php';
break;
case self::TEMPLATE_HEADER_FOOTER:
$template_path = __DIR__ . '/templates/header-footer.php';
break;
}
return $template_path;
}
/**
* Register template control.
*
* Adds custom controls to any given document.
*
* Fired by `update_post_metadata` action.
*
* @since 2.0.0
* @access public
*
* @param Document $document The document instance.
*/
public function action_register_template_control( $document ) {
if ( $document instanceof PageBase || $document instanceof LibraryPageDocument ) {
$this->register_template_control( $document );
}
}
/**
* Register template control.
*
* Adds custom controls to any given document.
*
* @since 2.0.0
* @access public
*
* @param Document $document The document instance.
* @param string $control_id Optional. The control ID. Default is `template`.
*/
public function register_template_control( $document, $control_id = 'template' ) {
if ( ! Utils::is_cpt_custom_templates_supported() ) {
return;
}
require_once ABSPATH . '/wp-admin/includes/template.php';
$document->start_injection( [
'of' => 'post_status',
'fallback' => [
'of' => 'post_title',
],
] );
$control_options = [
'options' => array_flip( get_page_templates( null, $document->get_main_post()->post_type ) ),
];
$this->add_template_controls( $document, $control_id, $control_options );
$document->end_injection();
}
// The $options variable is an array of $control_options to overwrite the default
public function add_template_controls( Document $document, $control_id, $control_options ) {
// Default Control Options
$default_control_options = [
'label' => esc_html__( 'Page Layout', 'elementor' ),
'type' => Controls_Manager::SELECT,
'default' => 'default',
'options' => [
'default' => esc_html__( 'Default', 'elementor' ),
],
];
$control_options = array_replace_recursive( $default_control_options, $control_options );
$document->add_control(
$control_id,
$control_options
);
$document->add_control(
$control_id . '_default_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => '<b>' . esc_html__( 'The default page template as defined in Elementor Panel → Hamburger Menu → Site Settings.', 'elementor' ) . '</b>',
'content_classes' => 'elementor-descriptor',
'condition' => [
$control_id => 'default',
],
]
);
$document->add_control(
$control_id . '_theme_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => '<b>' . esc_html__( 'Default Page Template from your theme.', 'elementor' ) . '</b>',
'content_classes' => 'elementor-descriptor',
'condition' => [
$control_id => self::TEMPLATE_THEME,
],
]
);
$document->add_control(
$control_id . '_canvas_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => '<b>' . esc_html__( 'No header, no footer, just Elementor', 'elementor' ) . '</b>',
'content_classes' => 'elementor-descriptor',
'condition' => [
$control_id => self::TEMPLATE_CANVAS,
],
]
);
$document->add_control(
$control_id . '_header_footer_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => '<b>' . esc_html__( 'This template includes the header, full-width content and footer', 'elementor' ) . '</b>',
'content_classes' => 'elementor-descriptor',
'condition' => [
$control_id => self::TEMPLATE_HEADER_FOOTER,
],
]
);
if ( $document instanceof Kit ) {
$document->add_control(
'reload_preview_description',
[
'type' => Controls_Manager::RAW_HTML,
'raw' => esc_html__( 'Changes will be reflected in the preview only after the page reloads.', 'elementor' ),
'content_classes' => 'elementor-descriptor',
]
);
}
}
/**
* Filter metadata update.
*
* Filters whether to update metadata of a specific type.
*
* Elementor don't allow WordPress to update the parent page template
* during `wp_update_post`.
*
* Fired by `update_{$meta_type}_metadata` filter.
*
* @since 2.0.0
* @access public
*
* @param bool $check Whether to allow updating metadata for the given type.
* @param int $object_id Object ID.
* @param string $meta_key Meta key.
*
* @return bool Whether to allow updating metadata of a specific type.
*/
public function filter_update_meta( $check, $object_id, $meta_key ) {
if ( '_wp_page_template' === $meta_key && Plugin::$instance->common ) {
/** @var \Elementor\Core\Common\Modules\Ajax\Module $ajax */
$ajax = Plugin::$instance->common->get_component( 'ajax' );
$ajax_data = $ajax->get_current_action_data();
$is_autosave_action = $ajax_data && 'save_builder' === $ajax_data['action'] && Document::STATUS_AUTOSAVE === $ajax_data['data']['status'];
// Don't allow WP to update the parent page template.
// (during `wp_update_post` from page-settings or save_plain_text).
if ( $is_autosave_action && ! wp_is_post_autosave( $object_id ) && Document::STATUS_DRAFT !== get_post_status( $object_id ) ) {
$check = false;
}
}
return $check;
}
/**
* Support `wp_body_open` action, available since WordPress 5.2.
*
* @since 2.7.0
* @access public
*/
public static function body_open() {
wp_body_open();
}
/**
* Page templates module constructor.
*
* Initializing Elementor page templates module.
*
* @since 2.0.0
* @access public
*/
public function __construct() {
add_action( 'init', [ $this, 'add_wp_templates_support' ] );
add_filter( 'template_include', [ $this, 'template_include' ], 11 /* After Plugins/WooCommerce */ );
add_action( 'elementor/documents/register_controls', [ $this, 'action_register_template_control' ] );
add_filter( 'update_post_metadata', [ $this, 'filter_update_meta' ], 10, 3 );
}
}

View File

@@ -0,0 +1,52 @@
<?php
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
\Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-canvas' );
?>
<!DOCTYPE html>
<html <?php language_attributes(); ?>>
<head>
<meta charset="<?php bloginfo( 'charset' ); ?>">
<?php if ( ! current_theme_supports( 'title-tag' ) ) : ?>
<title><?php echo wp_get_document_title(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped ?></title>
<?php endif; ?>
<?php wp_head(); ?>
<?php
// Keep the following line after `wp_head()` call, to ensure it's not overridden by another templates.
Utils::print_unescaped_internal_string( Utils::get_meta_viewport( 'canvas' ) );
?>
</head>
<body <?php body_class(); ?>>
<?php
Elementor\Modules\PageTemplates\Module::body_open();
/**
* Before canvas page template content.
*
* Fires before the content of Elementor canvas page template.
*
* @since 1.0.0
*/
do_action( 'elementor/page_templates/canvas/before_content' );
\Elementor\Plugin::$instance->modules_manager->get_modules( 'page-templates' )->print_content();
/**
* After canvas page template content.
*
* Fires after the content of Elementor canvas page template.
*
* @since 1.0.0
*/
do_action( 'elementor/page_templates/canvas/after_content' );
wp_footer();
?>
</body>
</html>

View File

@@ -0,0 +1,30 @@
<?php
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
\Elementor\Plugin::$instance->frontend->add_body_class( 'elementor-template-full-width' );
get_header();
/**
* Before Header-Footer page template content.
*
* Fires before the content of Elementor Header-Footer page template.
*
* @since 2.0.0
*/
do_action( 'elementor/page_templates/header-footer/before_content' );
\Elementor\Plugin::$instance->modules_manager->get_modules( 'page-templates' )->print_content();
/**
* After Header-Footer page template content.
*
* Fires after the content of Elementor Header-Footer page template.
*
* @since 2.0.0
*/
do_action( 'elementor/page_templates/header-footer/after_content' );
get_footer();

View File

@@ -0,0 +1,70 @@
<?php
namespace Elementor\Modules\PerformanceLab;
use Elementor\Core\Base\Module as BaseModule;
use Elementor\Plugin;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends BaseModule {
const PERFORMANCE_LAB_FUNCTION_NAME = 'webp_uploads_img_tag_update_mime_type';
const PERFORMANCE_LAB_OPTION_NAME = 'site-health/webp-support';
public function get_name() {
return 'performance-lab';
}
private function is_performance_lab_is_active() {
if ( function_exists( self::PERFORMANCE_LAB_FUNCTION_NAME ) ) {
$perflab_modules_settings = get_option( self::PERFORMANCE_LAB_OPTION_NAME, [] );
if ( isset( $perflab_modules_settings ) && isset( $perflab_modules_settings[ self::PERFORMANCE_LAB_OPTION_NAME ] ) &&
'1' === $perflab_modules_settings[ self::PERFORMANCE_LAB_OPTION_NAME ]['enabled'] ) {
return true;
}
}
return false;
}
private function performance_lab_get_webp_src( $attachment_id, $size, $url ) {
$image_object = wp_get_attachment_image_src( $attachment_id, $size );
$image_src = call_user_func( self::PERFORMANCE_LAB_FUNCTION_NAME, $image_object[0], 'webp', $attachment_id );
if ( ! empty( $image_src ) ) {
return $image_src;
}
return $url;
}
private function replace_css_with_webp( $value, $css_property, $matches ) {
if ( 0 === strpos( $css_property, 'background-image' ) && '{{URL}}' === $matches[0] ) {
$value['url'] = $this->performance_lab_get_webp_src( $value['id'], 'full', $value['url'] );
}
return $value;
}
public function __construct() {
parent::__construct();
if ( $this->is_performance_lab_is_active() ) {
add_filter( 'elementor/files/css/property', function( $value, $css_property, $matches ) {
return $this->replace_css_with_webp( $value, $css_property, $matches );
}, 10, 3 );
}
if ( is_admin() ) {
add_action( 'activated_plugin', function( $plugin ) {
if ( 'performance-lab/load.php' === $plugin ) {
Plugin::$instance->files_manager->clear_cache();
}
} );
add_action( 'deactivated_plugin', function( $plugin ) {
if ( 'performance-lab/load.php' === $plugin ) {
Plugin::$instance->files_manager->clear_cache();
}
} );
}
}
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems;
use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager;
use Elementor\Modules\Promotions\AdminMenuItems\Interfaces\Promotion_Menu_Item;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Base_Promotion_Item implements Promotion_Menu_Item {
public function get_name() {
return 'base_promotion';
}
public function is_visible() {
return true;
}
public function get_parent_slug() {
return Settings::PAGE_ID;
}
public function get_capability() {
return 'manage_options';
}
public function get_cta_text() {
return esc_html__( 'Upgrade Now', 'elementor' );
}
public function get_image_url() {
return ELEMENTOR_ASSETS_URL . 'images/go-pro-wp-dashboard.svg';
}
public function get_promotion_description() {
return '';
}
public function render() {
$config = [
'title' => $this->get_promotion_title(),
'description' => $this->get_promotion_description(),
'image' => $this->get_image_url(),
'upgrade_text' => $this->get_cta_text(),
'upgrade_url' => $this->get_cta_url(),
];
$config = Filtered_Promotions_Manager::get_filtered_promotion_data( $config, 'elementor/' . $this->get_name() . '/custom_promotion', 'upgrade_url' );
$description = $config['description'] ?? $this->get_promotion_description() ?? '';
?>
<div class="wrap">
<div class="elementor-blank_state">
<img src="<?php echo esc_url( $config['image'] ?? $this->get_image_url() ); ?>" loading="lazy" />
<h3><?php echo esc_html( $config['title'] ?? $this->get_promotion_title() ); ?></h3>
<?php if ( $description ) : ?>
<p><?php echo esc_html( $description ); ?></p>
<?php endif; ?>
<a class="elementor-button go-pro" href="<?php echo esc_url( $config['upgrade_url'] ?? $this->get_cta_url() ); ?>">
<?php echo esc_html( $config['upgrade_text'] ?? $this->get_cta_text() ); ?>
</a>
</div>
</div>
<?php
}
}

View File

@@ -0,0 +1,109 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page;
use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager;
use Elementor\Settings;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
abstract class Base_Promotion_Template implements Admin_Menu_Item_With_Page {
abstract protected function get_promotion_title():string;
abstract protected function get_cta_url():string;
abstract protected function get_content_lines():array;
abstract protected function get_video_url():string;
public function is_visible() {
return true;
}
public function get_parent_slug() {
return Settings::PAGE_ID;
}
public function get_capability() {
return 'manage_options';
}
protected function get_cta_text() {
return esc_html__( 'Upgrade Now', 'elementor' );
}
/**
* Should the promotion have a side note.
* @return string
*/
protected function get_side_note():string {
return '';
}
private function get_lines() {
ob_start();
if ( ! empty( $this->get_content_lines() ) ) {
?>
<ul>
<?php foreach ( $this->get_content_lines() as $item ) { ?>
<li><?php Utils::print_unescaped_internal_string( $item ); ?></li>
<?php } ?>
</ul>
<?php
}
return ob_get_clean();
}
public function render() {
$promotion_data = $this->get_promotion_data();
?>
<div class="e-feature-promotion">
<div class="e-feature-promotion_data">
<h3><?php Utils::print_unescaped_internal_string( $promotion_data['promotion_title'] ); ?></h3>
<?php Utils::print_unescaped_internal_string( $promotion_data['lines'] ); ?>
<a class="elementor-button go-pro" href="<?php echo esc_url( $promotion_data['cta_url'] ); ?>" target="_blank">
<?php Utils::print_unescaped_internal_string( $promotion_data['cta_text'] ); ?>
</a>
<?php if ( ! empty( $promotion_data['side_note'] ) ) { ?>
<div class="side-note">
<p><?php Utils::print_unescaped_internal_string( $promotion_data['side_note'] ); ?></p>
</div>
<?php } ?>
</div>
<iframe class="e-feature-promotion_iframe" src="<?php Utils::print_unescaped_internal_string( $promotion_data['video_url'] ); ?>&rel=0" title="Elementor" frameborder="0" allow="accelerometer; autoplay; clipboard-write; encrypted-media; gyroscope; picture-in-picture; web-share" allowfullscreen></iframe>
</div>
<?php
}
/**
* @return array|null
*/
private function get_promotion_data(): ?array {
return Filtered_Promotions_Manager::get_filtered_promotion_data( $this->build_promotion_data_array(), 'elementor/' . $this->get_name() . '/custom_promotion', 'cta_url' );
}
/**
* @return array
*/
private function build_promotion_data_array(): array {
return [
'promotion_title' => $this->get_promotion_title(),
'cta_url' => $this->get_cta_url(),
'cta_text' => $this->get_cta_text(),
'video_url' => $this->get_video_url(),
'lines' => $this->get_lines(),
'side_note' => $this->get_side_note(),
];
}
}

View File

@@ -0,0 +1,45 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Custom_Code_Promotion_Item extends Base_Promotion_Template {
public function get_name() {
return 'custom_code';
}
public function get_label() {
return esc_html__( 'Custom Code', 'elementor' );
}
public function get_page_title() {
return esc_html__( 'Custom Code', 'elementor' );
}
protected function get_promotion_title(): string {
return esc_html__( 'Enjoy Creative Freedom with Custom Code', 'elementor' );
}
protected function get_content_lines():array {
return [
esc_html__( 'Add Custom Code snippets anywhere on your website, including the header or footer to measure your pages performance*', 'elementor' ),
esc_html__( 'Use Custom Code to create sophisticated custom interactions to engage visitors', 'elementor' ),
esc_html__( 'Leverage Elementor AI to instantly generate Custom Code for Elementor', 'elementor' ),
];
}
protected function get_side_note():string {
return esc_html__( '* Requires an Advanced subscription or higher', 'elementor' );
}
protected function get_cta_url():string {
return 'https://go.elementor.com/go-pro-custom-code/';
}
protected function get_video_url():string {
return 'https://www.youtube-nocookie.com/embed/IOovQd1hJUg?si=xeBJ_mRZxRH1l5O6';
}
}

View File

@@ -0,0 +1,44 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Custom_Fonts_Promotion_Item extends Base_Promotion_Template {
public function get_name() {
return 'custom_fonts';
}
public function get_label() {
return esc_html__( 'Custom Fonts', 'elementor' );
}
public function get_page_title() {
return esc_html__( 'Custom Fonts', 'elementor' );
}
protected function get_promotion_title(): string {
return esc_html__( 'Stay on brand with a Custom Font', 'elementor' );
}
protected function get_content_lines(): array {
return [
esc_html__( 'Upload any font to keep your website true to your brand', 'elementor' ),
sprintf(
/* translators: %s: br */
esc_html__( 'Remain GDPR compliant with Custom Fonts that let you disable %s Google Fonts from your website', 'elementor' ),
'<br />'
),
];
}
protected function get_cta_url(): string {
return 'https://go.elementor.com/go-pro-custom-fonts/';
}
protected function get_video_url(): string {
return 'https://www.youtube-nocookie.com/embed/j_guJkm28eY?si=cdd2TInwuGDTtCGD';
}
}

View File

@@ -0,0 +1,47 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Custom_Icons_Promotion_Item extends Base_Promotion_Template {
public function get_name() {
return 'custom_icons';
}
public function get_label() {
return esc_html__( 'Custom Icons', 'elementor' );
}
public function get_page_title() {
return esc_html__( 'Custom Icons', 'elementor' );
}
protected function get_promotion_title():string {
return sprintf(
/* translators: %s: br */
esc_html( 'Enjoy creative freedom %s with Custom Icons', 'elementor' ),
'<br />'
);
}
protected function get_content_lines(): array {
return [
sprintf(
esc_html__( 'Expand your icon library beyond FontAwesome and add icon %s libraries of your choice', 'elementor' ),
'<br />'
),
esc_html__( 'Add any icon, anywhere on your website', 'elementor' ),
];
}
protected function get_cta_url(): string {
return 'https://go.elementor.com/go-pro-custom-icons/';
}
protected function get_video_url(): string {
return 'https://www.youtube-nocookie.com/embed/PsowinxDWfM?si=SV9Z3TLz3_XEy5C6';
}
}

View File

@@ -0,0 +1,50 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Form_Submissions_Promotion_Item extends Base_Promotion_Template {
public function get_name() {
return 'submissions';
}
public function get_label() {
return esc_html__( 'Submissions', 'elementor' );
}
public function get_page_title() {
return esc_html__( 'Submissions', 'elementor' );
}
public function get_promotion_title():string {
return sprintf(
/* translators: %s: br */
esc_html( 'Create Forms and Collect Leads %s with Elementor Pro', 'elementor' ),
'<br />'
);
}
protected function get_content_lines(): array {
return [
esc_html__( 'Create single or multi-step forms to engage and convert visitors', 'elementor' ),
esc_html__( 'Use any field to collect the information you need', 'elementor' ),
esc_html__( 'Integrate your favorite marketing software*', 'elementor' ),
esc_html__( 'Collect lead submissions directly within your WordPress Admin to manage, analyze and perform bulk actions on the submitted lead*', 'elementor' ),
];
}
protected function get_cta_url():string {
return 'https://go.elementor.com/go-pro-submissions/';
}
protected function get_video_url():string {
return 'https://www.youtube-nocookie.com/embed/LNfnwba9C-8?si=JLHk3UAexnvTfU1a';
}
protected function get_side_note():string {
return esc_html__( '* Requires an Advanced subscription or higher', 'elementor' );
}
}

View File

@@ -0,0 +1,53 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page;
use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager;
use Elementor\Settings;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Go_Pro_Promotion_Item implements Admin_Menu_Item_With_Page {
const URL = 'https://go.elementor.com/pro-admin-menu/';
public function get_name() {
return 'admin_menu_promo';
}
public function is_visible() {
return true;
}
public function get_parent_slug() {
return Settings::PAGE_ID;
}
public function get_label() {
$upgrade_text = esc_html__( 'Upgrade', 'elementor' );
return apply_filters( 'elementor/admin_menu/custom_promotion', [ 'upgrade_text' => $upgrade_text ] )['upgrade_text'] ?? $upgrade_text;
}
public function get_page_title() {
return '';
}
public function get_capability() {
return 'manage_options';
}
public static function get_url() {
$url = self::URL;
$filtered_url = apply_filters( 'elementor/admin_menu/custom_promotion', [ 'upgrade_url' => $url ] )['upgrade_url'] ?? '';
$promotion_data = Filtered_Promotions_Manager::get_filtered_promotion_data( [ 'upgrade_url' => $filtered_url ], 'elementor/admin_menu/custom_promotion', 'upgrade_url' );
return $promotion_data ['upgrade_url'];
}
public function render() {
// Redirects from the module on `admin_init`.
die;
}
}

View File

@@ -0,0 +1,21 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems\Interfaces;
use Elementor\Core\Admin\Menu\Interfaces\Admin_Menu_Item_With_Page;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
interface Promotion_Menu_Item extends Admin_Menu_Item_With_Page {
public function get_image_url();
public function get_promotion_title();
public function render_promotion_description();
public function get_cta_text();
public function get_cta_url();
}

View File

@@ -0,0 +1,72 @@
<?php
namespace Elementor\Modules\Promotions\AdminMenuItems;
use Elementor\Core\Utils\Promotions\Filtered_Promotions_Manager;
use Elementor\TemplateLibrary\Source_Local;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Popups_Promotion_Item extends Base_Promotion_Item {
private array $promotion_data;
public function __construct() {
$this->promotion_data = [
'title' => esc_html__( 'Get Popup Builder', 'elementor' ),
'content' => esc_html__(
'The Popup Builder lets you take advantage of all the amazing features in Elementor, so you can build beautiful & highly converting popups. Get Elementor Pro and start designing your popups today.',
'elementor'
),
'action_button' => [
'text' => esc_html__( 'Upgrade Now', 'elementor' ),
'url' => 'https://go.elementor.com/go-pro-popup-builder/',
],
];
$this->promotion_data = Filtered_Promotions_Manager::get_filtered_promotion_data( $this->promotion_data, 'elementor/templates/popup', 'action_button', 'url' );
}
public function get_parent_slug() {
return Source_Local::ADMIN_MENU_SLUG;
}
public function get_name() {
return 'popups';
}
public function get_label() {
return esc_html__( 'Popups', 'elementor' );
}
public function get_page_title() {
return esc_html__( 'Popups', 'elementor' );
}
public function get_promotion_title() {
return $this->promotion_data['title'];
}
public function get_promotion_description() {
return $this->promotion_data['content'];
}
/**
* @deprecated use get_promotion_description instead
* @return void
*/
public function render_promotion_description() {
echo $this->get_promotion_description(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
}
public function get_cta_url() {
return $this->promotion_data['action_button']['url'];
}
public function get_cta_text() {
return $this->promotion_data['action_button']['text'];
}
}

View File

@@ -0,0 +1,82 @@
<?php
namespace Elementor\Modules\Promotions;
use Elementor\Api;
use Elementor\Core\Admin\Menu\Admin_Menu_Manager;
use Elementor\Core\Base\Module as Base_Module;
use Elementor\Modules\Promotions\AdminMenuItems\Custom_Code_Promotion_Item;
use Elementor\Modules\Promotions\AdminMenuItems\Custom_Fonts_Promotion_Item;
use Elementor\Modules\Promotions\AdminMenuItems\Custom_Icons_Promotion_Item;
use Elementor\Modules\Promotions\AdminMenuItems\Form_Submissions_Promotion_Item;
use Elementor\Modules\Promotions\AdminMenuItems\Go_Pro_Promotion_Item;
use Elementor\Modules\Promotions\AdminMenuItems\Popups_Promotion_Item;
use Elementor\Widgets_Manager;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends Base_Module {
const ADMIN_MENU_PRIORITY = 100;
const ADMIN_MENU_PROMOTIONS_PRIORITY = 120;
public static function is_active() {
return ! Utils::has_pro();
}
public function get_name() {
return 'promotions';
}
public function __construct() {
parent::__construct();
add_action( 'admin_init', function () {
$this->handle_external_redirects();
} );
add_action( 'elementor/admin/menu/register', function ( Admin_Menu_Manager $admin_menu ) {
$this->register_menu_items( $admin_menu );
}, static::ADMIN_MENU_PRIORITY );
add_action( 'elementor/admin/menu/register', function ( Admin_Menu_Manager $admin_menu ) {
$this->register_promotion_menu_item( $admin_menu );
}, static::ADMIN_MENU_PROMOTIONS_PRIORITY );
add_action( 'elementor/widgets/register', function( Widgets_Manager $manager ) {
foreach ( Api::get_promotion_widgets() as $widget_data ) {
$manager->register( new Widgets\Pro_Widget_Promotion( [], [
'widget_name' => $widget_data['name'],
'widget_title' => $widget_data['title'],
] ) );
}
} );
}
private function handle_external_redirects() {
if ( empty( $_GET['page'] ) ) {
return;
}
if ( 'go_elementor_pro' === $_GET['page'] ) {
wp_redirect( Go_Pro_Promotion_Item::get_url() );
die;
}
}
private function register_menu_items( Admin_Menu_Manager $admin_menu ) {
$admin_menu->register( 'e-form-submissions', new Form_Submissions_Promotion_Item() );
$admin_menu->register( 'elementor_custom_fonts', new Custom_Fonts_Promotion_Item() );
$admin_menu->register( 'elementor_custom_icons', new Custom_Icons_Promotion_Item() );
$admin_menu->register( 'elementor_custom_code', new Custom_Code_Promotion_Item() );
$admin_menu->register( 'popup_templates', new Popups_Promotion_Item() );
}
private function register_promotion_menu_item( Admin_Menu_Manager $admin_menu ) {
$admin_menu->register( 'go_elementor_pro', new Go_Pro_Promotion_Item() );
}
}

View File

@@ -0,0 +1,91 @@
<?php
namespace Elementor\Modules\Promotions\Widgets;
use Elementor\Widget_Base;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly.
}
class Pro_Widget_Promotion extends Widget_Base {
private $widget_data;
public function hide_on_search() {
return true;
}
public function show_in_panel() {
return false;
}
public function get_name() {
return $this->widget_data['widget_name'];
}
public function get_title() {
return $this->widget_data['widget_title'];
}
public function on_import( $element ) {
$element['settings']['__should_import'] = true;
return $element;
}
protected function register_controls() {}
protected function render() {
if ( $this->is_editor_render() ) {
$this->render_promotion();
} else {
$this->render_empty_content();
}
}
private function is_editor_render(): bool {
return \Elementor\Plugin::$instance->editor->is_edit_mode();
}
private function render_promotion() {
?>
<div class="e-container">
<span class="e-badge"><i class="eicon-lock" aria-hidden="true"></i> <?php echo esc_html__( 'Pro', 'elementor' ); ?></span>
<p>
<img src="<?php echo esc_url( $this->get_promotion_image_url() ); ?>" loading="lazy" alt="Go Pro">
<?php
echo sprintf(
esc_html__( 'This result includes the Elementor Pro %s widget. Upgrade now to unlock it and grow your web creation toolkit.', 'elementor' ),
esc_html( $this->widget_data['widget_title'] )
);
?>
</p>
<div class="e-actions">
<a href="#" class="e-btn e-btn-txt e-promotion-delete"><?php echo esc_html__( 'Remove', 'elementor' ); ?></a>
<a href="https://go.elementor.com/go-pro-element-pro/" rel="noreferrer" target="_blank" class="e-btn go-pro elementor-clickable e-promotion-go-pro"><?php echo esc_html__( 'Go Pro', 'elementor' ); ?></a>
</div>
</div>
<?php
}
private function get_promotion_image_url(): string {
return ELEMENTOR_ASSETS_URL . 'images/go-pro.svg';
}
private function render_empty_content() {
echo ' ';
}
protected function content_template() {}
public function __construct( $data = [], $args = null ) {
$this->widget_data = [
'widget_name' => $args['widget_name'],
'widget_title' => $args['widget_title'],
];
parent::__construct( $data, $args );
}
public function render_plain_content( $instance = [] ) {}
}

View File

@@ -0,0 +1,562 @@
<?php
namespace Elementor\Modules\SafeMode;
use Elementor\Plugin;
use Elementor\Settings;
use Elementor\Tools;
use Elementor\TemplateLibrary\Source_Local;
use Elementor\Core\Common\Modules\Ajax\Module as Ajax;
use Elementor\Utils;
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Module extends \Elementor\Core\Base\Module {
const OPTION_ENABLED = 'elementor_safe_mode';
const OPTION_TOKEN = self::OPTION_ENABLED . '_token';
const MU_PLUGIN_FILE_NAME = 'elementor-safe-mode.php';
const DOCS_HELPED_URL = 'https://go.elementor.com/safe-mode-helped/';
const DOCS_DIDNT_HELP_URL = 'https://go.elementor.com/safe-mode-didnt-helped/';
const DOCS_MU_PLUGINS_URL = 'https://go.elementor.com/safe-mode-mu-plugins/';
const DOCS_TRY_SAFE_MODE_URL = 'https://go.elementor.com/safe-mode/';
const EDITOR_NOTICE_TIMEOUT = 30000; /* ms */
public function get_name() {
return 'safe-mode';
}
public function register_ajax_actions( Ajax $ajax ) {
$ajax->register_ajax_action( 'enable_safe_mode', [ $this, 'ajax_enable_safe_mode' ] );
$ajax->register_ajax_action( 'disable_safe_mode', [ $this, 'disable_safe_mode' ] );
}
/**
* @param Tools $tools_page
*/
public function add_admin_button( $tools_page ) {
$tools_page->add_fields( Settings::TAB_GENERAL, 'tools', [
'safe_mode' => [
'label' => esc_html__( 'Safe Mode', 'elementor' ),
'field_args' => [
'type' => 'select',
'std' => $this->is_enabled() ? 'global' : '',
'options' => [
'' => esc_html__( 'Disable', 'elementor' ),
'global' => esc_html__( 'Enable', 'elementor' ),
],
'desc' => esc_html__( 'Safe Mode allows you to troubleshoot issues by only loading the editor, without loading the theme or any other plugin.', 'elementor' ),
],
],
] );
}
public function on_update_safe_mode( $value ) {
if ( 'yes' === $value || 'global' === $value ) {
$this->enable_safe_mode();
} else {
$this->disable_safe_mode();
}
return $value;
}
/**
* @throws \Exception
*/
public function ajax_enable_safe_mode( $data ) {
if ( ! current_user_can( 'install_plugins' ) ) {
throw new \Exception( 'Access denied.' );
}
// It will run `$this->>update_safe_mode`.
update_option( 'elementor_safe_mode', 'yes' );
$document = Plugin::$instance->documents->get( $data['editor_post_id'] );
if ( $document ) {
return add_query_arg( 'elementor-mode', 'safe', $document->get_edit_url() );
}
return false;
}
public function enable_safe_mode() {
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
WP_Filesystem();
$this->update_allowed_plugins();
if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
wp_mkdir_p( WPMU_PLUGIN_DIR );
add_option( 'elementor_safe_mode_created_mu_dir', true );
}
if ( ! is_dir( WPMU_PLUGIN_DIR ) ) {
wp_die( esc_html__( 'Cannot enable Safe Mode', 'elementor' ) );
}
$results = copy_dir( __DIR__ . '/mu-plugin/', WPMU_PLUGIN_DIR );
if ( is_wp_error( $results ) ) {
return;
}
$token = hash( 'sha256', wp_rand() );
// Only who own this key can use 'elementor-safe-mode'.
update_option( self::OPTION_TOKEN, $token );
// Save for later use.
setcookie( self::OPTION_TOKEN, $token, time() + HOUR_IN_SECONDS, COOKIEPATH, '', is_ssl(), true );
}
public function disable_safe_mode() {
if ( ! current_user_can( 'install_plugins' ) ) {
return;
}
$file_path = WP_CONTENT_DIR . '/mu-plugins/elementor-safe-mode.php';
if ( file_exists( $file_path ) ) {
unlink( $file_path );
}
if ( get_option( 'elementor_safe_mode_created_mu_dir' ) ) {
// It will be removed only if it's empty and don't have other mu-plugins.
@rmdir( WPMU_PLUGIN_DIR );
}
delete_option( 'elementor_safe_mode' );
delete_option( 'elementor_safe_mode_allowed_plugins' );
delete_option( 'theme_mods_elementor-safe' );
delete_option( 'elementor_safe_mode_created_mu_dir' );
delete_option( self::OPTION_TOKEN );
setcookie( self::OPTION_TOKEN, '', 1, '', '', is_ssl(), true );
}
public function filter_preview_url( $url ) {
return add_query_arg( 'elementor-mode', 'safe', $url );
}
public function filter_template() {
return ELEMENTOR_PATH . 'modules/page-templates/templates/canvas.php';
}
public function print_safe_mode_css() {
?>
<style>
.elementor-safe-mode-toast {
position: absolute;
z-index: 10000; /* Over the loading layer */
bottom: 10px;
width: 400px;
line-height: 30px;
color: var(--e-a-color-txt);
background: var(--e-a-bg-default);
padding: 20px 25px 25px;
box-shadow: 0 5px 20px rgba(0, 0, 0, 0.15);
border-radius: 5px;
font-family: var(--e-a-font-family);
}
body.rtl .elementor-safe-mode-toast {
left: 10px;
}
body:not(.rtl) .elementor-safe-mode-toast {
right: 10px;
}
#elementor-try-safe-mode {
display: none;
}
.elementor-safe-mode-toast .elementor-toast-content {
font-size: 13px;
line-height: 22px;
}
.elementor-safe-mode-toast .elementor-toast-content a {
color: var(--e-a-color-info);
}
.elementor-safe-mode-toast .elementor-toast-content hr {
margin: 15px auto;
border: 0 none;
border-block-start: var(--e-a-border);
}
.elementor-safe-mode-toast header {
display: flex;
align-items: center;
justify-content: space-between;
flex-wrap: wrap;
margin-block-end: 20px;
}
.elementor-safe-mode-toast header > * {
margin-block-start: 10px;
}
.elementor-safe-mode-toast header i {
font-size: 25px;
color: var(--e-a-color-warning);
}
.elementor-safe-mode-toast header i {
margin-inline-end: 10px;
}
.elementor-safe-mode-toast header h2 {
flex-grow: 1;
font-size: 18px;
}
.elementor-safe-mode-list-item {
margin-block-start: 10px;
list-style: outside;
}
.elementor-safe-mode-list-item {
margin-inline-start: 15px;
}
.elementor-safe-mode-list-item b {
font-size: 14px;
}
.elementor-safe-mode-list-item-content {
font-style: italic;
color: var(--e-a-color-txt);
}
.elementor-safe-mode-list-item-title {
font-weight: 500;
}
.elementor-safe-mode-mu-plugins {
background-color: var(--e-a-bg-hover);
color: var(--e-a-color-txt-hover);
margin-block-start: 20px;
padding: 10px 15px;
}
</style>
<?php
}
public function print_safe_mode_notice() {
$this->print_safe_mode_css()
?>
<div class="elementor-safe-mode-toast" id="elementor-safe-mode-message">
<header>
<i class="eicon-warning"></i>
<h2><?php echo esc_html__( 'Safe Mode ON', 'elementor' ); ?></h2>
<a class="elementor-button elementor-safe-mode-button elementor-disable-safe-mode" target="_blank" href="<?php echo esc_url( $this->get_admin_page_url() ); ?>">
<?php echo esc_html__( 'Disable Safe Mode', 'elementor' ); ?>
</a>
</header>
<div class="elementor-toast-content">
<ul class="elementor-safe-mode-list">
<li class="elementor-safe-mode-list-item">
<div class="elementor-safe-mode-list-item-title"><?php echo esc_html__( 'Editor successfully loaded?', 'elementor' ); ?></div>
<div class="elementor-safe-mode-list-item-content">
<?php
echo esc_html__( 'The issue was probably caused by one of your plugins or theme.', 'elementor' );
echo ' ';
printf(
/* translators: %1$s Link open tag, %2$s: Link close tag. */
esc_html__( '%1$sClick here%2$s to troubleshoot', 'elementor' ),
'<a href="' . self::DOCS_HELPED_URL . '" target="_blank">', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'</a>'
);
?>
</div>
</li>
<li class="elementor-safe-mode-list-item">
<div class="elementor-safe-mode-list-item-title"><?php echo esc_html__( 'Still experiencing issues?', 'elementor' ); ?></div>
<div class="elementor-safe-mode-list-item-content">
<?php
printf(
/* translators: %1$s Link open tag, %2$s: Link close tag. */
esc_html__( '%1$sClick here%2$s to troubleshoot', 'elementor' ),
'<a href="' . self::DOCS_DIDNT_HELP_URL . '" target="_blank">', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'</a>'
);
?>
</div>
</li>
</ul>
<?php
$mu_plugins = wp_get_mu_plugins();
if ( 1 < count( $mu_plugins ) ) : ?>
<div class="elementor-safe-mode-mu-plugins">
<?php
printf(
/* translators: %1$s Link open tag, %2$s: Link close tag. */
esc_html__( 'Please note! We couldn\'t deactivate all of your plugins on Safe Mode. Please %1$sread more%2$s about this issue', 'elementor' ),
'<a href="' . self::DOCS_MU_PLUGINS_URL . '" target="_blank">', // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
'</a>'
);
?>
</div>
<?php endif; ?>
</div>
</div>
<script>
var ElementorSafeMode = function() {
var attachEvents = function() {
jQuery( '.elementor-disable-safe-mode' ).on( 'click', function( e ) {
if ( ! elementorCommon || ! elementorCommon.ajax ) {
return;
}
e.preventDefault();
elementorCommon.ajax.addRequest(
'disable_safe_mode', {
success: function() {
if ( -1 === location.href.indexOf( 'elementor-mode=safe' ) ) {
location.reload();
} else {
// Need to remove the URL from browser history.
location.replace( location.href.replace( '&elementor-mode=safe', '' ) );
}
},
error: function() {
alert( 'An error occurred.' );
},
},
true
);
} );
};
var init = function() {
attachEvents();
};
init();
};
new ElementorSafeMode();
</script>
<?php
}
public function print_try_safe_mode() {
if ( ! $this->is_allowed_post_type() ) {
return;
}
$this->print_safe_mode_css();
?>
<div class="elementor-safe-mode-toast" id="elementor-try-safe-mode">
<?php if ( current_user_can( 'install_plugins' ) ) : ?>
<header>
<i class="eicon-warning"></i>
<h2><?php echo esc_html__( 'Can\'t Edit?', 'elementor' ); ?></h2>
<a class="elementor-button e-primary elementor-safe-mode-button elementor-enable-safe-mode" target="_blank" href="<?php echo esc_url( $this->get_admin_page_url() ); ?>">
<?php echo esc_html__( 'Enable Safe Mode', 'elementor' ); ?>
</a>
</header>
<div class="elementor-toast-content">
<?php echo esc_html__( 'Having problems loading Elementor? Please enable Safe Mode to troubleshoot.', 'elementor' ); ?>
<a href="<?php Utils::print_unescaped_internal_string( self::DOCS_TRY_SAFE_MODE_URL ); ?>" target="_blank"><?php echo esc_html__( 'Learn More', 'elementor' ); ?></a>
</div>
<?php else : ?>
<header>
<i class="eicon-warning"></i>
<h2><?php echo esc_html__( 'Can\'t Edit?', 'elementor' ); ?></h2>
</header>
<div class="elementor-toast-content">
<?php echo esc_html__( 'If you are experiencing a loading issue, contact your site administrator to troubleshoot the problem using Safe Mode.', 'elementor' ); ?>
<a href="<?php Utils::print_unescaped_internal_string( self::DOCS_TRY_SAFE_MODE_URL ); ?>" target="_blank"><?php echo esc_html__( 'Learn More', 'elementor' ); ?></a>
</div>
<?php endif; ?>
</div>
<script>
var ElementorTrySafeMode = function() {
var attachEvents = function() {
jQuery( '.elementor-enable-safe-mode' ).on( 'click', function( e ) {
if ( ! elementorCommon || ! elementorCommon.ajax ) {
return;
}
e.preventDefault();
elementorCommon.ajax.addRequest(
'enable_safe_mode', {
data: {
editor_post_id: '<?php
// PHPCS - the method get_post_id is safe.
echo Plugin::$instance->editor->get_post_id(); // phpcs:ignore WordPress.Security.EscapeOutput.OutputNotEscaped
?>',
},
success: function( url ) {
location.assign( url );
},
error: function() {
alert( 'An error occurred.' );
},
},
true
);
} );
};
var isElementorLoaded = function() {
if ( 'undefined' === typeof elementor ) {
return false;
}
if ( ! elementor.loaded ) {
return false;
}
if ( jQuery( '#elementor-loading' ).is( ':visible' ) ) {
return false;
}
return true;
};
var handleTrySafeModeNotice = function() {
var $notice = jQuery( '#elementor-try-safe-mode' );
if ( isElementorLoaded() ) {
$notice.remove();
return;
}
if ( ! $notice.data( 'visible' ) ) {
$notice.show().data( 'visible', true );
}
// Re-check after 500ms.
setTimeout( handleTrySafeModeNotice, 500 );
};
var init = function() {
setTimeout( handleTrySafeModeNotice, <?php Utils::print_unescaped_internal_string( self::EDITOR_NOTICE_TIMEOUT ); ?> );
attachEvents();
};
init();
};
new ElementorTrySafeMode();
</script>
<?php
}
public function run_safe_mode() {
remove_action( 'elementor/editor/footer', [ $this, 'print_try_safe_mode' ] );
// Avoid notices like for comment.php.
add_filter( 'deprecated_file_trigger_error', '__return_false' );
add_filter( 'template_include', [ $this, 'filter_template' ], 999 );
add_filter( 'elementor/document/urls/preview', [ $this, 'filter_preview_url' ] );
add_action( 'elementor/editor/footer', [ $this, 'print_safe_mode_notice' ] );
add_action( 'elementor/editor/before_enqueue_scripts', [ $this, 'register_scripts' ], 11 /* After Common Scripts */ );
}
public function register_scripts() {
wp_add_inline_script( 'elementor-common', 'elementorCommon.ajax.addRequestConstant( "elementor-mode", "safe" );' );
}
private function is_enabled() {
return get_option( self::OPTION_ENABLED, '' );
}
private function get_admin_page_url() {
// A fallback URL if the Js doesn't work.
return Tools::get_url();
}
public function plugin_action_links( $actions ) {
$actions['disable'] = '<a href="' . self::get_admin_page_url() . '">' . esc_html__( 'Disable Safe Mode', 'elementor' ) . '</a>';
return $actions;
}
public function on_deactivated_plugin( $plugin ) {
if ( ELEMENTOR_PLUGIN_BASE === $plugin ) {
$this->disable_safe_mode();
return;
}
$allowed_plugins = get_option( 'elementor_safe_mode_allowed_plugins', [] );
$plugin_key = array_search( $plugin, $allowed_plugins, true );
if ( $plugin_key ) {
unset( $allowed_plugins[ $plugin_key ] );
update_option( 'elementor_safe_mode_allowed_plugins', $allowed_plugins );
}
}
public function update_allowed_plugins() {
$allowed_plugins = [
'elementor' => ELEMENTOR_PLUGIN_BASE,
];
if ( defined( 'ELEMENTOR_PRO_PLUGIN_BASE' ) ) {
$allowed_plugins['elementor_pro'] = ELEMENTOR_PRO_PLUGIN_BASE;
}
if ( defined( 'WC_PLUGIN_BASENAME' ) ) {
$allowed_plugins['woocommerce'] = WC_PLUGIN_BASENAME;
}
update_option( 'elementor_safe_mode_allowed_plugins', $allowed_plugins );
}
public function __construct() {
if ( current_user_can( 'install_plugins' ) ) {
add_action( 'elementor/admin/after_create_settings/elementor-tools', [ $this, 'add_admin_button' ] );
}
add_action( 'elementor/ajax/register_actions', [ $this, 'register_ajax_actions' ] );
$plugin_file = self::MU_PLUGIN_FILE_NAME;
add_filter( "plugin_action_links_{$plugin_file}", [ $this, 'plugin_action_links' ] );
// Use pre_update, in order to catch cases that $value === $old_value and it not updated.
add_filter( 'pre_update_option_elementor_safe_mode', [ $this, 'on_update_safe_mode' ], 10, 2 );
add_action( 'elementor/safe_mode/init', [ $this, 'run_safe_mode' ] );
add_action( 'elementor/editor/footer', [ $this, 'print_try_safe_mode' ] );
if ( $this->is_enabled() ) {
add_action( 'activated_plugin', [ $this, 'update_allowed_plugins' ] );
add_action( 'deactivated_plugin', [ $this, 'on_deactivated_plugin' ] );
}
}
private function is_allowed_post_type() {
$allowed_post_types = [
'post',
'page',
'product',
Source_Local::CPT,
];
$current_post_type = get_post_type( Plugin::$instance->editor->get_post_id() );
return in_array( $current_post_type, $allowed_post_types );
}
}

View File

@@ -0,0 +1,136 @@
<?php
/**
* Plugin Name: Elementor Safe Mode
* Description: Safe Mode allows you to troubleshoot issues by only loading the editor, without loading the theme or any other plugin.
* Plugin URI: https://elementor.com/?utm_source=safe-mode&utm_campaign=plugin-uri&utm_medium=wp-dash
* Author: Elementor.com
* Version: 1.0.0
* Author URI: https://elementor.com/?utm_source=safe-mode&utm_campaign=author-uri&utm_medium=wp-dash
*
* Text Domain: elementor
*
* @package Elementor
* @category Safe Mode
*
* Elementor is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* any later version.
*
* Elementor is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*/
if ( ! defined( 'ABSPATH' ) ) {
exit; // Exit if accessed directly
}
class Safe_Mode {
const OPTION_ENABLED = 'elementor_safe_mode';
const OPTION_TOKEN = self::OPTION_ENABLED . '_token';
public function is_enabled() {
return get_option( self::OPTION_ENABLED );
}
public function is_valid_token() {
$token = isset( $_COOKIE[ self::OPTION_TOKEN ] )
? wp_kses_post( wp_unslash( $_COOKIE[ self::OPTION_TOKEN ] ) )
: null;
if ( $token && get_option( self::OPTION_TOKEN ) === $token ) {
return true;
}
return false;
}
public function is_requested() {
return ! empty( $_REQUEST['elementor-mode'] ) && 'safe' === $_REQUEST['elementor-mode'];
}
public function is_editor() {
return is_admin() && isset( $_GET['action'] ) && 'elementor' === $_GET['action'];
}
public function is_editor_preview() {
return isset( $_GET['elementor-preview'] );
}
public function is_editor_ajax() {
// PHPCS - There is already nonce verification in the Ajax Manager
return is_admin() && isset( $_POST['action'] ) && 'elementor_ajax' === $_POST['action']; // phpcs:ignore WordPress.Security.NonceVerification.Missing
}
public function add_hooks() {
add_filter( 'pre_option_active_plugins', function () {
return get_option( 'elementor_safe_mode_allowed_plugins' );
} );
add_filter( 'pre_option_stylesheet', function () {
return 'elementor-safe';
} );
add_filter( 'pre_option_template', function () {
return 'elementor-safe';
} );
add_action( 'elementor/init', function () {
do_action( 'elementor/safe_mode/init' );
} );
}
/**
* Plugin row meta.
*
* Adds row meta links to the plugin list table
*
* Fired by `plugin_row_meta` filter.
*
* @access public
*
* @param array $plugin_meta An array of the plugin's metadata, including
* the version, author, author URI, and plugin URI.
* @param string $plugin_file Path to the plugin file, relative to the plugins
* directory.
*
* @return array An array of plugin row meta links.
*/
public function plugin_row_meta( $plugin_meta, $plugin_file, $plugin_data, $status ) {
if ( basename( __FILE__ ) === $plugin_file ) {
$row_meta = [
'docs' => '<a href="https://go.elementor.com/safe-mode/" target="_blank">' . esc_html__( 'Learn More', 'elementor' ) . '</a>',
];
$plugin_meta = array_merge( $plugin_meta, $row_meta );
}
return $plugin_meta;
}
public function __construct() {
add_filter( 'plugin_row_meta', [ $this, 'plugin_row_meta' ], 10, 4 );
$enabled_type = $this->is_enabled();
if ( ! $enabled_type || ! $this->is_valid_token() ) {
return;
}
if ( ! $this->is_requested() && 'global' !== $enabled_type ) {
return;
}
if ( ! $this->is_editor() && ! $this->is_editor_preview() && ! $this->is_editor_ajax() ) {
return;
}
$this->add_hooks();
}
}
new Safe_Mode();

Some files were not shown because too many files have changed in this diff Show More